# HG changeset patch # User Augie Fackler # Date 1255750421 14400 # Node ID 1fd3cfa47c5e21b0e9a1211ee6c677abad459a33 # Parent 990e07054f291ce695ba4d3f31907b15b3080139 Support for single-directory clones. diff --git a/hgsubversion/__init__.py b/hgsubversion/__init__.py --- a/hgsubversion/__init__.py +++ b/hgsubversion/__init__.py @@ -66,6 +66,8 @@ wrapcmds = { # cmd: generic, target, fix 'file mapping Subversion usernames to Mercurial authors'), ('', 'filemap', '', 'file containing rules for remapping Subversion repository paths'), + ('', 'layout', 'auto', ('import standard layout or single ' + 'directory? Can be standard, single, or auto.')), ]), } diff --git a/hgsubversion/editor.py b/hgsubversion/editor.py --- a/hgsubversion/editor.py +++ b/hgsubversion/editor.py @@ -94,7 +94,7 @@ class RevisionData(object): self.ui.flush() if p[-1] == '/': dir = p[len(root):] - new = [dir + f for f, k in svn.list_files(dir, r) if k == 'f'] + new = [p + f for f, k in svn.list_files(dir, r) if k == 'f'] files.update(new) else: files.add(p[len(root):]) @@ -107,7 +107,7 @@ class RevisionData(object): if i % 50 == 0: svn.init_ra_and_client() i += 1 - data, mode = svn.get_file(p, r) + data, mode = svn.get_file(p[len(root):], r) self.set(p, data, 'x' in mode, 'l' in mode) self.missing = set() @@ -304,7 +304,7 @@ class HgEditor(delta.Editor): def open_directory(self, path, parent_baton, base_revision, dir_pool=None): self.current.batons[path] = path p_, branch = self.meta.split_branch_path(path)[:2] - if p_ == '': + if p_ == '' or (self.meta.layout == 'single' and p_): self.current.emptybranches[branch] = False return path diff --git a/hgsubversion/replay.py b/hgsubversion/replay.py --- a/hgsubversion/replay.py +++ b/hgsubversion/replay.py @@ -29,14 +29,13 @@ def convert_rev(ui, meta, svn, r, tbdelt current.findmissing(svn) # update externals - - if current.externals: + # TODO fix and re-enable externals for single-directory clones + if current.externals and not meta.layout == 'single': # accumulate externals records for all branches revnum = current.rev.revnum branches = {} for path, entry in current.externals.iteritems(): - if not meta.is_path_valid(path): ui.warn('WARNING: Invalid path %s in externals\n' % path) continue @@ -56,7 +55,9 @@ def convert_rev(ui, meta, svn, r, tbdelt # register externals file changes for bp, external in branches.iteritems(): - path = bp + '/.hgsvnexternals' + if bp and bp[-1] != '/': + bp += '/' + path = (bp and bp + '.hgsvnexternals') or '.hgsvnexternals' if external: current.set(path, external.write(), False, False) else: diff --git a/hgsubversion/stupid.py b/hgsubversion/stupid.py --- a/hgsubversion/stupid.py +++ b/hgsubversion/stupid.py @@ -103,6 +103,8 @@ def diff_branchrev(ui, svn, meta, branch error. """ def make_diff_path(branch): + if meta.layout == 'single': + return '' if branch == 'trunk' or branch is None: return 'trunk' elif branch.startswith('../'): @@ -203,7 +205,10 @@ def diff_branchrev(ui, svn, meta, branch for p in r.paths: if p.startswith(diff_path) and r.paths[p].action == 'D': - p2 = p[len(diff_path)+1:].strip('/') + if diff_path: + p2 = p[len(diff_path)+1:].strip('/') + else: + p2 = p if p2 in parentctx: files_data[p2] = None continue @@ -221,7 +226,10 @@ def diff_branchrev(ui, svn, meta, branch raise IOError() if path in binary_files: - data, mode = svn.get_file(diff_path + '/' + path, r.revnum) + pa = path + if diff_path: + pa = diff_path + '/' + path + data, mode = svn.get_file(pa, r.revnum) isexe = 'x' in mode islink = 'l' in mode else: @@ -236,6 +244,10 @@ def diff_branchrev(ui, svn, meta, branch data = parentctx[path].data() copied = copies.get(path) + # TODO this branch feels like it should not be required, + # and this may actually imply a bug in getcopies + if copied not in parentctx.manifest(): + copied = None return context.memfilectx(path=path, data=data, islink=islink, isexec=isexe, copied=copied) @@ -347,7 +359,7 @@ def fetch_externals(svn, branchpath, r, dirs.update([p for p,k in svn.list_files(branchpath, r.revnum) if k == 'd']) dirs.add('') else: - branchprefix = branchpath + '/' + branchprefix = (branchpath and branchpath + '/') or branchpath for path, e in r.paths.iteritems(): if e.action == 'D': continue @@ -369,7 +381,8 @@ def fetch_externals(svn, branchpath, r, # Retrieve new or updated values for dir in dirs: try: - values = svn.list_props(branchpath + '/' + dir, r.revnum) + dpath = (branchpath and branchpath + '/' + dir) or dir + values = svn.list_props(dpath, r.revnum) externals[dir] = values.get('svn:externals', '') except IOError: externals[dir] = '' @@ -394,7 +407,7 @@ def fetch_branchrev(svn, meta, branch, b if kind == 'f': files.append(path) else: - branchprefix = branchpath + '/' + branchprefix = (branchpath and branchpath + '/') or '' for path, e in r.paths.iteritems(): if not path.startswith(branchprefix): continue @@ -423,7 +436,10 @@ def fetch_branchrev(svn, meta, branch, b copies = getcopies(svn, meta, branch, branchpath, r, files, parentctx) def filectxfn(repo, memctx, path): - data, mode = svn.get_file(branchpath + '/' + path, r.revnum) + svnpath = path + if branchpath: + svnpath = branchpath + '/' + path + data, mode = svn.get_file(svnpath, r.revnum) isexec = 'x' in mode islink = 'l' in mode copied = copies.get(path) @@ -509,7 +525,6 @@ def branches_in_paths(meta, tbdelta, pat if branchname and branchname.startswith('../'): continue branches[branchname] = branchpath - return branches def convert_rev(ui, meta, svn, r, tbdelta): @@ -549,7 +564,6 @@ def convert_rev(ui, meta, svn, r, tbdelt date = meta.fixdate(r.date) check_deleted_branches = set(tbdelta['branches'][1]) for b in branches: - parentctx = meta.repo[meta.get_parent_revision(r.revnum, b)] if parentctx.branch() != (b or 'default'): check_deleted_branches.add(b) @@ -570,9 +584,10 @@ def convert_rev(ui, meta, svn, r, tbdelt files_touched, filectxfn2 = fetch_branchrev( svn, meta, b, branches[b], r, parentctx) - externals = fetch_externals(svn, branches[b], r, parentctx) - if externals is not None: - files_touched.append('.hgsvnexternals') + if meta.layout != 'single': + externals = fetch_externals(svn, branches[b], r, parentctx) + if externals is not None: + files_touched.append('.hgsvnexternals') def filectxfn(repo, memctx, path): if path == '.hgsvnexternals': diff --git a/hgsubversion/svncommands.py b/hgsubversion/svncommands.py --- a/hgsubversion/svncommands.py +++ b/hgsubversion/svncommands.py @@ -32,17 +32,23 @@ def verify(ui, repo, *args, **opts): if args: url = args[0] svn = svnrepo.svnremoterepo(ui, url).svn + meta = repo.svnmeta(svn.uuid, svn.subdir) btypes = {'default': 'trunk'} - branchpath = btypes.get(ctx.branch(), 'branches/%s' % ctx.branch()) - branchpath = posixpath.normpath(branchpath) + if meta.layout == 'standard': + branchpath = btypes.get(ctx.branch(), 'branches/%s' % ctx.branch()) + else: + branchpath = '' svnfiles = set() result = 0 - for fn, type in svn.list_files(branchpath, srev): + for fn, type in svn.list_files(posixpath.normpath(branchpath), srev): if type != 'f': continue svnfiles.add(fn) - data, mode = svn.get_file(branchpath + '/' + fn, srev) + fp = fn + if branchpath: + fp = branchpath + '/' + fn + data, mode = svn.get_file(posixpath.normpath(fp), srev) fctx = ctx[fn] dmatch = fctx.data() == data mmatch = fctx.flags() == mode @@ -87,6 +93,8 @@ def rebuildmeta(ui, repo, hg_repo_path, os.unlink(maps.TagMap.filepath(repo)) tags = maps.TagMap(repo) + layout = None + skipped = set() for rev in repo: @@ -126,6 +134,18 @@ def rebuildmeta(ui, repo, hg_repo_path, assert revpath.startswith(subdir), ('That does not look like the ' 'right location in the repo.') + if layout is None: + if (subdir or '/') == revpath: + layout = 'single' + else: + layout = 'standard' + f = open(os.path.join(svnmetadir, 'layout'), 'w') + f.write(layout) + f.close() + elif layout == 'single': + assert (subdir or '/') == revpath, ('Possible layout detection' + ' defect in replay') + # write repository uuid if required if uuid is None: uuid = convinfo[4:40] @@ -142,17 +162,17 @@ def rebuildmeta(ui, repo, hg_repo_path, # find commitpath, write to revmap commitpath = revpath[len(subdir)+1:] - bp = posixpath.normpath('/'.join([subdir, 'branches', ctx.branch()])) - if revpath == bp: - commitpath = ctx.branch() - elif commitpath == 'trunk': - commitpath = '' - elif commitpath.startswith('tags'): - if ctx.extra().get('close'): - continue - commitpath = '../' + commitpath + if layout == 'standard': + if commitpath.startswith('branches/'): + commitpath = commitpath[len('branches/'):] + elif commitpath == 'trunk': + commitpath = '' + else: + if commitpath.startswith('tags/') and ctx.extra().get('close'): + continue + commitpath = '../' + commitpath else: - assert False, 'unhandled rev %s: %s' % (rev, convinfo) + commitpath = '' revmap.write('%s %s %s\n' % (revision, ctx.hex(), commitpath)) revision = int(revision) diff --git a/hgsubversion/svnmeta.py b/hgsubversion/svnmeta.py --- a/hgsubversion/svnmeta.py +++ b/hgsubversion/svnmeta.py @@ -69,6 +69,13 @@ class SVNMeta(object): f.close() else: self.tag_locations = tag_locations + if os.path.exists(self.layoutfile): + f = open(self.layoutfile) + self._layout = f.read().strip() + f.close() + self.repo.ui.setconfig('hgsubversion', 'layout', self._layout) + else: + self._layout = None pickle_atomic(self.tag_locations, self.tag_locations_file, self.meta_data_dir) # ensure nested paths are handled properly @@ -82,6 +89,21 @@ class SVNMeta(object): self.lastdate = '1970-01-01 00:00:00 -0000' self.filemap = maps.FileMap(repo) + @property + def layout(self): + # this method can't determine the layout, but it needs to be + # resolved into something other than auto before this ever + # gets called + if not self._layout or self._layout == 'auto': + lo = self.repo.ui.config('hgsubversion', 'layout', default='auto') + if lo == 'auto': + raise hgutil.Abort('layout not yet determined') + self._layout = lo + f = open(self.layoutfile, 'w') + f.write(self._layout) + f.close() + return self._layout + @property def editor(self): if not hasattr(self, '_editor'): @@ -127,6 +149,10 @@ class SVNMeta(object): def authors_file(self): return os.path.join(self.meta_data_dir, 'authors') + @property + def layoutfile(self): + return os.path.join(self.meta_data_dir, 'layout') + def fixdate(self, date): if date is not None: date = date.replace('T', ' ').replace('Z', '').split('.')[0] @@ -145,6 +171,8 @@ class SVNMeta(object): def localname(self, path): """Compute the local name for a branch located at path. """ + if self.layout == 'single': + return 'default' if path == 'trunk': return None elif path.startswith('branches/'): @@ -152,6 +180,8 @@ class SVNMeta(object): return '../%s' % path def remotename(self, branch): + if self.layout == 'single': + return '' if branch == 'default' or branch is None: return 'trunk' elif branch.startswith('../'): @@ -160,23 +190,27 @@ class SVNMeta(object): def genextra(self, revnum, branch): extra = {} - branchpath = 'trunk' - if branch: - extra['branch'] = branch - if branch.startswith('../'): - branchpath = branch[3:] - else: - branchpath = 'branches/%s' % branch - subdir = self.subdir if subdir and subdir[-1] == '/': subdir = subdir[:-1] if subdir and subdir[0] != '/': subdir = '/' + subdir + if self.layout == 'single': + path = subdir or '/' + else: + branchpath = 'trunk' + if branch: + extra['branch'] = branch + if branch.startswith('../'): + branchpath = branch[3:] + else: + branchpath = 'branches/%s' % branch + path = '%s/%s' % (subdir , branchpath) + extra['convert_revision'] = 'svn:%(uuid)s%(path)s@%(rev)s' % { 'uuid': self.uuid, - 'path': '%s/%s' % (subdir , branchpath), + 'path': path, 'rev': revnum, } return extra @@ -185,6 +219,8 @@ class SVNMeta(object): '''Normalize a path to strip of leading slashes and our subdir if we have one. ''' + if self.subdir and path == self.subdir[:-1]: + return '' if path and path[0] == '/': path = path[1:] if path and path.startswith(self.subdir): @@ -200,6 +236,8 @@ class SVNMeta(object): Note that it's only a tag if it was copied from the path '' in a branch (or tag) we have, for our purposes. """ + if self.layout == 'single': + return False path = self.normalize(path) for tagspath in self.tag_locations: onpath = path.startswith(tagspath) @@ -217,9 +255,11 @@ class SVNMeta(object): If existing=True, will return None, None, None if the file isn't on some known branch. If existing=False, then it will guess what the branch would be if it were - known. + known. Server-side branch path should be relative to our subdirectory. """ path = self.normalize(path) + if self.layout == 'single': + return (path, None, '') if self.is_path_tag(path): tag = self.is_path_tag(path) matched = [t for t in self.tags.iterkeys() if tag.startswith(t+'/')] @@ -325,6 +365,19 @@ class SVNMeta(object): return int(revnum), self.localname(self.normalize(branch)) def update_branch_tag_map_for_rev(self, revision): + """Given a revision object, determine changes to branches and tags. + + Returns: a dict of { + 'tags': (added_tags, rmtags), + 'branches': (added_branches, self.closebranches), + } where adds are dicts where the keys are branch/tag names and + values are the place the branch/tag came from. The deletions are + sets of the deleted branches. + """ + if self.layout == 'single': + return {'tags': ({}, set()), + 'branches': ({None: (None, 0, -1), }, set()), + } paths = revision.paths added_branches = {} added_tags = {} diff --git a/hgsubversion/svnwrap/svn_swig_wrapper.py b/hgsubversion/svnwrap/svn_swig_wrapper.py --- a/hgsubversion/svnwrap/svn_swig_wrapper.py +++ b/hgsubversion/svnwrap/svn_swig_wrapper.py @@ -345,7 +345,8 @@ class SubversionRepo(object): dir: the directory to list, no leading slash rev: the revision at which to list the directory, defaults to HEAD """ - if dir[-1] == '/': + # TODO this should just not accept leading slashes like the docstring says + if dir and dir[-1] == '/': dir = dir[:-1] if revision is None: revision = self.HEAD @@ -555,6 +556,7 @@ class SubversionRepo(object): otherwise. If the file does not exist at this revision, raise IOError. """ + assert not path.startswith('/') mode = '' try: out = cStringIO.StringIO() @@ -633,7 +635,9 @@ class SubversionRepo(object): def path2url(self, path): """Build svn URL for path, URL-escaping path. """ - assert path[0] != '/' + if not path or path == '.': + return self.svn_url + assert path[0] != '/', path return '/'.join((self.svn_url, urllib.quote(path).rstrip('/'), )) diff --git a/hgsubversion/wrappers.py b/hgsubversion/wrappers.py --- a/hgsubversion/wrappers.py +++ b/hgsubversion/wrappers.py @@ -228,6 +228,16 @@ def pull(repo, source, heads=[], force=F svn = svnrepo.svnremoterepo(repo.ui, svn_url).svn meta = repo.svnmeta(svn.uuid, svn.subdir) + layout = repo.ui.config('hgsubversion', 'layout', 'auto') + if layout == 'auto': + rootlist = svn.list_dir('', revision=(stopat_rev or None)) + if sum(map(lambda x: x in rootlist, ('branches', 'tags', 'trunk'))): + layout = 'standard' + else: + layout = 'single' + repo.ui.setconfig('hgsubversion', 'layout', layout) + repo.ui.note('using %s layout\n' % layout) + start = max(meta.revmap.seen, skipto_rev) initializing_repo = meta.revmap.seen <= 0 ui = repo.ui @@ -351,9 +361,10 @@ optionmap = { 'defaulthost': ('hgsubversion', 'defaulthost'), 'defaultauthors': ('hgsubversion', 'defaultauthors'), 'usebranchnames': ('hgsubversion', 'usebranchnames'), + 'layout': ('hgsubversion', 'layout'), } -dontretain = { 'hgsubversion': set(['authormap', 'filemap']) } +dontretain = { 'hgsubversion': set(['authormap', 'filemap', 'layout', ]) } def clone(orig, ui, source, dest=None, **opts): """ diff --git a/tests/comprehensive/test_stupid_pull.py b/tests/comprehensive/test_stupid_pull.py --- a/tests/comprehensive/test_stupid_pull.py +++ b/tests/comprehensive/test_stupid_pull.py @@ -9,9 +9,9 @@ from tests import test_util from hgsubversion import wrappers -def _do_case(self, name): +def _do_case(self, name, layout): subdir = test_util.subdir.get(name, '') - self._load_fixture_and_fetch(name, subdir=subdir, stupid=False) + self._load_fixture_and_fetch(name, subdir=subdir, stupid=False, layout=layout) assert len(self.repo) > 0, 'Repo had no changes, maybe you need to add a subdir entry in test_util?' wc2_path = self.wc_path + '_stupid' u = ui.ui() @@ -19,22 +19,28 @@ def _do_case(self, name): if subdir: checkout_path += '/' + subdir u.setconfig('hgsubversion', 'stupid', '1') + u.setconfig('hgsubversion', 'layout', layout) hg.clone(u, test_util.fileurl(checkout_path), wc2_path, update=False) + if layout == 'single': + self.assertEqual(len(self.repo.heads()), 1) self.repo2 = hg.repository(ui.ui(), wc2_path) self.assertEqual(self.repo.heads(), self.repo2.heads()) -def buildmethod(case, name): - m = lambda self: self._do_case(case) +def buildmethod(case, name, layout): + m = lambda self: self._do_case(case, layout) m.__name__ = name - m.__doc__ = 'Test stupid produces same as real on %s.' % case + m.__doc__ = 'Test stupid produces same as real on %s. (%s)' % (case, layout) return m attrs = {'_do_case': _do_case, } for case in (f for f in os.listdir(test_util.FIXTURES) if f.endswith('.svndump')): name = 'test_' + case[:-len('.svndump')] - attrs[name] = buildmethod(case, name) + attrs[name] = buildmethod(case, name, 'auto') + name += '_single' + attrs[name] = buildmethod(case, name, 'single') + StupidPullTests = type('StupidPullTests', (test_util.TestBase, ), attrs) diff --git a/tests/comprehensive/test_verify.py b/tests/comprehensive/test_verify.py --- a/tests/comprehensive/test_verify.py +++ b/tests/comprehensive/test_verify.py @@ -14,19 +14,19 @@ from mercurial import ui from hgsubversion import svncommands -def _do_case(self, name, stupid): +def _do_case(self, name, stupid, layout): subdir = test_util.subdir.get(name, '') - repo = self._load_fixture_and_fetch(name, subdir=subdir, stupid=stupid) + repo = self._load_fixture_and_fetch(name, subdir=subdir, stupid=stupid, layout=layout) assert len(self.repo) > 0 for i in repo: ctx = repo[i] self.assertEqual(svncommands.verify(repo.ui, repo, rev=ctx.node()), 0) -def buildmethod(case, name, stupid): - m = lambda self: self._do_case(case, stupid) +def buildmethod(case, name, stupid, layout): + m = lambda self: self._do_case(case, stupid, layout) m.__name__ = name - bits = case, stupid and 'stupid' or 'real' - m.__doc__ = 'Test verify on %s with %s replay.' % bits + bits = case, stupid and 'stupid' or 'real', layout + m.__doc__ = 'Test verify on %s with %s replay. (%s)' % bits return m attrs = {'_do_case': _do_case} @@ -35,10 +35,16 @@ for case in fixtures: # this fixture results in an empty repository, don't use it if case == 'project_root_not_repo_root.svndump': continue - name = 'test_' + case[:-len('.svndump')] - attrs[name] = buildmethod(case, name, False) - name += '_stupid' - attrs[name] = buildmethod(case, name, True) + bname = 'test_' + case[:-len('.svndump')] + attrs[bname] = buildmethod(case, bname, False, 'standard') + name = bname + '_stupid' + attrs[name] = buildmethod(case, name, True, 'standard') + name = bname + '_single' + attrs[name] = buildmethod(case, name, False, 'single') + # Disabled because the "stupid and real are the same" tests + # verify this plus even more. + # name = bname + '_single_stupid' + # attrs[name] = buildmethod(case, name, True, 'single') VerifyTests = type('VerifyTests', (test_util.TestBase,), attrs) diff --git a/tests/test_rebuildmeta.py b/tests/test_rebuildmeta.py --- a/tests/test_rebuildmeta.py +++ b/tests/test_rebuildmeta.py @@ -10,9 +10,12 @@ from mercurial import ui from hgsubversion import svncommands from hgsubversion import svnmeta -def _do_case(self, name, stupid): +def _do_case(self, name, stupid, single): subdir = test_util.subdir.get(name, '') - self._load_fixture_and_fetch(name, subdir=subdir, stupid=stupid) + layout = 'auto' + if single: + layout = 'single' + self._load_fixture_and_fetch(name, subdir=subdir, stupid=stupid, layout=layout) assert len(self.repo) > 0 wc2_path = self.wc_path + '_clone' u = ui.ui() @@ -27,7 +30,7 @@ def _do_case(self, name, stupid): self.assertTrue(os.path.isdir(os.path.join(src.path, 'svn')), 'no .hg/svn directory in the destination!') dest = hg.repository(u, os.path.dirname(dest.path)) - for tf in ('rev_map', 'uuid', 'tagmap', ): + for tf in ('rev_map', 'uuid', 'tagmap', 'layout', ): stf = os.path.join(src.path, 'svn', tf) self.assertTrue(os.path.isfile(stf), '%r is missing!' % stf) dtf = os.path.join(dest.path, 'svn', tf) @@ -54,11 +57,15 @@ def _do_case(self, name, stupid): self.assertEqual(srcinfo[2], destinfo[2]) -def buildmethod(case, name, stupid): - m = lambda self: self._do_case(case, stupid) +def buildmethod(case, name, stupid, single): + m = lambda self: self._do_case(case, stupid, single) m.__name__ = name - m.__doc__ = ('Test rebuildmeta on %s with %s replay.' % - (case, (stupid and 'stupid') or 'real')) + m.__doc__ = ('Test rebuildmeta on %s with %s replay. (%s)' % + (case, + (stupid and 'stupid') or 'real', + (single and 'single') or 'standard', + ) + ) return m @@ -68,10 +75,13 @@ for case in [f for f in os.listdir(test_ # this fixture results in an empty repository, don't use it if case == 'project_root_not_repo_root.svndump': continue - name = 'test_' + case[:-len('.svndump')] - attrs[name] = buildmethod(case, name, False) - name += '_stupid' - attrs[name] = buildmethod(case, name, True) + bname = 'test_' + case[:-len('.svndump')] + attrs[bname] = buildmethod(case, bname, False, False) + name = bname + '_stupid' + attrs[name] = buildmethod(case, name, True, False) + name = bname + '_single' + attrs[name] = buildmethod(case, name, False, True) + RebuildMetaTests = type('RebuildMetaTests', (test_util.TestBase, ), attrs) diff --git a/tests/test_single_dir_clone.py b/tests/test_single_dir_clone.py new file mode 100644 --- /dev/null +++ b/tests/test_single_dir_clone.py @@ -0,0 +1,72 @@ +import shutil + +import test_util + + +class TestSingleDir(test_util.TestBase): + def test_clone_single_dir_simple(self): + repo = self._load_fixture_and_fetch('branch_from_tag.svndump', + stupid=False, + layout='single', + subdir='') + self.assertEqual(repo.branchtags().keys(), ['default']) + self.assertEqual(repo['tip'].manifest().keys(), + ['trunk/beta', + 'tags/copied_tag/alpha', + 'trunk/alpha', + 'tags/copied_tag/beta', + 'branches/branch_from_tag/alpha', + 'tags/tag_r3/alpha', + 'tags/tag_r3/beta', + 'branches/branch_from_tag/beta']) + + def test_auto_detect_single(self): + repo = self._load_fixture_and_fetch('branch_from_tag.svndump', + stupid=False, + layout='auto') + self.assertEqual(repo.branchtags().keys(), ['default', + 'branch_from_tag']) + oldmanifest = test_util.filtermanifest(repo['default'].manifest().keys()) + # remove standard layout + shutil.rmtree(self.wc_path) + # try again with subdir to get single dir clone + repo = self._load_fixture_and_fetch('branch_from_tag.svndump', + stupid=False, + layout='auto', + subdir='trunk') + self.assertEqual(repo.branchtags().keys(), ['default', ]) + self.assertEqual(repo['default'].manifest().keys(), oldmanifest) + + def test_externals_single(self): + repo = self._load_fixture_and_fetch('externals.svndump', + stupid=False, + layout='single') + for rev in repo: + assert '.hgsvnexternals' not in repo[rev].manifest() + return # TODO enable test when externals in single are fixed + expect = """[.] + -r2 ^/externals/project2@2 deps/project2 +[subdir] + ^/externals/project1 deps/project1 +[subdir2] + ^/externals/project1 deps/project1 +""" + test = 2 + self.assertEqual(self.repo[test]['.hgsvnexternals'].data(), expect) + + def test_externals_single_whole_repo(self): + # This is the test which demonstrates the brokenness of externals + return # TODO enable test when externals in single are fixed + repo = self._load_fixture_and_fetch('externals.svndump', + stupid=False, + layout='single', + subdir='') + for rev in repo: + rc = repo[rev] + if '.hgsvnexternals' in rc: + extdata = rc['.hgsvnexternals'].data() + assert '[.]' not in extdata + print extdata + expect = '' # Not honestly sure what this should be... + test = 4 + self.assertEqual(self.repo[test]['.hgsvnexternals'].data(), expect) diff --git a/tests/test_util.py b/tests/test_util.py --- a/tests/test_util.py +++ b/tests/test_util.py @@ -82,6 +82,10 @@ subdir = {'truncatedhistory.svndump': '/ FIXTURES = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'fixtures') +def filtermanifest(manifest): + return filter(lambda x: x not in ('.hgtags', '.hgsvnexternals', ), + manifest) + def fileurl(path): path = os.path.abspath(path) drive, path = os.path.splitdrive(path) @@ -103,16 +107,21 @@ def load_svndump_fixture(path, fixture_n stdout=subprocess.PIPE, stderr=subprocess.STDOUT) proc.communicate() -def load_fixture_and_fetch(fixture_name, repo_path, wc_path, stupid=False, subdir='', noupdate=True): +def load_fixture_and_fetch(fixture_name, repo_path, wc_path, stupid=False, subdir='', + noupdate=True, layout='auto'): load_svndump_fixture(repo_path, fixture_name) if subdir: repo_path += '/' + subdir - _ui = ui.ui() - _ui.setconfig('hgsubversion', 'stupid', str(stupid)) + confvars = locals() + def conf(): + for var in ('stupid', 'layout'): + _ui = ui.ui() + _ui.setconfig('hgsubversion', var, confvars[var]) + return _ui + _ui = conf() commands.clone(_ui, fileurl(repo_path), wc_path, noupdate=noupdate) - _ui = ui.ui() - _ui.setconfig('hgsubversion', 'stupid', str(stupid)) + _ui = conf() return hg.repository(_ui, wc_path) def rmtree(path): @@ -170,10 +179,15 @@ class TestBase(unittest.TestCase): os.chdir(self.oldwd) setattr(ui.ui, self.patch[0].func_name, self.patch[0]) - def _load_fixture_and_fetch(self, fixture_name, subdir='', stupid=False): + def _load_fixture_and_fetch(self, fixture_name, subdir=None, stupid=False, layout='auto'): + if layout == 'single': + if subdir is None: + subdir = 'trunk' + elif subdir is None: + subdir = '' return load_fixture_and_fetch(fixture_name, self.repo_path, self.wc_path, subdir=subdir, - stupid=stupid) + stupid=stupid, layout=layout) # define this as a property so that it reloads anytime we need it @property