Added .revision_id() to all the VCSs.
authorW. Trevor King <wking@drexel.edu>
Thu, 23 Jul 2009 18:19:15 +0000 (14:19 -0400)
committerW. Trevor King <wking@drexel.edu>
Thu, 23 Jul 2009 18:19:15 +0000 (14:19 -0400)
This makes it easier to compare recent revisions without a human
around to give you revision numbers.

libbe/arch.py
libbe/bzr.py
libbe/darcs.py
libbe/git.py
libbe/hg.py
libbe/rcs.py

index 2f45aa924fa497ff23ef0e84051be6bc89eb536f..1bdc8ae90cb83b851afa0b7f3da67d37062171b0 100644 (file)
@@ -176,7 +176,6 @@ class Arch(RCS):
         self._get_archive_project_name(root)
 
         return root
-
     def _get_archive_name(self, root):
         status,output,error = self._u_invoke_client("archives")
         lines = output.split('\n')
@@ -188,7 +187,6 @@ class Arch(RCS):
             if os.path.realpath(location) == os.path.realpath(root):
                 self._archive_name = archive
         assert self._archive_name != None
-
     def _get_archive_project_name(self, root):
         # get project names
         status,output,error = self._u_invoke_client("tree-version", directory=root)
@@ -281,6 +279,16 @@ class Arch(RCS):
         assert revpath.startswith(self._archive_project_name()+'--')
         revision = revpath[len(self._archive_project_name()+'--'):]
         return revpath
+    def _rcs_revision_id(self, index):
+        status,output,error = self._u_invoke_client("logs")
+        logs = output.splitlines()
+        first_log = logs.pop(0)
+        assert first_log == "base-0", first_log
+        try:
+            log = logs[index]
+        except IndexError:
+            return None
+        return "%s--%s" % (self._archive_project_name(), log)
 
 class CantAddFile(Exception):
     def __init__(self, file):
index b33292c1ef1e1bbf676e21622af637c937af197e..7457815a619d5addc97c491b95ae3233ebd8fdd8 100644 (file)
@@ -93,6 +93,14 @@ class Bzr(RCS):
         assert len(match.groups()) == 1
         revision = match.groups()[0]
         return revision
+    def _rcs_revision_id(self, index):
+        status,output,error = self._u_invoke_client("revno")
+        current_revision = int(output)
+        if index >= current_revision or index < -current_revision:
+            return None
+        if index >= 0:
+            return str(index+1) # bzr commit 0 is the empty tree.
+        return str(current_revision+index+1)
     def postcommit(self):
         try:
             self._u_invoke_client('merge')
index e7132c017cafe2834d11ca3925e8a0b6c4e34eb3..0720ed98ddee4fd76da55c9476e17d97fdfe7f9f 100644 (file)
@@ -18,8 +18,13 @@ import codecs
 import os
 import re
 import sys
-import unittest
+try: # import core module, Python >= 2.5
+    from xml.etree import ElementTree
+except ImportError: # look for non-core module
+    from elementtree import ElementTree
+from xml.sax.saxutils import unescape
 import doctest
+import unittest
 
 import rcs
 from rcs import RCS
@@ -138,24 +143,36 @@ class Darcs(RCS):
         args = ['record', '--all', '--author', id, '--logfile', commitfile]
         status,output,error = self._u_invoke_client(*args)
         empty_strings = ["No changes!"]
-        revision = None
         if self._u_any_in_string(empty_strings, output) == True:
             if allow_empty == False:
                 raise rcs.EmptyCommit()
-            else: # we need a extra call to get the current revision
-                args = ["changes", "--last=1", "--xml"]
-                status,output,error = self._u_invoke_client(*args)
-                revline = re.compile("[ \t]*<name>(.*)</name>")
-                # note that darcs does _not_ make an empty revision.
-                # this returns the last non-empty revision id...
+            # note that darcs does _not_ make an empty revision.
+            # this returns the last non-empty revision id...
+            revision = self._rcs_revision_id(-1)
         else:
             revline = re.compile("Finished recording patch '(.*)'")
-        match = revline.search(output)
-        assert match != None, output+error
-        assert len(match.groups()) == 1
-        revision = match.groups()[0]
+            match = revline.search(output)
+            assert match != None, output+error
+            assert len(match.groups()) == 1
+            revision = match.groups()[0]
         return revision
-
+    def _rcs_revision_id(self, index):
+        status,output,error = self._u_invoke_client("changes", "--xml")
+        revisions = []
+        xml_str = output.encode("unicode_escape").replace(r"\n", "\n")
+        element = ElementTree.XML(xml_str)
+        assert element.tag == "changelog", element.tag
+        for patch in element.getchildren():
+            assert patch.tag == "patch", patch.tag
+            for child in patch.getchildren():
+                if child.tag == "name":
+                    text = unescape(unicode(child.text).decode("unicode_escape").strip())
+                    revisions.append(text)
+        revisions.reverse()
+        try:
+            return revisions[index]
+        except IndexError:
+            return None
 \f    
 rcs.make_rcs_testcase_subclasses(Darcs, sys.modules[__name__])
 
