# HG changeset patch # User Augie Fackler # Date 1229821499 21600 # Node ID 22162380c4b96c38ce0d39d63d167fa3b6550ca4 # Parent 4da9f20aef01ffe4857348c51f660cbccd7e7f69 Improve branch closing in the case of a single-rev replacement of one branch with another. Includes a test case. diff --git a/fetch_command.py b/fetch_command.py --- 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() diff --git a/hg_delta_editor.py b/hg_delta_editor.py --- 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: diff --git a/tests/fixtures/replace_trunk.sh b/tests/fixtures/replace_trunk.sh 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 diff --git a/tests/fixtures/replace_trunk_with_branch.svndump b/tests/fixtures/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 + + + + diff --git a/tests/test_fetch_branches.py b/tests/test_fetch_branches.py --- 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), ]