convert: fix svn_source.latest()
authorPatrick Mezard <pmezard@gmail.com>
Sat, 26 Jan 2008 14:45:04 +0100
changeset 5955 c4496b7c10ce
parent 5954 851402e53337
child 5956 094638b3cbed
convert: fix svn_source.latest()
hgext/convert/subversion.py
tests/test-convert-svn-move
tests/test-convert-svn-move.out
--- a/hgext/convert/subversion.py	Sat Jan 26 14:45:04 2008 +0100
+++ b/hgext/convert/subversion.py	Sat Jan 26 14:45:04 2008 +0100
@@ -200,9 +200,9 @@
         except IOError, e:
             pass
 
-        self.last_changed = self.latest(self.module, latest)
-
-        self.head = self.revid(self.last_changed)
+        self.head = self.latest(self.module, latest)
+        self.last_changed = self.revnum(self.head)
+        
         self._changescache = None
 
         if os.path.exists(os.path.join(url, '.svn/entries')):
@@ -252,8 +252,7 @@
         if trunk:
             oldmodule = self.module or ''
             self.module += '/' + trunk
-            lt = self.latest(self.module, self.last_changed)
-            self.head = self.revid(lt)
+            self.head = self.latest(self.module, self.last_changed)
 
         # First head in the list is the module's head
         self.heads = [self.head]
@@ -266,10 +265,10 @@
                                         self.ctx)
             for branch in branchnames.keys():
                 module = '%s/%s/%s' % (oldmodule, branches, branch)
-                brevnum = self.latest(module, self.last_changed)
-                brev = self.revid(brevnum, module)
-                self.ui.note('found branch %s at %d\n' % (branch, brevnum))
-                self.heads.append(brev)
+                brevid = self.latest(module, self.last_changed)
+                self.ui.note('found branch %s at %d\n' % 
+                             (branch, self.revnum(brevid)))
+                self.heads.append(brevid)
 
         return self.heads
 
@@ -369,7 +368,10 @@
         return uuid, mod, revnum
 
     def latest(self, path, stop=0):
-        'find the latest revision affecting path, up to stop'
+        """Find the latest revid affecting path, up to stop. It may return
+        a revision in a different module, since a branch may be moved without
+        a change being reported.
+        """
         if not stop:
             stop = svn.ra.get_latest_revnum(self.ra)
         try:
@@ -381,7 +383,28 @@
         if not dirent:
             raise util.Abort('%s not found up to revision %d' % (path, stop))
 
