changeset 316:c3c647aff97c

Merge with danchr's changes.
author Augie Fackler <durin42@gmail.com>
date Sun, 03 May 2009 21:44:53 -0500
parents 963d27a0b1c2 (diff) 15b8bab03504 (current diff)
children 5dc8fee7fc96
files hg_delta_editor.py
diffstat 9 files changed, 764 insertions(+), 92 deletions(-) [+]
line wrap: on
line diff
--- a/cmdutil.py
+++ b/cmdutil.py
@@ -230,7 +230,7 @@ def commit_from_rev(ui, repo, rev_ctx, h
         file_data[file] = base_data, new_data, action
 
     def svnpath(p):
-        return '%s/%s' % (branch_path, p)
+        return ('%s/%s' % (branch_path, p)).rstrip('/')
 
     changeddirs = []
     for d, v1, v2 in extchanges:
--- a/hg_delta_editor.py
+++ b/hg_delta_editor.py
@@ -299,8 +299,8 @@ class HgChangeReceiver(delta.Editor):
             return path[len(test)+1:], self._localname(test), test
         if existing:
             return None, None, None
-        if path.startswith('trunk/'):
-            path = test.split('/')[1:]
+        if path == 'trunk' or path.startswith('trunk/'):
+            path = path.split('/')[1:]
             test = 'trunk'
         elif path.startswith('branches/'):
             elts = path.split('/')
@@ -460,7 +460,7 @@ class HgChangeReceiver(delta.Editor):
             return branch[3:]
         return 'branches/%s' % branch
 
-    def __determine_parent_branch(self, p, src_path, src_rev, revnum):
+    def _determine_parent_branch(self, p, src_path, src_rev, revnum):
         if src_path is not None:
             src_file, src_branch = self._path_and_branch_for_path(src_path)
             src_tag = self._is_path_tag(src_path)
@@ -502,40 +502,50 @@ class HgChangeReceiver(delta.Editor):
                       and t_name in self.tags):
                         tags_to_delete.add(t_name)
                 continue
-            # At this point we know the path is not a tag. In that case, we only care if it
-            # is the root of a new branch (in this function). This is determined by the
-            # following checks:
-            # 1. Is the file located inside any currently known branch?
-            #     If yes, then we're done with it, this isn't interesting.
-            # 2. Does the file have copyfrom information that means it is a copy from the root
-            #    of some other branch?
-            #     If yes, then we're done: this is a new branch, and we record the copyfrom in
-            #     added_branches
-            # 3. Neither of the above. This could be a branch, but it might never work out for
-            #    us. It's only ever a branch (as far as we're concerned) if it gets committed
-            #    to, which we have to detect at file-write time anyway. So we do nothing here.
-            # 4. It's the root of an already-known branch, with an action of 'D'. We mark the
-            #    branch as deleted.
-            # 5. It's the parent directory of one or more already-known branches, so we mark them
-            #    as deleted.
-            # 6. It's a branch being replaced by another branch - the action will be 'R'.
+            # At this point we know the path is not a tag. In that
+            # case, we only care if it is the root of a new branch (in
+            # this function). This is determined by the following
+            # checks:
+            # 1. Is the file located inside any currently known
+            #    branch?  If yes, then we're done with it, this isn't
+            #    interesting.
+            # 2. Does the file have copyfrom information? If yes, then
+            #    we're done: this is a new branch, and we record the
+            #    copyfrom in added_branches if it comes from the root
+            #    of another branch, or create it from scratch.
+            # 3. Neither of the above. This could be a branch, but it
+            #    might never work out for us. It's only ever a branch
+            #    (as far as we're concerned) if it gets committed to,
+            #    which we have to detect at file-write time anyway. So
+            #    we do nothing here.
+            # 4. It's the root of an already-known branch, with an
+            #    action of 'D'. We mark the branch as deleted.
+            # 5. It's the parent directory of one or more
+            #    already-known branches, so we mark them as deleted.
+            # 6. It's a branch being replaced by another branch - the
+            #    action will be 'R'.
             fi, br = self._path_and_branch_for_path(p)
             if fi is not None:
                 if fi == '':
                     if paths[p].action == 'D':
                         self.branches_to_delete.add(br) # case 4
                     elif paths[p].action == 'R':
