[PATCH 2 of 2] svn subrepos: work around checkout obstructions (issue2752)

Augie Fackler durin42 at gmail.com
Fri Jun 17 15:25:53 CDT 2011


# HG changeset patch
# User Augie Fackler <durin42 at gmail.com>
# Date 1308342170 18000
# Node ID de50486e0144e7a58f8a8db33bef527e5b7bb7b6
# Parent  fa18b3700dafa530544456e544c3d9b41749ec78
svn subrepos: work around checkout obstructions (issue2752)

We do this by ensuring the working copy is clean and then blowing away
the working copy and replacing it with one from the desired path. We
could probably use 'svn switch' to do this more efficiently, but
there's some subtle logic required to get that right and this is
more likely to work reliably.

diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -526,7 +526,7 @@
         self._ctx = ctx
         self._ui = ctx._repo.ui
 
-    def _svncommand(self, commands, filename=''):
+    def _svncommand(self, commands, filename='', failok=False):
         cmd = ['svn']
         extrakw = {}
         if not self._ui.interactive():
@@ -551,15 +551,16 @@
                               universal_newlines=True, env=env, **extrakw)
         stdout, stderr = p.communicate()
         stderr = stderr.strip()
-        if p.returncode:
-            raise util.Abort(stderr or 'exited with code %d' % p.returncode)
-        if stderr:
-            self._ui.warn(stderr + '\n')
-        return stdout
+        if not failok:
+            if p.returncode:
+                raise util.Abort(stderr or 'exited with code %d' % p.returncode)
+            if stderr:
+                self._ui.warn(stderr + '\n')
+        return stdout, stderr
 
     @propertycache
     def _svnversion(self):
-        output = self._svncommand(['--version'], filename=None)
+        output, err = self._svncommand(['--version'], filename=None)
         m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
         if not m:
             raise util.Abort(_('cannot retrieve svn tool version'))
@@ -569,7 +570,7 @@
         # Get the working directory revision as well as the last
         # commit revision so we can compare the subrepo state with
         # both. We used to store the working directory one.
-        output = self._svncommand(['info', '--xml'])
+        output, err = self._svncommand(['info', '--xml'])
         doc = xml.dom.minidom.parseString(output)
         entries = doc.getElementsByTagName('entry')
         lastrev, rev = '0', '0'
@@ -588,7 +589,7 @@
         if the working directory was changed, and extchanges is
         True if any of these changes concern an external entry.
         """
-        output = self._svncommand(['status', '--xml'])
+        output, err = self._svncommand(['status', '--xml'])
         externals, changes = [], []
         doc = xml.dom.minidom.parseString(output)
         for e in doc.getElementsByTagName('entry'):
@@ -623,13 +624,13 @@
         if extchanged:
             # Do not try to commit externals
             raise util.Abort(_('cannot commit svn externals'))
-        commitinfo = self._svncommand(['commit', '-m', text])
+        commitinfo, err = self._svncommand(['commit', '-m', text])
         self._ui.status(commitinfo)
         newrev = re.search('Committed revision ([0-9]+).', commitinfo)
         if not newrev:
             raise util.Abort(commitinfo.splitlines()[-1])
         newrev = newrev.groups()[0]
-        self._ui.status(self._svncommand(['update', '-r', newrev]))
+        self._ui.status(self._svncommand(['update', '-r', newrev])[0])
         return newrev
 
     def remove(self):
@@ -663,9 +664,15 @@
         if self._svnversion >= (1, 5):
             args.append('--force')
         args.extend([state[0], '--revision', state[1]])
-        status = self._svncommand(args)
+        status, err = self._svncommand(args, failok=True)
         if not re.search('Checked out revision [0-9]+.', status):
-            raise util.Abort(status.splitlines()[-1])
+            if ('is already a working copy for a different URL' in err
+                and (self._wcchanged() == (False, False))):
+                # obstructed but clean working copy, so just blow it away.
+                self.remove()
+                self.get(state, overwrite=False)
+                return
+            raise util.Abort((status or err).splitlines()[-1])
         self._ui.status(status)
 
     def merge(self, state):
diff --git a/tests/test-subrepo-svn.t b/tests/test-subrepo-svn.t
--- a/tests/test-subrepo-svn.t
+++ b/tests/test-subrepo-svn.t
@@ -489,3 +489,33 @@
   $ if "$TESTDIR/hghave" -q svn15; then
   > hg up 2 >/dev/null 2>&1 || echo update failed
   > fi
+
+Modify one of the externals to point to a different path so we can
+test having obstructions when switching branches on checkout:
+  $ hg checkout tip
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo "obstruct =        [svn]       $SVNREPO/externals" >> .hgsub
+  $ svn co -r5 --quiet "$SVNREPO"/externals obstruct
+  $ hg commit -m 'Start making obstructed wc'
+  committing subrepository obstruct
+  $ hg book other
+  $ hg co -r 'p1(tip)'
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo "obstruct =        [svn]       $SVNREPO/src" >> .hgsub
+  $ svn co -r5 --quiet "$SVNREPO"/src obstruct
+  $ hg commit -m 'Other branch which will be obstructed'
+  committing subrepository obstruct
+  created new head
+
+Switching back to the head where we have another path mapped to the
+same subrepo should work if the subrepo is clean.
+  $ hg co other
+  A    $TESTTMP/rebaserepo/obstruct/other
+  Checked out revision 1.
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+This is surprising, but is also correct based on the current code:
+  $ echo "updating should (maybe) fail" > obstruct/other
+  $ hg co tip
+  abort: crosses branches (merge branches or use --clean to discard changes)
+  [255]


More information about the Mercurial-devel mailing list