index 2f9ffa9500f97ba7bb0e8d4d50980b82b07bcc64..2b45679fdcad7aa7e225ca87673030206e836ce4 100644 (file)
@@ -111,7 +111,18 @@ class Git(RCS):
         assert match != None, output+error
         assert len(match.groups()) == 3
         revision = match.groups()[1]
-        return revision
+        full_revision = self._rcs_revision_id(-1)
+        assert full_revision.startswith(revision), \
+            "Mismatched revisions:\n%s\n%s" % (revision, full_revision)
+        return full_revision
+    def _rcs_revision_id(self, index):
+        args = ["rev-list", "--first-parent", "--reverse", "HEAD"]
+        status,output,error = self._u_invoke_client(*args)
+        commits = output.splitlines()
+        try:
+            return commits[index]
+        except IndexError:
+            return None
 
 \f    
 rcs.make_rcs_testcase_subclasses(Git, sys.modules[__name__])
index a20eeb5ca73f49e3daf7c4759c69cced8de37786..fcda829f8305fa85f34746a26c6b5dd0c94342ac 100644 (file)
@@ -80,14 +80,14 @@ class Hg(RCS):
             strings = ["nothing changed"]
             if self._u_any_in_string(strings, output) == True:
                 raise rcs.EmptyCommit()
-        status,output,error = self._u_invoke_client('identify')
-        revision = None
-        revline = re.compile("(.*) tip")
-        match = revline.search(output)
-        assert match != None, output+error
-        assert len(match.groups()) == 1
-        revision = match.groups()[0]
-        return revision
+        return self._rcs_revision_id(-1)
+    def _rcs_revision_id(self, index, style="id"):
+        args = ["identify", "--rev", str(int(index)), "--%s" % style]
+        kwargs = {"expect": (0,255)}
+        status,output,error = self._u_invoke_client(*args, **kwargs)
+        if status == 0:
+            return output.strip()
+        return None
 
 \f    
 rcs.make_rcs_testcase_subclasses(Hg, sys.modules[__name__])
index 1e1cfa7b7f73e3c16133e3db8d91f7541d194a3e..d979df03563af586e978db3cbb0ffc936422f6cb 100644 (file)
@@ -214,6 +214,16 @@ class RCS(object):
         changes to commit.
         """
         return None
+    def _rcs_revision_id(self, index):
+        """
+        Return the name of the <index>th revision.  Index will be an
+        integer (possibly <= 0).  The choice of which branch to follow
+        when crossing branches/merges is not defined.
+
+        Return None if revision IDs are not supported, or if the
+        specified revision does not exist.
+        """
+        return None
     def installed(self):
         try:
             self._rcs_help()
@@ -407,6 +417,18 @@ class RCS(object):
         pass
     def postcommit(self, directory):
         pass
+    def revision_id(self, index=None):
+        """
+        Return the name of the <index>th revision.  The choice of
+        which branch to follow when crossing branches/merges is not
+        defined.
+
+        Return None if index==None, revision IDs are not supported, or
+        if the specified revision does not exist.
+        """
+        if index == None:
+            return None
+        return self._rcs_revision_id(index)
     def _u_any_in_string(self, list, string):
         """
         Return True if any of the strings in list are in string.
@@ -814,6 +836,30 @@ class RCS_commit_TestCase(RCSTestCase):
             self.failUnlessEqual(
                 self.test_contents['rev_1'], committed_contents)
 
+    def test_revision_id_as_committed(self):
+        """Check for compatibility between .commit() and .revision_id()"""
+        if not self.rcs.versioned:
+            self.failUnlessEqual(self.rcs.revision_id(5), None)
+            return
+        committed_revisions = []
+        for path in self.test_files:
+            full_path = self.full_path(path)
+            self.rcs.set_file_contents(
+                full_path, self.test_contents['rev_1'])
+            revision = self.rcs.commit("Initial %s contents." % path)
+            committed_revisions.append(revision)
+            self.rcs.set_file_contents(
+                full_path, self.test_contents['uncommitted'])
+            revision = self.rcs.commit("Altered %s contents." % path)
+            committed_revisions.append(revision)
+        for i,revision in enumerate(committed_revisions):
+            self.failUnlessEqual(self.rcs.revision_id(i), revision)
+            i += -len(committed_revisions) # check negative indices
+            self.failUnlessEqual(self.rcs.revision_id(i), revision)
+        i = len(committed_revisions)
+        self.failUnlessEqual(self.rcs.revision_id(i), None)
+        self.failUnlessEqual(self.rcs.revision_id(-i-1), None)
+
 
 class RCS_duplicate_repo_TestCase(RCSTestCase):
     """Test cases for RCS.duplicate_repo method."""