--- a/contrib/perf.py Fri Jan 01 14:29:36 2010 +0100
+++ b/contrib/perf.py Fri Jan 01 17:11:48 2010 +0100
@@ -32,7 +32,7 @@
def perfwalk(ui, repo, *pats):
try:
m = cmdutil.match(repo, pats, {})
- timer(lambda: len(list(repo.dirstate.walk(m, True, False))))
+ timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
except:
try:
m = cmdutil.match(repo, pats, {})
@@ -150,4 +150,3 @@
'perftemplating': (perftemplating, []),
'perfdiffwd': (perfdiffwd, []),
}
-
--- a/hgext/inotify/__init__.py Fri Jan 01 14:29:36 2010 +0100
+++ b/hgext/inotify/__init__.py Fri Jan 01 17:11:48 2010 +0100
@@ -42,11 +42,11 @@
# to start an inotify server if it won't start.
_inotifyon = True
- def status(self, match, ignored, clean, unknown=True):
+ def status(self, match, subrepos, ignored, clean, unknown=True):
files = match.files()
if '.' in files:
files = []
- if self._inotifyon and not ignored and not self._dirty:
+ if self._inotifyon and not ignored and not subrepos and not self._dirty:
cli = client(ui, repo)
try:
result = cli.statusquery(files, match, False,
@@ -70,7 +70,7 @@
result = r2
return result
return super(inotifydirstate, self).status(
- match, ignored, clean, unknown)
+ match, subrepos, ignored, clean, unknown)
repo.dirstate.__class__ = inotifydirstate
--- a/mercurial/context.py Fri Jan 01 14:29:36 2010 +0100
+++ b/mercurial/context.py Fri Jan 01 17:11:48 2010 +0100
@@ -638,7 +638,8 @@
return self._parents[0].ancestor(c2) # punt on two parents for now
def walk(self, match):
- return sorted(self._repo.dirstate.walk(match, True, False))
+ return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
+ True, False))
def dirty(self, missing=False):
"check whether a working directory is modified"
--- a/mercurial/dirstate.py Fri Jan 01 14:29:36 2010 +0100
+++ b/mercurial/dirstate.py Fri Jan 01 17:11:48 2010 +0100
@@ -425,7 +425,7 @@
return True
return False
- def walk(self, match, unknown, ignored):
+ def walk(self, match, subrepos, unknown, ignored):
'''
Walk recursively through the directory tree, finding all files
matched by match.
@@ -486,7 +486,8 @@
files = set(match.files())
if not files or '.' in files:
files = ['']
- results = {'.hg': None}
+ results = dict.fromkeys(subrepos)
+ results['.hg'] = None
# step 1: find all explicit files
for ff in sorted(files):
@@ -564,11 +565,12 @@
if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
st = None
results[nf] = st
-
+ for s in subrepos:
+ del results[s]
del results['.hg']
return results
- def status(self, match, ignored, clean, unknown):
+ def status(self, match, subrepos, ignored, clean, unknown):
'''Determine the status of the working copy relative to the
dirstate and return a tuple of lists (unsure, modified, added,
removed, deleted, unknown, ignored, clean), where:
@@ -609,7 +611,8 @@
dadd = deleted.append
cadd = clean.append
- for fn, st in self.walk(match, listunknown, listignored).iteritems():
+ for fn, st in self.walk(match, subrepos, listunknown,
+ listignored).iteritems():
if fn not in dmap:
if (listignored or match.exact(fn)) and self._dirignore(fn):
if listignored:
--- a/mercurial/localrepo.py Fri Jan 01 14:29:36 2010 +0100
+++ b/mercurial/localrepo.py Fri Jan 01 17:11:48 2010 +0100
@@ -1000,7 +1000,9 @@
match.bad = bad
if working: # we need to scan the working dir
- s = self.dirstate.status(match, listignored, listclean, listunknown)
+ subrepos = ctx1.substate.keys()
+ s = self.dirstate.status(match, subrepos, listignored,
+ listclean, listunknown)
cmp, modified, added, removed, deleted, unknown, ignored, clean = s
# check for any possibly clean files
--- a/mercurial/subrepo.py Fri Jan 01 14:29:36 2010 +0100
+++ b/mercurial/subrepo.py Fri Jan 01 17:11:48 2010 +0100
@@ -5,23 +5,23 @@
# 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 = ('', '')
+nullstate = ('', '', 'empty')
def state(ctx):
p = config.config()
def read(f, sections=None, remap=None):
if f in ctx:
- try:
- p.parse(f, ctx[f].data(), sections, remap)
- except IOError, err:
- if err.errno != errno.ENOENT:
- raise
- read('.hgsub')
+ p.parse(f, ctx[f].data(), sections, remap, read)
+ else:
+ raise util.Abort(_("subrepo spec file %s not found") % f)
+
+ if '.hgsub' in ctx:
+ read('.hgsub')
rev = {}
if '.hgsubstate' in ctx:
@@ -35,7 +35,13 @@
state = {}
for path, src in p[''].items():
- state[path] = (src, rev.get(path, ''))
+ kind = 'hg'
+ if src.startswith('['):
+ if ']' not in src:
+ raise util.Abort(_('missing ] in subrepo source'))
+ kind, src = src.split(']', 1)
+ kind = kind[1:]
+ state[path] = (src, rev.get(path, ''), kind)
return state
@@ -45,6 +51,7 @@
for s in sorted(state)]), '')
def submerge(repo, wctx, mctx, actx):
+ # working context, merging context, ancestor context
if mctx == actx: # backwards?
actx = wctx.p1()
s1 = wctx.substate
@@ -56,7 +63,7 @@
def debug(s, msg, r=""):
if r:
- r = "%s:%s" % r
+ r = "%s:%s:%s" % r
repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
for s, l in s1.items():
@@ -105,7 +112,7 @@
continue
elif s not in sa:
debug(s, "remote added, get", r)
- wctx.sub(s).get(r)
+ mctx.sub(s).get(r)
sm[s] = r
elif r != sa[s]:
if repo.ui.promptchoice(
@@ -145,9 +152,9 @@
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)
+ if state[2] not in types:
+ raise util.Abort(_('unknown subrepo type %s') % t)
+ return types[state[2]](ctx, path, state[:2])
# subrepo classes need to implement the following methods:
# __init__(self, ctx, path, state)
@@ -204,7 +211,7 @@
hg.clean(self._repo, node.nullid, False)
def _get(self, state):
- source, revision = state
+ source, revision, kind = state
try:
self._repo.lookup(revision)
except error.RepoError:
@@ -216,7 +223,7 @@
def get(self, state):
self._get(state)
- source, revision = state
+ source, revision, kind = state
self._repo.ui.debug("getting subrepo %s\n" % self._path)
hg.clean(self._repo, revision, False)
@@ -242,3 +249,89 @@
dsturl = _abssource(self._repo, True)
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,
+ }
--- a/tests/test-subrepo Fri Jan 01 14:29:36 2010 +0100
+++ b/tests/test-subrepo Fri Jan 01 17:11:48 2010 +0100
@@ -104,3 +104,9 @@
hg pull | sed 's/ .*sub/ ...sub/g'
hg up # should pull t
cat t/t
+
+echo % bogus subrepo path aborts
+echo 'bogus=[boguspath' >> .hgsub
+hg ci -m 'bogus subrepo path'
+
+exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-subrepo-svn Fri Jan 01 17:11:48 2010 +0100
@@ -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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-subrepo-svn.out Fri Jan 01 17:11:48 2010 +0100
@@ -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
--- a/tests/test-subrepo.out Fri Jan 01 14:29:36 2010 +0100
+++ b/tests/test-subrepo.out Fri Jan 01 17:11:48 2010 +0100
@@ -57,7 +57,7 @@
ancestor 1f14a2e2d3ec local f0d2028bf86d+ remote 1831e14459c4
.hgsubstate: versions differ -> m
subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
- subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad
+ subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
getting subrepo t
resolving manifests
overwrite True partial False
@@ -79,7 +79,7 @@
ancestor 1831e14459c4 local e45c8b14af55+ remote f94576341bcf
.hgsubstate: versions differ -> m
subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
- subrepo t: both sides changed, merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4
+ subrepo t: both sides changed, merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
merging subrepo t
searching for copies back to rev 2
resolving manifests
@@ -201,3 +201,5 @@
added 1 changesets with 1 changes to 1 files
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
blah
+% bogus subrepo path aborts
+abort: missing ] in subrepo source