-                        added_branches.update(self.__determine_parent_branch(p, paths[p].copyfrom_path,
-                                                                             paths[p].copyfrom_rev,
-                                                                             revision.revnum))
+                        parent = self._determine_parent_branch(
+                            p, paths[p].copyfrom_path, paths[p].copyfrom_rev,
+                            revision.revnum)
+                        added_branches.update(parent)
                 continue # case 1
             if paths[p].action == 'D':
-                # check for case 5
                 for known in self.branches:
                     if self._svnpath(known).startswith(p):
                         self.branches_to_delete.add(known) # case 5
-            added_branches.update(self.__determine_parent_branch(p, paths[p].copyfrom_path,
-                                                                 paths[p].copyfrom_rev, revision.revnum))
+            parent = self._determine_parent_branch(
+                p, paths[p].copyfrom_path, paths[p].copyfrom_rev, revision.revnum)
+            if not parent and paths[p].copyfrom_path:
+                bpath, branch = self._path_and_branch_for_path(p, False)
+                if bpath is not None and branch not in self.branches:
+                    parent = {branch: (None, 0, revision.revnum)}
+            added_branches.update(parent)
         for t in tags_to_delete:
             del self.tags[t]
         for br in self.branches_to_delete:
@@ -802,7 +812,7 @@ class HgChangeReceiver(delta.Editor):
                 base = 'link ' + base
             self.set_file(svnpath, base, 'x' in fctx.flags(), 'l' in fctx.flags())
         else:
-            self.missing_plaintexts.add(path)
+            self.missing_plaintexts.add(svnpath)
 
     def delete_entry(self, path, revision_bogus, parent_baton, pool=None):
         br_path, branch = self._path_and_branch_for_path(path)
--- a/svnexternals.py
+++ b/svnexternals.py
@@ -78,7 +78,6 @@ class BadDefinition(Exception):
 
 re_defold = re.compile(r'^(.*?)\s+(?:-r\s*(\d+)\s+)?([a-zA-Z]+://.*)$')
 re_defnew = re.compile(r'^(?:-r\s*(\d+)\s+)?((?:[a-zA-Z]+://|\^/).*)\s+(.*)$')
-re_pegrev = re.compile(r'^(.*)@(\d+)$')
 re_scheme = re.compile(r'^[a-zA-Z]+://')
 
 def parsedefinition(line):
@@ -89,19 +88,18 @@ def parsedefinition(line):
     # potential quotes. svn documentation is not really talkative about
     # these either.
     line = line.strip()
+    pegrev = None
     m = re_defnew.search(line)
     if m:
         rev, source, path = m.group(1, 2, 3)
+        if '@' in source:
+            source, pegrev = source.rsplit('@', 1)
     else:
         m = re_defold.search(line)
         if not m:
             raise BadDefinition()
         path, rev, source = m.group(1, 2, 3)
