# HG changeset patch # User Augie Fackler # Date 1239130138 18000 # Node ID f71af18c4379aeb262006bbef1c4cf6b87d63d72 # Parent 330f0b15d4173450465408d5ba0a8fdc8867cd9e# Parent d79843a3d42c566840d3f885088328f2d4387d41 Merge with crew. diff --git a/fetch_command.py b/fetch_command.py --- a/fetch_command.py +++ b/fetch_command.py @@ -29,8 +29,7 @@ def fetch_revisions(ui, svn_url, hg_repo """pull new revisions from Subversion """ svn_url = util.normalize_url(svn_url) - old_encoding = merc_util._encoding - merc_util._encoding = 'UTF-8' + old_encoding = util.swap_out_encoding() skipto_rev=int(skipto_rev) have_replay = not stupid if have_replay and not callable( @@ -98,7 +97,8 @@ def fetch_revisions(ui, svn_url, hg_repo ui.status('Got a 502, retrying (%s)\n' % tries) else: raise merc_util.Abort(*e.args) - merc_util._encoding = old_encoding + util.swap_out_encoding(old_encoding) + fetch_revisions = util.register_subcommand('pull')(fetch_revisions) @@ -106,6 +106,7 @@ def cleanup_file_handles(svn, count): if count % 50 == 0: svn.init_ra_and_client() + def replay_convert_rev(hg_editor, svn, r): hg_editor.set_current_rev(r) svn.get_replay(r.revnum, hg_editor) diff --git a/hg_delta_editor.py b/hg_delta_editor.py --- a/hg_delta_editor.py +++ b/hg_delta_editor.py @@ -81,6 +81,8 @@ class HgChangeReceiver(delta.Editor): subdir is the subdirectory of the edits *on the svn server*. It is needed for stripping paths off in certain cases. """ + if repo and repo.ui and not ui_: + ui_ = repo.ui if not ui_: ui_ = ui.ui() self.ui = ui_ @@ -230,10 +232,14 @@ class HgChangeReceiver(delta.Editor): parentdir = '/'.join(path[:-1]) filepaths = [p for p in filepaths if not '/'.join(p).startswith(parentdir)] branchpath = self._normalize_path(parentdir) + if branchpath.startswith('tags/'): + continue branchname = self._localname(branchpath) if branchpath.startswith('trunk/'): branches[self._localname('trunk')] = 'trunk' continue + if branchname and branchname.startswith('../'): + continue branches[branchname] = branchpath return branches @@ -285,10 +291,17 @@ class HgChangeReceiver(delta.Editor): if path.startswith('trunk/'): path = test.split('/')[1:] test = 'trunk' + elif path.startswith('branches/'): + elts = path.split('/') + test = '/'.join(elts[:2]) + path = '/'.join(elts[2:]) else: path = test.split('/')[-1] test = '/'.join(test.split('/')[:-1]) - return path, self._localname(test), test + ln = self._localname(test) + if ln and ln.startswith('../'): + return None, None, None + return path, ln, test def set_current_rev(self, rev): """Set the revision we're currently converting. @@ -355,6 +368,8 @@ class HgChangeReceiver(delta.Editor): return True def _is_path_valid(self, path): + if path is None: + return False subpath = self._split_branch_path(path)[0] if subpath is None: return False @@ -839,7 +854,7 @@ class HgChangeReceiver(delta.Editor): delete_entry = stash_exception_on_self(delete_entry) def open_file(self, path, parent_baton, base_revision, p=None): - self.current_file = 'foobaz' + self.current_file = None fpath, branch = self._path_and_branch_for_path(path) if fpath: self.current_file = path @@ -884,7 +899,7 @@ class HgChangeReceiver(delta.Editor): def add_file(self, path, parent_baton, copyfrom_path, copyfrom_revision, file_pool=None): - self.current_file = 'foobaz' + self.current_file = None self.base_revision = None if path in self.deleted_files: del self.deleted_files[path] @@ -946,6 +961,7 @@ class HgChangeReceiver(delta.Editor): source_rev = copyfrom_revision cp_f, source_branch = self._path_and_branch_for_path(copyfrom_path) if cp_f == '' and br_path == '': + assert br_path is not None self.branches[branch] = source_branch, source_rev, self.current_rev.revnum new_hash = self.get_parent_revision(source_rev + 1, source_branch) diff --git a/push_cmd.py b/push_cmd.py --- a/push_cmd.py +++ b/push_cmd.py @@ -11,12 +11,20 @@ import fetch_command import utility_commands +class BaseException(Exception): + pass + + +class NoFilesException(BaseException): + """Exception raised when you try and commit without files. + """ + + def push_revisions_to_subversion(ui, repo, hg_repo_path, svn_url, stupid=False, **opts): """push revisions starting at a specified head back to Subversion. """ - oldencoding = merc_util._encoding - merc_util._encoding = 'UTF-8' + old_encoding = util.swap_out_encoding() hge = hg_delta_editor.HgChangeReceiver(hg_repo_path, ui_=ui) svn_commit_hashes = dict(zip(hge.revmap.itervalues(), @@ -50,7 +58,12 @@ def push_revisions_to_subversion(ui, rep and c.node() in svn_commit_hashes] # 2. Commit oldest revision that needs to be pushed base_revision = svn_commit_hashes[base_n][0] - commit_from_rev(ui, repo, old_ctx, hge, svn_url, base_revision) + try: + commit_from_rev(ui, repo, old_ctx, hge, svn_url, base_revision) + except NoFilesException: + ui.warn("Could not push revision %s because it had no changes in svn.\n" % + old_ctx) + return 1 # 3. Fetch revisions from svn r = fetch_command.fetch_revisions(ui, svn_url, hg_repo_path, stupid=stupid) @@ -87,7 +100,7 @@ def push_revisions_to_subversion(ui, rep rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid))) hge = hg_delta_editor.HgChangeReceiver(hg_repo_path, ui_=ui) svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys())) - merc_util._encoding = oldencoding + util.swap_out_encoding(old_encoding) return 0 push_revisions_to_subversion = util.register_subcommand('push')(push_revisions_to_subversion) # for git expats @@ -268,6 +281,8 @@ def commit_from_rev(ui, repo, rev_ctx, h addeddirs = [svnpath(d) for d in addeddirs] deleteddirs = [svnpath(d) for d in deleteddirs] new_target_files += addeddirs + deleteddirs + changeddirs + if not new_target_files: + raise NoFilesException() try: svn.commit(new_target_files, rev_ctx.description(), file_data, base_revision, set(addeddirs), set(deleteddirs), diff --git a/rebuildmeta.py b/rebuildmeta.py --- a/rebuildmeta.py +++ b/rebuildmeta.py @@ -82,6 +82,8 @@ def rebuildmeta(ui, repo, hg_repo_path, branchinfofile = open(os.path.join(svnmetadir, 'branch_info'), 'w') pickle.dump(branchinfo, branchinfofile) branchinfofile.close() + + # now handle tags tagsinfo = {} realtags = svn.tags tagsleft = realtags.items() @@ -100,10 +102,11 @@ def rebuildmeta(ui, repo, hg_repo_path, older_tags = svn.tags_at_rev(rev) newsrc, newrev = older_tags[src] tagsleft.append((tag, (newsrc, newrev))) - if source.startswith('branches/') or source == 'trunk': + continue + else: source = determinebranch(source) - if rev <= last_rev: - tagsinfo[tag] = source, rev + if rev <= last_rev and (source or 'default') in repo.branchtags(): + tagsinfo[tag] = source, rev tagsinfofile = open(os.path.join(svnmetadir, 'tag_info'), 'w') pickle.dump(tagsinfo, tagsinfofile) tagsinfofile.close() @@ -111,10 +114,11 @@ rebuildmeta = util.register_subcommand(' rebuildmeta = util.command_needs_no_url(rebuildmeta) def determinebranch(branch): - if branch.startswith('branches'): + assert not branch.startswith('tags/'), "Tags can't be tags of other tags." + if branch.startswith('branches/'): branch = branch[len('branches/'):] elif branch == 'trunk': branch = None else: - assert False, 'Unhandled case while regenerating metadata.' + branch = '../' + branch return branch diff --git a/svnwrap/svn_swig_wrapper.py b/svnwrap/svn_swig_wrapper.py --- a/svnwrap/svn_swig_wrapper.py +++ b/svnwrap/svn_swig_wrapper.py @@ -211,6 +211,8 @@ class SubversionRepo(object): source = hist.paths[path].copyfrom_path source_rev = 0 for p in hist.paths: + if not p.startswith(path): + continue if hist.paths[p].copyfrom_rev: # We assume that the revision of the source tree as it was # copied was actually the revision of the highest revision @@ -247,26 +249,8 @@ class SubversionRepo(object): The reason this is lazy is so that you can use the same repo object to perform RA calls to get deltas. """ - # NB: you'd think this would work, but you'd be wrong. I'm pretty - # convinced there must be some kind of svn bug here. - #return self.fetch_history_at_paths(['tags', 'trunk', 'branches'], - # start=start) - # this does the same thing, but at the repo root + filtering. It's - # kind of tough cookies, sadly. - for r in self.fetch_history_at_paths([''], start=start, - chunk_size=chunk_size): - should_yield = False - i = 0 - paths = list(r.paths.keys()) - while i < len(paths) and not should_yield: - p = paths[i] - if (p.startswith('trunk') or p.startswith('tags') - or p.startswith('branches')): - should_yield = True - i += 1 - if should_yield: - yield r - + return self.fetch_history_at_paths([''], start=start, + chunk_size=chunk_size) def fetch_history_at_paths(self, paths, start=None, stop=None, chunk_size=1000): diff --git a/tests/test_externals.py b/tests/test_externals.py --- a/tests/test_externals.py +++ b/tests/test_externals.py @@ -10,9 +10,8 @@ class TestFetchExternals(test_util.TestB f['t1'] = 'dir1 -r10 svn://foobar' f['t 2'] = 'dir2 -r10 svn://foobar' f['t3'] = ['dir31 -r10 svn://foobar', 'dir32 -r10 svn://foobar'] - - refext = """\ -[t 2] + + refext = """[t 2] dir2 -r10 svn://foobar [t1] dir1 -r10 svn://foobar @@ -32,52 +31,46 @@ class TestFetchExternals(test_util.TestB def test_externals(self, stupid=False): repo = self._load_fixture_and_fetch('externals.svndump', stupid=stupid) - ref0 = """\ -[.] + ref0 = """[.] ^/externals/project1 deps/project1 """ self.assertEqual(ref0, repo[0]['.hgsvnexternals'].data()) - ref1 = """\ -[.] + ref1 = """[.] ^/externals/project1 deps/project1 ^/externals/project2 deps/project2 """ self.assertEqual(ref1, repo[1]['.hgsvnexternals'].data()) - ref2 = """\ -[.] + ref2 = """[.] ^/externals/project2 deps/project2 [subdir] ^/externals/project1 deps/project1 [subdir2] ^/externals/project1 deps/project1 """ - self.assertEqual(ref2, repo[2]['.hgsvnexternals'].data()) + actual = repo[2]['.hgsvnexternals'].data() + self.assertEqual(ref2, actual) - ref3 = """\ -[.] + ref3 = """[.] ^/externals/project2 deps/project2 [subdir] ^/externals/project1 deps/project1 """ self.assertEqual(ref3, repo[3]['.hgsvnexternals'].data()) - ref4 = """\ -[subdir] + ref4 = """[subdir] ^/externals/project1 deps/project1 """ self.assertEqual(ref4, repo[4]['.hgsvnexternals'].data()) - ref5 = """\ -[.] + ref5 = """[.] ^/externals/project2 deps/project2 [subdir2] ^/externals/project1 deps/project1 """ self.assertEqual(ref5, repo[5]['.hgsvnexternals'].data()) - ref6 = """\ -[.] + ref6 = """[.] ^/externals/project2 deps/project2 """ self.assertEqual(ref6, repo[6]['.hgsvnexternals'].data()) @@ -96,9 +89,8 @@ class TestPushExternals(test_util.TestBa def test_push_externals(self, stupid=False): # Add a new reference on an existing and non-existing directory changes = [ - ('.hgsvnexternals', '.hgsvnexternals', - """\ -[dir] + ('.hgsvnexternals', '.hgsvnexternals', + """[dir] ../externals/project2 deps/project2 [subdir1] ../externals/project1 deps/project1 @@ -115,9 +107,8 @@ class TestPushExternals(test_util.TestBa # Remove all references from one directory, add a new one # to the other (test multiline entries) changes = [ - ('.hgsvnexternals', '.hgsvnexternals', - """\ -[subdir1] + ('.hgsvnexternals', '.hgsvnexternals', + """[subdir1] ../externals/project1 deps/project1 ../externals/project2 deps/project2 """), diff --git a/tests/test_fetch_command.py b/tests/test_fetch_command.py --- a/tests/test_fetch_command.py +++ b/tests/test_fetch_command.py @@ -85,9 +85,7 @@ class TestBasicRepoLayout(test_util.Test self.assertEqual(node.hex(repo['default'].node()), '434ed487136c1b47c1e8f952edb4dc5a8e6328df') assert 'README' not in repo - self.assertEqual(repo['tip'].branch(), - '../branches') - + assert '../branches' not in repo def test_files_copied_from_outside_btt(self): repo = self._load_fixture_and_fetch( @@ -99,7 +97,7 @@ class TestBasicRepoLayout(test_util.Test def test_file_renamed_in_from_outside_btt(self): repo = self._load_fixture_and_fetch( 'file_renamed_in_from_outside_btt.svndump') - self.assert_('LICENSE.file' in repo['tip']) + self.assert_('LICENSE.file' in repo['default']) def test_renamed_dir_in_from_outside_btt_not_repo_root(self): repo = self._load_fixture_and_fetch( diff --git a/tests/test_push_command.py b/tests/test_push_command.py --- a/tests/test_push_command.py +++ b/tests/test_push_command.py @@ -87,6 +87,32 @@ class PushTests(test_util.TestBase): self.repo_path, self.wc_path) + def test_cant_push_empty_ctx(self): + repo = self.repo + def file_callback(repo, memctx, path): + if path == 'adding_file': + return context.memfilectx(path=path, + data='foo', + islink=False, + isexec=False, + copied=False) + raise IOError() + ctx = context.memctx(repo, + (repo['default'].node(), node.nullid), + 'automated test', + [], + file_callback, + 'an_author', + '2008-10-07 20:59:48 -0500', + {'branch': 'default',}) + new_hash = repo.commitctx(ctx) + hg.update(repo, repo['tip'].node()) + old_tip = repo['tip'].node() + self.pushrevisions() + tip = self.repo['tip'] + self.assertEqual(tip.node(), old_tip) + + def test_push_to_default(self, commit=True): repo = self.repo old_tip = repo['tip'].node() diff --git a/tools/bisect-find-bad.sh b/tools/bisect-find-bad.sh --- a/tools/bisect-find-bad.sh +++ b/tools/bisect-find-bad.sh @@ -1,10 +1,4 @@ #!/bin/bash -/bin/rm -rf * -svn export `hg svn info 2> /dev/null | grep '^URL: ' | sed 's/URL: //'` -`hg svn parent | sed 's/.*: //;s/ .*//'` . --force -if [ `hg st | wc -l` = 0 ] ; then - exit 0 -else - hg revert --all - hg purge - exit 1 -fi +. $(dirname $0)/common.sh +verify_current_revision $1 +exit $? diff --git a/tools/common.sh b/tools/common.sh new file mode 100644 --- /dev/null +++ b/tools/common.sh @@ -0,0 +1,26 @@ +function verify_current_revision() +{ + /bin/rm -rf * + exportcmd="svn export `hg svn info 2> /dev/null | grep '^URL: ' | sed 's/URL: //'` -`hg svn parent | sed 's/.*: //;s/ .*//'` . --force" + `echo $exportcmd` > /dev/null + x=$? + if [[ "$x" != "0" ]] ; then + echo $exportcmd + echo 'export failed!' + return 255 + fi + if [[ "`hg st | wc -l | python -c 'import sys; print sys.stdin.read().strip()'`" == "0" ]] ; then + return 0 + else + if [[ $1 != "keep" ]] ; then + revert_all_files + fi + return 1 + fi +} + +function revert_all_files() +{ + hg revert --all + hg purge +} diff --git a/tools/verify-all-heads.sh b/tools/verify-all-heads.sh --- a/tools/verify-all-heads.sh +++ b/tools/verify-all-heads.sh @@ -1,7 +1,9 @@ #!/bin/sh -for b in `hg branches | cut -f 1 -d ' '` ; do +. $(dirname $0)/common.sh + +for b in `hg branches -a | cut -f 1 -d ' ' | grep -v closed-branches` ; do hg co $b || break echo Verifying $b - $(dirname $0)/bisect-find-bad.sh > /dev/null || break + verify_current_revision keep > /dev/null || break echo $b Verified. done diff --git a/util.py b/util.py --- a/util.py +++ b/util.py @@ -3,6 +3,11 @@ import shutil from mercurial import hg from mercurial import node +from mercurial import util +try: + from mercurial import encoding +except ImportError: + encoding = None svn_subcommands = { } def register_subcommand(name): @@ -94,7 +99,8 @@ def outgoing_revisions(ui, repo, hg_edit and sourcerev.node() != node.nullid): outgoing_rev_hashes.append(sourcerev.node()) sourcerev = sourcerev.parents() - assert len(sourcerev) == 1 + if len(sourcerev) != 1: + raise util.Abort("Sorry, can't find svn parent of a merge revision.") sourcerev = sourcerev[0] if sourcerev.node() != node.nullid: return outgoing_rev_hashes @@ -133,3 +139,14 @@ def describe_revision(ui, r): def describe_commit(ui, h, b): ui.note(' committed to "%s" as %s\n' % ((b or 'default'), node.short(h))) + + +def swap_out_encoding(new_encoding="UTF-8"): + """ Utility for mercurial incompatibility changes, can be removed after 1.3""" + if encoding is None: + old = util._encoding + util._encoding = new_encoding + else: + old = encoding.encoding + encoding.encoding = new_encoding + return old diff --git a/utility_commands.py b/utility_commands.py --- a/utility_commands.py +++ b/utility_commands.py @@ -72,7 +72,11 @@ def run_svn_info(ui, repo, hg_repo_path, svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys())) parent = find_wc_parent_rev(ui, repo, hge, svn_commit_hashes) - r, br = svn_commit_hashes[parent.node()] + pn = parent.node() + if pn not in svn_commit_hashes: + ui.status('Not a child of an svn revision.\n') + return 0 + r, br = svn_commit_hashes[pn] subdir = parent.extra()['convert_revision'][40:].split('@')[0] if br == None: branchpath = '/trunk'