# HG changeset patch # User Augie Fackler # Date 1228940945 21600 # Node ID 2242dd1163c6deabff95158f6a165e879c812653 # Parent 3a9d6cd18332280ddb9baf75ef96936d5cd85864 hg_delta_editor: fix bad parent revision calculation in the case of a branch recycling a name. Also implemented marking branches as closed in both replay and stupid paths. diff --git a/fetch_command.py b/fetch_command.py --- a/fetch_command.py +++ b/fetch_command.py @@ -32,8 +32,8 @@ def fetch_revisions(ui, svn_url, hg_repo merc_util._encoding = 'UTF-8' skipto_rev=int(skipto_rev) have_replay = not stupid - if have_replay and not callable(delta.svn_txdelta_apply(None, None, - None)[0]): #pragma: no cover + if have_replay and not callable( + delta.svn_txdelta_apply(None, None, None)[0]): #pragma: no cover ui.status('You are using old Subversion SWIG bindings. Replay will not' ' work until you upgrade to 1.5.0 or newer. Falling back to' ' a slower method that may be buggier. Please upgrade, or' @@ -124,7 +124,7 @@ def replay_convert_rev(hg_editor, svn, r if p[-1] == '/': dirpath = p[len(rootpath):] files_to_grab.update((dirpath + f for f,k in - svn.list_files(dirpath, r.revnum) + svn.list_files(dirpath, r.revnum) if k == 'f')) else: files_to_grab.add(p[len(rootpath):]) @@ -176,7 +176,7 @@ def mempatchproxy(parentctx, files): class mempatch(patchfile): def __init__(self, ui, fname, opener, missing=False): patchfile.__init__(self, ui, fname, None, False) - + def readlines(self, fname): if fname not in parentctx: raise IOError('Cannot find %r to patch' % fname) @@ -328,7 +328,7 @@ def stupid_diff_branchrev(ui, svn, hg_ed data = data[len('link '):] elif path in parentctx: data = parentctx[path].data() - + copied = copies.get(path) return context.memfilectx(path=path, data=data, islink=islink, isexec=isexe, copied=copied) @@ -477,17 +477,19 @@ def stupid_fetch_branchrev(svn, hg_edito def stupid_svn_server_pull_rev(ui, svn, hg_editor, r): # this server fails at replay branches = hg_editor.branches_in_paths(r.paths) + deleted_branches = {} + date = r.date.replace('T', ' ').replace('Z', '').split('.')[0] + date += ' -0000' for b in branches: parentctx = hg_editor.repo[hg_editor.get_parent_revision(r.revnum, b)] kind = svn.checkpath(branches[b], r.revnum) if kind != 'd': # Branch does not exist at this revision. Get parent revision and # remove everything. - files_touched = parentctx.manifest().keys() - def filectxfn(repo, memctx, path): - raise IOError() + deleted_branches[b] = parentctx.node() + continue else: - try: + try: files_touched, filectxfn = stupid_diff_branchrev( ui, svn, hg_editor, b, r, parentctx) except BadPatchApply, e: @@ -496,8 +498,6 @@ def stupid_svn_server_pull_rev(ui, svn, files_touched, filectxfn = stupid_fetch_branchrev( svn, hg_editor, b, branches[b], r, parentctx) - date = r.date.replace('T', ' ').replace('Z', '').split('.')[0] - date += ' -0000' extra = {} if b: extra['branch'] = b @@ -522,6 +522,31 @@ 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')) + 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() + closed = node.nullid + if 'closed-branches' in hg_editor.repo.branchtags(): + closed = hg_editor.repo['closed-branches'].node() + parents = (parent, closed) + current_ctx = context.memctx(hg_editor.repo, + parents, + r.message or '...', + files_touched, + filectxfn, + '%s%s' % (r.author, + hg_editor.author_host), + date, + {'branch': 'closed-branches'}) + ha = hg_editor.repo.commitctx(current_ctx) + ui.status('Marked branch %s as closed.' % (b or 'default')) + hg_editor._save_metadata() class BadPatchApply(Exception): pass diff --git a/hg_delta_editor.py b/hg_delta_editor.py --- a/hg_delta_editor.py +++ b/hg_delta_editor.py @@ -107,11 +107,11 @@ class HgChangeReceiver(delta.Editor): self.author_host = author_host def __setup_repo(self, repo_path): - '''Verify the repo is going to work out for us. + """Verify the repo is going to work out for us. This method will fail an assertion if the repo exists but doesn't have the Subversion metadata. - ''' + """ if os.path.isdir(repo_path) and len(os.listdir(repo_path)): self.repo = hg.repository(self.ui, repo_path) assert os.path.isfile(self.revmap_file) @@ -141,6 +141,7 @@ class HgChangeReceiver(delta.Editor): self.missing_plaintexts = set() self.commit_branches_empty = {} self.base_revision = None + self.branches_to_delete = set() def _save_metadata(self): '''Save the Subversion metadata. This should really be called after @@ -164,11 +165,11 @@ class HgChangeReceiver(delta.Editor): return self._split_branch_path(path)[:2] def _split_branch_path(self, path): - '''Figure out which branch inside our repo this path represents, and + """Figure out which branch inside our repo this path represents, and also figure out which path inside that branch it is. Raises an exception if it can't perform its job. - ''' + """ path = self._normalize_path(path) if path.startswith('trunk'): p = path[len('trunk'):] @@ -186,8 +187,8 @@ class HgChangeReceiver(delta.Editor): return None, None, None def set_current_rev(self, rev): - '''Set the revision we're currently converting. - ''' + """Set the revision we're currently converting. + """ self.current_rev = rev def set_file(self, path, data, isexec=False, islink=False): @@ -236,19 +237,23 @@ class HgChangeReceiver(delta.Editor): continue if num <= number and num > real_num: real_num = num - if real_num == 0: - if branch in self.branches: - parent_branch = self.branches[branch][0] - parent_branch_rev = self.branches[branch][1] - if parent_branch_rev <= 0: - return None, None - branch_created_rev = self.branches[branch][2] - if parent_branch == 'trunk': - parent_branch = None - if branch_created_rev <= number+1 and branch != parent_branch: - return self.get_parent_svn_branch_and_rev( - parent_branch_rev+1, - parent_branch) + if branch in self.branches: + parent_branch = self.branches[branch][0] + parent_branch_rev = self.branches[branch][1] + # check to see if this branch already existed and is the same + if parent_branch_rev < real_num: + return real_num, branch + # if that wasn't true, then this is the a new branch with the + # same name as some old deleted branch + if parent_branch_rev <= 0 and real_num == 0: + return None, None + branch_created_rev = self.branches[branch][2] + if parent_branch == 'trunk': + parent_branch = None + if branch_created_rev <= number+1 and branch != parent_branch: + return self.get_parent_svn_branch_and_rev( + parent_branch_rev+1, + parent_branch) if real_num != 0: return real_num, branch return None, None @@ -265,12 +270,12 @@ class HgChangeReceiver(delta.Editor): paths = revision.paths added_branches = {} added_tags = {} + self.branches_to_delete = set() tags_to_delete = set() - branches_to_delete = set() for p in sorted(paths): fi, br = self._path_and_branch_for_path(p) if fi is not None: - if fi == '' and br not in self.branches: + if fi == '' and paths[p].action != 'D': src_p = paths[p].copyfrom_path src_rev = paths[p].copyfrom_rev src_tag = self._is_path_tag(src_p) @@ -294,7 +299,7 @@ class HgChangeReceiver(delta.Editor): elif fi == '' and br in self.branches: br2 = br or 'default' if br2 not in self.repo.branchtags() and paths[p].action == 'D': - branches_to_delete.add(br) + self.branches_to_delete.add(br) else: t_name = self._is_path_tag(p) if t_name == False: @@ -320,7 +325,7 @@ class HgChangeReceiver(delta.Editor): tags_to_delete.add(t_name) for t in tags_to_delete: del self.tags[t] - for br in branches_to_delete: + for br in self.branches_to_delete: del self.branches[br] self.tags.update(added_tags) self.branches.update(added_branches) @@ -359,9 +364,12 @@ class HgChangeReceiver(delta.Editor): parents = (self.get_parent_revision(rev.revnum, branch), revlog.nullid) if branch is not None: - if branch not in self.branches and branch not in self.repo.branchtags(): + 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] @@ -396,6 +404,29 @@ 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.' % (branch or 'default')) for branch in self.commit_branches_empty: ha = self.get_parent_revision(rev.revnum, branch) if ha == node.nullid: @@ -404,8 +435,11 @@ class HgChangeReceiver(delta.Editor): def del_all_files(*args): raise IOError extra = {} - if branch: - extra['branch'] = branch + if parent_ctx.children(): + # Target isn't an active head, no need to do things to it. + continue + if branch in self.branches_to_delete: + extra['branch'] = 'closed-branch' # True here means nuke all files files = [] if self.commit_branches_empty[branch]: @@ -468,6 +502,8 @@ class HgChangeReceiver(delta.Editor): @stash_exception_on_self def delete_entry(self, path, revision_bogus, parent_baton, pool=None): br_path, branch = self._path_and_branch_for_path(path) + if br_path == '': + self.branches_to_delete.add(branch) if br_path is not None: ha = self.get_parent_revision(self.current_rev.revnum, branch) if ha == revlog.nullid: diff --git a/tests/fixtures/branch_rename_to_trunk.sh b/tests/fixtures/branch_rename_to_trunk.sh new file mode 100755 --- /dev/null +++ b/tests/fixtures/branch_rename_to_trunk.sh @@ -0,0 +1,40 @@ +#!/bin/sh +mkdir temp +cd temp +svnadmin create repo +svn co file://`pwd`/repo wc +cd wc +mkdir branches trunk tags +svn add * +svn ci -m 'btt' +cd trunk +for a in alpha beta gamma delta ; do + echo $a > $a + svn add $a +done +svn ci -m 'Add files.' +cd .. +svn up +svn cp trunk branches/dev_branch +svn ci -m 'branch' +cd branches/dev_branch +svn rm delta +echo narf > alpha +echo iota > iota +svn add iota +svn ci -m 'branch changes' +cd ../.. +svn up +svn mv trunk branches/old_trunk +svn ci -m 'move trunk to a branch' +svn up +svn mv branches/dev_branch trunk +svn ci -m 'move dev to trunk' +cd .. +cd .. +svnadmin dump temp/repo > branch_rename_to_trunk.svndump +echo +echo 'Complete.' +echo 'You probably want to clean up temp now.' +echo 'Dump in branch_rename_to_trunk.svndump' +exit 0 diff --git a/tests/fixtures/branch_rename_to_trunk.svndump b/tests/fixtures/branch_rename_to_trunk.svndump new file mode 100644 --- /dev/null +++ b/tests/fixtures/branch_rename_to_trunk.svndump @@ -0,0 +1,252 @@ +SVN-fs-dump-format-version: 2 + +UUID: f6ca240b-44b4-4753-9ebe-569095e6ee32 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2008-12-10T21:37:23.126889Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 102 +Content-length: 102 + +K 7 +svn:log +V 3 +btt +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2008-12-10T21:37:24.068831Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: tags +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 + + +Revision-number: 2 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 10 +Add files. +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2008-12-10T21:37:25.115468Z +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/delta +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 6 +Text-content-md5: d2840cc81bc032bd1141b56687d0f93c +Content-length: 16 + +PROPS-END +delta + + +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: 3 +Prop-content-length: 105 +Content-length: 105 + +K 7 +svn:log +V 6 +branch +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2008-12-10T21:37:28.044281Z +PROPS-END + +Node-path: branches/dev_branch +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk + + +Revision-number: 4 +Prop-content-length: 114 +Content-length: 114 + +K 7 +svn:log +V 14 +branch changes +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2008-12-10T21:37:29.085784Z +PROPS-END + +Node-path: branches/dev_branch/alpha +Node-kind: file +Node-action: change +Text-content-length: 5 +Text-content-md5: 5e723ed52db2000686425ca28bc5ba4a +Content-length: 5 + +narf + + +Node-path: branches/dev_branch/iota +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 5 +Text-content-md5: ebcf3971120220589f1dfbf8d56e25b9 +Content-length: 15 + +PROPS-END +iota + + +Node-path: branches/dev_branch/delta +Node-action: delete + + +Revision-number: 5 +Prop-content-length: 122 +Content-length: 122 + +K 7 +svn:log +V 22 +move trunk to a branch +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2008-12-10T21:37:32.056153Z +PROPS-END + +Node-path: branches/old_trunk +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 4 +Node-copyfrom-path: trunk + + +Node-path: trunk +Node-action: delete + + +Revision-number: 6 +Prop-content-length: 117 +Content-length: 117 + +K 7 +svn:log +V 17 +move dev to trunk +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2008-12-10T21:37:35.046793Z +PROPS-END + +Node-path: branches/dev_branch +Node-action: delete + + +Node-path: trunk +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: branches/dev_branch + + 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 @@ -1,6 +1,8 @@ import sys import unittest +from mercurial import node + import test_util @@ -28,6 +30,17 @@ 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', + stupid) + self.assertEqual(node.hex(repo['tip'].node()), + 'b479347c1f56d1fafe5e32a7ce0d1b7099637784') + self.assertEqual(repo['tip'].parents()[0].branch(), 'dev_branch') + self.assertEqual(repo['old_trunk'].parents()[0].branch(), 'default') + + def test_renamed_branch_to_trunk_stupid(self): + self.test_renamed_branch_to_trunk(stupid=True) def suite(): all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchBranches), diff --git a/tests/test_tags.py b/tests/test_tags.py --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -77,8 +77,12 @@ class TestTags(test_util.TestBase): repo = self._load_fixture_and_fetch('tag_by_rename_branch.svndump', stupid=stupid) repo = self.getrepo() + self.assertEqual(repo['tip'], repo['closed-branches']) self.assertEqual(node.hex(repo['tip'].node()), - '1b941f92acc343939274bd8bbf25984fa9706bb9') + 'dd2dccc3180631192f058468ec7215899223a6d8') + taggedrev = repo['tip'].parents()[0] + self.assertEqual(node.hex(taggedrev.node()), + '68f5f7d82b00a2efe3aca28b615ebab98235d55f') self.assertEqual(node.hex(repo['tag/dummy'].node()), '68f5f7d82b00a2efe3aca28b615ebab98235d55f')