-    # Look for peg revisions
-    m = re_pegrev.search(source)
-    if m:
-        source, rev = m.group(1, 2)
-    return (path, rev, source)
+    return (path, rev, source, pegrev)
 
 def parsedefinitions(ui, repo, svnroot, exts):
     """Return (targetdir, revision, source) tuples. Fail if nested
@@ -111,7 +109,7 @@ def parsedefinitions(ui, repo, svnroot, 
     for base in sorted(exts):
         for line in exts[base]:
             try:
-                path, rev, source = parsedefinition(line)
+                path, rev, source, pegrev = parsedefinition(line)
             except BadDefinition:
                 ui.warn(_('ignoring invalid external definition: %r' % line))
                 continue
@@ -124,7 +122,7 @@ def parsedefinitions(ui, repo, svnroot, 
                 continue
             wpath = hgutil.pconvert(os.path.join(base, path))
             wpath = hgutil.canonpath(repo.root, '', wpath)
-            defs.append((wpath, rev, source))
+            defs.append((wpath, rev, source, pegrev))
     # Check target dirs are not nested
     defs.sort()
     for i, d in enumerate(defs):
@@ -189,13 +187,18 @@ class externalsupdater:
         self.repo = repo
         self.ui = ui
 
-    def update(self, wpath, rev, source):
+    def update(self, wpath, rev, source, pegrev):
         path = self.repo.wjoin(wpath)
         revspec = []
         if rev:
             revspec = ['-r', rev]
         if os.path.isdir(path):
             exturl, extroot, extrev = getsvninfo(path)
+            # Comparing the source paths is not enough, but I don't
+            # know how to compare path+pegrev. The following update
+            # might fail if the path was replaced by another unrelated
+            # one. It can be fixed manually by deleting the externals
+            # and updating again.
             if source == exturl:
                 if extrev != rev:
                     self.ui.status(_('updating external on %s@%s\n') %
@@ -208,6 +211,10 @@ class externalsupdater:
         cwd = os.path.join(self.repo.root, cwd)
         if not os.path.isdir(cwd):
             os.makedirs(cwd)
+        if not pegrev and rev:
+            pegrev = rev
+        if pegrev:
+            source = '%s@%s' % (source, pegrev)
         self.ui.status(_('fetching external %s@%s\n') % (wpath, rev or 'HEAD'))
         self.svn(['co'] + revspec + [source, dest], cwd)
 
@@ -264,7 +271,7 @@ def updateexternals(ui, args, repo, **op
     actions = computeactions(ui, repo, svnroot, oldext, newext)
     for action, ext in actions:
         if action == 'u':
-            updater.update(ext[0], ext[1], ext[2])
+            updater.update(ext[0], ext[1], ext[2], ext[3])
         elif action == 'd':
             updater.delete(ext[0])
         else:
--- a/tests/fixtures/externals.sh
+++ b/tests/fixtures/externals.sh
@@ -38,13 +38,13 @@ svn ci -m "set externals on ."
 # Add another one
 cat > externals <<EOF
 ^/externals/project1 deps/project1
-^/externals/project2 deps/project2
+-r2 ^/externals/project2@2 deps/project2
 EOF
 svn propset -F externals svn:externals .
 svn ci -m "update externals on ."
 # Suppress an external and add one on a subdir
 cat > externals <<EOF
-^/externals/project2 deps/project2
+-r2 ^/externals/project2@2 deps/project2
 EOF
 svn propset -F externals svn:externals .
 mkdir subdir
@@ -72,6 +72,14 @@ svn ci -m 'remove externals subdir'
 # Remove the property on subdir2
 svn propdel svn:externals subdir2
 svn ci -m 'remove externals subdir2'
+# Kill project2 externals, peg revision should preserve it
+cd ..
+svn up
+svn rm externals/project2
+svn ci -m 'remove externals project2'
+cd trunk
+echo a >> a
+svn ci -m 'change a'
 cd ../..
 
 svnadmin dump testrepo > ../externals.svndump
--- a/tests/fixtures/externals.svndump
+++ b/tests/fixtures/externals.svndump
@@ -1,6 +1,6 @@
 SVN-fs-dump-format-version: 2
 
-UUID: ab7049d2-24fc-4035-b126-5f7012d66a0d
+UUID: ac40e40a-7fbf-47e1-90a7-c1de12a5b013
 
 Revision-number: 0
 Prop-content-length: 56
@@ -9,7 +9,7 @@ Content-length: 56
 K 8
 svn:date
 V 27
-2009-01-12T23:19:30.150669Z
+2009-05-03T14:07:14.149596Z
 PROPS-END
 
 Revision-number: 1
@@ -27,7 +27,7 @@ pmezard
 K 8
 svn:date
 V 27
-2009-01-12T23:19:30.267365Z
+2009-05-03T14:07:14.234026Z
 PROPS-END
 
 Node-path: branches
@@ -72,7 +72,7 @@ pmezard
 K 8
 svn:date
 V 27
-2009-01-12T23:19:31.232927Z
+2009-05-03T14:07:15.135488Z
 PROPS-END
 
 Node-path: externals/project1
@@ -90,6 +90,7 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 2
 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
 Content-length: 12
 
 PROPS-END
@@ -111,6 +112,7 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 2
 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
 Content-length: 12
 
 PROPS-END
@@ -132,7 +134,7 @@ pmezard
 K 8
 svn:date
 V 27
-2009-01-12T23:19:32.212750Z
+2009-05-03T14:07:16.180110Z
 PROPS-END
 
 Node-path: trunk
@@ -155,6 +157,7 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 2
 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
 Content-length: 12
 
 PROPS-END
@@ -176,20 +179,20 @@ pmezard
 K 8
 svn:date
 V 27
-2009-01-12T23:19:33.180250Z
+2009-05-03T14:07:17.092210Z
 PROPS-END
 
 Node-path: trunk
 Node-kind: dir
 Node-action: change
-Prop-content-length: 105
-Content-length: 105
+Prop-content-length: 111
+Content-length: 111
 
 K 13
 svn:externals
-V 70
+V 76
 ^/externals/project1 deps/project1
-^/externals/project2 deps/project2
+-r2 ^/externals/project2@2 deps/project2
 
 PROPS-END
 
@@ -209,19 +212,19 @@ pmezard
 K 8
 svn:date
 V 27
-2009-01-12T23:19:34.270854Z
+2009-05-03T14:07:18.165337Z
 PROPS-END
 
 Node-path: trunk
 Node-kind: dir
 Node-action: change
-Prop-content-length: 70
-Content-length: 70
+Prop-content-length: 76
+Content-length: 76
 
 K 13
 svn:externals
-V 35
-^/externals/project2 deps/project2
+V 41
+-r2 ^/externals/project2@2 deps/project2
 
 PROPS-END
 
@@ -269,7 +272,7 @@ pmezard
 K 8
 svn:date
 V 27
-2009-01-12T23:19:37.185743Z
+2009-05-03T14:07:21.092911Z
 PROPS-END
 
 Node-path: branches/branch1
@@ -277,19 +280,6 @@ Node-kind: dir
 Node-action: add
 Node-copyfrom-rev: 5
 Node-copyfrom-path: trunk
-Prop-content-length: 94
-Content-length: 94
-
-K 13
-svn:externals
-V 35
-^/externals/project2 deps/project2
-
-K 13
-svn:mergeinfo
-V 0
-
-PROPS-END
 
 
 Node-path: branches/branch1/subdir2
@@ -316,7 +306,7 @@ pmezard
 K 8
 svn:date
 V 27
-2009-01-12T23:19:39.198328Z
+2009-05-03T14:07:23.097507Z
 PROPS-END
 
 Node-path: branches/branch2
@@ -324,12 +314,8 @@ Node-kind: dir
 Node-action: add
 Node-copyfrom-rev: 5
 Node-copyfrom-path: trunk
-Prop-content-length: 34
-Content-length: 34
-
-K 13
-svn:mergeinfo
-V 0
+Prop-content-length: 10
+Content-length: 10
 
 PROPS-END
 
@@ -358,7 +344,7 @@ pmezard
 K 8
 svn:date
 V 27
-2009-01-12T23:19:40.183807Z
+2009-05-03T14:07:24.086967Z
 PROPS-END
 
 Node-path: trunk/subdir
@@ -380,7 +366,7 @@ pmezard
 K 8
 svn:date
 V 27
-2009-01-12T23:19:41.180477Z
+2009-05-03T14:07:25.093557Z
 PROPS-END
 
 Node-path: trunk/subdir2
@@ -392,3 +378,55 @@ Content-length: 10
 PROPS-END
 
 
+Revision-number: 10
+Prop-content-length: 127
+Content-length: 127
+
+K 7
+svn:log
+V 25
+remove externals project2
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-03T14:07:27.088306Z
+PROPS-END
+
+Node-path: externals/project2
+Node-action: delete
+
+
+Revision-number: 11
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 8
+change a
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-03T14:07:28.065953Z
+PROPS-END
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb
+Text-content-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29
+Content-length: 4
+
+a
+a
+
+
new file mode 100755
--- /dev/null
+++ b/tests/fixtures/renamedproject.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# Convert a project moving from a non-canonical to canonical
+# layout, exercizing the missing plaintext code paths. It also tests
+# branch creations where the branch source is not a canonical branch.
+#
+
+mkdir temp
+cd temp
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+
+mkdir project-orig
+cd project-orig
+echo a > a
+echo b > b
+echo c > c
+mkdir d
+echo a > d/a
+cd ..
+
+# Let's suppose it was actually branched in a previous life
+mkdir project-branch
+cd project-branch
+echo a > a
+echo b > b
+cd ..
+
+svn import project-orig $svnurl/project-orig -m "init project"
+svn import project-branch $svnurl/project-branch -m "init branch"
+
+svn mkdir $svnurl/project -m "create new project hierarchy"
+svn mv $svnurl/project-orig $svnurl/project/project -m "rename as project"
+svn mv $svnurl/project/project $svnurl/project/trunk -m "rename as project"
+
+svn mkdir $svnurl/project/branches -m "add branches root"
+svn mv $svnurl/project-branch $svnurl/project/misplaced -m "incorrect move of the branch"
+svn mv $svnurl/project/misplaced $svnurl/project/branches/branch -m "move of the branch"
+
+svn co $svnurl/project
+cd project
+echo a >> trunk/a
+svn ci -m "change a"
+echo a >> trunk/a
+echo b >> trunk/b
+svn rm trunk/c
+echo a >> trunk/d/a
+svn ci -m "change files in trunk"
+# Try the same thing with the branch
+echo a >> branches/branch/a
+svn rm branches/branch/b
+svn ci -m "change a in branch"
+cd ..
+
+# Add this to make test_rebuildmeta happy, needs something to convert
+svn import project-orig $svnurl/trunk -m "init fake trunk for rebuild_meta"
+
+svnadmin dump testrepo > ../renamedproject.svndump
new file mode 100644
--- /dev/null
+++ b/tests/fixtures/renamedproject.svndump
@@ -0,0 +1,536 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 169a5fe1-3c9f-4eef-ad86-f932c54e53dc
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-05-01T17:53:40.957980Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.129010Z
+PROPS-END
+
+Node-path: project-orig
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: project-orig/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: project-orig/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Text-content-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b
+Content-length: 12
+
+PROPS-END
+b
+
+
+Node-path: project-orig/c
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Text-content-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69
+Content-length: 12
+
+PROPS-END
+c
+
+
+Node-path: project-orig/d
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: project-orig/d/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
+Content-length: 12
+
+PROPS-END
+a
+
+
+Revision-number: 2
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 11
+init branch
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.193926Z
+PROPS-END
+
+Node-path: project-branch
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: project-branch/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: project-branch/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Text-content-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b
+Content-length: 12
+
+PROPS-END
+b
+
+
+Revision-number: 3
+Prop-content-length: 130
+Content-length: 130
+
+K 7
+svn:log
+V 28
+create new project hierarchy
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.236131Z
+PROPS-END
+
+Node-path: project
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 4
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 17
+rename as project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.288164Z
+PROPS-END
+
+Node-path: project/project
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: project-orig
+
+
+Node-path: project-orig
+Node-action: delete
+
+
+Revision-number: 5
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 17
+rename as project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.335332Z
+PROPS-END
+
+Node-path: project/trunk
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: project/project
+
+
+Node-path: project/project
+Node-action: delete
+
+
+Revision-number: 6
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 17
+add branches root
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.377684Z
+PROPS-END
+
+Node-path: project/branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 7
+Prop-content-length: 130
+Content-length: 130
+
+K 7
+svn:log
+V 28
+incorrect move of the branch
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.426928Z
+PROPS-END
+
+Node-path: project/misplaced
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 6
+Node-copyfrom-path: project-branch
+
+
+Node-path: project-branch
+Node-action: delete
+
+
+Revision-number: 8
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 18
+move of the branch
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.478644Z
+PROPS-END
+
+Node-path: project/branches/branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 7
+Node-copyfrom-path: project/misplaced
+
+
+Node-path: project/misplaced
+Node-action: delete
+
+
+Revision-number: 9
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 8
+change a
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:42.078428Z
+PROPS-END
+
+Node-path: project/trunk/a
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb
+Text-content-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29
+Content-length: 4
+
+a
+a
+
+
+Revision-number: 10
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 21
+change files in trunk
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:43.109915Z
+PROPS-END
+
+Node-path: project/trunk/a
+Node-kind: file
+Node-action: change
+Text-content-length: 6
+Text-content-md5: 7d4ebf8f298d22fc349a91725b00af1c
+Text-content-sha1: 92f31bc48f52339253fce6cad9f2f0c95b302f7e
+Content-length: 6
+
+a
+a
+a
+
+
+Node-path: project/trunk/b
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 06ac26ed8b614fc0b141e4542aa067c2
+Text-content-sha1: f6980469e74f7125178e88ec571e06fe6ce86e95
+Content-length: 4
+
+b
+b
+
+
+Node-path: project/trunk/d/a
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb
+Text-content-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29
+Content-length: 4
+
+a
+a
+
+
+Node-path: project/trunk/c
+Node-action: delete
+
+
+Revision-number: 11
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 18
+change a in branch
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:44.100553Z
+PROPS-END
+
+Node-path: project/branches/branch/a
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb
+Text-content-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29
+Content-length: 4
+
+a
+a
+
+
+Node-path: project/branches/branch/b
+Node-action: delete
+
+
+Revision-number: 12
+Prop-content-length: 134
+Content-length: 134
+
+K 7
+svn:log
+V 32
+init fake trunk for rebuild_meta
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:45.089483Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: trunk/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Text-content-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b
+Content-length: 12
+
+PROPS-END
+b
+
+
+Node-path: trunk/c
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Text-content-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69
+Content-length: 12
+
+PROPS-END
+c
+
+
+Node-path: trunk/d
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/d/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
+Content-length: 12
+
+PROPS-END
+a
+
+
--- a/tests/test_externals.py
+++ b/tests/test_externals.py
@@ -33,19 +33,19 @@ class TestFetchExternals(test_util.TestB
         # Taken from svn book
         samples = [
             ('third-party/sounds             http://svn.example.com/repos/sounds',
-             ('third-party/sounds', None, 'http://svn.example.com/repos/sounds')),
+             ('third-party/sounds', None, 'http://svn.example.com/repos/sounds', None)),
             ('third-party/skins -r148        http://svn.example.com/skinproj',
-             ('third-party/skins', '148', 'http://svn.example.com/skinproj')),
+             ('third-party/skins', '148', 'http://svn.example.com/skinproj', None)),
             ('third-party/skins -r 148        http://svn.example.com/skinproj',
-             ('third-party/skins', '148', 'http://svn.example.com/skinproj')),
+             ('third-party/skins', '148', 'http://svn.example.com/skinproj', None)),
             ('http://svn.example.com/repos/sounds third-party/sounds',
-             ('third-party/sounds', None, 'http://svn.example.com/repos/sounds')),
+             ('third-party/sounds', None, 'http://svn.example.com/repos/sounds', None)),
             ('-r148 http://svn.example.com/skinproj third-party/skins',
-             ('third-party/skins', '148', 'http://svn.example.com/skinproj')),
+             ('third-party/skins', '148', 'http://svn.example.com/skinproj', None)),
             ('-r 148 http://svn.example.com/skinproj third-party/skins',
-             ('third-party/skins', '148', 'http://svn.example.com/skinproj')),
+             ('third-party/skins', '148', 'http://svn.example.com/skinproj', None)),
             ('http://svn.example.com/skin-maker@21 third-party/skins/toolkit',
-             ('third-party/skins/toolkit', '21', 'http://svn.example.com/skin-maker')),
+             ('third-party/skins/toolkit', None, 'http://svn.example.com/skin-maker', '21')),
             ]
         
         for line, expected in samples:
@@ -60,12 +60,12 @@ class TestFetchExternals(test_util.TestB
         self.assertEqual(ref0, repo[0]['.hgsvnexternals'].data())
         ref1 = """[.]
  ^/externals/project1 deps/project1
