frk.py
author Bobby Holley <bobbyholley@gmail.com>
Mon, 12 Jul 2010 14:35:24 -0400
changeset 2 8723e06e57c8
permissions -rw-r--r--
added the frk extension
# Mercurial extension allowing revisions to be specified in terms of common ancestors.
#
# Copyright 2010 Bobby Holley <bobbyholley@gmail.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.

'''Adds the ability to address revisions based in the nearest common ancestor of
   two revisions (called frk, short for 'fork in the graph'). This syntax can be
   used anywhere a revision would normally be specified with a SHA-1 identifier
   or local revision number.

   Syntax:
   frk[rev1,rev2]: The nearest common ancestor itself
   lfrk[rev1,rev2]: The first child of frk[rev1,rev2] on the path to rev1
   rfrk[rev1,rev2]: The first child of frk[rev1,rev2] on the path to rev2

   Note that lfrk[rev1,rev2] == rfrk[rev2,rev1]

   b
   |    d
   |   /
   o  /
   | o
   |/
   x    c
   |   /
   |  y
   | /
   |/
   a

   In the above example:
     frk[b,c] == a
     rfrk[b,c] == lfrk[c,b] == y
     lfrk[b,c] == rfrk[c,b] == x
     frk[b,d] == x

   This extension assumes that there is only one child of frk that is an ancestor of rev1,
   that only one child of frk that is an ancestor of rev2, and that these children are
   distinct. The behavior in other use cases is undefined.
   '''

import re
from mercurial import util

# Hook called to let us set up the repo structure. We dynamically subclass the
# repository object to modify the 'lookup' method, as described in extensions.py
def reposetup(ui, repo):
    class frkrepo(repo.__class__):

        def lookup(self, key):
            matches = re.search('^((l|r)?frk)\[(.+),(.*)\]$', key)
            if matches:

                # Parse the arguments
                subcmd = matches.groups()[0]
                rev1   = self.lookup(matches.groups()[2])
                rev2   = self.lookup(matches.groups()[3])

                # Get the common ancestor
                frk = self.changelog.ancestor(rev1, rev2)

                # We just want the common ancestor. This is equivalent to
                # the revision obtain from hg debugancestor
                if (subcmd == 'frk'):
                    return frk

                # Go one commit down the path from frk to rev1
                elif (subcmd == 'lfrk'):

                    # changelog.children() gives us an array of SHA-1 identifiers
                    for child in self.changelog.children(frk):

                        # Descendants operates on local rev numbers
                        childrev = self.changelog.rev(child)
                        rev1rev = self.changelog.rev(rev1)
                        if rev1rev in self.changelog.descendants(childrev):
                            return self.lookup(child)
                    raise util.Abort("Then how is rev1 a descendant of frk?")

                # Go one commit down the path from frk to rev2
                elif (subcmd == 'rfrk'):

                    # changelog.children() gives us an array of SHA-1 identifiers
                    for child in self.changelog.children(frk):

                        # Descendants operates on local rev numbers
                        childrev = self.changelog.rev(child)
                        rev2rev = self.changelog.rev(rev2)
                        if rev2rev in self.changelog.descendants(childrev):
                            return self.lookup(child)

                    raise util.Abort("Then how is rev2 a descendant of frk?")

                # We should have a valid command if the regexp succeeded
                else:
                    raise util.Abort("How did we get here?")

            # Default behavior
            return super(frkrepo, self).lookup(key)

    # The magic
    repo.__class__ = frkrepo