# HG changeset patch # User Martin Geisler # Date 1262362308 -3600 # Node ID b42186efcf7f4f359560d856e0a216eab3b8610c # Parent a9f60014a20f9eb4c7621a48bf25be52b759a423# Parent cd477be6f2fc9b1ea4be441d13a83d9a0927c73e Merge with mpm diff -r a9f60014a20f -r b42186efcf7f contrib/perf.py --- 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, []), } - diff -r a9f60014a20f -r b42186efcf7f hgext/inotify/__init__.py --- 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 diff -r a9f60014a20f -r b42186efcf7f mercurial/context.py --- 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" diff -r a9f60014a20f -r b42186efcf7f mercurial/dirstate.py --- 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: diff -r a9f60014a20f -r b42186efcf7f mercurial/localrepo.py --- 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 diff -r a9f60014a20f -r b42186efcf7f mercurial/subrepo.py --- 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, + } diff -r a9f60014a20f -r b42186efcf7f tests/test-subrepo --- 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 diff -r a9f60014a20f -r b42186efcf7f tests/test-subrepo-svn --- /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 diff -r a9f60014a20f -r b42186efcf7f tests/test-subrepo-svn.out --- /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 diff -r a9f60014a20f -r b42186efcf7f tests/test-subrepo.out --- 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