# HG changeset patch # User Patrick Mezard # Date 2008-03-29 16:15:45 # Node ID 5efd447a9b8d0a678b4f8a55b111f5d9b020d73c # Parent 0e91ef0b52e7f9eed59e6be0867ce06b5a9bb55d convert: follow svn tags history (issue953) diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py --- a/hgext/convert/subversion.py +++ b/hgext/convert/subversion.py @@ -369,18 +369,58 @@ class svn_source(converter_source): if self.tags is None: return tags - start = self.revnum(self.head) + # svn tags are just a convention, project branches left in a + # 'tags' directory. There is no other relationship than + # ancestry, which is expensive to discover and makes them hard + # to update incrementally. Worse, past revisions may be + # referenced by tags far away in the future, requiring a deep + # history traversal on every calculation. Current code + # performs a single backward traversal, tracking moves within + # the tags directory (tag renaming) and recording a new tag + # everytime a project is copied from outside the tags + # directory. It also lists deleted tags, this behaviour may + # change in the future. + pendings = [] + tagspath = self.tags + start = svn.ra.get_latest_revnum(self.ra) try: - for entry in get_log(self.url, [self.tags], self.startrev, start): - orig_paths, revnum, author, date, message = entry - for path in orig_paths: - if not path.startswith(self.tags+'/'): + for entry in get_log(self.url, [self.tags], start, self.startrev): + origpaths, revnum, author, date, message = entry + copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p,e + in origpaths.iteritems() if e.copyfrom_path] + copies.sort() + # Apply moves/copies from more specific to general + copies.reverse() + + srctagspath = tagspath + if copies and copies[-1][2] == tagspath: + # Track tags directory moves + srctagspath = copies.pop()[0] + + for source, sourcerev, dest in copies: + if not dest.startswith(tagspath + '/'): continue - ent = orig_paths[path] - source = ent.copyfrom_path - rev = ent.copyfrom_rev - tag = path.split('/')[-1] - tags[tag] = self.revid(rev, module=source) + for tag in pendings: + if tag[0].startswith(dest): + tagpath = source + tag[0][len(dest):] + tag[:2] = [tagpath, sourcerev] + break + else: + pendings.append([source, sourcerev, dest.split('/')[-1]]) + + # Tell tag renamings from tag creations + remainings = [] + for source, sourcerev, tagname in pendings: + if source.startswith(srctagspath): + remainings.append([source, sourcerev, tagname]) + continue + # From revision may be fake, get one with changes + tagid = self.latest(source, sourcerev) + if tagid: + tags[tagname] = tagid + pendings = remainings + tagspath = srctagspath + except SubversionException, (inst, num): self.ui.note('no tags found at revision %d\n' % start) return tags diff --git a/tests/test-convert-svn-tags b/tests/test-convert-svn-tags --- a/tests/test-convert-svn-tags +++ b/tests/test-convert-svn-tags @@ -27,6 +27,7 @@ cd projA mkdir trunk mkdir branches mkdir tags +mkdir unrelated cd .. svnurl=file://$svnpath/svn-repo/projA @@ -42,12 +43,23 @@ echo a >> trunk/a svn ci -m changea echo a >> trunk/a svn ci -m changea2 +# Add an unrelated commit to test that tags are bound to the +# correct "from" revision and not a dummy one +echo a >> unrelated/dummy +svn add unrelated/dummy +svn ci -m unrelatedchange echo % tag current revision svn up svn copy trunk tags/trunk.v1 -svn ci -m "tagging trunk.v1" +svn copy trunk tags/trunk.badtag +svn ci -m "tagging trunk.v1 trunk.badtag" echo a >> trunk/a svn ci -m changea3 +echo % fix the bad tag +# trunk.badtag should not show in converted tags +svn up +svn mv tags/trunk.badtag tags/trunk.goodtag +svn ci -m "fix trunk.badtag" cd .. echo % convert diff --git a/tests/test-convert-svn-tags.out b/tests/test-convert-svn-tags.out --- a/tests/test-convert-svn-tags.out +++ b/tests/test-convert-svn-tags.out @@ -1,11 +1,13 @@ % initial svn import Adding projA/trunk +Adding projA/unrelated Adding projA/branches Adding projA/tags Committed revision 1. % update svn repository A A/trunk +A A/unrelated A A/branches A A/tags Checked out revision 1. @@ -19,15 +21,30 @@ Committed revision 3. Sending trunk/a Transmitting file data . Committed revision 4. +A unrelated/dummy +Adding unrelated/dummy +Transmitting file data . +Committed revision 5. % tag current revision -At revision 4. +At revision 5. A tags/trunk.v1 +A tags/trunk.badtag +Adding tags/trunk.badtag Adding tags/trunk.v1 -Committed revision 5. +Committed revision 6. Sending trunk/a Transmitting file data . -Committed revision 6. +Committed revision 7. +% fix the bad tag +At revision 7. +A tags/trunk.goodtag +D tags/trunk.badtag/a +D tags/trunk.badtag +Deleting tags/trunk.badtag +Adding tags/trunk.goodtag + +Committed revision 8. % convert initializing destination A-hg repository scanning source... @@ -43,7 +60,7 @@ o 5 update tags tags: tip | o 4 changea3 tags: | -o 3 changea2 tags: trunk.v1 +o 3 changea2 tags: trunk.v1 trunk.goodtag | o 2 changea tags: | @@ -53,3 +70,4 @@ o 0 init projA tags: tip trunk.v1 +trunk.goodtag