convert: follow svn tags history (issue953)
authorPatrick Mezard <pmezard@gmail.com>
Sat, 29 Mar 2008 17:15:45 +0100
changeset 6399 5efd447a9b8d
parent 6398 0e91ef0b52e7
child 6400 c237b27e1350
convert: follow svn tags history (issue953)
hgext/convert/subversion.py
tests/test-convert-svn-tags
tests/test-convert-svn-tags.out
--- a/hgext/convert/subversion.py	Sat Mar 29 17:15:43 2008 +0100
+++ b/hgext/convert/subversion.py	Sat Mar 29 17:15:45 2008 +0100
@@ -369,18 +369,58 @@
         if self.tags is None:
             return tags
 
-        start = self.revnum(self.head)
+        # svn tags are just a convention, project branches left in a
+        # 'tags' directory. There is no other relationship than
+        # ancestry, which is expensive to discover and makes them hard
+        # to update incrementally.  Worse, past revisions may be
+        # referenced by tags far away in the future, requiring a deep
+        # history traversal on every calculation.  Current code
+        # performs a single backward traversal, tracking moves within
+        # the tags directory (tag renaming) and recording a new tag
+        # everytime a project is copied from outside the tags
+        # directory. It also lists deleted tags, this behaviour may
+        # change in the future.
+        pendings = []
+        tagspath = self.tags
+        start = svn.ra.get_latest_revnum(self.ra)
         try:
-            for entry in get_log(self.url, [self.tags], self.startrev, start):
-                orig_paths, revnum, author, date, message = entry
-                for path in orig_paths:
-                    if not path.startswith(self.tags+'/'):
+            for entry in get_log(self.url, [self.tags], start, self.startrev):
+                origpaths, revnum, author, date, message = entry
+                copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p,e 
+                          in origpaths.iteritems() if e.copyfrom_path]
+                copies.sort()
+                # Apply moves/copies from more specific to general
+                copies.reverse()
+
+                srctagspath = tagspath
+                if copies and copies[-1][2] == tagspath:
+                    # Track tags directory moves
+                    srctagspath = copies.pop()[0]
+
+                for source, sourcerev, dest in copies:
+                    if not dest.startswith(tagspath + '/'):
                         continue
-                    ent = orig_paths[path]
-                    source = ent.copyfrom_path
-                    rev = ent.copyfrom_rev
-                    tag = path.split('/')[-1]
-                    tags[tag] = self.revid(rev, module=source)
+                    for tag in pendings:
+                        if tag[0].startswith(dest):
+                            tagpath = source + tag[0][len(dest):]
+                            tag[:2] = [tagpath, sourcerev]
+                            break
+                    else:
+                        pendings.append([source, sourcerev, dest.split('/')[-1]])
+
+                # Tell tag renamings from tag creations
+                remainings = []
+                for source, sourcerev, tagname in pendings:
+                    if source.startswith(srctagspath):
+                        remainings.append([source, sourcerev, tagname])
+                        continue
+                    # From revision may be fake, get one with changes
+                    tagid = self.latest(source, sourcerev)
+                    if tagid:
+                        tags[tagname] = tagid
+                pendings = remainings
+                tagspath = srctagspath
+
         except SubversionException, (inst, num):
             self.ui.note('no tags found at revision %d\n' % start)
         return tags
--- a/tests/test-convert-svn-tags	Sat Mar 29 17:15:43 2008 +0100
+++ b/tests/test-convert-svn-tags	Sat Mar 29 17:15:45 2008 +0100
@@ -27,6 +27,7 @@
 mkdir trunk
 mkdir branches
 mkdir tags
+mkdir unrelated
 cd ..
 
 svnurl=file://$svnpath/svn-repo/projA
@@ -42,12 +43,23 @@
 svn ci -m changea
 echo a >> trunk/a
 svn ci -m changea2
+# Add an unrelated commit to test that tags are bound to the
+# correct "from" revision and not a dummy one
+echo a >> unrelated/dummy
+svn add unrelated/dummy
+svn ci -m unrelatedchange
 echo % tag current revision
 svn up
 svn copy trunk tags/trunk.v1
-svn ci -m "tagging trunk.v1"
+svn copy trunk tags/trunk.badtag
+svn ci -m "tagging trunk.v1 trunk.badtag"
 echo a >> trunk/a
 svn ci -m changea3
+echo % fix the bad tag
+# trunk.badtag should not show in converted tags
+svn up
+svn mv tags/trunk.badtag tags/trunk.goodtag
+svn ci -m "fix trunk.badtag"
 cd ..
 
 echo % convert
--- a/tests/test-convert-svn-tags.out	Sat Mar 29 17:15:43 2008 +0100
+++ b/tests/test-convert-svn-tags.out	Sat Mar 29 17:15:45 2008 +0100
@@ -1,11 +1,13 @@
 % initial svn import
 Adding         projA/trunk
+Adding         projA/unrelated
 Adding         projA/branches
 Adding         projA/tags
 
 Committed revision 1.
 % update svn repository
 A    A/trunk
+A    A/unrelated
 A    A/branches
 A    A/tags
 Checked out revision 1.
@@ -19,15 +21,30 @@
 Sending        trunk/a
 Transmitting file data .
 Committed revision 4.
+A         unrelated/dummy
+Adding         unrelated/dummy
+Transmitting file data .
+Committed revision 5.
 % tag current revision
-At revision 4.
+At revision 5.
 A         tags/trunk.v1
+A         tags/trunk.badtag
+Adding         tags/trunk.badtag
 Adding         tags/trunk.v1
 
-Committed revision 5.
+Committed revision 6.
 Sending        trunk/a
 Transmitting file data .
-Committed revision 6.
+Committed revision 7.
+% fix the bad tag
+At revision 7.
+A         tags/trunk.goodtag
+D         tags/trunk.badtag/a
+D         tags/trunk.badtag
+Deleting       tags/trunk.badtag
+Adding         tags/trunk.goodtag
+
+Committed revision 8.
 % convert
 initializing destination A-hg repository
 scanning source...
@@ -43,7 +60,7 @@
 |
 o  4 changea3 tags:
 |
-o  3 changea2 tags: trunk.v1
+o  3 changea2 tags: trunk.v1 trunk.goodtag
 |
 o  2 changea tags:
 |
@@ -53,3 +70,4 @@
 
 tip
 trunk.v1
+trunk.goodtag