# HG changeset patch # User Graham Booker # Date 1231035303 21600 # Node ID a336e3e826489f5a22fefc9297040a62103f7643 # Parent 33ebdcb75bcd3596bfd1f49ffcc7bae62df6c8a5 Fetch: add a filemap argument for use in converting old repositories to Mercurial. diff --git a/__init__.py b/__init__.py --- a/__init__.py +++ b/__init__.py @@ -45,6 +45,7 @@ cmdtable = { [('u', 'svn-url', '', 'Path to the Subversion server.'), ('', 'stupid', False, 'Be stupid and use diffy replay.'), ('A', 'authors', '', 'username mapping filename'), + ('', 'filemap', '', 'remap file to exclude paths or include only certain paths'), ], svncommand.generate_help(), ), @@ -54,6 +55,7 @@ cmdtable = { ('', 'stupid', False, 'Be stupid and use diffy replay.'), ('T', 'tag-locations', 'tags', 'Relative path to Subversion tags.'), ('A', 'authors', '', 'username mapping filename'), + ('', 'filemap', '', 'remap file to exclude paths or include only certain paths'), ], 'hg svnclone source [dest]'), } diff --git a/fetch_command.py b/fetch_command.py --- a/fetch_command.py +++ b/fetch_command.py @@ -25,6 +25,7 @@ def print_your_svn_is_old_message(ui): # def fetch_revisions(ui, svn_url, hg_repo_path, skipto_rev=0, stupid=None, tag_locations='tags', authors=None, + filemap=None, **opts): """Pull new revisions from Subversion. """ @@ -50,7 +51,8 @@ def fetch_revisions(ui, svn_url, hg_repo subdir=svn.subdir, author_host=author_host, tag_locations=tag_locations, - authors=authors) + authors=authors, + filemap=filemap) if os.path.exists(hg_editor.uuid_file): uuid = open(hg_editor.uuid_file).read() assert uuid == svn.uuid @@ -493,6 +495,8 @@ def stupid_fetch_branchrev(svn, hg_edito for path, e in r.paths.iteritems(): if not path.startswith(branchprefix): continue + if not hg_editor._is_path_valid(branchprefix + path): + continue kind = svn.checkpath(path, r.revnum) path = path[len(branchprefix):] if kind == 'f': @@ -560,13 +564,19 @@ def stupid_svn_server_pull_rev(ui, svn, if path == '.hgsvnexternals': if not externals: raise IOError() - return context.memfilectx(path=path, data=externals.write(), + return context.memfilectx(path=path, data=externals.write(), islink=False, isexec=False, copied=None) return filectxfn2(repo, memctx, path) - + extra = util.build_extra(r.revnum, b, svn.uuid, svn.subdir) if '' in files_touched: files_touched.remove('') + removedFiles = [] + for file in files_touched: + if not hg_editor._is_file_included(file): + removedFiles.append(file) + for file in removedFiles: + files_touched.remove(file) if parentctx.node() != node.nullid or files_touched: # TODO(augie) remove this debug code? Or maybe it's sane to have it. for f in files_touched: diff --git a/hg_delta_editor.py b/hg_delta_editor.py --- a/hg_delta_editor.py +++ b/hg_delta_editor.py @@ -62,7 +62,8 @@ class HgChangeReceiver(delta.Editor): def __init__(self, path=None, repo=None, ui_=None, subdir='', author_host='', tag_locations=['tags'], - authors=None): + authors=None, + filemap=None): """path is the path to the target hg repo. subdir is the subdirectory of the edits *on the svn server*. @@ -114,6 +115,10 @@ class HgChangeReceiver(delta.Editor): self.readauthors(authors) if self.authors: self.writeauthors() + self.includepaths = {} + self.excludepaths = {} + if filemap and os.path.exists(filemap): + self.readfilemap(filemap) def __setup_repo(self, repo_path): """Verify the repo is going to work out for us. @@ -229,9 +234,41 @@ class HgChangeReceiver(delta.Editor): if path and path[0] == '/': path = path[1:] return path + + def _is_file_included(self, subpath): + def checkpathinmap(path, mapping): + def rpairs(name): + yield '.', name + e = len(name) + while e != -1: + yield name[:e], name[e+1:] + e = name.rfind('/', 0, e) + + for pre, suf in rpairs(path): + try: + return mapping[pre] + except KeyError, err: + pass + return None + + if len(self.includepaths) and len(subpath): + inc = checkpathinmap(subpath, self.includepaths) + else: + inc = subpath + if len(self.excludepaths) and len(subpath): + exc = checkpathinmap(subpath, self.excludepaths) + else: + exc = None + if inc is None or exc is not None: + return False + return True def _is_path_valid(self, path): - return self._split_branch_path(path)[0] is not None + subpath = self._split_branch_path(path)[0] + if subpath is None: + return False + return self._is_file_included(subpath) + def _is_path_tag(self, path): """If path represents the path to a tag, returns the tag name. @@ -561,6 +598,40 @@ class HgChangeReceiver(delta.Editor): f.write("%s=%s\n" % (author, self.authors[author])) f.close() + def readfilemap(self, filemapfile): + self.ui.status( + ('Reading filemap %s\n') + % filemapfile) + def addpathtomap(path, mapping, mapname): + if path in mapping: + self.ui.warn( + ('%d alreading in %s list\n') + % (path, mapname)) + else: + mapping[path] = path + + f = open(filemapfile, 'r') + for line in f: + if line.strip() == '': + continue + try: + cmd, path = line.split(' ', 1) + cmd = cmd.strip() + path = path.strip() + if cmd == 'include': + addpathtomap(path, self.includepaths, 'include') + elif cmd == 'exclude': + addpathtomap(path, self.excludepaths, 'exclude') + else: + self.ui.warn( + ('Unknown filemap command %s\n') + % cmd) + except IndexError: + self.ui.warn( + ('Ignoring bad line in filemap %s: %s\n') + % (filemapfile, line.rstrip())) + f.close() + @property def meta_data_dir(self): return os.path.join(self.path, '.hg', 'svn') 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 @@ -4,6 +4,7 @@ import os import unittest from mercurial import ui +from mercurial import node import test_util import fetch_command @@ -52,6 +53,38 @@ class MapTests(test_util.TestBase): def test_author_map_closing_author_stupid(self): self.test_author_map_closing_author(True) + + def test_file_map(self, stupid=False): + test_util.load_svndump_fixture(self.repo_path, 'replace_trunk_with_branch.svndump') + filemap = open(self.filemap, 'w') + filemap.write("include alpha\n") + filemap.close() + fetch_command.fetch_revisions(ui.ui(), + svn_url=test_util.fileurl(self.repo_path), + hg_repo_path=self.wc_path, + stupid=stupid, + filemap=self.filemap) + self.assertEqual(node.hex(self.repo[0].node()), '88e2c7492d83e4bf30fbb2dcbf6aa24d60ac688d') + self.assertEqual(node.hex(self.repo['default'].node()), 'e524296152246b3837fe9503c83b727075835155') + + def test_file_map_stupid(self): + self.test_file_map(True) + + def test_file_map_exclude(self, stupid=False): + test_util.load_svndump_fixture(self.repo_path, 'replace_trunk_with_branch.svndump') + filemap = open(self.filemap, 'w') + filemap.write("exclude alpha\n") + filemap.close() + fetch_command.fetch_revisions(ui.ui(), + svn_url=test_util.fileurl(self.repo_path), + hg_repo_path=self.wc_path, + stupid=stupid, + filemap=self.filemap) + self.assertEqual(node.hex(self.repo[0].node()), '2c48f3525926ab6c8b8424bcf5eb34b149b61841') + self.assertEqual(node.hex(self.repo['default'].node()), '86fc12d173716139d5bd1d36866038d355009f45') + + def test_file_map_exclude_stupid(self): + self.test_file_map_exclude(True) def suite():