changeset 908:c4ee11a5d04c

pull: add a hgsubversion.unsafeskip option to omit unwanted revs
author Bryan O'Sullivan <bryano@fb.com>
date Wed, 16 May 2012 16:52:25 -0700
parents 173065f9b715
children e42a05915edf
files hgsubversion/help/subversion.rst hgsubversion/wrappers.py tests/fixtures/delete_restore_trunk.sh tests/fixtures/delete_restore_trunk.svndump tests/test_pull.py tests/test_util.py
diffstat 6 files changed, 254 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/hgsubversion/help/subversion.rst
+++ b/hgsubversion/help/subversion.rst
@@ -352,6 +352,24 @@ The following options only have an effec
     contain tags. The default is to only look in ``tags``. This option has no
     effect for single-directory clones.
 
+  ``hgsubversion.unsafeskip``
+
+    A space or comma separated list of Subversion revision numbers to
+    skip over when pulling or cloning.  This can be useful for
+    troublesome commits, such as someone accidentally deleting trunk
+    and then restoring it.  (In delete-and-restore cases, you may also
+    need to clone or pull in multiple steps, to help hgsubversion
+    track history correctly.)
+
+    NOTE: this option is dangerous.  Careless use can make it
+    impossible to pull later Subversion revisions cleanly, e.g. if the
+    content of a file depends on changes made in a skipped rev.
+    Skipping a rev may also prevent future invocations of ``hg svn
+    verify`` from succeeding (if the contents of the Mercurial repo
+    become out of step with the contents of the Subversion repo).  If
+    you use this option, be sure to carefully check the result of a
+    pull afterwards.
+
 Please note that some of these options may be specified as command line options
 as well, and when done so, will override the configuration. If an authormap,
 filemap or branchmap is specified, its contents will be read and stored for use
