# HG changeset patch # User Augie Fackler # Date 1245811107 18000 # Node ID 0d3b5acb1d5178ee121f06d56bfaa8ac429dbae5 # Parent cbd2300433797d4f2706bf9c2414391662e25446 tags: handle edits to tags as gracefully as possible diff --git a/hgsubversion/editor.py b/hgsubversion/editor.py --- a/hgsubversion/editor.py +++ b/hgsubversion/editor.py @@ -191,7 +191,8 @@ class HgEditor(delta.Editor): fpath, branch = self.meta.split_branch_path(path, existing=False)[:2] if not fpath: return - if branch not in self.meta.branches: + if (branch not in self.meta.branches and + not self.meta.is_path_tag(self.meta.remotename(branch))): # we know this branch will exist now, because it has at least one file. Rock. self.meta.branches[branch] = None, 0, self.current.rev.revnum self.current.file = path diff --git a/hgsubversion/replay.py b/hgsubversion/replay.py --- a/hgsubversion/replay.py +++ b/hgsubversion/replay.py @@ -111,13 +111,22 @@ def convert_rev(ui, meta, svn, r, tbdelt continue extra = meta.genextra(rev.revnum, branch) + tag = False if branch is not None: - if (branch not in meta.branches - and branch not in meta.repo.branchtags()): + tag = meta.is_path_tag(meta.remotename(branch)) + if (not (tag and tag in meta.tags) and + (branch not in meta.branches + and branch not in meta.repo.branchtags())): continue - parent_ctx = meta.repo.changectx(parents[0]) - if '.hgsvnexternals' not in parent_ctx and '.hgsvnexternals' in files: + parentctx = meta.repo.changectx(parents[0]) + if tag: + if parentctx.node() == node.nullid: + continue + extra.update({'branch': parentctx.extra().get('branch', None), + 'close': 1}) + + if '.hgsvnexternals' not in parentctx and '.hgsvnexternals' in files: # Do not register empty externals files if (files['.hgsvnexternals'] in current.files and not current.files[files['.hgsvnexternals']]): @@ -128,7 +137,7 @@ def convert_rev(ui, meta, svn, r, tbdelt if current_file in current.deleted: raise IOError() copied = current.copies.get(current_file) - flags = parent_ctx.flags(path) + flags = parentctx.flags(path) is_exec = current.execfiles.get(current_file, 'x' in flags) is_link = current.symlinks.get(current_file, 'l' in flags) if current_file in current.files: @@ -139,13 +148,13 @@ def convert_rev(ui, meta, svn, r, tbdelt ui.warn('file marked as link, but contains data: ' '%s (%r)\n' % (current_file, flags)) else: - data = parent_ctx.filectx(path).data() + data = parentctx.filectx(path).data() return context.memfilectx(path=path, data=data, islink=is_link, isexec=is_exec, copied=copied) - if not meta.usebranchnames: + if not meta.usebranchnames or extra.get('branch', None) == 'default': extra.pop('branch', None) current_ctx = context.memctx(meta.repo, parents, @@ -158,8 +167,10 @@ def convert_rev(ui, meta, svn, r, tbdelt new_hash = meta.repo.commitctx(current_ctx) util.describe_commit(ui, new_hash, branch) - if (rev.revnum, branch) not in meta.revmap: + if (rev.revnum, branch) not in meta.revmap and not tag: meta.revmap[rev.revnum, branch] = new_hash + if tag: + meta.movetag(tag, new_hash, parentctx.extra().get('branch', None), rev, date) # 2. handle branches that need to be committed without any files for branch in current.emptybranches: @@ -178,6 +189,7 @@ def convert_rev(ui, meta, svn, r, tbdelt 'Please report this issue.') extra = meta.genextra(rev.revnum, branch) + if not meta.usebranchnames: extra.pop('branch', None) diff --git a/hgsubversion/stupid.py b/hgsubversion/stupid.py --- a/hgsubversion/stupid.py +++ b/hgsubversion/stupid.py @@ -601,9 +601,18 @@ def convert_rev(ui, meta, svn, r, tbdelt assert f[0] != '/' extra = meta.genextra(r.revnum, b) - if not meta.usebranchnames: - extra.pop('branch', None) + tag = False + tag = meta.is_path_tag(meta.remotename(b)) + + if tag: + if parentctx.node() == node.nullid: + continue + extra.update({'branch': parentctx.extra().get('branch', None), + 'close': 1}) + + if not meta.usebranchnames or extra.get('branch', None) == 'default': + extra.pop('branch', None) current_ctx = context.memctx(meta.repo, [parentctx.node(), revlog.nullid], r.message or util.default_commit_msg, @@ -615,10 +624,14 @@ def convert_rev(ui, meta, svn, r, tbdelt ha = meta.repo.commitctx(current_ctx) branch = extra.get('branch', None) - if not branch in meta.branches: + if (not branch in meta.branches + and not meta.is_path_tag(meta.remotename(branch))): meta.branches[branch] = None, 0, r.revnum - meta.revmap[r.revnum, b] = ha + if not tag: + meta.revmap[r.revnum, b] = ha util.describe_commit(ui, ha, b) + if tag: + meta.movetag(tag, ha, parentctx.extra().get('branch', None), r, date) # These are branches with 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. diff --git a/hgsubversion/svncommands.py b/hgsubversion/svncommands.py --- a/hgsubversion/svncommands.py +++ b/hgsubversion/svncommands.py @@ -120,6 +120,10 @@ def rebuildmeta(ui, repo, hg_repo_path, commitpath = commitpath[len('branches/'):] elif commitpath == 'trunk': commitpath = '' + elif commitpath.startswith('tags'): + if ctx.extra().get('close'): + continue + commitpath = '../' + commitpath else: assert False, 'Unhandled case in rebuildmeta' revmap.write('%s %s %s\n' % (revision, ctx.hex(), commitpath)) diff --git a/hgsubversion/svnmeta.py b/hgsubversion/svnmeta.py --- a/hgsubversion/svnmeta.py +++ b/hgsubversion/svnmeta.py @@ -145,7 +145,6 @@ class SVNMeta(object): def localname(self, path): """Compute the local name for a branch located at path. """ - assert not path.startswith('tags/') if path == 'trunk': return None elif path.startswith('branches/'): @@ -164,7 +163,10 @@ class SVNMeta(object): branchpath = 'trunk' if branch: extra['branch'] = branch - branchpath = 'branches/%s' % branch + if branch.startswith('../'): + branchpath = branch[3:] + else: + branchpath = 'branches/%s' % branch subdir = self.subdir if subdir and subdir[-1] == '/': @@ -218,8 +220,16 @@ class SVNMeta(object): known. """ path = self.normalize(path) - if path.startswith('tags/'): - return None, None, None + if self.is_path_tag(path): + tag = self.is_path_tag(path) + matched = [t for t in self.tags.iterkeys() if tag.startswith(t+'/')] + if not matched: + return None, None, None + matched.sort(cmp=lambda x,y: cmp(len(x),len(y)), reverse=True) + brpath = tag[len(matched[0])+1:] + svrpath = path[:-(len(brpath)+1)] + ln = self.localname(svrpath) + return brpath, ln, svrpath test = '' path_comps = path.split('/') while self.localname(test) not in self.branches and len(path_comps): @@ -299,7 +309,11 @@ class SVNMeta(object): def get_parent_revision(self, number, branch): '''Get the parent revision hash for a commit on a specific branch. ''' - r, br = self.get_parent_svn_branch_and_rev(number, branch) + tag = self.is_path_tag(self.remotename(branch)) + if tag and tag in self.tags: + br, r = self.tags[tag] + else: + r, br = self.get_parent_svn_branch_and_rev(number, branch) if r is not None: return self.revmap[r, br] return revlog.nullid @@ -398,6 +412,39 @@ class SVNMeta(object): self.tags.update(tbdelta['tags'][0]) self.branches.update(tbdelta['branches'][0]) + def movetag(self, tag, hash, branch, rev, date): + if branch == 'default': + branch = None + parentctx = self.repo[self.get_parent_revision(rev.revnum, branch)] + if '.hgtags' in parentctx: + tagdata = parentctx.filectx('.hgtags').data() + else: + tagdata = '' + tagdata += '%s %s\n' % (node.hex(hash), tag, ) + def hgtagsfn(repo, memctx, path): + assert path == '.hgtags' + return context.memfilectx(path=path, + data=tagdata, + islink=False, + isexec=False, + copied=False) + pextra = parentctx.extra() + ctx = context.memctx(self.repo, + (parentctx.node(), node.nullid), + rev.message or '...', + ['.hgtags', ], + hgtagsfn, + self.authors[rev.author], + date, + pextra) + new_hash = self.repo.commitctx(ctx) + prefix, revnum = pextra['convert_revision'].rsplit('@', 1) + branch = self.localname(self.normalize('/' + prefix.split('/', 1)[1])) + assert self.revmap[int(revnum), branch] == parentctx.node() + self.revmap[int(revnum), branch] = new_hash + + util.describe_commit(self.ui, new_hash, branch) + def committags(self, delta, rev, endbranches): date = self.fixdate(rev.date) diff --git a/tests/fixtures/commit-to-tag.sh b/tests/fixtures/commit-to-tag.sh new file mode 100755 --- /dev/null +++ b/tests/fixtures/commit-to-tag.sh @@ -0,0 +1,49 @@ +#!/bin/sh +mkdir temp +cd temp +svnadmin create repo +REPOPATH="file://`pwd`/repo" +svn co $REPOPATH wc +cd wc +mkdir -p branches/magic trunk tags +svn add * +svn ci -m 'btt' +cd branches/magic +for a in alpha beta gamma; do + echo $a > $a + svn add $a + svn ci -m "Add file $a" +done +cd ../.. +svn up +svn cp $REPOPATH/branches/magic $REPOPATH/tags/will-edit -m 'Make tag to edit' +svn up + +cd branches/magic +for a in delta iota lambda; do + echo $a > $a + svn add $a + svn ci -m "Add file $a" +done +cd ../.. + +cd tags/will-edit +svn rm alpha +svn ci -m 'removed alpha on a tag. Moves tag, implicit branch.' +cd ../.. + +cd branches/magic +for a in omega; do + echo $a > $a + svn add $a + svn ci -m "Add file $a" +done +cd ../.. + +cd ../.. +svnadmin dump temp/repo > commit-to-tag.svndump +echo +echo 'Complete.' +echo 'You probably want to clean up temp now.' +echo 'Dump in commit-to-tag.svndump' +exit 0 diff --git a/tests/fixtures/commit-to-tag.svndump b/tests/fixtures/commit-to-tag.svndump new file mode 100644 --- /dev/null +++ b/tests/fixtures/commit-to-tag.svndump @@ -0,0 +1,325 @@ +SVN-fs-dump-format-version: 2 + +UUID: af82cc90-c2d2-43cd-b1aa-c8a78449440a + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2009-06-21T18:31:52.697525Z +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 +2009-06-21T18:31:53.092074Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: branches/magic +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: 114 +Content-length: 114 + +K 7 +svn:log +V 14 +Add file alpha +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2009-06-21T18:31:54.113073Z +PROPS-END + +Node-path: branches/magic/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 + + +Revision-number: 3 +Prop-content-length: 113 +Content-length: 113 + +K 7 +svn:log +V 13 +Add file beta +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2009-06-21T18:31:55.071743Z +PROPS-END + +Node-path: branches/magic/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 + + +Revision-number: 4 +Prop-content-length: 114 +Content-length: 114 + +K 7 +svn:log +V 14 +Add file gamma +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2009-06-21T18:31:56.073236Z +PROPS-END + +Node-path: branches/magic/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: 5 +Prop-content-length: 116 +Content-length: 116 + +K 7 +svn:log +V 16 +Make tag to edit +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2009-06-21T18:31:58.056126Z +PROPS-END + +Node-path: tags/will-edit +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 4 +Node-copyfrom-path: branches/magic + + +Revision-number: 6 +Prop-content-length: 114 +Content-length: 114 + +K 7 +svn:log +V 14 +Add file delta +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2009-06-21T18:31:59.064850Z +PROPS-END + +Node-path: branches/magic/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 + + +Revision-number: 7 +Prop-content-length: 113 +Content-length: 113 + +K 7 +svn:log +V 13 +Add file iota +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2009-06-21T18:32:00.080479Z +PROPS-END + +Node-path: branches/magic/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 + + +Revision-number: 8 +Prop-content-length: 115 +Content-length: 115 + +K 7 +svn:log +V 15 +Add file lambda +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2009-06-21T18:32:01.079819Z +PROPS-END + +Node-path: branches/magic/lambda +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 7 +Text-content-md5: 8c8a4646591ee0d9a43d3149320ed577 +Content-length: 17 + +PROPS-END +lambda + + +Revision-number: 9 +Prop-content-length: 151 +Content-length: 151 + +K 7 +svn:log +V 51 +removed alpha on a tag. Moves tag, implicit branch. +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2009-06-21T18:32:02.140778Z +PROPS-END + +Node-path: tags/will-edit/alpha +Node-action: delete + + +Revision-number: 10 +Prop-content-length: 114 +Content-length: 114 + +K 7 +svn:log +V 14 +Add file omega +K 10 +svn:author +V 5 +durin +K 8 +svn:date +V 27 +2009-06-21T18:32:03.083676Z +PROPS-END + +Node-path: branches/magic/omega +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 6 +Text-content-md5: 14723c69541ee556d75c581b787dc217 +Content-length: 16 + +PROPS-END +omega + + diff --git a/tests/test_rebuildmeta.py b/tests/test_rebuildmeta.py --- a/tests/test_rebuildmeta.py +++ b/tests/test_rebuildmeta.py @@ -32,9 +32,9 @@ def _do_case(self, name, stupid): self.assertTrue(os.path.isfile(stf), '%r is missing!' % stf) dtf = os.path.join(dest.path, 'svn', tf) self.assertTrue(os.path.isfile(dtf), '%r is missing!' % tf) - self.assertEqual(open(stf).read(), - open(dtf).read()) - self.assertEqual(src.branchtags(), dest.branchtags()) + old, new = open(stf).read(), open(dtf).read() + self.assertEqual(old, new) + self.assertEqual(src.branchtags(), dest.branchtags()) srcbi = pickle.load(open(os.path.join(src.path, 'svn', 'branch_info'))) destbi = pickle.load(open(os.path.join(dest.path, 'svn', 'branch_info'))) self.assertEqual(sorted(srcbi.keys()), sorted(destbi.keys())) @@ -42,7 +42,7 @@ def _do_case(self, name, stupid): for branch in destbi: srcinfo = srcbi[branch] destinfo = destbi[branch] - if destinfo[:2] == (None, 0): + if srcinfo[:2] == (None, 0) or destinfo[:2] == (None, 0): self.assert_(srcinfo[2] <= destinfo[2]) self.assertEqual(srcinfo[0], destinfo[0]) else: diff --git a/tests/test_tags.py b/tests/test_tags.py --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -86,6 +86,28 @@ class TestTags(test_util.TestBase): 'versions/branch_version': 'I\x89\x1c>z#\xfc._K#@:\xd6\x1f\x96\xd6\x83\x1b|', }) + def test_edited_tag_stupid(self): + self.test_edited_tag(True) + + def test_edited_tag(self, stupid=False): + repo = self._load_fixture_and_fetch('commit-to-tag.svndump', + stupid=stupid) + self.assertEqual(len(repo.heads()), 2) + heads = repo.heads() + openheads = [h for h in heads if not repo[h].extra().get('close', False)] + closedheads = set(heads) - set(openheads) + self.assertEqual(len(openheads), 1) + self.assertEqual(len(closedheads), 1) + tagged = list(closedheads)[0] + self.assertEqual( + repo[tagged].extra(), + {'close': '1', + 'branch': 'magic', + 'convert_revision': 'svn:af82cc90-c2d2-43cd-b1aa-c8a78449440a/tags/will-edit@9'}) + self.assertEqual(tagged, repo.tags()['will-edit']) + self.assertEqual(repo['will-edit'].manifest().keys(), ['beta', + 'gamma', + ]) def test_tags_in_unusual_location(self): repo = self._load_fixture_and_fetch('tag_name_same_as_branch.svndump')