diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py --- a/mercurial/subrepo.py +++ b/mercurial/subrepo.py @@ -5,7 +5,7 @@ # 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 @@ -250,6 +250,88 @@ class hgsubrepo(object): other = hg.repository(self._repo.ui, dsturl) self._repo.push(other, force) +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(status) + + def merge(self, state): + old = int(self._state[1]) + new = int(state[1]) + if new > old: + self.get(state) + + def push(self, force): + # nothing for svn + pass + types = { 'hg': hgsubrepo, + 'svn': svnsubrepo, } 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,72 @@ +#!/bin/sh + +"$TESTDIR/hghave" svn || exit 80 + +escapedwd=$(pwd | \ + python -c \ + "import sys,urllib; print urllib.pathname2url(sys.stdin.read().strip())" + ) +filterpath="sed s+$escapedwd+/root+" + +echo % create subversion repo + +SVNREPO="file://$escapedwd/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 "s = [svn]$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,48 @@ +% 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:///root/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:///root/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 +A s/alpha +Checked out revision 2. +3 files updated, 0 files merged, 0 files removed, 0 files unresolved +% debugsub in clone +path s + source file:///root/svn-repo + revision 2