--- a/artemis.py Sat Dec 29 02:58:03 2007 -0500
+++ b/artemis.py Sat Dec 29 22:44:27 2007 -0500
@@ -37,25 +37,34 @@
property_match = property_match and (mbox[0][property] == value)
if not show_all and not property_match: continue
if not date_match(util.parsedate(mbox[0]['date'], [date_format])[0]): continue
- print "%s [%s]: %s (%d)" % (issue[len(issues_path)+1:], # +1 for trailing /
+ print "%s (%d) [%s]: %s" % (issue[len(issues_path)+1:], # +1 for trailing /
+ len(mbox)-1, # number of replies (-1 for self)
mbox[0]['State'],
- mbox[0]['Subject'],
- len(mbox)-1) # number of replies (-1 for self)
+ mbox[0]['Subject'])
-def add(ui, repo):
- """Adds a new issue"""
+def add(ui, repo, id = None, comment = 0):
+ """Adds a new issue, or comment to an existing issue ID or its comment COMMENT"""
+ comment = int(comment)
+
# First, make sure issues have a directory
issues_path = os.path.join(repo.root, issues_dir)
if not os.path.exists(issues_path): os.mkdir(issues_path)
+
+ if id:
+ issue_fn, issue_id = _find_issue(ui, repo, id)
+ if not issue_fn:
+ ui.warn('No such issue')
+ return
user = ui.username()
- default_issue_text = "From: %s\nDate: %s\n" % (user,
- time.strftime(date_format))
- default_issue_text += "State: %s\nSubject: brief description\n\n" % default_state
- default_issue_text += "Detailed description."
+ default_issue_text = "From: %s\nDate: %s\n" % (user, time.strftime(date_format))
+ if not id:
+ default_issue_text += "State: %s\n" % default_state
+ default_issue_text += "Subject: brief description\n\n"
+ default_issue_text += "Detailed description."
issue = ui.edit(default_issue_text, user)
if issue.strip() == '':
@@ -70,40 +79,53 @@
msg.set_from('artemis', True)
# Pick random filename
- issue_fn = issues_path
- while os.path.exists(issue_fn):
- issue_id = "%x" % random.randint(2**63, 2**64-1)
- issue_fn = os.path.join(issues_path, issue_id)
- msg.add_header('Message-Id', "%s-0-artemis@%s" % (issue_id, socket.gethostname()))
+ if not id:
+ issue_fn = issues_path
+ while os.path.exists(issue_fn):
+ issue_id = "%x" % random.randint(2**63, 2**64-1)
+ issue_fn = os.path.join(issues_path, issue_id)
+ # else: issue_fn already set
# Add message to the mailbox
mbox = mailbox.mbox(issue_fn)
+ if id and comment not in mbox:
+ ui.warn('No such comment number in mailbox, commenting on the issue itself\n')
+ if not id:
+ msg.add_header('Message-Id', "%s-0-artemis@%s" % (issue_id, socket.gethostname()))
+ else:
+ msg.add_header('Message-Id', "%s-%d-artemis@%s" % (issue_id, len(mbox), socket.gethostname()))
+ msg.add_header('References', mbox[(comment < len(mbox) and comment) or 0]['Message-Id'])
mbox.add(msg)
mbox.close()
- # Add the new mailbox to the repository
- repo.add([issue_fn[(len(repo.root)+1):]]) # +1 for the trailing /
+ # If adding issue, add the new mailbox to the repository
+ if not id:
+ repo.add([issue_fn[(len(repo.root)+1):]]) # +1 for the trailing /
+ ui.status('Added new issue %s\n' % issue_id)
-def show(ui, repo, id, comment = None):
+def show(ui, repo, id, comment = 0, **opts):
"""Shows issue ID, or possibly its comment COMMENT"""
- issue = _find_issue(ui, repo, id)
+ comment = int(comment)
+ issue, id = _find_issue(ui, repo, id)
if not issue: return
-
- # Read the issue
mbox = mailbox.mbox(issue)
- msg = mbox[0]
- ui.write(msg.as_string())
- # Walk the mailbox, and output comments
+ if opts['all']:
+ ui.write('='*70 + '\n')
+ for i in xrange(len(mbox)):
+ _write_message(ui, mbox[i], i)
+ ui.write('-'*70 + '\n')
+ return
+ _show_mbox(ui, mbox, comment)
def update(ui, repo, id, **opts):
- """Update properties of issue ID, or add a comment to it or its comment COMMENT"""
+ """Update properties of issue ID"""
- issue = _find_issue(ui, repo, id)
+ issue, id = _find_issue(ui, repo, id)
if not issue: return
properties = _get_properties(opts['property'])
@@ -113,15 +135,26 @@
msg = mbox[0]
# Fix the properties
+ properties_text = ''
for property, value in properties:
msg.replace_header(property, value)
+ properties_text += '%s=%s\n' % (property, value)
mbox[0] = msg
+
+ # Write down a comment about updated properties
+ if properties and not opts['no_property_comment']:
+ user = ui.username()
+ properties_text = "From: %s\nDate: %s\nSubject: properties changes %s\n\n%s" % (user, time.strftime(date_format),
+ [property for property, value in properties],
+ properties_text)
+ msg = mailbox.mboxMessage(properties_text)
+ msg.add_header('Message-Id', "%s-%d-artemis@%s" % (id, len(mbox), socket.gethostname()))
+ msg.add_header('References', mbox[0]['Message-Id'])
+ mbox.add(msg)
mbox.flush()
- # Deal with comments
-
# Show updated message
- ui.write(mbox[0].as_string())
+ _show_mbox(ui, mbox, 0)
def _find_issue(ui, repo, id):
@@ -131,17 +164,62 @@
issues = glob.glob(os.path.join(issues_path, id + '*'))
if len(issues) == 0:
- return False
+ return False, 0
elif len(issues) > 1:
ui.status("Multiple choices:\n")
for i in issues: ui.status(' ', i[len(issues_path)+1:], '\n')
- return False
+ return False, 0
- return issues[0]
+ return issues[0], issues[0][len(issues_path)+1:]
def _get_properties(property_list):
return [p.split('=') for p in property_list]
+def _write_message(ui, message, index = 0):
+ if index: ui.write("Comment: %d\n" % index)
+ if ui.verbose:
+ ui.write(message.as_string().strip() + '\n')
+ else:
+ if 'From' in message: ui.write('From: %s\n' % message['From'])
+ if 'Date' in message: ui.write('Date: %s\n' % message['Date'])
+ if 'Subject' in message: ui.write('Subject: %s\n' % message['Subject'])
+ if 'State' in message: ui.write('State: %s\n' % message['State'])
+ ui.write('\n' + message.get_payload().strip() + '\n')
+
+def _show_mbox(ui, mbox, comment):
+ # Output the issue (or comment)
+ if comment >= len(mbox):
+ comment = 0
+ ui.warn('Comment out of range, showing the issue itself\n')
+ msg = mbox[comment]
+ ui.write('='*70 + '\n')
+ if comment:
+ ui.write('Subject: %s\n' % mbox[0]['Subject'])
+ ui.write('State: %s\n' % mbox[0]['State'])
+ ui.write('-'*70 + '\n')
+ _write_message(ui, msg, comment)
+ ui.write('-'*70 + '\n')
+
+ # Read the mailbox into the messages and children dictionaries
+ messages = {}
+ children = {}
+ for i in xrange(len(mbox)):
+ m = mbox[i]
+ messages[m['Message-Id']] = (i,m)
+ children.setdefault(m['References'], []).append(m['Message-Id'])
+ children[None] = [] # Safeguard against infinte loop on empty Message-Id
+
+ # Iterate over children
+ id = msg['Message-Id']
+ id_stack = (id in children and map(lambda x: (x, 1), reversed(children[id]))) or []
+ if not id_stack: return
+ ui.write('Comments:\n')
+ while id_stack:
+ id,offset = id_stack.pop()
+ id_stack += (id in children and map(lambda x: (x, offset+1), reversed(children[id]))) or []
+ index, msg = messages[id]
+ ui.write(' '*offset + ('%d: ' % index) + msg['Subject'] + '\n')
+ ui.write('-'*70 + '\n')
cmdtable = {
@@ -155,14 +233,14 @@
_('hg ilist [OPTIONS]')),
'iadd': (add,
[],
- _('hg iadd')),
+ _('hg iadd [ID] [COMMENT]')),
'ishow': (show,
- [('v', 'verbose', None, 'list the comments')],
- _('hg ishow ID [COMMENT]')),
+ [('a', 'all', None, 'list all comments')],
+ _('hg ishow [OPTIONS] ID [COMMENT]')),
'iupdate': (update,
[('p', 'property', [],
'update properties (e.g., -p state=fixed)'),
- ('c', 'comment', 0,
- 'add a comment to issue or its comment COMMENT')],
- _('hg iupdate [OPTIONS] ID [COMMENT]'))
+ ('n', 'no-property-comment', None,
+ 'do not add a comment about changed properties')],
+ _('hg iupdate [OPTIONS] ID'))
}