Source

editcommitmsgs / editcommitmsgs.py

Full commit
# editcommitmsgs.py - enables editing commit messages for all patches in on go
#
# Copyright 2010, 2010 Erik Zielke <ez@aragost.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""
This extension do only have an effect if Mercurial Queues (mq) is enables.
Then it will allow you to edit commit messages of all patches in a single file.
"""
import os

from mercurial import extensions
from mercurial import cmdutil
from mercurial.i18n import _

_patchlinestarter = "HG: Patch: "

def editcommitmsgs(ui, repo, **opts):
    """Edit commit message of all applied patches"""
    mq = extensions.find('mq')
    
    if repo[None].dirty():
        ui.warn(_('abort: local changes found, refresh first\n'))
        return

    if not repo.mq.applied:
        ui.warn(_('No patches applied\n'))
        return

    allmessages = introductiontext()
    patchmap = {}
    appliedbefore = repo.mq.applied[:]

    #temporarily disable writing
    oldwrite = ui.write
    def nowrite(*_args, **_kwargs):
        """Noop write function"""
        pass
    ui.write = nowrite

    for appliedpatch in repo.mq.applied:
        patchheader = mq.patchheader(
            os.path.join(repo.mq.path, appliedpatch.name))
        patchmap[appliedpatch.name] = patchheader
        allmessages += _patchlinestarter + appliedpatch.name + '\n'
        if opts.get('showstatus'):
            allmessages += ('\n').join(
                fullstatus(repo, appliedpatch.node)) + '\n'
        allmessages += ('\n').join(patchheader.message) + '\n'
        allmessages += "HG: --\n"

    editedmessages = ui.edit(allmessages, ui.username)
    if editedmessages != allmessages:
        commitmessages = parsemessage(ui, editedmessages, appliedbefore)
        if commitmessages:
            savecommitcomments(ui, repo, mq, appliedbefore, commitmessages)
            ui.write = oldwrite
        else:
            ui.write = oldwrite
            return None

def fullstatus(repo, node1):
    """Gets status between ode1 and its first parent."""
    ctx = repo[node1]
    node2 = ctx.p1().node()
    modified, added, removed = repo.status(node2, node1,
                                           cmdutil.match(repo))[:3]

    fulltext = []
    fulltext.append(_("HG: user: %s") % ctx.user())
    fulltext.extend([_("HG: added %s") % f for f in added])
    fulltext.extend([_("HG: changed %s") % f for f in modified])
    fulltext.extend([_("HG: removed %s") % f for f in removed])
    if not added and not modified and not removed:
        fulltext.append(_("HG: no files changed"))
    return fulltext


def introductiontext():
    """"Returns an introduction text for the edit file"""
    return _("HG: Enter commit messages for the different patches.\n"
       "HG: Lines beginning with 'HG:' is used to apply the\n"
       "HG: messages to the right patch, and gives some status on the patch\n"
       "HG: Which can help you write the commit messages, so please do not\n"
       "HG: edit them\n"
       "HG: --\n"
       "HG: Applied patches:\n"
       "HG: --\n")

def parsemessage(ui, text, appliedbefore):
    """Parses the edited text.

    It parses it to a map from patch name to lines of the commit messages
    """
    currentpatch = None
    commitcomments = {}
    for line in text.splitlines():
        if line.startswith(_patchlinestarter):
            currentpatch = line.replace(_patchlinestarter, "")
        elif line.startswith("HG: "):
            continue
        else:
            if currentpatch != None:
                if not currentpatch in commitcomments:
                    commitcomments[currentpatch] = []
                commitcomments[currentpatch].append(line)

    
    appliedbeforenames = []
    for patch in appliedbefore:
        appliedbeforenames.append(patch.name)
    
    if set(commitcomments.keys()) != set(appliedbeforenames):
        ui.warn(_("abort: Could not find comments for all patches\n"))
        return None
                                                                 
    return commitcomments

def savecommitcomments(ui, repo, mq, appliedbefore, commitmessages):
    """Saves commit messages.

    It does so by first popping all applied patches, then pushing them back
    one by one, while updating the commit messages.
    """
    mq.pop(ui, repo, all=True)
    for appliedpatch in appliedbefore:
        mq.push(ui, repo, appliedpatch.name)
        message = '\n'.join(commitmessages[appliedpatch.name])
        mq.refresh(ui, repo, message=message)

def uisetup(ui):
    """Adds the command to command table if mq is found."""
    try:
        extensions.find('mq')
    except KeyError:
        ui.warn(_('editcommitmsgs: could not find mq extensions\n'))
        return

    ccmdtable = {"qeditcommitmsgs":
      (editcommitmsgs,
       [('s', 'showstatus', None, _('show status of changed files')),])}
    cmdtable.update(ccmdtable)

cmdtable = {}