[PATCH 2 of 2] subrepo: Subversion support

durin42 at gmail.com durin42 at gmail.com
Tue Dec 8 15:15:07 CST 2009


# HG changeset patch
# User Augie Fackler <durin42 at gmail.com>
# Date 1260296745 21600
# Node ID e7cf66df1d49d1f48822b6be5ca77ab3f66afcc8
# Parent  7bb991c4c3da3996272858798959de9de48e4f99
subrepo: Subversion support

diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -5,12 +5,21 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2, incorporated herein by reference.
 
-import errno, os
+import errno, os, re
 from i18n import _
 import config, util, node, error
 hg = None
 
-nullstate = ('', '')
+class proxysubrepo(object):
+    def __init__(self, *args):
+        self.args = args
+
+    def get(self, state):
+        self.__class__ = state[2]
+        self.__init__(*self.args)
+        return self.get(state)
+
+nullstate = ('', '', proxysubrepo)
 
 def state(ctx):
     p = config.config()
@@ -34,8 +43,14 @@
                 raise
 
     state = {}
+    sections = set(p.sections())
+    map(sections.discard, ['svn', ''])
+    if sections:
+        raise util.Abort(_('unrecognized subrepo types %s') % ', '.join(sections))
     for path, src in p[''].items():
-        state[path] = (src, rev.get(path, ''))
+        state[path] = (src, rev.get(path, ''), hgsubrepo)
+    for path, src in p['svn'].items():
+        state[path] = (src, rev.get(path, ''), svnsubrepo)
 
     return state
 
@@ -56,7 +71,7 @@
 
     def debug(s, msg, r=""):
         if r:
-            r = "%s:%s" % r
+            r = "%s:%s" % r[:-1]
         repo.ui.debug("  subrepo %s: %s %s\n" % (s, msg, r))
 
     for s, l in s1.items():
@@ -145,9 +160,86 @@
 
     util.path_auditor(ctx._repo.root)(path)
     state = ctx.substate.get(path, nullstate)
-    if state[0].startswith('['): # future expansion
-        raise error.Abort('unknown subrepo source %s' % state[0])
-    return hgsubrepo(ctx, path, state)
+    return state[2](ctx, path, state[:2])
+
+class svnsubrepo(object):
+    def __init__(self, ctx, path, state):
+        self._path = path
+        self._state = state
+        self._ctx = ctx
+        self._ui = ctx._repo.ui
+
+    def _svncommand(self, commands):
+        cmd = ['svn'] + commands + [self._path]
+        cmd = [util.shellquote(arg) for arg in cmd]
+        cmd = util.quotecommand(' '.join(cmd))
+        write, read, err = util.popen3(cmd)
+        retdata = read.read()
+        err = err.read().strip()
+        if err:
+            raise util.Abort(err)
+        return retdata
+
+    def _wcrev(self):
+        info = self._svncommand(['info'])
+        mat = re.search('Revision: ([\d]+)\n', info)
+        if not mat:
+            return 0
+        return mat.groups()[0]
+
+    def _url(self):
+        info = self._svncommand(['info'])
+        mat = re.search('URL: ([^\n]+)\n', info)
+        if not mat:
+            return 0
+        return mat.groups()[0]
+
+    def _wcclean(self):
+        status = self._svncommand(['status'])
+        status = '\n'.join([s for s in status.splitlines() if s[0] != '?'])
+        if status.strip():
+            return False
+        return True
+
+    def dirty(self):
+        if self._wcrev() == self._state[1] and self._wcclean():
+            return False
+        return True
+
+    def commit(self, text, user, date):
+        # user and date are out of our hands since svn is centralized
+        if self._wcclean():
+            return self._wcrev()
+        commitinfo = self._svncommand(['commit', '-m', text])
+        self._ui.status(commitinfo)
+        newrev = re.search('Committed revision ([\d]+).', commitinfo)
+        if not newrev:
+            raise util.Abort(commitinfo.splitlines()[-1])
+        newrev = newrev.groups()[0]
+        self._ui.status(self._svncommand(['update', '-r', newrev]))
+        return newrev
+
+    def remove(self):
+        if self.dirty():
+            self._repo.ui.warn('Not removing repo %s because'
+                               'it has changes.\n' % self._path)
+            return
+        self._repo.ui.note('removing subrepo %s\n' % self._path)
+        shutil.rmtree(self._ctx.repo.join(self._path))
+
+    def get(self, state):
+        status = self._svncommand(['checkout', state[0], '--revision', state[1]])
+        if not re.search('Checked out revision [\d]+.', status):
+            raise util.Abort(status.splitlines()[-1])
+        self._ui.status()
+
+    def merge(self, state):
+        # Pretty sure we should not implement merge on svn subrepos
+        raise NotImplementedError()
+
+    def push(self, force):
+        # nothing for svn
+        pass
 
 class hgsubrepo(object):
     def __init__(self, ctx, path, state):
