13 from email.mime.image import MIMEImage |
13 from email.mime.image import MIMEImage |
14 from email.mime.multipart import MIMEMultipart |
14 from email.mime.multipart import MIMEMultipart |
15 from email.mime.text import MIMEText |
15 from email.mime.text import MIMEText |
16 |
16 |
17 |
17 |
18 state = {'new': 'new', 'fixed': 'fixed'} |
18 state = {'new': 'new', 'fixed': ['fixed', 'resolved']} |
19 state['default'] = state['new'] |
19 state['default'] = state['new'] |
20 issues_dir = ".issues" |
20 issues_dir = ".issues" |
21 filter_prefix = ".filter" |
21 filter_prefix = ".filter" |
22 date_format = '%a, %d %b %Y %H:%M:%S' |
22 date_format = '%a, %d %b %Y %H:%M:%S' |
23 maildir_dirs = ['new','cur','tmp'] |
23 maildir_dirs = ['new','cur','tmp'] |
57 mbox = mailbox.Maildir(issue, factory=mailbox.MaildirMessage) |
57 mbox = mailbox.Maildir(issue, factory=mailbox.MaildirMessage) |
58 root = _find_root_key(mbox) |
58 root = _find_root_key(mbox) |
59 property_match = True |
59 property_match = True |
60 for property,value in properties: |
60 for property,value in properties: |
61 property_match = property_match and (mbox[root][property] == value) |
61 property_match = property_match and (mbox[root][property] == value) |
62 if not show_all and (not properties or not property_match) and (properties or mbox[root]['State'].upper() == state['fixed'].upper()): continue |
62 if not show_all and (not properties or not property_match) and (properties or mbox[root]['State'].upper() in [f.upper() for f in state['fixed']]): continue |
63 |
63 |
64 |
64 |
65 if match_date and not date_match(util.parsedate(mbox[root]['date'])[0]): continue |
65 if match_date and not date_match(util.parsedate(mbox[root]['date'])[0]): continue |
66 ui.write("%s (%3d) [%s]: %s\n" % (issue[len(issues_path)+1:], # +1 for trailing / |
66 ui.write("%s (%3d) [%s]: %s\n" % (issue[len(issues_path)+1:], # +1 for trailing / |
67 len(mbox)-1, # number of replies (-1 for self) |
67 len(mbox)-1, # number of replies (-1 for self) |
68 mbox[root]['State'], |
68 _status_msg(mbox[root]), |
69 mbox[root]['Subject'])) |
69 mbox[root]['Subject'])) |
70 |
70 |
71 |
71 |
72 def iadd(ui, repo, id = None, comment = 0, **opts): |
72 def iadd(ui, repo, id = None, comment = 0, **opts): |
73 """Adds a new issue, or comment to an existing issue ID or its comment COMMENT""" |
73 """Adds a new issue, or comment to an existing issue ID or its comment COMMENT""" |
91 if not id: |
91 if not id: |
92 default_issue_text += "State: %s\n" % state['default'] |
92 default_issue_text += "State: %s\n" % state['default'] |
93 default_issue_text += "Subject: brief description\n\n" |
93 default_issue_text += "Subject: brief description\n\n" |
94 default_issue_text += "Detailed description." |
94 default_issue_text += "Detailed description." |
95 |
95 |
96 issue = ui.edit(default_issue_text, user) |
96 # Get properties, and figure out if we need an explicit comment |
97 if issue.strip() == '': |
97 properties = _get_properties(opts['property']) |
98 ui.warn('Empty issue, ignoring\n') |
98 no_comment = id and properties and opts['no_property_comment'] |
99 return |
99 |
100 if issue.strip() == default_issue_text: |
100 # Create the text |
101 ui.warn('Unchanged issue text, ignoring\n') |
101 if not no_comment: |
102 return |
102 issue = ui.edit(default_issue_text, user) |
|
103 |
|
104 if issue.strip() == '': |
|
105 ui.warn('Empty issue, ignoring\n') |
|
106 return |
|
107 if issue.strip() == default_issue_text: |
|
108 ui.warn('Unchanged issue text, ignoring\n') |
|
109 return |
|
110 else: |
|
111 # Write down a comment about updated properties |
|
112 properties_subject = ', '.join(['%s=%s' % (property, value) for (property, value) in properties]) |
|
113 |
|
114 issue = "From: %s\nDate: %s\nSubject: changed properties (%s)\n" % \ |
|
115 (user, util.datestr(format = date_format), properties_subject) |
103 |
116 |
104 # Create the message |
117 # Create the message |
105 msg = mailbox.MaildirMessage(issue) |
118 msg = mailbox.MaildirMessage(issue) |
106 if opts['attach']: |
119 if opts['attach']: |
107 outer = _attach_files(msg, opts['attach']) |
120 outer = _attach_files(msg, opts['attach']) |
115 issue_id = _random_id() |
128 issue_id = _random_id() |
116 issue_fn = os.path.join(issues_path, issue_id) |
129 issue_fn = os.path.join(issues_path, issue_id) |
117 # else: issue_fn already set |
130 # else: issue_fn already set |
118 |
131 |
119 # Add message to the mailbox |
132 # Add message to the mailbox |
120 mbox = mailbox.Maildir(issue_fn) |
133 mbox = mailbox.Maildir(issue_fn, factory=mailbox.MaildirMessage) |
121 keys = _order_keys_date(mbox) |
134 keys = _order_keys_date(mbox) |
122 mbox.lock() |
135 mbox.lock() |
123 if id and comment >= len(mbox): |
136 if id and comment >= len(mbox): |
124 ui.warn('No such comment number in mailbox, commenting on the issue itself\n') |
137 ui.warn('No such comment number in mailbox, commenting on the issue itself\n') |
125 |
138 |
126 if not id: |
139 if not id: |
127 outer.add_header('Message-Id', "<%s-0-artemis@%s>" % (issue_id, socket.gethostname())) |
140 outer.add_header('Message-Id', "<%s-0-artemis@%s>" % (issue_id, socket.gethostname())) |
|
141 root = 0 |
128 else: |
142 else: |
129 root = keys[0] |
143 root = keys[0] |
130 outer.add_header('Message-Id', "<%s-%s-artemis@%s>" % (issue_id, _random_id(), socket.gethostname())) |
144 outer.add_header('Message-Id', "<%s-%s-artemis@%s>" % (issue_id, _random_id(), socket.gethostname())) |
131 outer.add_header('References', mbox[(comment < len(mbox) and keys[comment]) or root]['Message-Id']) |
145 outer.add_header('References', mbox[(comment < len(mbox) and keys[comment]) or root]['Message-Id']) |
132 outer.add_header('In-Reply-To', mbox[(comment < len(mbox) and keys[comment]) or root]['Message-Id']) |
146 outer.add_header('In-Reply-To', mbox[(comment < len(mbox) and keys[comment]) or root]['Message-Id']) |
133 repo.add([issue_fn[(len(repo.root)+1):] + '/new/' + mbox.add(outer)]) # +1 for the trailing / |
147 repo.add([issue_fn[(len(repo.root)+1):] + '/new/' + mbox.add(outer)]) # +1 for the trailing / |
|
148 |
|
149 # Fix properties in the root message |
|
150 msg = mbox[root] |
|
151 if properties: |
|
152 for property, value in properties: |
|
153 if property in msg: |
|
154 msg.replace_header(property, value) |
|
155 else: |
|
156 msg.add_header(property, value) |
|
157 mbox[root] = msg |
|
158 |
134 mbox.close() |
159 mbox.close() |
135 |
160 |
136 # If adding issue, add the new mailbox to the repository |
161 # If adding issue, add the new mailbox to the repository |
137 if not id: |
162 if not id: |
138 ui.status('Added new issue %s\n' % issue_id) |
163 ui.status('Added new issue %s\n' % issue_id) |
139 |
164 else: |
|
165 _show_mbox(ui, mbox, 0) |
140 |
166 |
141 def ishow(ui, repo, id, comment = 0, **opts): |
167 def ishow(ui, repo, id, comment = 0, **opts): |
142 """Shows issue ID, or possibly its comment COMMENT""" |
168 """Shows issue ID, or possibly its comment COMMENT""" |
143 |
169 |
144 comment = int(comment) |
170 comment = int(comment) |
179 fp.write(part.get_payload(decode = True)) |
205 fp.write(part.get_payload(decode = True)) |
180 fp.close() |
206 fp.close() |
181 counter += 1 |
207 counter += 1 |
182 |
208 |
183 |
209 |
184 def iupdate(ui, repo, id, **opts): |
|
185 """Update properties of issue ID""" |
|
186 |
|
187 issue, id = _find_issue(ui, repo, id) |
|
188 if not issue: return |
|
189 |
|
190 _create_missing_dirs(os.path.join(repo.root, issues_dir), id) |
|
191 |
|
192 properties = _get_properties(opts['property']) |
|
193 |
|
194 # Read the issue |
|
195 mbox = mailbox.Maildir(issue, factory=mailbox.MaildirMessage) |
|
196 root = _find_root_key(mbox) |
|
197 msg = mbox[root] |
|
198 |
|
199 # Fix the properties |
|
200 properties_text = '' |
|
201 for property, value in properties: |
|
202 if property in msg: |
|
203 msg.replace_header(property, value) |
|
204 else: |
|
205 msg.add_header(property, value) |
|
206 properties_text += '%s=%s\n' % (property, value) |
|
207 mbox.lock() |
|
208 mbox[root] = msg |
|
209 |
|
210 # Write down a comment about updated properties |
|
211 if properties and not opts['no_property_comment']: |
|
212 user = ui.username() |
|
213 properties_text = "From: %s\nDate: %s\nSubject: properties changes (%s)\n\n%s" % \ |
|
214 (user, util.datestr(format = date_format), |
|
215 _pretty_list(list(set([property for property, value in properties]))), |
|
216 properties_text) |
|
217 msg = mailbox.mboxMessage(properties_text) |
|
218 msg.add_header('Message-Id', "<%s-%s-artemis@%s>" % (id, _random_id(), socket.gethostname())) |
|
219 msg.add_header('References', mbox[root]['Message-Id']) |
|
220 msg.add_header('In-Reply-To', mbox[root]['Message-Id']) |
|
221 #msg.set_from('artemis', True) |
|
222 repo.add([issue[(len(repo.root)+1):] + '/new/' + mbox.add(msg)]) # +1 for the trailing / |
|
223 mbox.close() |
|
224 |
|
225 # Show updated message |
|
226 _show_mbox(ui, mbox, 0) |
|
227 |
|
228 |
|
229 def _find_issue(ui, repo, id): |
210 def _find_issue(ui, repo, id): |
230 issues_path = os.path.join(repo.root, issues_dir) |
211 issues_path = os.path.join(repo.root, issues_dir) |
231 if not os.path.exists(issues_path): return False |
212 if not os.path.exists(issues_path): return False |
232 |
213 |
233 issues = glob.glob(os.path.join(issues_path, id + '*')) |
214 issues = glob.glob(os.path.join(issues_path, id + '*')) |
312 def _order_keys_date(mbox): |
293 def _order_keys_date(mbox): |
313 keys = mbox.keys() |
294 keys = mbox.keys() |
314 root = _find_root_key(mbox) |
295 root = _find_root_key(mbox) |
315 keys.sort(lambda k1,k2: -(k1 == root) or cmp(util.parsedate(mbox[k1]['date']), util.parsedate(mbox[k2]['date']))) |
296 keys.sort(lambda k1,k2: -(k1 == root) or cmp(util.parsedate(mbox[k1]['date']), util.parsedate(mbox[k2]['date']))) |
316 return keys |
297 return keys |
317 |
|
318 def _pretty_list(lst): |
|
319 s = '' |
|
320 for i in lst: |
|
321 s += i + ', ' |
|
322 return s[:-2] |
|
323 |
298 |
324 def _random_id(): |
299 def _random_id(): |
325 return "%x" % random.randint(2**63, 2**64-1) |
300 return "%x" % random.randint(2**63, 2**64-1) |
326 |
301 |
327 def _create_missing_dirs(issues_path, issue): |
302 def _create_missing_dirs(issues_path, issue): |
376 # Set the filename parameter |
351 # Set the filename parameter |
377 attachment.add_header('Content-Disposition', 'attachment', filename=filename) |
352 attachment.add_header('Content-Disposition', 'attachment', filename=filename) |
378 outer.attach(attachment) |
353 outer.attach(attachment) |
379 return outer |
354 return outer |
380 |
355 |
|
356 def _status_msg(msg): |
|
357 if msg['State'] == 'resolved': |
|
358 return 'resolved=' + msg['resolution'] |
|
359 else: |
|
360 return msg['State'] |
|
361 |
381 cmdtable = { |
362 cmdtable = { |
382 'ilist': (ilist, |
363 'ilist': (ilist, |
383 [('a', 'all', False, |
364 [('a', 'all', False, |
384 'list all issues (by default only those with state new)'), |
365 'list all issues (by default only those with state new)'), |
385 ('p', 'property', [], |
366 ('p', 'property', [], |
387 ('d', 'date', '', 'restrict to issues matching the date (e.g., -d ">12/28/2007)"'), |
368 ('d', 'date', '', 'restrict to issues matching the date (e.g., -d ">12/28/2007)"'), |
388 ('f', 'filter', '', 'restrict to pre-defined filter (in %s/%s*)' % (issues_dir, filter_prefix))], |
369 ('f', 'filter', '', 'restrict to pre-defined filter (in %s/%s*)' % (issues_dir, filter_prefix))], |
389 _('hg ilist [OPTIONS]')), |
370 _('hg ilist [OPTIONS]')), |
390 'iadd': (iadd, |
371 'iadd': (iadd, |
391 [('a', 'attach', [], |
372 [('a', 'attach', [], |
392 'attach file(s) (e.g., -a filename1 -a filename2)')], |
373 'attach file(s) (e.g., -a filename1 -a filename2)'), |
393 _('hg iadd [ID] [COMMENT]')), |
374 ('p', 'property', [], |
|
375 'update properties (e.g., -p state=fixed)'), |
|
376 ('n', 'no-property-comment', None, |
|
377 'do not add a comment about changed properties')], |
|
378 _('hg iadd [OPTIONS] [ID] [COMMENT]')), |
394 'ishow': (ishow, |
379 'ishow': (ishow, |
395 [('a', 'all', None, 'list all comments'), |
380 [('a', 'all', None, 'list all comments'), |
396 ('x', 'extract', [], 'extract attachments')], |
381 ('x', 'extract', [], 'extract attachments')], |
397 _('hg ishow [OPTIONS] ID [COMMENT]')), |
382 _('hg ishow [OPTIONS] ID [COMMENT]')), |
398 'iupdate': (iupdate, |
|
399 [('p', 'property', [], |
|
400 'update properties (e.g., -p state=fixed)'), |
|
401 ('n', 'no-property-comment', None, |
|
402 'do not add a comment about changed properties')], |
|
403 _('hg iupdate [OPTIONS] ID')) |
|
404 } |
383 } |
405 |
384 |
406 # vim: expandtab |
385 # vim: expandtab |