--- a/hgsubversion/wrappers.py
+++ b/hgsubversion/wrappers.py
@@ -317,6 +317,13 @@ def pull(repo, source, heads=[], force=F
         if start < 0:
             start = 0
 
+    skiprevs = repo.ui.configlist('hgsubversion', 'unsafeskip', '')
+    try:
+        skiprevs = set(map(int, skiprevs))
+    except ValueError:
+        raise hgutil.Abort('unrecognised Subversion revisions %r: '
+                           'only numbers work.' % checkout)
+
     oldrevisions = len(meta.revmap)
     if stopat_rev:
         total = stopat_rev - start
@@ -328,6 +335,9 @@ def pull(repo, source, heads=[], force=F
             # start converting revisions
             firstrun = True
             for r in svn.revisions(start=start, stop=stopat_rev):
+                if r.revnum in skiprevs:
+                    ui.status('[r%d SKIPPED]\n' % r.revnum)
+                    continue
                 lastpulled = r.revnum
                 if (r.author is None and
                     r.message == 'This is an empty revision for padding.'):
new file mode 100755
--- /dev/null
+++ b/tests/fixtures/delete_restore_trunk.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+set -e
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+cd wc
+mkdir branches trunk tags
+svn add *
+svn ci -m 'btt'
+echo foo > trunk/foo
+svn add trunk/foo
+svn ci -m 'add file'
+svn up
+svn rm trunk
+svn ci -m 'delete trunk'
+svn up
+cd ..
+svn cp -m 'restore trunk' file://`pwd`/repo/trunk@2 file://`pwd`/repo/trunk
+cd wc
+svn up
+echo bar >> trunk/foo
+svn ci -m 'append to file'
+svn up
+cd ../..
+svnadmin dump temp/repo > delete_restore_trunk.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in branch_delete_parent_dir.svndump'
+exit 0
new file mode 100644
--- /dev/null
+++ b/tests/fixtures/delete_restore_trunk.svndump
@@ -0,0 +1,167 @@
+SVN-fs-dump-format-version: 2
+
+UUID: fca176f4-a346-479b-ae2c-78c8442c3809
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2012-05-16T22:55:55.613464Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 103
+Content-length: 103
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 6
+bryano
+K 8
+svn:date
+V 27
+2012-05-16T22:55:56.081065Z
+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: 108
+Content-length: 108
+
+K 7
+svn:log
+V 8
+add file
+K 10
+svn:author
+V 6
+bryano
+K 8
+svn:date
+V 27
+2012-05-16T22:55:57.071178Z
+PROPS-END
+
+Node-path: trunk/foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Text-content-sha1: f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 3
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 12
+delete trunk
+K 10
+svn:author
+V 6
+bryano
+K 8
+svn:date
+V 27
+2012-05-16T22:55:59.058026Z
+PROPS-END
+
+Node-path: trunk
+Node-action: delete
+
+
+Revision-number: 4
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 13
+restore trunk
+K 10
+svn:author
+V 6
+bryano
+K 8
+svn:date
+V 27
+2012-05-16T22:56:01.055887Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 5
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 14
+append to file
+K 10
+svn:author
+V 6
+bryano
+K 8
+svn:date
+V 27
+2012-05-16T22:56:02.060991Z
+PROPS-END
+
+Node-path: trunk/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 8
+Text-content-md5: f47c75614087a8dd938ba4acff252494
+Text-content-sha1: 4e48e2c9a3d2ca8a708cb0cc545700544efb5021
+Content-length: 8
+
+foo
+bar
+
+
--- a/tests/test_pull.py
+++ b/tests/test_pull.py
@@ -6,14 +6,16 @@ from mercurial import node
 from mercurial import ui
 from mercurial import util as hgutil
 from mercurial import commands
+from hgsubversion import verify
 
 class TestPull(test_util.TestBase):
     def setUp(self):
         super(TestPull, self).setUp()
 
-    def _loadupdate(self, fixture_name):
-        repo, repo_path = self.load_and_fetch(fixture_name, stupid=False,
-                                              noupdate=False)
+    def _loadupdate(self, fixture_name, *args, **kwargs):
+        kwargs = kwargs.copy()
+        kwargs.update(stupid=False, noupdate=False)
+        repo, repo_path = self.load_and_fetch(fixture_name, *args, **kwargs)
         return repo, repo_path
 
     def test_nochanges(self):
@@ -58,6 +60,26 @@ class TestPull(test_util.TestBase):
         commands.pull(repo.ui, repo)
         self.assertEqual(oldheads, map(node.hex, repo.heads()))
 
+    def test_skip_basic(self):
+        repo, repo_path = self._loadupdate('single_rev.svndump')
+        self.add_svn_rev(repo_path, {'trunk/alpha': 'Changed'})
+        self.add_svn_rev(repo_path, {'trunk/beta': 'More changed'})
+        self.add_svn_rev(repo_path, {'trunk/gamma': 'Even more changeder'})
+        repo.ui.setconfig('hgsubversion', 'unsafeskip', '3 4')
+        commands.pull(repo.ui, repo)
+        tip = repo['tip'].rev()
+        self.assertEqual(tip, 1)
+        self.assertEquals(verify.verify(repo.ui, repo, rev=tip), 1)
+
+    def test_skip_delete_restore(self):
+        repo, repo_path = self._loadupdate('delete_restore_trunk.svndump',
+                                           rev=2)
+        repo.ui.setconfig('hgsubversion', 'unsafeskip', '3 4')
+        commands.pull(repo.ui, repo)
+        tip = repo['tip'].rev()
+        self.assertEqual(tip, 1)
+        self.assertEquals(verify.verify(repo.ui, repo, rev=tip), 0)
+
 def suite():
     import unittest, sys
     return unittest.findTestCases(sys.modules[__name__])
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -302,7 +302,7 @@ class TestBase(unittest.TestCase):
         return path
 
     def fetch(self, repo_path, subdir=None, stupid=False, layout='auto', startrev=0,
-              externals=None, noupdate=True, dest=None):
+              externals=None, noupdate=True, dest=None, rev=None):
         if layout == 'single':
             if subdir is None:
                 subdir = 'trunk'
@@ -323,6 +323,8 @@ class TestBase(unittest.TestCase):
             cmd.append('--stupid')
         if noupdate:
             cmd.append('--noupdate')
+        if rev is not None:
+            cmd.append('--rev=%s' % rev)
         if externals:
             cmd[:0] = ['--config', 'hgsubversion.externals=%s' % externals]