-        return dirent.created_rev
+        # stat() gives us the previous revision on this line of development, but
+        # it might be in *another module*. Fetch the log and detect renames down
+        # to the latest revision.
+        stream = get_log(self.url, [path], stop, dirent.created_rev)
+        try:
+            for entry in stream:
+                paths, revnum, author, date, message = entry
+                if revnum <= dirent.created_rev:
+                    break
+
+                for p in paths:
+                    if not path.startswith(p) or not paths[p].copyfrom_path:
+                        continue
+                    newpath = paths[p].copyfrom_path + path[len(p):]
+                    self.ui.debug("branch renamed from %s to %s at %d\n" % 
+                                  (path, newpath, revnum))
+                    path = newpath
+                    break
+        finally:
+            stream.close()
+
+        return self.revid(dirent.created_rev, path)
 
     def get_blacklist(self):
         """Avoid certain revision numbers.
@@ -624,10 +647,11 @@
                 ent = orig_paths[self.module]
                 if ent.copyfrom_path:
                     # ent.copyfrom_rev may not be the actual last revision
-                    prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
-                    parents = [self.revid(prev, ent.copyfrom_path)]
-                    self.ui.note('found parent of branch %s at %d: %s\n' % \
-                                     (self.module, prev, ent.copyfrom_path))
+                    previd = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
+                    parents = [previd]
+                    prevmodule, prevnum = self.revsplit(previd)[1:]
+                    self.ui.note('found parent of branch %s at %d: %s\n' %
+                                 (self.module, prevnum, prevmodule))
                 else:
                     self.ui.debug("No copyfrom path, don't know what to do.\n")
 
@@ -704,7 +728,7 @@
                     firstrevnum = self.revnum(firstcset.rev)
                     if firstrevnum > 1:
                         latest = self.latest(self.module, firstrevnum - 1)
-                        firstcset.parents.append(self.revid(latest))
+                        firstcset.parents.append(latest)
                 except util.Abort:
                     pass
         except SubversionException, (inst, num):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-svn-move	Sat Jan 26 14:45:04 2008 +0100
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+"$TESTDIR/hghave" svn svn-bindings || exit 80
+
+fix_path()
+{
+    tr '\\' /
+}
+
+echo "[extensions]" >> $HGRCPATH
+echo "convert = " >> $HGRCPATH
+echo "hgext.graphlog =" >> $HGRCPATH
+
+svnadmin create svn-repo
+
+svnpath=`pwd | fix_path`
+# SVN wants all paths to start with a slash. Unfortunately,
+# Windows ones don't. Handle that.
+expr $svnpath : "\/" > /dev/null
+if [ $? -ne 0 ]; then
+    svnpath='/'$svnpath
+fi
+
+echo % initial svn import
+mkdir projA
+cd projA
+mkdir trunk
+mkdir trunk/d1
+echo b > trunk/d1/b
+cd ..
+
+svnurl=file://$svnpath/svn-repo/projA
+svn import -m "init projA" projA $svnurl | fix_path
+
+# Build a module renaming chain which used to confuse the converter.
+echo % update svn repository
+svn co $svnurl A | fix_path
+cd A
+svn mv $svnurl/trunk $svnurl/subproject -m movedtrunk
+svn up
+mkdir subproject/trunk
+svn add subproject/trunk
+svn ci -m createtrunk
+mkdir subproject/branches
+svn add subproject/branches
+svn ci -m createbranches
+svn mv $svnurl/subproject/d1 $svnurl/subproject/trunk/d1 -m moved1
+svn up
+echo b >> subproject/trunk/d1/b
+svn ci -m changeb
+svn mv $svnurl/subproject/trunk/d1 $svnurl/subproject/branches/d1 -m moved1again
+cd ..
+
+echo % convert trunk and branches
+hg convert --datesort $svnurl/subproject A-hg
+
+cd A-hg
+hg glog --template '#rev# #desc|firstline# files: #files#\n'
+hg branches | sed 's/:.*/:/'
+cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-svn-move.out	Sat Jan 26 14:45:04 2008 +0100
@@ -0,0 +1,68 @@
+% initial svn import
+Adding         projA/trunk
+Adding         projA/trunk/d1
+Adding         projA/trunk/d1/b
+
+Committed revision 1.
+% update svn repository
+A    A/trunk
+A    A/trunk/d1
+A    A/trunk/d1/b
+Checked out revision 1.
+
+Committed revision 2.
+D    trunk
+A    subproject
+A    subproject/d1
+A    subproject/d1/b
+Updated to revision 2.
+A         subproject/trunk
+Adding         subproject/trunk
+
+Committed revision 3.
+A         subproject/branches
+Adding         subproject/branches
+
+Committed revision 4.
+
+Committed revision 5.
+A    subproject/trunk/d1
+A    subproject/trunk/d1/b
+D    subproject/d1
+Updated to revision 5.
+Sending        subproject/trunk/d1/b
+Transmitting file data .
+Committed revision 6.
+
+Committed revision 7.
+% convert trunk and branches
+initializing destination A-hg repository
+scanning source...
+sorting...
+converting...
+7 init projA
+6 createtrunk
+5 moved1
+4 moved1
+3 changeb
+2 changeb
+1 moved1again
+0 moved1again
+o  7 moved1again files: d1/b
+|
+| o  6 moved1again files:
+| |
+o |  5 changeb files: d1/b
+| |
+| o  4 changeb files: b
+| |
+o |  3 moved1 files: d1/b
+| |
+| o  2 moved1 files:
+| |
+o |  1 createtrunk files:
+ /
+o  0 init projA files: b
+
+default                        7:
+d1                             6: