changeset 147:22162380c4b9

Improve branch closing in the case of a single-rev replacement of one branch with another. Includes a test case.
author Augie Fackler <durin42@gmail.com>
date Sat, 20 Dec 2008 19:04:59 -0600
parents 4da9f20aef01
children 0c5f6420a8b5
files fetch_command.py hg_delta_editor.py tests/fixtures/replace_trunk.sh tests/fixtures/replace_trunk_with_branch.svndump tests/test_fetch_branches.py
diffstat 5 files changed, 270 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/fetch_command.py
+++ b/fetch_command.py
@@ -481,8 +481,11 @@ def stupid_svn_server_pull_rev(ui, svn, 
     deleted_branches = {}
     date = r.date.replace('T', ' ').replace('Z', '').split('.')[0]
     date += ' -0000'
+    check_deleted_branches = set()
     for b in branches:
         parentctx = hg_editor.repo[hg_editor.get_parent_revision(r.revnum, b)]
+        if parentctx.branch() != (b or 'default'):
+            check_deleted_branches.add(b)
         kind = svn.checkpath(branches[b], r.revnum)
         if kind != 'd':
             # Branch does not exist at this revision. Get parent revision and
@@ -523,12 +526,24 @@ def stupid_svn_server_pull_rev(ui, svn, 
             hg_editor._save_metadata()
             ui.status('committed as %s on branch %s\n' %
                       (node.hex(ha),  b or 'default'))
+    # These are branches which would have an 'R' status in svn log. This means they were
+    # replaced by some other branch, so we need to verify they get marked as closed.
+    for branch in check_deleted_branches:
+        branchedits = sorted(filter(lambda x: x[0][1] == branch and x[0][0] < r.revnum,
+                                    hg_editor.revmap.iteritems()), reverse=True)
+        is_closed = False
+        if len(branchedits) > 0:
+            branchtip = branchedits[0][1]
+            for child in hg_editor.repo[branchtip].children():
+                if child.branch() == 'closed-branches':
+                    is_closed = True
+                    break
+            if not is_closed:
+                deleted_branches[branch] = branchtip
     for b, parent in deleted_branches.iteritems():
         if parent == node.nullid:
             continue
         parentctx = hg_editor.repo[parent]
-        if parentctx.children():
-            continue
         files_touched = parentctx.manifest().keys()
         def filectxfn(repo, memctx, path):
             raise IOError()
--- a/hg_delta_editor.py
+++ b/hg_delta_editor.py
@@ -361,6 +361,37 @@ class HgChangeReceiver(delta.Editor):
             if b not in branch_batches:
                 branch_batches[b] = []
             branch_batches[b].append((p, f))
+        # close any branches that need it
+        closed_revs = set()
+        for branch in self.branches_to_delete:
+            closed = revlog.nullid
+            if 'closed-branches' in self.repo.branchtags():
+                closed = self.repo['closed-branches'].node()
+            branchedits = sorted(filter(lambda x: x[0][1] == branch and x[0][0] < rev.revnum,
+                                        self.revmap.iteritems()), reverse=True)
+            if len(branchedits) < 1:
+                # can't close a branch that never existed
+                continue
+            ha = branchedits[0][1]
+            closed_revs.add(ha)
+            # self.get_parent_revision(rev.revnum, branch)
+            parentctx = self.repo.changectx(ha)
+            parents = (ha, closed)
+            def del_all_files(*args):
+                raise IOError
+            files = parentctx.manifest().keys()
+            current_ctx = context.memctx(self.repo,
+                                         parents,
+                                         rev.message or ' ',
+                                         files,
+                                         del_all_files,
+                                         '%s%s' % (rev.author,
+                                                   self.author_host),
+                                         date,
+                                         {'branch': 'closed-branches'})
+            new_hash = self.repo.commitctx(current_ctx)
+            self.ui.status('Marked branch %s as closed.\n' % (branch or
+                                                              'default'))
         for branch, files in branch_batches.iteritems():
             if branch in self.commit_branches_empty and files:
                 del self.commit_branches_empty[branch]
@@ -369,13 +400,13 @@ class HgChangeReceiver(delta.Editor):
 
             parents = (self.get_parent_revision(rev.revnum, branch),
                        revlog.nullid)
+            if parents[0] in closed_revs and branch in self.branches_to_delete:
+                continue
             if branch is not None:
                 if (branch not in self.branches
                     and branch not in self.repo.branchtags()):
                     continue
                 extra['branch'] = branch
-            if (branch in self.branches_to_delete):
-                continue
             parent_ctx = self.repo.changectx(parents[0])
             def filectxfn(repo, memctx, path):
                 current_file = files[path]
@@ -410,29 +441,6 @@ class HgChangeReceiver(delta.Editor):
             if (rev.revnum, branch) not in self.revmap:
                 self.add_to_revmap(rev.revnum, branch, new_hash)
         # now we handle branches that need to be committed without any files
-        for branch in self.branches_to_delete:
-            closed = revlog.nullid
-            if 'closed-branches' in self.repo.branchtags():
-                closed = self.repo['closed-branches'].node()
-            ha = self.get_parent_revision(rev.revnum, branch)
-            parentctx = self.repo.changectx(ha)
-            if parentctx.children():
-                continue
-            parents = (ha, closed)
-            def del_all_files(*args):
-                raise IOError
-            files = parentctx.manifest().keys()
-            current_ctx = context.memctx(self.repo,
-                                         parents,
-                                         rev.message or ' ',
-                                         files,
-                                         del_all_files,
-                                         '%s%s' % (rev.author,
-                                                   self.author_host),
-                                         date,
-                                         {'branch': 'closed-branches'})
-            new_hash = self.repo.commitctx(current_ctx)
-            self.ui.status('Marked branch %s as closed.\n' % (branch or 'default'))
         for branch in self.commit_branches_empty:
             ha = self.get_parent_revision(rev.revnum, branch)
             if ha == node.nullid:
new file mode 100755
--- /dev/null
+++ b/tests/fixtures/replace_trunk.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+RSVN="`pwd`/rsvn.py"
+export PATH=/bin:/usr/bin
+mkdir temp
+cd temp
+
+svnadmin create repo
+svn co file://`pwd`/repo wc
+
+cd wc
+mkdir trunk branches
+cd trunk
+for a in alpha beta gamma ; do
+    echo $a > $a
+done
+cd ..
+svn add *
+svn ci -m 'initial'
+
+svn up
+svn cp trunk branches/test
+svn ci -m 'branch'
+
+svn up
+echo foo >> branches/test/alpha
+svn ci -m 'Mod.'
+
+cd ..
+echo rdelete trunk > tmp
+echo rcopy branches/test trunk >> tmp
+python $RSVN --message=blah --username=evil `pwd`/repo < tmp
+
+svnadmin dump repo > ../replace_trunk_with_branch.svndump
new file mode 100644
--- /dev/null
+++ b/tests/fixtures/replace_trunk_with_branch.svndump
@@ -0,0 +1,171 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 5b65bade-98f3-4993-a01f-b7a6710da339
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-12-16T16:37:53.651472Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 106
+Content-length: 106
+
+K 7
+svn:log
+V 7
+initial
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2008-12-16T16:37:54.139835Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 2
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 6
+branch
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2008-12-16T16:37:57.116844Z
+PROPS-END
+
+Node-path: branches/test
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 3
+Prop-content-length: 103
+Content-length: 103
+
+K 7
+svn:log
+V 4
+Mod.
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2008-12-16T16:37:59.118952Z
+PROPS-END
+
+Node-path: branches/test/alpha
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 54add8695a828f4e66224b1ecfd9c576
+Content-length: 10
+
+alpha
+foo
+
+
+Revision-number: 4
+Prop-content-length: 102
+Content-length: 102
+
+K 7
+svn:log
+V 4
+blah
+K 10
+svn:author
+V 4
+evil
+K 8
+svn:date
+V 27
+2008-12-16T16:38:00.236345Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: delete
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: branches/test
+
+
+
+
--- a/tests/test_fetch_branches.py
+++ b/tests/test_fetch_branches.py
@@ -30,11 +30,11 @@ class TestFetchBranches(test_util.TestBa
 
     def test_unorderedbranch_stupid(self):
         self.test_unorderedbranch(True)
-        
+
     def test_renamed_branch_to_trunk(self, stupid=False):
-        repo = self._load_fixture_and_fetch('branch_rename_to_trunk.svndump', 
+        repo = self._load_fixture_and_fetch('branch_rename_to_trunk.svndump',
                                             stupid)
-        self.assertEqual(node.hex(repo['tip'].node()),
+        self.assertEqual(node.hex(repo['default'].node()),
                          'b479347c1f56d1fafe5e32a7ce0d1b7099637784')
         self.assertEqual(repo['tip'].parents()[0].branch(), 'dev_branch')
         self.assertEqual(repo['old_trunk'].parents()[0].branch(), 'default')
@@ -42,6 +42,18 @@ class TestFetchBranches(test_util.TestBa
     def test_renamed_branch_to_trunk_stupid(self):
         self.test_renamed_branch_to_trunk(stupid=True)
 
+    def test_replace_trunk_with_branch(self, stupid=False):
+        repo = self._load_fixture_and_fetch('replace_trunk_with_branch.svndump',
+                                            stupid)
+        self.assertEqual(repo['default'].parents()[0].branch(), 'test')
+        self.assertEqual(node.hex(repo['closed-branches'].parents()[0].node()),
+                         'f46d6f10e6329a069503af6c0c12903994c083b2')
+        self.assertEqual(node.hex(repo['default'].node()),
+                         '7bb5386f1a8e752888183cd86e43bdaf9abd1a95')
+
+    def test_replace_trunk_with_branch_stupid(self):
+        self.test_replace_trunk_with_branch(stupid=True)
+
 def suite():
     all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchBranches),
           ]