Commits

David Schleimer  committed 9a7e3db

layouts: add support for an infix between tbt and the hg root

  • Participants
  • Parent commits 791382a

Comments (0)

Files changed (9)

File hgsubversion/__init__.py

          'list of paths to search for tags in Subversion repositories'),
         ('', 'branchdir', '',
          'path to search for branches in subversion repositories'),
+        ('', 'infix', '',
+         'path relative to trunk, branch an tag dirs to import'),
         ('A', 'authors', '',
          'file mapping Subversion usernames to Mercurial authors'),
         ('', 'filemap', '',

File hgsubversion/help/subversion.rst

 directories. By default, hgsubversion will use this layout whenever it finds any
 of these directories at the specified directory on the server.  Standard layout
 also supports alternate names for the ``branches`` directory and multiple tags
-locations.
+locations.  Finally, Standard Layout supports selecting a subdirectory relative
+to ``trunk``, and each branch and tag dir.  This is useful if you have a single
+``trunk``, ``branches``, and ``tags`` with several projects inside, and you wish
+to import only a single project.
 
 If you instead want to clone just a single directory or branch, clone the
 specific directory path. In the example above, to get *only* trunk, you would
     default is ``branches``.  This option has no effect for
     single-directory clones.
 
+  ``hgsubversion.infix``
+
+    Specifies a path to strip between relative to the trunk/branch/tag
+    root as the mercurial root.  This can be used to import a single
+    sub-project when you have several sub-projects under a single
+    trunk/branches/tags layout in subversion.
+
   ``hgsubversion.filemap``
 
     Path to a file for filtering files during the conversion. Files may either

File hgsubversion/layouts/base.py

 
         local_path should be relative to the root of the Mercurial working dir
 
+        Note that it is permissible to return a longer branch_path
+        than is passed in iff the path that is passed in is a parent
+        directory of exactly one branch.  This is intended to handle
+        the case where we are importing a particular subdirectory of
+        asubversion branch structure.
+
         """
         self.__unimplemented('split_remote_name')

File hgsubversion/layouts/standard.py

         if self._branch_dir[-1] != '/':
             self._branch_dir += '/'
 
+        self._infix = ui.config('hgsubversion', 'infix', '').strip('/')
+        if self._infix:
+            self._infix = '/' + self._infix
+
+        self._trunk = 'trunk%s' % self._infix
+
     def localname(self, path):
-        if path == 'trunk':
+        if path == self._trunk:
             return None
-        elif path.startswith(self._branch_dir):
-            return path[len(self._branch_dir):]
+        elif path.startswith(self._branch_dir) and path.endswith(self._infix):
+            path = path[len(self._branch_dir):]
+            if self._infix:
+                path = path[:-len(self._infix)]
+            return path
         return  '../%s' % path
 
     def remotename(self, branch):
         if branch == 'default' or branch is None:
-            return 'trunk'
+            path = self._trunk
         elif branch.startswith('../'):
-            return branch[3:]
-        return '%s%s' % (self._branch_dir, branch)
+            path =  branch[3:]
+        else:
+            path = ''.join((self._branch_dir, branch, self._infix))
+
+        return path
 
     def remotepath(self, branch, subdir='/'):
         if subdir == '/':
             subdir = ''
-        branchpath = 'trunk'
+        branchpath = self._trunk
         if branch and branch != 'default':
             if branch.startswith('../'):
                 branchpath = branch[3:]
             else:
-                branchpath = '%s%s' % (self._branch_dir, branch)
+                branchpath = ''.join((self._branch_dir, branch, self._infix))
 
         return '%s/%s' % (subdir or '', branchpath)
 
             return candidate, '/'.join(components)
 
         if path == 'trunk' or path.startswith('trunk/'):
-            return 'trunk', path[len('trunk/'):]
+            return self._trunk, path[len(self._trunk) + 1:]
 
         if path.startswith(self._branch_dir):
             path = path[len(self._branch_dir):]
             components = path.split('/', 1)
-            branch_path = '%s%s' % (self._branch_dir, components[0])
+            branch_path = ''.join((self._branch_dir, components[0]))
             if len(components) == 1:
                 local_path = ''
             else:
                 local_path = components[1]
+
+            if local_path == '':
+                branch_path += self._infix
+            elif local_path.startswith(self._infix[1:] + '/'):
+                branch_path += self._infix
+                local_path = local_path[len(self._infix):]
             return branch_path, local_path
 
         components = path.split('/')

File hgsubversion/svnmeta.py

             src_file, src_branch = self.split_branch_path(src_path)[:2]
             src_tag = self.get_path_tag(src_path)
             if src_tag or src_file == '':
-                ln = self.localname(p)
+                brpath, fpath = self.layoutobj.split_remote_name(p,
+                                                                 self.branches)
+                # we'll sometimes get a different path out of
+                # split_remate_name than the one we passed in, but
+                # only for the root of a branch, since the svn copies
+                # of those will sometimes be of parent directories of
+                # our root
+                if fpath == '':
+                    ln = self.localname(brpath)
+                else:
+                    ln = self.localname(p)
                 if src_tag in self.tags:
                     changeid = self.tags[src_tag]
                     src_rev, src_branch = self.get_source_rev(changeid)[:2]

File hgsubversion/wrappers.py

     'tagpaths': ('hgsubversion', 'tagpaths'),
     'authors': ('hgsubversion', 'authormap'),
     'branchdir': ('hgsubversion', 'branchdir'),
+    'infix': ('hgsubversion', 'infix'),
     'filemap': ('hgsubversion', 'filemap'),
     'branchmap': ('hgsubversion', 'branchmap'),
     'tagmap': ('hgsubversion', 'tagmap'),

File tests/fixtures/subprojects.sh

+#!/usr/bin/env bash
+
+set -e
+
+mkdir temp
+cd temp
+
+svnadmin create testrepo
+svn checkout file://`pwd`/testrepo client
+
+cd client
+mkdir trunk
+mkdir -p branches
+mkdir -p tags
+
+svn add trunk branches tags
+svn commit -m "Initial commit"
+
+mkdir trunk/project trunk/other
+echo "project trunk" > trunk/project/file
+echo "other trunk" > trunk/other/phile
+svn add trunk/project trunk/other
+svn commit -m "Added file and phile in trunk"
+
+svn up
+
+svn cp trunk tags/tag_from_trunk
+svn ci -m 'created tag from trunk'
+
+svn up
+
+svn cp trunk branches/branch
+svn ci -m 'created branch from trunk'
+
+svn up
+
+echo "project branch" > branches/branch/project/file
+svn ci -m "committed to the project branch"
+
+svn up
+
+echo "trunk2" > trunk/project/file
+svn ci -m "committed to trunk again"
+
+svn up
+
+echo "other branch" > branches/branch/other/phile
+svn ci -m "committed to the other branch"
+
+svn up
+
+svn cp branches/branch tags/tag_from_branch
+svn ci -m "create tag from branch"
+
+cd ..
+svnadmin dump testrepo > ../subprojects.svndump
+
+echo "Created subprojects.svndump"
+echo "You might want to clean up ${PWD} now"

File tests/fixtures/subprojects.svndump

+SVN-fs-dump-format-version: 2
+
+UUID: 03c99a5f-42f9-43e0-bb0d-03549a88a7e4
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2013-07-23T22:47:56.963334Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 120
+Content-length: 120
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-07-23T22:47:57.401454Z
+K 7
+svn:log
+V 14
+Initial commit
+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: 135
+Content-length: 135
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-07-23T22:47:57.849874Z
+K 7
+svn:log
+V 29
+Added file and phile in trunk
+PROPS-END
+
+Node-path: trunk/other
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/other/phile
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: fe5279547ba9d8c257b67c1938853896
+Text-content-sha1: 6c94bf284aa7bc931c358ae3dfcfb4fc9f335579
+Content-length: 22
+
+PROPS-END
+other trunk
+
+
+Node-path: trunk/project
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/project/file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 14
+Text-content-md5: d61b3a5935cb974e41082d9eb8eb912e
+Text-content-sha1: 1e7f7740062dc540ab20fb6cf395cad3c55f396f
+Content-length: 24
+
+PROPS-END
+project trunk
+
+
+Revision-number: 3
+Prop-content-length: 128
+Content-length: 128
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-07-23T22:47:58.281764Z
+K 7
+svn:log
+V 22
+created tag from trunk
+PROPS-END
+
+Node-path: tags/tag_from_trunk
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 131
+Content-length: 131
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-07-23T22:47:59.456625Z
+K 7
+svn:log
+V 25
+created branch from trunk
+PROPS-END
+
+Node-path: branches/branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+
+
+Revision-number: 5
+Prop-content-length: 137
+Content-length: 137
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-07-23T22:47:59.862054Z
+K 7
+svn:log
+V 31
+committed to the project branch
+PROPS-END
+
+Node-path: branches/branch/project/file
+Node-kind: file
+Node-action: change
+Text-content-length: 15
+Text-content-md5: 64cdb38c10361681c4c2918a222a3102
+Text-content-sha1: 545ef3bb672a1dd01fb9bd2a2eb7621882a4c701
+Content-length: 15
+
+project branch
+
+
+Revision-number: 6
+Prop-content-length: 130
+Content-length: 130
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-07-23T22:48:00.345069Z
+K 7
+svn:log
+V 24
+committed to trunk again
+PROPS-END
+
+Node-path: trunk/project/file
+Node-kind: file
+Node-action: change
+Text-content-length: 7
+Text-content-md5: 28d0a7e7ef2864416b7a9398623e4d09
+Text-content-sha1: 91454e2d3487f712490f17481157e389c11a6fe0
+Content-length: 7
+
+trunk2
+
+
+Revision-number: 7
+Prop-content-length: 135
+Content-length: 135
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-07-23T22:48:00.751804Z
+K 7
+svn:log
+V 29
+committed to the other branch
+PROPS-END
+
+Node-path: branches/branch/other/phile
+Node-kind: file
+Node-action: change
+Text-content-length: 13
+Text-content-md5: 7c133b867f55c0ba8688e1f111ddebaf
+Text-content-sha1: aee59a1c349cedc1ab035263bd7f14d58c6ab33b
+Content-length: 13
+
+other branch
+
+
+Revision-number: 8
+Prop-content-length: 128
+Content-length: 128
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-07-23T22:48:01.199203Z
+K 7
+svn:log
+V 22
+create tag from branch
+PROPS-END
+
+Node-path: tags/tag_from_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 7
+Node-copyfrom-path: branches/branch
+
+

File tests/test_fetch_branches.py

         expected_tags = set(['tip', 'tag_from_trunk', 'tag_from_branch'])
         self.assertEqual(tags, expected_tags)
 
+    def test_subproject_fetch(self):
+        config = {
+            'hgsubversion.infix': 'project',
+            }
+        repo = self._load_fixture_and_fetch('subprojects.svndump',
+                                            layout='standard',
+                                            config=config)
+
+        heads = set([repo[n].branch() for n in repo.heads()])
+        expected_heads = set(['default', 'branch'])
+        self.assertEqual(heads, expected_heads)
+
+        tags = set(repo.tags())
+        expected_tags = set(['tip', 'tag_from_trunk', 'tag_from_branch'])
+        self.assertEqual(tags, expected_tags)
+
+        for head in repo.heads():
+            ctx = repo[head]
+            self.assertFalse('project/file' in ctx, 'failed to strip infix')
+            self.assertTrue('file' in ctx, 'failed to track a simple file')
+            self.assertFalse('other/phile' in ctx, 'pulled in other project')
+            self.assertFalse('phile' in ctx, 'merged other project in repo')
+
+
 def suite():
     all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestFetchBranches),
           ]