diff -r 0bd1e95af89f -r bf71e2069dbd artemis.py --- 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')) }