Commits

Graham Booker committed a336e3e

Fetch: add a filemap argument for use in converting old repositories to
Mercurial.

Comments (0)

Files changed (4)

          [('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(),
          ),
           ('', '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]'),
 }
 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.
     """
                                                  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
         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':
                 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:
     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*.
             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.
         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.
             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')

tests/test_fetch_mappings.py

 import unittest
 
 from mercurial import ui
+from mercurial import node
 
 import test_util
 import fetch_command
 
     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():