- ^/externals/project2 deps/project2
+ -r2 ^/externals/project2@2 deps/project2
 """
         self.assertEqual(ref1, repo[1]['.hgsvnexternals'].data())
 
         ref2 = """[.]
- ^/externals/project2 deps/project2
+ -r2 ^/externals/project2@2 deps/project2
 [subdir]
  ^/externals/project1 deps/project1
 [subdir2]
@@ -75,7 +75,7 @@ class TestFetchExternals(test_util.TestB
         self.assertEqual(ref2, actual)
 
         ref3 = """[.]
- ^/externals/project2 deps/project2
+ -r2 ^/externals/project2@2 deps/project2
 [subdir]
  ^/externals/project1 deps/project1
 """
@@ -87,14 +87,14 @@ class TestFetchExternals(test_util.TestB
         self.assertEqual(ref4, repo[4]['.hgsvnexternals'].data())
 
         ref5 = """[.]
- ^/externals/project2 deps/project2
+ -r2 ^/externals/project2@2 deps/project2
 [subdir2]
  ^/externals/project1 deps/project1
 """
         self.assertEqual(ref5, repo[5]['.hgsvnexternals'].data())
 
         ref6 = """[.]
- ^/externals/project2 deps/project2
+ -r2 ^/externals/project2@2 deps/project2
 """
         self.assertEqual(ref6, repo[6]['.hgsvnexternals'].data())
 
--- a/tests/test_fetch_branches.py
+++ b/tests/test_fetch_branches.py
@@ -9,10 +9,11 @@ import wrappers
 
 
 class TestFetchBranches(test_util.TestBase):
-    def _load_fixture_and_fetch(self, fixture_name, stupid, noupdate=True):
+    def _load_fixture_and_fetch(self, fixture_name, stupid, noupdate=True,
+                                subdir=''):
         return test_util.load_fixture_and_fetch(fixture_name, self.repo_path,
                                                 self.wc_path, stupid=stupid,
-                                                noupdate=noupdate)
+                                                noupdate=noupdate, subdir=subdir)
 
     def _load_fixture_and_fetch_with_anchor(self, fixture_name, anchor):
         test_util.load_svndump_fixture(self.repo_path, fixture_name)
@@ -84,6 +85,19 @@ class TestFetchBranches(test_util.TestBa
         self.assertEqual(repo[None].branch(), 'branch')
         self.assertEqual(repo[None].parents()[0], repo[repo.branchheads()[0]])
 
+    def test_branches_weird_moves(self, stupid=False):
+        repo = self._load_fixture_and_fetch('renamedproject.svndump', stupid,
+                                            subdir='project')
+        heads = [repo[n] for n in repo.heads()]
+        heads = dict((ctx.branch(), ctx) for ctx in heads)
+        mdefault = sorted(heads['default'].manifest().keys())
+        mbranch = sorted(heads['branch'].manifest().keys())
+        self.assertEqual(mdefault, ['a', 'b', 'd/a'])
+        self.assertEqual(mbranch, ['a'])
+
+    def test_branches_weird_moves_stupid(self):
+        self.test_branches_weird_moves(True)
+
 def suite():
     all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchBranches),
           ]