# HG changeset patch # User Dirkjan Ochtman # Date 1267477818 -3600 # Node ID 8e025a6f0db41c9e98aa570db754eaeef2380826 # Parent 00393e9abff8e22707b88e948a8c141ca0cab891 add basic branchmap functionality, to rename branches diff --git a/hgsubversion/__init__.py b/hgsubversion/__init__.py --- a/hgsubversion/__init__.py +++ b/hgsubversion/__init__.py @@ -73,6 +73,7 @@ wrapcmds = { # cmd: generic, target, fix 'file containing rules for remapping Subversion repository paths'), ('', 'layout', 'auto', ('import standard layout or single ' 'directory? Can be standard, single, or auto.')), + ('', 'branchmap', '', 'file containing rules for branch conversion'), ]), } diff --git a/hgsubversion/maps.py b/hgsubversion/maps.py --- a/hgsubversion/maps.py +++ b/hgsubversion/maps.py @@ -292,3 +292,60 @@ class FileMap(object): msg = 'ignoring bad line in filemap %s: %s\n' self.ui.warn(msg % (fn, line.rstrip())) f.close() + +class BranchMap(dict): + '''Facility for controlled renaming of branch names. Example: + + oldname = newname + other = default + + All changes on the oldname branch will now be on the newname branch; all + changes on other will now be on default (have no branch name set). + ''' + + def __init__(self, ui, path): + self.ui = ui + self.path = path + self.super = super(BranchMap, self) + self.super.__init__() + self.load(path) + + def load(self, path): + '''Load mappings from a file at the specified path.''' + if not os.path.exists(path): + return + + writing = False + if path != self.path: + writing = open(self.path, 'a') + + self.ui.note('reading branchmap from %s\n' % path) + f = open(path, 'r') + for number, line in enumerate(f): + + if writing: + writing.write(line) + + line = line.split('#')[0] + if not line.strip(): + continue + + try: + src, dst = line.split('=', 1) + except (IndexError, ValueError): + msg = 'ignoring line %i in branch map %s: %s\n' + self.ui.warn(msg % (number, path, line.rstrip())) + continue + + src = src.strip() + dst = dst.strip() + self.ui.debug('adding branch %s to branch map\n' % src) + if src in self and dst != self[src]: + msg = 'overriding branch: "%s" to "%s" (%s)\n' + self.ui.warn(msg % (self[src], dst, src)) + self[src] = dst + + f.close() + if writing: + writing.flush() + writing.close() diff --git a/hgsubversion/replay.py b/hgsubversion/replay.py --- a/hgsubversion/replay.py +++ b/hgsubversion/replay.py @@ -163,8 +163,7 @@ def convert_rev(ui, meta, svn, r, tbdelt islink=is_link, isexec=is_exec, copied=copied) - if not meta.usebranchnames or extra.get('branch', None) == 'default': - extra.pop('branch', None) + meta.mapbranch(extra) current_ctx = context.memctx(meta.repo, parents, rev.message or '...', diff --git a/hgsubversion/stupid.py b/hgsubversion/stupid.py --- a/hgsubversion/stupid.py +++ b/hgsubversion/stupid.py @@ -637,8 +637,7 @@ def convert_rev(ui, meta, svn, r, tbdelt extra.update({'branch': parentctx.extra().get('branch', None), 'close': 1}) - if not meta.usebranchnames or extra.get('branch', None) == 'default': - extra.pop('branch', None) + meta.mapbranch(extra) current_ctx = context.memctx(meta.repo, [parentctx.node(), revlog.nullid], r.message or util.default_commit_msg, diff --git a/hgsubversion/svnmeta.py b/hgsubversion/svnmeta.py --- a/hgsubversion/svnmeta.py +++ b/hgsubversion/svnmeta.py @@ -86,6 +86,9 @@ class SVNMeta(object): self.authors = maps.AuthorMap(self.ui, self.authors_file, defaulthost=author_host) if authors: self.authors.load(authors) + self.branchmap = maps.BranchMap(self.ui, self.branchmapfile) + if self.ui.config('hgsubversion', 'branchmap'): + self.branchmap.load(self.ui.config('hgsubversion', 'branchmap')) self.lastdate = '1970-01-01 00:00:00 -0000' self.filemap = maps.FileMap(repo) @@ -152,6 +155,10 @@ class SVNMeta(object): def authors_file(self): return os.path.join(self.meta_data_dir, 'authors') + @property + def branchmapfile(self): + return os.path.join(self.meta_data_dir, 'branchmap') + @property def layoutfile(self): return os.path.join(self.meta_data_dir, 'layout') @@ -218,6 +225,17 @@ class SVNMeta(object): } return extra + def mapbranch(self, extra, close=False): + if close: + extra['close'] = 1 + if extra.get('branch') == 'default': + extra.pop('branch', None) + mapped = self.branchmap.get(extra.get('branch')) + if not self.usebranchnames or mapped == 'default': + extra.pop('branch', None) + elif mapped: + extra['branch'] = mapped + def normalize(self, path): '''Normalize a path to strip of leading slashes and our subdir if we have one. @@ -601,13 +619,12 @@ class SVNMeta(object): return context.memfilectx(path='.hgtags', data=src, islink=False, isexec=False, copied=None) + extra = self.genextra(rev.revnum, b) if fromtag: extra['branch'] = parent.extra().get('branch', 'default') - if not self.usebranchnames: - extra.pop('branch', None) - if b in endbranches or fromtag: - extra['close'] = 1 + self.mapbranch(extra, b in endbranches or fromtag) + ctx = context.memctx(self.repo, (parent.node(), node.nullid), rev.message or ' ', @@ -629,9 +646,7 @@ class SVNMeta(object): pctx = self.repo[node] files = pctx.manifest().keys() extra = self.genextra(rev.revnum, branch) - extra['close'] = 1 - if self.usebranchnames: - extra['branch'] = branch or 'default' + self.mapbranch(extra, True) ctx = context.memctx(self.repo, (node, revlog.nullid), rev.message or util.default_commit_msg, diff --git a/hgsubversion/wrappers.py b/hgsubversion/wrappers.py --- a/hgsubversion/wrappers.py +++ b/hgsubversion/wrappers.py @@ -375,6 +375,7 @@ optionmap = { 'tagpaths': ('hgsubversion', 'tagpaths'), 'authors': ('hgsubversion', 'authormap'), 'filemap': ('hgsubversion', 'filemap'), + 'branchmap': ('hgsubversion', 'branchmap'), 'stupid': ('hgsubversion', 'stupid'), 'defaulthost': ('hgsubversion', 'defaulthost'), 'defaultauthors': ('hgsubversion', 'defaultauthors'), diff --git a/tests/fixtures/branchmap.sh b/tests/fixtures/branchmap.sh new file mode 100755 --- /dev/null +++ b/tests/fixtures/branchmap.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# inspired by Python r62868 + +mkdir temp +cd temp +svnadmin create repo +svn co file://`pwd`/repo wc +export REPO=file://`pwd`/repo +cd wc +mkdir branches trunk tags +svn add * +svn ci -m 'btt' + +echo a > trunk/a +svn add trunk/a +svn ci -m 'Add file.' +svn up + +svn cp trunk branches/badname +svn ci -m 'Branch to be renamed.' +svn up + +svn cp trunk branches/feature +svn ci -m 'Branch to be unnamed.' +svn up + +cd ../.. +svnadmin dump temp/repo > branchmap.svndump +echo +echo 'Complete.' +echo 'You probably want to clean up temp now.' +echo 'Dump in branchmap.svndump' +exit 0 diff --git a/tests/fixtures/branchmap.svndump b/tests/fixtures/branchmap.svndump new file mode 100644 --- /dev/null +++ b/tests/fixtures/branchmap.svndump @@ -0,0 +1,140 @@ +SVN-fs-dump-format-version: 2 + +UUID: e45f170d-c19e-4c14-836b-556bb41c9429 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2010-02-23T17:55:37.753400Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 100 +Content-length: 100 + +K 7 +svn:log +V 3 +btt +K 10 +svn:author +V 3 +djc +K 8 +svn:date +V 27 +2010-02-23T17:55:38.066481Z +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: 106 +Content-length: 106 + +K 7 +svn:log +V 9 +Add file. +K 10 +svn:author +V 3 +djc +K 8 +svn:date +V 27 +2010-02-23T17:55:39.058424Z +PROPS-END + +Node-path: trunk/a +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2 +Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3 +Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b +Content-length: 12 + +PROPS-END +a + + +Revision-number: 3 +Prop-content-length: 119 +Content-length: 119 + +K 7 +svn:log +V 21 +Branch to be renamed. +K 10 +svn:author +V 3 +djc +K 8 +svn:date +V 27 +2010-02-23T17:55:42.051658Z +PROPS-END + +Node-path: branches/badname +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk + + +Revision-number: 4 +Prop-content-length: 119 +Content-length: 119 + +K 7 +svn:log +V 21 +Branch to be unnamed. +K 10 +svn:author +V 3 +djc +K 8 +svn:date +V 27 +2010-02-23T17:55:45.050455Z +PROPS-END + +Node-path: branches/feature +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: trunk + + diff --git a/tests/test_fetch_mappings.py b/tests/test_fetch_mappings.py --- a/tests/test_fetch_mappings.py +++ b/tests/test_fetch_mappings.py @@ -20,6 +20,10 @@ class MapTests(test_util.TestBase): def filemap(self): return os.path.join(self.tmpdir, 'filemap') + @property + def branchmap(self): + return os.path.join(self.tmpdir, 'branchmap') + def test_author_map(self, stupid=False): test_util.load_svndump_fixture(self.repo_path, 'replace_trunk_with_branch.svndump') authormap = open(self.authors, 'w') @@ -101,6 +105,25 @@ class MapTests(test_util.TestBase): def test_file_map_exclude_stupid(self): self.test_file_map_exclude(True) + def test_branchmap(self, stupid=False): + test_util.load_svndump_fixture(self.repo_path, 'branchmap.svndump') + branchmap = open(self.branchmap, 'w') + branchmap.write("badname = good-name # stuffy\n") + branchmap.write("feature = default\n") + branchmap.close() + _ui = ui.ui() + _ui.setconfig('hgsubversion', 'stupid', str(stupid)) + _ui.setconfig('hgsubversion', 'branchmap', self.branchmap) + commands.clone(_ui, test_util.fileurl(self.repo_path), + self.wc_path, branchmap=self.branchmap) + branches = set(self.repo[i].branch() for i in self.repo) + self.assert_('badname' not in branches) + self.assert_('good-name' in branches) + self.assertEquals(self.repo[2].branch(), 'default') + + def test_branchmap_stupid(self): + self.test_branchmap(True) + def suite(): return unittest.TestLoader().loadTestsFromTestCase(MapTests)