Commits

John Mulligan  committed c0879f8

initial commit:
adds tweakmsg.py extension and ignore file

  • Participants

Comments (0)

Files changed (2)

+\.py[co]$
+#!/usr/bin/env python
+"""manipulate commit messages
+
+Features:
+ - Add comments to commit messages
+ - Comments appear as a normal part of commit description
+
+All message manipulations are versioned as part of the
+.hgmsg file which works in a way similar to hg tags.
+
+---
+Author: John Mulligan <phlogistonjohn@asynchrono.us>
+"""
+# TODO:
+#   verify that commiting comments wont mess with pending merges
+#   override/edit existing commit messages?
+
+
+from mercurial import changelog, util
+from mercurial.i18n import _
+from mercurial.node import short
+import os
+
+_changelog_read = changelog.changelog.read
+
+
+
+def new_changelog_read(mangler):
+    def changelog_read(self, node):
+        (manifest, user, t, files, desc, extra) = _changelog_read(self, node)
+        desc = mangler.update(node, desc)
+        return (manifest, user, t, files, desc, extra)
+    return changelog_read
+
+
+class messsagemangler(object):
+    def __init__(self, ui, repo):
+        self.ui = ui
+        self.repo = repo
+        self._source = self.ui.config('messagemangler', 'source', '.hgmsg')
+        self.source = os.path.join(self.repo.root, self._source)
+        self.config = None
+        self.readconfig()
+
+    def readconfig(self):
+        self.config = util.configparser()
+        if os.path.isfile(self.source):
+            self.config.read(self.source)
+
+    def sections(self, node):
+        prefix = '%s:' % node.encode('hex')
+        chg = [ ss for ss in self.config.sections() if ss.startswith(prefix) ]
+        chg.sort()
+        return chg
+
+    def comments(self, node, sep=None):
+        if sep is None:
+            sep = '--\n'
+        comments = ['']
+        for section in self.sections(node):
+            msgtype = self.config.get(section, 'type')
+            if msgtype not in 'comment note'.split():
+                continue
+            text = self.config.get(section, 'text')
+            text = text.strip()
+            if self.config.has_option(section, 'user'):
+                text += '\n ~ %s' % self.config.get(section, 'user')
+            text += '\n'
+            comments.append(text)
+        return sep.join(comments)
+
+    def update(self, node, desc):
+        desc = '%s\n%s' % (desc, self.comments(node))
+        return desc
+
+    def addcomment(self, node, text, user=None):
+        user = user or ui.username()
+        node = node.encode('hex')
+        index = 0
+        while True:
+            section = '%s:%d' % (node, index)
+            if not self.config.has_section(section):
+                break
+            index += 1
+        self.config.add_section(section)
+        self.config.set(section, 'type', 'comment')
+        self.config.set(section, 'user', user)
+        self.config.set(section, 'text', text)
+        try:
+            fp = open(self.source, 'wt')
+        except IOError, e:
+            raise util.Abort(e)
+        self.config.write(fp)
+        fp.close()
+        
+    def commit(self, repo, msg, user):
+        files = [ self._source ]
+        if self.source not in repo.dirstate:
+            repo.add(files)
+        repo.commit(files, msg, user)
+        
+
+def reposetup(ui, repo):
+    mangler = messsagemangler(ui, repo)
+    changelog.changelog.read = new_changelog_read(mangler)
+
+
+def comment(ui, repo, rev=None, user=None, **kwargs):
+    """add a comment to the current or given revision
+
+    Comments are similar to tags, and are stored as a file
+    named '.hgmsg'. This file is revisioned like other project files
+    and can be hand-edited if needed.
+    
+    Comments will appear beneath the changeset description.
+
+    If REV is not given the comment will be added for the tip.
+    """
+    user = user or ui.username()
+    node = repo.lookup(realrev(repo, rev))
+    desc = repo.changelog.read(node)[4]
+    text = ui.edit(prepdesc(desc), user).strip()
+    if not text:
+        raise util.Abort(_('empty comment'))
+    mangler = messsagemangler(ui, repo)
+    mangler.addcomment(node, text, user)
+    if kwargs['no_commit']:
+        # done, if we're not commiting
+        return
+    message = kwargs.get('message', '')
+    if not message:
+        mtxt = _('Added user comment for changeset %s')
+        message = mtxt % short(repo.changectx(rev).node())
+    mangler.commit(repo, message, user)
+
+
+def prepdesc(desc):
+    text = []
+    text.append(_("HG: Enter a comment." 
+                  "  Lines beginning with 'HG:' are removed."))
+    text.append('HG: --')
+    for line in desc.split('\n'):
+        text.append('HG: %s' % line)
+
+    return '\n\n%s\n' % '\n'.join(text)
+
+
+def realrev(repo, rev):
+    if rev is not None:
+        return rev
+    branch = repo.workingctx().branch()
+    try:
+        return repo.branchtags()[branch]
+    except KeyError:
+        if branch == 'default':
+            return 'tip'
+        raise util.Abort(_('branch %s not found') % branch)
+
+
+
+cmdtable = {
+    "comment": (comment,
+                [
+                ('u', 'user', '', _('user adding the comment')),
+                ('m', 'message', '', _('commit message')),
+                ('', 'no-commit', None, _("don't commit new comment")),
+                ],
+                "hg comment [-u USER] [REV]"),
+}
+