@@ -186,7 +278,7 @@
         hg.clean(self._repo, node.nullid, False)
 
     def _get(self, state):
-        source, revision = state
+        source, revision, _cls = state
         try:
             self._repo.lookup(revision)
         except error.RepoError:
@@ -198,7 +290,7 @@
 
     def get(self, state):
         self._get(state)
-        source, revision = state
+        source, revision, _cls = state
         self._repo.ui.debug("getting subrepo %s\n" % self._path)
         hg.clean(self._repo, revision, False)
 
diff --git a/tests/test-subrepo-svn b/tests/test-subrepo-svn
new file mode 100755
--- /dev/null
+++ b/tests/test-subrepo-svn
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+"$TESTDIR/hghave" svn || exit 80
+
+filterpath="sed s%$(pwd)%%"
+
+echo % create subversion repo
+
+SVNREPO="file://$(pwd)/svn-repo"
+WCROOT="$(pwd)/svn-wc"
+svnadmin create svn-repo
+svn co $SVNREPO svn-wc
+cd svn-wc
+echo alpha > alpha
+svn add alpha
+svn ci -m 'Add alpha'
+cd ..
+
+echo % create hg repo
+
+rm -rf sub
+mkdir sub
+cd sub
+hg init t
+cd t
+
+echo % first revision, no sub
+echo a > a
+hg ci -Am0
+
+echo % add first svn sub
+echo "[svn]" > .hgsub
+echo "s = $SVNREPO" >> .hgsub
+svn co --quiet $SVNREPO s
+hg add .hgsub
+hg ci -m1
+echo % debugsub
+hg debugsub | $filterpath
+
+echo
+echo % change file in svn and hg, commit
+echo a >> a
+echo alpha >> s/alpha
+hg commit -m 'Message!'
+hg debugsub | $filterpath
+
+echo
+echo a > s/a
+echo % should be empty despite change to s/a
+hg st
+
+echo
+echo % add a commit from svn
+pushd "$WCROOT" > /dev/null
+svn up
+echo xyz >> alpha
+svn ci -m 'amend a from svn'
+popd > /dev/null
+echo % this commit from hg will fail
+echo zzz >> s/alpha
+hg ci -m 'amend alpha from hg'
+
+echo
+echo % clone
+cd ..
+hg clone t tc
+cd tc
+echo % debugsub in clone
+hg debugsub | $filterpath
diff --git a/tests/test-subrepo-svn.out b/tests/test-subrepo-svn.out
new file mode 100644
--- /dev/null
+++ b/tests/test-subrepo-svn.out
@@ -0,0 +1,46 @@
+% create subversion repo
+Checked out revision 0.
+A         alpha
+Adding         alpha
+Transmitting file data .
+Committed revision 1.
+% create hg repo
+% first revision, no sub
+adding a
+% add first svn sub
+committing subrepository s
+% debugsub
+path s
+ source   file:///svn-repo
+ revision 1
+
+% change file in svn and hg, commit
+committing subrepository s
+Sending        s/alpha
+Transmitting file data .
+Committed revision 2.
+At revision 2.
+path s
+ source   file:///svn-repo
+ revision 2
+
+% should be empty despite change to s/a
+
+% add a commit from svn
+U    alpha
+Updated to revision 2.
+Sending        alpha
+Transmitting file data .
+Committed revision 3.
+% this commit from hg will fail
+committing subrepository s
+abort: svn: Commit failed (details follow):
+svn: File '/alpha' is out of date
+
+% clone
+updating to branch default
+3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% debugsub in clone
+path s
+ source   file:///svn-repo
+ revision 2


More information about the Mercurial-devel mailing list