src/topic/__init__.py
author Augie Fackler <augie@google.com>
Wed, 10 Jun 2015 15:03:39 -0400
changeset 1844 862cabc132fd
parent 1843 0ba067a97d06
child 1845 24d8053020a2
permissions -rw-r--r--
topic: add ability to change topic of non-public changes This is a little crude, but it gets the job done. You probably don't want to use this without evolution for now.

# __init__.py - topic extension
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""Adds topic branches. Topic branches are lightweight branches which
dissappear when changes are finalized.

This is sort of similar to a bookmark, but it applies to a whole
series instead of a single revision.
"""
import functools

from mercurial import cmdutil
from mercurial import commands
from mercurial import context
from mercurial import extensions
from mercurial import namespaces
from mercurial import obsolete
from mercurial import phases
from mercurial import util

from . import revset as topicrevset

cmdtable = {}
command = cmdutil.command(cmdtable)

def _namemap(repo, name):
    return [ctx.node() for ctx in
            repo.set('not public() and extra(topic, %s)', name)]

def _nodemap(repo, node):
    ctx = repo[node]
    t = ctx.extra().get('topic', '')
    if t and ctx.phase() > phases.public:
        return [t]
    return []

def reposetup(ui, repo):
    orig = repo.__class__
    class topicrepo(repo.__class__):
        def commitctx(self, ctx, error=None):
            current = self.currenttopic
            if current:
                ctx.extra()['topic'] = current
            return orig.commitctx(self, ctx, error=error)

        @property
        def topics(self):
            topics = set(['', self.currenttopic])
            for c in self.set('not public()'):
                topics.add(c.extra().get('topic', ''))
            topics.remove('')
            return topics

        @property
        def currenttopic(self):
            return self.vfs.tryread('topic')

    if util.safehasattr(repo, 'names'):
        repo.names.addnamespace(namespaces.namespace(
            'topics', 'topic', namemap=_namemap, nodemap=_nodemap))
    repo.__class__ = topicrepo

@command('topics', [
    ('', 'clear', False, 'clear active topic if any'),
    ('', 'change', '', 'revset of existing revisions to change topic'),
])
def topics(ui, repo, topic=None, clear=False, change=None):
    """View current topic, set current topic, or see all topics."""
    if not obsolete.isenabled(repo, obsolete.createmarkersopt):
        raise util.Abort('current reality means you should only '
                         'use topic with obsolete enabled')
    if change:
        if topic is None and not clear:
            raise util.Abort('changing topic requires a topic name or --clear')
        if any(not c.mutable() for c in repo.set('%r and public()', change)):
            raise util.Abort("can't change topic of a public change")
        rewrote = 0
        needevolve = False
        for c in repo.set('%r', change):
            def filectxfn(repo, ctx, path):
                try:
                    return c[path]
                except error.ManifestLookupError:
                    return None
            fixedextra = dict(c.extra())
            newtopic = None if clear else topic
            if fixedextra.get('topic', None) == topic:
                continue
            if clear and 'topic' in fixedextra:
                del fixedextra['topic']
            else:
                fixedextra['topic'] = topic
            c.parents()
            mc = context.memctx(
                repo, (c.p1().node(), c.p2().node()), c.description(),
                c.files(), filectxfn,
                user=c.user(), date=c.date(), extra=fixedextra)
            newnode = repo.commitctx(mc)
            needevolve = needevolve or (len(c.children()) > 0)
            obsolete.createmarkers(repo, [(c, (repo[newnode],))])
            rewrote += 1
        ui.status('changed topic on %d changes\n' % rewrote)
        if needevolve:
            evolvetarget = 'topic(%s)' % topic if topic else 'not topic()'
            ui.status('please run hg evolve --rev "%s" now\n' % evolvetarget)
    if clear:
        if repo.vfs.exists('topic'):
            repo.vfs.unlink('topic')
        return
    if topic is not None:
        with repo.vfs.open('topic', 'w') as f:
            f.write(topic)
        return
    current = repo.currenttopic
    for t in sorted(repo.topics):
        marker = '*' if t == current else ' '
        ui.write(' %s %s\n' % (marker, t))


def updatewrap(orig, ui, repo, *args, **kwargs):
    ret = orig(ui, repo, *args, **kwargs)
    pctx = repo['.']
    if pctx.phase() == phases.public and repo.vfs.exists('topic'):
        repo.vfs.unlink('topic')
    else:
        # inherit the topic of the parent revision
        t = pctx.extra().get('topic', '')
        if t and pctx.phase() > phases.public:
            with repo.vfs.open('topic', 'w') as f:
                f.write(t)
    return ret

extensions.wrapcommand(commands.table, 'update', updatewrap)
topicrevset.modsetup()