editcommitmsgs /

# - enables editing commit messages for all patches in on go
# Copyright 2010 aragost Trifork <>
# 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'))

    if not
        ui.warn(_('No patches applied\n'))

    allmessages = introductiontext()
    patchmap = {}
    appliedbefore =[:]

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

    for appliedpatch in
        patchheader = mq.patchheader(
        patchmap[] = patchheader
        allmessages += _patchlinestarter + + '\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, oldwrite)
        if commitmessages:
            savecommitcomments(ui, repo, mq, appliedbefore, commitmessages)
            ui.write = oldwrite
            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,

    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, oldwrite):
    """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: "):
            if currentpatch != None:
                if not currentpatch in commitcomments:
                    commitcomments[currentpatch] = []

    appliedbeforenames = []
    for patch in appliedbefore:
    if set(commitcomments.keys()) != set(appliedbeforenames):
        f = open('editcommitmsgs.backup', 'w')
        ui.write = oldwrite
        ui.warn(_("abort: Could not find comments for all patches\n"))
        ui.write(_("backup saved to editcommitmsgs.backup\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,
        message = '\n'.join(commitmessages[])
        mq.refresh(ui, repo, message=message)

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

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

cmdtable = {}