changeset 228:f71af18c4379

Merge with crew.
author Augie Fackler <durin42@gmail.com>
date Tue, 07 Apr 2009 13:48:58 -0500
parents 330f0b15d417 (diff) d79843a3d42c (current diff)
children 7f20914e52e8
files fetch_command.py hg_delta_editor.py rebuildmeta.py svnwrap/svn_swig_wrapper.py utility_commands.py
diffstat 13 files changed, 154 insertions(+), 76 deletions(-) [+]
line wrap: on
line diff
--- 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)
--- 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)
--- 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),
--- 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
--- 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):
--- 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
 """),
--- 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(
--- 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()
--- 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 $?
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
+}
--- 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
--- 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
--- 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'