changeset 1093:791382a21cc4

layouts: add support for configuring branches directory This should work for both alternately named directories (e.g. releases instead of branches), as well as subdirs (branches/releases), and combinations thereof (releases/public).
author David Schleimer <dschleimer@fb.com>
date Tue, 11 Jun 2013 17:53:31 -0700
parents cd0d14e25757
children 9a7e3dbd0f6e
files hgsubversion/__init__.py hgsubversion/help/subversion.rst hgsubversion/layouts/standard.py hgsubversion/wrappers.py tests/fixtures/misspelled_branches_tags.sh tests/fixtures/misspelled_branches_tags.svndump tests/fixtures/subdir_branches_tags.sh tests/fixtures/subdir_branches_tags.svndump tests/test_fetch_branches.py
diffstat 9 files changed, 628 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/hgsubversion/__init__.py
+++ b/hgsubversion/__init__.py
@@ -84,6 +84,8 @@ wrapcmds = { # cmd: generic, target, fix
     'clone': (False, 'sources', True, True, [
         ('T', 'tagpaths', '',
          'list of paths to search for tags in Subversion repositories'),
+        ('', 'branchdir', '',
+         'path to search for branches in subversion repositories'),
         ('A', 'authors', '',
          'file mapping Subversion usernames to Mercurial authors'),
         ('', 'filemap', '',
--- a/hgsubversion/help/subversion.rst
+++ b/hgsubversion/help/subversion.rst
@@ -32,7 +32,9 @@ all its tags and branches. In such cases
 trunk, as in the example above. This is known as `standard layout`, and works
 with repositories that use the conventional ``trunk``, ``tags`` and ``branches``
 directories. By default, hgsubversion will use this layout whenever it finds any
-of these directories at the specified directory on the server.
+of these directories at the specified directory on the server.  Standard layout
+also supports alternate names for the ``branches`` directory and multiple tags
+locations.
 
 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
@@ -300,6 +302,12 @@ settings:
     Path to a file for changing branch names during the conversion from
     Subversion to Mercurial.
 
+  ``hgsubversion.branchdir``
+
+    Specifies the subdirectory to look for branches under.  The
+    default is ``branches``.  This option has no effect for
+    single-directory clones.
+
   ``hgsubversion.filemap``
 
     Path to a file for filtering files during the conversion. Files may either
--- a/hgsubversion/layouts/standard.py
+++ b/hgsubversion/layouts/standard.py
@@ -12,11 +12,17 @@ class StandardLayout(base.BaseLayout):
 
         self._tag_locations = None
 
+        self._branch_dir = ui.config('hgsubversion', 'branchdir', 'branches')
+        if self._branch_dir[0] == '/':
+            self._branch_dir = self._branch_dir[1:]
+        if self._branch_dir[-1] != '/':
+            self._branch_dir += '/'
+
     def localname(self, path):
         if path == 'trunk':
             return None
-        elif path.startswith('branches/'):
-            return path[len('branches/'):]
+        elif path.startswith(self._branch_dir):
+            return path[len(self._branch_dir):]
         return  '../%s' % path
 
     def remotename(self, branch):
@@ -24,7 +30,7 @@ class StandardLayout(base.BaseLayout):
             return 'trunk'
         elif branch.startswith('../'):
             return branch[3:]
-        return 'branches/%s' % branch
+        return '%s%s' % (self._branch_dir, branch)
 
     def remotepath(self, branch, subdir='/'):
         if subdir == '/':
@@ -34,7 +40,7 @@ class StandardLayout(base.BaseLayout):
             if branch.startswith('../'):
                 branchpath = branch[3:]
             else:
-                branchpath = 'branches/%s' % branch
+                branchpath = '%s%s' % (self._branch_dir, branch)
 
         return '%s/%s' % (subdir or '', branchpath)
 
@@ -88,14 +94,17 @@ class StandardLayout(base.BaseLayout):
             return candidate, '/'.join(components)
 
         if path == 'trunk' or path.startswith('trunk/'):
-            branch_path = 'trunk'
-            local_path = '/'.join(path.split('/')[1:])
-        elif path.startswith('branches/'):
-            components = path.split('/')
-            branch_path = '/'.join(components[:2])
-            local_path = '/'.join(components[2:])
-        else:
-            components = path.split('/')
-            branch_path = '/'.join(components[:-1])
-            local_path = components[-1]
-        return branch_path, local_path
+            return 'trunk', path[len('trunk/'):]
+
+        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])
+            if len(components) == 1:
+                local_path = ''
+            else:
+                local_path = components[1]
+            return branch_path, local_path
+
+        components = path.split('/')
+        return '/'.join(components[:-1]), components[-1]
--- a/hgsubversion/wrappers.py
+++ b/hgsubversion/wrappers.py
@@ -533,6 +533,7 @@ def rebase(orig, ui, repo, **opts):
 optionmap = {
     'tagpaths': ('hgsubversion', 'tagpaths'),
     'authors': ('hgsubversion', 'authormap'),
+    'branchdir': ('hgsubversion', 'branchdir'),
     'filemap': ('hgsubversion', 'filemap'),
     'branchmap': ('hgsubversion', 'branchmap'),
     'tagmap': ('hgsubversion', 'tagmap'),
new file mode 100755
--- /dev/null
+++ b/tests/fixtures/misspelled_branches_tags.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+
+set -e
+
+mkdir temp
+cd temp
+
+svnadmin create testrepo
+svn checkout file://`pwd`/testrepo client
+
+cd client
+mkdir trunk
+mkdir branchez
+mkdir tagz
+
+svn add trunk branchez tagz
+svn commit -m "Initial commit"
+
+echo "trunk" >> trunk/file
+svn add trunk/file
+svn commit -m "Added file in trunk"
+
+svn cp trunk tagz/tag_from_trunk
+svn ci -m 'created tag from trunk'
+
+svn cp trunk branchez/branch
+svn ci -m 'created branch from trunk'
+
+echo "branch" > branchez/branch/file
+svn ci -m "committed to the branch"
+
+svn cp branchez/branch tagz/tag_from_branch
+svn ci -m "create tag from branch"
+
+cd ..
+svnadmin dump testrepo > ../misspelled_branches_tags.svndump
+
+echo "Created misspelled_branches_tags.svndump"
+echo "You might want to clean up ${PWD} now"
new file mode 100644
--- /dev/null
+++ b/tests/fixtures/misspelled_branches_tags.svndump
@@ -0,0 +1,227 @@
+SVN-fs-dump-format-version: 2
+
+UUID: a4f285b8-14d5-4bc0-92c8-0e5438624f2e
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2013-06-13T00:26:00.303912Z
+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-06-13T00:26:00.666275Z
+K 7
+svn:log
+V 14
+Initial commit
+PROPS-END
+
+Node-path: branchez
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: tagz
+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: 125
+Content-length: 125
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-06-13T00:26:00.997106Z
+K 7
+svn:log
+V 19
+Added file in trunk
+PROPS-END
+
+Node-path: trunk/file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: edf45fe5c98c5367733b39bbb2bb20d9
+Text-content-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239
+Content-length: 16
+
+PROPS-END
+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-06-13T00:26:01.435764Z
+K 7
+svn:log
+V 22
+created tag from trunk
+PROPS-END
+
+Node-path: tagz/tag_from_trunk
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Node-path: tagz/tag_from_trunk/file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/file
+Text-copy-source-md5: edf45fe5c98c5367733b39bbb2bb20d9
+Text-copy-source-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239
+
+
+Revision-number: 4
+Prop-content-length: 131
+Content-length: 131
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-06-13T00:26:01.816716Z
+K 7
+svn:log
+V 25
+created branch from trunk
+PROPS-END
+
+Node-path: branchez/branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Node-path: branchez/branch/file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/file
+Text-copy-source-md5: edf45fe5c98c5367733b39bbb2bb20d9
+Text-copy-source-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239
+
+
+Revision-number: 5
+Prop-content-length: 129
+Content-length: 129
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-06-13T00:26:02.232496Z
+K 7
+svn:log
+V 23
+committed to the branch
+PROPS-END
+
+Node-path: branchez/branch/file
+Node-kind: file
+Node-action: change
+Text-content-length: 7
+Text-content-md5: 99df69f80e72a660346459fa63c31fd4
+Text-content-sha1: f49390feacc0a7fb2b36ad16dc0bc44036193402
+Content-length: 7
+
+branch
+
+
+Revision-number: 6
+Prop-content-length: 128
+Content-length: 128
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-06-13T00:26:02.596105Z
+K 7
+svn:log
+V 22
+create tag from branch
+PROPS-END
+
+Node-path: tagz/tag_from_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: branchez/branch
+
+
+Node-path: tagz/tag_from_branch/file
+Node-kind: file
+Node-action: delete
+
+Node-path: tagz/tag_from_branch/file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branchez/branch/file
+Text-copy-source-md5: 99df69f80e72a660346459fa63c31fd4
+Text-copy-source-sha1: f49390feacc0a7fb2b36ad16dc0bc44036193402
+
+
+
+
new file mode 100755
--- /dev/null
+++ b/tests/fixtures/subdir_branches_tags.sh
@@ -0,0 +1,39 @@
+#!/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 bran/ches
+mkdir -p ta/gs
+
+svn add trunk bran ta
+svn commit -m "Initial commit"
+
+echo "trunk" >> trunk/file
+svn add trunk/file
+svn commit -m "Added file in trunk"
+
+svn cp trunk ta/gs/tag_from_trunk
+svn ci -m 'created tag from trunk'
+
+svn cp trunk bran/ches/branch
+svn ci -m 'created branch from trunk'
+
+echo "branch" > bran/ches/branch/file
+svn ci -m "committed to the branch"
+
+svn cp bran/ches/branch ta/gs/tag_from_branch
+svn ci -m "create tag from branch"
+
+cd ..
+svnadmin dump testrepo > ../subdir_branches_tags.svndump
+
+echo "Created subdir_branches_tags.svndump"
+echo "You might want to clean up ${PWD} now"
new file mode 100644
--- /dev/null
+++ b/tests/fixtures/subdir_branches_tags.svndump
@@ -0,0 +1,245 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 591b9313-8b8d-45af-bb0f-4d8efe82f2b0
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2013-06-13T00:25:39.145214Z
+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-06-13T00:25:39.542218Z
+K 7
+svn:log
+V 14
+Initial commit
+PROPS-END
+
+Node-path: bran
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: bran/ches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: ta
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: ta/gs
+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: 125
+Content-length: 125
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-06-13T00:25:39.963701Z
+K 7
+svn:log
+V 19
+Added file in trunk
+PROPS-END
+
+Node-path: trunk/file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: edf45fe5c98c5367733b39bbb2bb20d9
+Text-content-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239
+Content-length: 16
+
+PROPS-END
+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-06-13T00:25:40.344923Z
+K 7
+svn:log
+V 22
+created tag from trunk
+PROPS-END
+
+Node-path: ta/gs/tag_from_trunk
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Node-path: ta/gs/tag_from_trunk/file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/file
+Text-copy-source-md5: edf45fe5c98c5367733b39bbb2bb20d9
+Text-copy-source-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239
+
+
+Revision-number: 4
+Prop-content-length: 131
+Content-length: 131
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-06-13T00:25:40.700526Z
+K 7
+svn:log
+V 25
+created branch from trunk
+PROPS-END
+
+Node-path: bran/ches/branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Node-path: bran/ches/branch/file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/file
+Text-copy-source-md5: edf45fe5c98c5367733b39bbb2bb20d9
+Text-copy-source-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239
+
+
+Revision-number: 5
+Prop-content-length: 129
+Content-length: 129
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-06-13T00:25:41.081165Z
+K 7
+svn:log
+V 23
+committed to the branch
+PROPS-END
+
+Node-path: bran/ches/branch/file
+Node-kind: file
+Node-action: change
+Text-content-length: 7
+Text-content-md5: 99df69f80e72a660346459fa63c31fd4
+Text-content-sha1: f49390feacc0a7fb2b36ad16dc0bc44036193402
+Content-length: 7
+
+branch
+
+
+Revision-number: 6
+Prop-content-length: 128
+Content-length: 128
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2013-06-13T00:25:41.512313Z
+K 7
+svn:log
+V 22
+create tag from branch
+PROPS-END
+
+Node-path: ta/gs/tag_from_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: bran/ches/branch
+
+
+Node-path: ta/gs/tag_from_branch/file
+Node-kind: file
+Node-action: delete
+
+Node-path: ta/gs/tag_from_branch/file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: bran/ches/branch/file
+Text-copy-source-md5: 99df69f80e72a660346459fa63c31fd4
+Text-copy-source-sha1: f49390feacc0a7fb2b36ad16dc0bc44036193402
+
+
+
+
--- a/tests/test_fetch_branches.py
+++ b/tests/test_fetch_branches.py
@@ -128,3 +128,45 @@ class TestFetchBranches(test_util.TestBa
         for f in ctx:
             self.assertTrue(not ctx[f].renamed())
 
+    def test_misspelled_branches_tags(self):
+        config = {
+            'hgsubversion.branchdir': 'branchez',
+            'hgsubversion.tagpaths': 'tagz',
+            }
+        '''Tests using the tags dir for branches and the branches dir for tags'''
+        repo = self._load_fixture_and_fetch('misspelled_branches_tags.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)
+
+    def test_subdir_branches_tags(self):
+        '''Tests using the tags dir for branches and the branches dir for tags'''
+        config = {
+            'hgsubversion.branchdir': 'bran/ches',
+            'hgsubversion.tagpaths': 'ta/gs',
+            }
+        repo = self._load_fixture_and_fetch('subdir_branches_tags.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)
+
+def suite():
+    all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestFetchBranches),
+          ]
+    return unittest.TestSuite(all_tests)