# HG changeset patch # User Bryan O'Sullivan # Date 1337212345 25200 # Node ID c4ee11a5d04c731600a31cfc8642035053a6e1cb # Parent 173065f9b7154e6b8793f1ade648f2ccd86f23b2 pull: add a hgsubversion.unsafeskip option to omit unwanted revs diff --git a/hgsubversion/help/subversion.rst b/hgsubversion/help/subversion.rst --- 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 diff --git a/hgsubversion/wrappers.py b/hgsubversion/wrappers.py --- 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.'): diff --git a/tests/fixtures/delete_restore_trunk.sh b/tests/fixtures/delete_restore_trunk.sh 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 diff --git a/tests/fixtures/delete_restore_trunk.svndump b/tests/fixtures/delete_restore_trunk.svndump 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 + + diff --git a/tests/test_pull.py b/tests/test_pull.py --- 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__]) diff --git a/tests/test_util.py b/tests/test_util.py --- 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]