35 property_match = True |
35 property_match = True |
36 for property,value in properties: |
36 for property,value in properties: |
37 property_match = property_match and (mbox[0][property] == value) |
37 property_match = property_match and (mbox[0][property] == value) |
38 if not show_all and not property_match: continue |
38 if not show_all and not property_match: continue |
39 if not date_match(util.parsedate(mbox[0]['date'], [date_format])[0]): continue |
39 if not date_match(util.parsedate(mbox[0]['date'], [date_format])[0]): continue |
40 print "%s [%s]: %s (%d)" % (issue[len(issues_path)+1:], # +1 for trailing / |
40 print "%s (%d) [%s]: %s" % (issue[len(issues_path)+1:], # +1 for trailing / |
|
41 len(mbox)-1, # number of replies (-1 for self) |
41 mbox[0]['State'], |
42 mbox[0]['State'], |
42 mbox[0]['Subject'], |
43 mbox[0]['Subject']) |
43 len(mbox)-1) # number of replies (-1 for self) |
44 |
44 |
45 |
45 |
46 def add(ui, repo, id = None, comment = 0): |
46 def add(ui, repo): |
47 """Adds a new issue, or comment to an existing issue ID or its comment COMMENT""" |
47 """Adds a new issue""" |
48 |
48 |
49 comment = int(comment) |
|
50 |
49 # First, make sure issues have a directory |
51 # First, make sure issues have a directory |
50 issues_path = os.path.join(repo.root, issues_dir) |
52 issues_path = os.path.join(repo.root, issues_dir) |
51 if not os.path.exists(issues_path): os.mkdir(issues_path) |
53 if not os.path.exists(issues_path): os.mkdir(issues_path) |
|
54 |
|
55 if id: |
|
56 issue_fn, issue_id = _find_issue(ui, repo, id) |
|
57 if not issue_fn: |
|
58 ui.warn('No such issue') |
|
59 return |
52 |
60 |
53 user = ui.username() |
61 user = ui.username() |
54 |
62 |
55 default_issue_text = "From: %s\nDate: %s\n" % (user, |
63 default_issue_text = "From: %s\nDate: %s\n" % (user, time.strftime(date_format)) |
56 time.strftime(date_format)) |
64 if not id: |
57 default_issue_text += "State: %s\nSubject: brief description\n\n" % default_state |
65 default_issue_text += "State: %s\n" % default_state |
58 default_issue_text += "Detailed description." |
66 default_issue_text += "Subject: brief description\n\n" |
|
67 default_issue_text += "Detailed description." |
59 |
68 |
60 issue = ui.edit(default_issue_text, user) |
69 issue = ui.edit(default_issue_text, user) |
61 if issue.strip() == '': |
70 if issue.strip() == '': |
62 ui.warn('Empty issue, ignoring\n') |
71 ui.warn('Empty issue, ignoring\n') |
63 return |
72 return |
68 # Create the message |
77 # Create the message |
69 msg = mailbox.mboxMessage(issue) |
78 msg = mailbox.mboxMessage(issue) |
70 msg.set_from('artemis', True) |
79 msg.set_from('artemis', True) |
71 |
80 |
72 # Pick random filename |
81 # Pick random filename |
73 issue_fn = issues_path |
82 if not id: |
74 while os.path.exists(issue_fn): |
83 issue_fn = issues_path |
75 issue_id = "%x" % random.randint(2**63, 2**64-1) |
84 while os.path.exists(issue_fn): |
76 issue_fn = os.path.join(issues_path, issue_id) |
85 issue_id = "%x" % random.randint(2**63, 2**64-1) |
77 msg.add_header('Message-Id', "%s-0-artemis@%s" % (issue_id, socket.gethostname())) |
86 issue_fn = os.path.join(issues_path, issue_id) |
|
87 # else: issue_fn already set |
78 |
88 |
79 # Add message to the mailbox |
89 # Add message to the mailbox |
80 mbox = mailbox.mbox(issue_fn) |
90 mbox = mailbox.mbox(issue_fn) |
|
91 if id and comment not in mbox: |
|
92 ui.warn('No such comment number in mailbox, commenting on the issue itself\n') |
|
93 if not id: |
|
94 msg.add_header('Message-Id', "%s-0-artemis@%s" % (issue_id, socket.gethostname())) |
|
95 else: |
|
96 msg.add_header('Message-Id', "%s-%d-artemis@%s" % (issue_id, len(mbox), socket.gethostname())) |
|
97 msg.add_header('References', mbox[(comment < len(mbox) and comment) or 0]['Message-Id']) |
81 mbox.add(msg) |
98 mbox.add(msg) |
82 mbox.close() |
99 mbox.close() |
83 |
100 |
84 # Add the new mailbox to the repository |
101 # If adding issue, add the new mailbox to the repository |
85 repo.add([issue_fn[(len(repo.root)+1):]]) # +1 for the trailing / |
102 if not id: |
86 |
103 repo.add([issue_fn[(len(repo.root)+1):]]) # +1 for the trailing / |
87 |
104 ui.status('Added new issue %s\n' % issue_id) |
88 def show(ui, repo, id, comment = None): |
105 |
|
106 |
|
107 def show(ui, repo, id, comment = 0, **opts): |
89 """Shows issue ID, or possibly its comment COMMENT""" |
108 """Shows issue ID, or possibly its comment COMMENT""" |
90 |
109 |
91 issue = _find_issue(ui, repo, id) |
110 comment = int(comment) |
|
111 issue, id = _find_issue(ui, repo, id) |
92 if not issue: return |
112 if not issue: return |
93 |
113 mbox = mailbox.mbox(issue) |
|
114 |
|
115 if opts['all']: |
|
116 ui.write('='*70 + '\n') |
|
117 for i in xrange(len(mbox)): |
|
118 _write_message(ui, mbox[i], i) |
|
119 ui.write('-'*70 + '\n') |
|
120 return |
|
121 |
|
122 _show_mbox(ui, mbox, comment) |
|
123 |
|
124 |
|
125 def update(ui, repo, id, **opts): |
|
126 """Update properties of issue ID""" |
|
127 |
|
128 issue, id = _find_issue(ui, repo, id) |
|
129 if not issue: return |
|
130 |
|
131 properties = _get_properties(opts['property']) |
|
132 |
94 # Read the issue |
133 # Read the issue |
95 mbox = mailbox.mbox(issue) |
134 mbox = mailbox.mbox(issue) |
96 msg = mbox[0] |
135 msg = mbox[0] |
97 ui.write(msg.as_string()) |
|
98 |
|
99 # Walk the mailbox, and output comments |
|
100 |
|
101 |
|
102 |
|
103 def update(ui, repo, id, **opts): |
|
104 """Update properties of issue ID, or add a comment to it or its comment COMMENT""" |
|
105 |
|
106 issue = _find_issue(ui, repo, id) |
|
107 if not issue: return |
|
108 |
|
109 properties = _get_properties(opts['property']) |
|
110 |
|
111 # Read the issue |
|
112 mbox = mailbox.mbox(issue) |
|
113 msg = mbox[0] |
|
114 |
136 |
115 # Fix the properties |
137 # Fix the properties |
|
138 properties_text = '' |
116 for property, value in properties: |
139 for property, value in properties: |
117 msg.replace_header(property, value) |
140 msg.replace_header(property, value) |
|
141 properties_text += '%s=%s\n' % (property, value) |
118 mbox[0] = msg |
142 mbox[0] = msg |
|
143 |
|
144 # Write down a comment about updated properties |
|
145 if properties and not opts['no_property_comment']: |
|
146 user = ui.username() |
|
147 properties_text = "From: %s\nDate: %s\nSubject: properties changes %s\n\n%s" % (user, time.strftime(date_format), |
|
148 [property for property, value in properties], |
|
149 properties_text) |
|
150 msg = mailbox.mboxMessage(properties_text) |
|
151 msg.add_header('Message-Id', "%s-%d-artemis@%s" % (id, len(mbox), socket.gethostname())) |
|
152 msg.add_header('References', mbox[0]['Message-Id']) |
|
153 mbox.add(msg) |
119 mbox.flush() |
154 mbox.flush() |
120 |
155 |
121 # Deal with comments |
|
122 |
|
123 # Show updated message |
156 # Show updated message |
124 ui.write(mbox[0].as_string()) |
157 _show_mbox(ui, mbox, 0) |
125 |
158 |
126 |
159 |
127 def _find_issue(ui, repo, id): |
160 def _find_issue(ui, repo, id): |
128 issues_path = os.path.join(repo.root, issues_dir) |
161 issues_path = os.path.join(repo.root, issues_dir) |
129 if not os.path.exists(issues_path): return False |
162 if not os.path.exists(issues_path): return False |
130 |
163 |
131 issues = glob.glob(os.path.join(issues_path, id + '*')) |
164 issues = glob.glob(os.path.join(issues_path, id + '*')) |
132 |
165 |
133 if len(issues) == 0: |
166 if len(issues) == 0: |
134 return False |
167 return False, 0 |
135 elif len(issues) > 1: |
168 elif len(issues) > 1: |
136 ui.status("Multiple choices:\n") |
169 ui.status("Multiple choices:\n") |
137 for i in issues: ui.status(' ', i[len(issues_path)+1:], '\n') |
170 for i in issues: ui.status(' ', i[len(issues_path)+1:], '\n') |
138 return False |
171 return False, 0 |
139 |
172 |
140 return issues[0] |
173 return issues[0], issues[0][len(issues_path)+1:] |
141 |
174 |
142 def _get_properties(property_list): |
175 def _get_properties(property_list): |
143 return [p.split('=') for p in property_list] |
176 return [p.split('=') for p in property_list] |
144 |
177 |
|
178 def _write_message(ui, message, index = 0): |
|
179 if index: ui.write("Comment: %d\n" % index) |
|
180 if ui.verbose: |
|
181 ui.write(message.as_string().strip() + '\n') |
|
182 else: |
|
183 if 'From' in message: ui.write('From: %s\n' % message['From']) |
|
184 if 'Date' in message: ui.write('Date: %s\n' % message['Date']) |
|
185 if 'Subject' in message: ui.write('Subject: %s\n' % message['Subject']) |
|
186 if 'State' in message: ui.write('State: %s\n' % message['State']) |
|
187 ui.write('\n' + message.get_payload().strip() + '\n') |
|
188 |
|
189 def _show_mbox(ui, mbox, comment): |
|
190 # Output the issue (or comment) |
|
191 if comment >= len(mbox): |
|
192 comment = 0 |
|
193 ui.warn('Comment out of range, showing the issue itself\n') |
|
194 msg = mbox[comment] |
|
195 ui.write('='*70 + '\n') |
|
196 if comment: |
|
197 ui.write('Subject: %s\n' % mbox[0]['Subject']) |
|
198 ui.write('State: %s\n' % mbox[0]['State']) |
|
199 ui.write('-'*70 + '\n') |
|
200 _write_message(ui, msg, comment) |
|
201 ui.write('-'*70 + '\n') |
|
202 |
|
203 # Read the mailbox into the messages and children dictionaries |
|
204 messages = {} |
|
205 children = {} |
|
206 for i in xrange(len(mbox)): |
|
207 m = mbox[i] |
|
208 messages[m['Message-Id']] = (i,m) |
|
209 children.setdefault(m['References'], []).append(m['Message-Id']) |
|
210 children[None] = [] # Safeguard against infinte loop on empty Message-Id |
|
211 |
|
212 # Iterate over children |
|
213 id = msg['Message-Id'] |
|
214 id_stack = (id in children and map(lambda x: (x, 1), reversed(children[id]))) or [] |
|
215 if not id_stack: return |
|
216 ui.write('Comments:\n') |
|
217 while id_stack: |
|
218 id,offset = id_stack.pop() |
|
219 id_stack += (id in children and map(lambda x: (x, offset+1), reversed(children[id]))) or [] |
|
220 index, msg = messages[id] |
|
221 ui.write(' '*offset + ('%d: ' % index) + msg['Subject'] + '\n') |
|
222 ui.write('-'*70 + '\n') |
145 |
223 |
146 |
224 |
147 cmdtable = { |
225 cmdtable = { |
148 'ilist': (list, |
226 'ilist': (list, |
149 [('a', 'all', None, |
227 [('a', 'all', None, |
153 ('d', 'date', '', 'restrict to issues matching the date (e.g., -d ">12/28/2007)"'), |
231 ('d', 'date', '', 'restrict to issues matching the date (e.g., -d ">12/28/2007)"'), |
154 ('f', 'filter', '', 'restrict to pre-defined filter (in %s/%s)' % (issues_dir, filter_filename))], |
232 ('f', 'filter', '', 'restrict to pre-defined filter (in %s/%s)' % (issues_dir, filter_filename))], |
155 _('hg ilist [OPTIONS]')), |
233 _('hg ilist [OPTIONS]')), |
156 'iadd': (add, |
234 'iadd': (add, |
157 [], |
235 [], |
158 _('hg iadd')), |
236 _('hg iadd [ID] [COMMENT]')), |
159 'ishow': (show, |
237 'ishow': (show, |
160 [('v', 'verbose', None, 'list the comments')], |
238 [('a', 'all', None, 'list all comments')], |
161 _('hg ishow ID [COMMENT]')), |
239 _('hg ishow [OPTIONS] ID [COMMENT]')), |
162 'iupdate': (update, |
240 'iupdate': (update, |
163 [('p', 'property', [], |
241 [('p', 'property', [], |
164 'update properties (e.g., -p state=fixed)'), |
242 'update properties (e.g., -p state=fixed)'), |
165 ('c', 'comment', 0, |
243 ('n', 'no-property-comment', None, |
166 'add a comment to issue or its comment COMMENT')], |
244 'do not add a comment about changed properties')], |
167 _('hg iupdate [OPTIONS] ID [COMMENT]')) |
245 _('hg iupdate [OPTIONS] ID')) |
168 } |
246 } |