Basic functionality in place; added feature request for integration with commit (ef1)
authorDmitriy Morozov <morozov@cs.duke.edu>
Sat, 29 Dec 2007 22:44:27 -0500
changeset 4 bf71e2069dbd
parent 3 0bd1e95af89f
child 5 cef66aa31468
Basic functionality in place; added feature request for integration with commit (ef1)
.issues/ef1b616f31345634
artemis.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.issues/ef1b616f31345634	Sat Dec 29 22:44:27 2007 -0500
@@ -0,0 +1,15 @@
+From artemis Sat Dec 29 21:16:55 2007
+From: Dmitriy Morozov <morozov@cs.duke.edu>
+Date: Sat, 29 Dec 2007 16:12:43 EST
+State: new
+Subject: intergration with commit
+Message-Id: ef1b616f31345634-0-artemis@metatron
+
+When an issue id is mentioned in a commit message, the text of the
+message should be added as a comment to the issue (prior to the
+commit).
+
+Similarly the subjects of the issues (with their ids) should be added
+to the initial commit message given by mercurial in the editor. They
+should appear in the ignored lines of the message (lines starting with
+HG, so that the user is reminded what issue each id stands for.
--- 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'))
 }