diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2011-10-01 20:49:36 +0000 |
---|---|---|
committer | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-09-27 13:27:51 +0000 |
commit | 921ced43c48c1d170452a7b251b94cc96ec8dd44 (patch) | |
tree | 3c4a89176ea67fe4c7bf7b375488361a823c95fa /mercurial/subrepo.py | |
parent | 9039c805b0a7e36220101323f82735f08a104b37 (diff) | |
download | mercurial-tarball-master.tar.gz |
Imported from /srv/lorry/lorry-area/mercurial-tarball/mercurial-1.9.3.tar.gz.HEADmercurial-1.9.3master
Diffstat (limited to 'mercurial/subrepo.py')
-rw-r--r-- | mercurial/subrepo.py | 316 |
1 files changed, 67 insertions, 249 deletions
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py index 437d8b9..b5068d0 100644 --- a/mercurial/subrepo.py +++ b/mercurial/subrepo.py @@ -8,7 +8,7 @@ import errno, os, re, xml.dom.minidom, shutil, posixpath import stat, subprocess, tarfile from i18n import _ -import config, scmutil, util, node, error, cmdutil, bookmarks, match as matchmod +import config, scmutil, util, node, error, cmdutil, bookmarks hg = None propertycache = util.propertycache @@ -43,22 +43,22 @@ def state(ctx, ui): rev = {} if '.hgsubstate' in ctx: try: - for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()): - l = l.lstrip() - if not l: - continue - try: - revision, path = l.split(" ", 1) - except ValueError: - raise util.Abort(_("invalid subrepository revision " - "specifier in .hgsubstate line %d") - % (i + 1)) + for l in ctx['.hgsubstate'].data().splitlines(): + revision, path = l.split(" ", 1) rev[path] = revision except IOError, err: if err.errno != errno.ENOENT: raise - def remap(src): + state = {} + for path, src in p[''].items(): + kind = 'hg' + if src.startswith('['): + if ']' not in src: + raise util.Abort(_('missing ] in subrepo source')) + kind, src = src.split(']', 1) + kind = kind[1:] + for pattern, repl in p.items('subpaths'): # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub # does a string decode. @@ -72,35 +72,8 @@ def state(ctx, ui): except re.error, e: raise util.Abort(_("bad subrepository pattern in %s: %s") % (p.source('subpaths', pattern), e)) - return src - state = {} - for path, src in p[''].items(): - kind = 'hg' - if src.startswith('['): - if ']' not in src: - raise util.Abort(_('missing ] in subrepo source')) - kind, src = src.split(']', 1) - kind = kind[1:] - src = src.lstrip() # strip any extra whitespace after ']' - - if not util.url(src).isabs(): - parent = _abssource(ctx._repo, abort=False) - if parent: - parent = util.url(parent) - parent.path = posixpath.join(parent.path or '', src) - parent.path = posixpath.normpath(parent.path) - joined = str(parent) - # Remap the full joined path and use it if it changes, - # else remap the original source. - remapped = remap(joined) - if remapped == joined: - src = remap(src) - else: - src = remapped - - src = remap(src) - state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind) + state[path] = (src.strip(), rev.get(path, ''), kind) return state @@ -200,8 +173,7 @@ def _updateprompt(ui, sub, dirty, local, remote): 'use (l)ocal source (%s) or (r)emote source (%s)?\n') % (subrelpath(sub), local, remote)) else: - msg = (_(' subrepository sources for %s differ (in checked out ' - 'version)\n' + msg = (_(' subrepository sources for %s differ (in checked out version)\n' 'use (l)ocal source (%s) or (r)emote source (%s)?\n') % (subrelpath(sub), local, remote)) return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0) @@ -209,35 +181,34 @@ def _updateprompt(ui, sub, dirty, local, remote): def reporelpath(repo): """return path to this (sub)repo as seen from outermost repo""" parent = repo - while util.safehasattr(parent, '_subparent'): + while hasattr(parent, '_subparent'): parent = parent._subparent - p = parent.root.rstrip(os.sep) - return repo.root[len(p) + 1:] + return repo.root[len(parent.root)+1:] def subrelpath(sub): """return path to this subrepo as seen from outermost repo""" - if util.safehasattr(sub, '_relpath'): + if hasattr(sub, '_relpath'): return sub._relpath - if not util.safehasattr(sub, '_repo'): + if not hasattr(sub, '_repo'): return sub._path return reporelpath(sub._repo) def _abssource(repo, push=False, abort=True): """return pull/push path of repo - either based on parent repo .hgsub info or on the top repo config. Abort or return None if no source found.""" - if util.safehasattr(repo, '_subparent'): + if hasattr(repo, '_subparent'): source = util.url(repo._subsource) if source.isabs(): return str(source) source.path = posixpath.normpath(source.path) parent = _abssource(repo._subparent, push, abort=False) if parent: - parent = util.url(util.pconvert(parent)) + parent = util.url(parent) parent.path = posixpath.join(parent.path or '', source.path) parent.path = posixpath.normpath(parent.path) return str(parent) else: # recursion reached top repo - if util.safehasattr(repo, '_subtoppath'): + if hasattr(repo, '_subtoppath'): return repo._subtoppath if push and repo.ui.config('paths', 'default-push'): return repo.ui.config('paths', 'default-push') @@ -268,7 +239,7 @@ def subrepo(ctx, path): hg = h scmutil.pathauditor(ctx._repo.root)(path) - state = ctx.substate[path] + state = ctx.substate.get(path, nullstate) if state[2] not in types: raise util.Abort(_('unknown subrepo type %s') % state[2]) return types[state[2]](ctx, path, state[:2]) @@ -284,11 +255,6 @@ class abstractsubrepo(object): """ raise NotImplementedError - def basestate(self): - """current working directory base state, disregarding .hgsubstate - state and working directory modifications""" - raise NotImplementedError - def checknested(self, path): """check if path is a subrepository within this repository""" return False @@ -317,14 +283,14 @@ class abstractsubrepo(object): """merge currently-saved state with the new state.""" raise NotImplementedError - def push(self, opts): + def push(self, force): """perform whatever action is analogous to 'hg push' This may be a no-op on some systems. """ raise NotImplementedError - def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly): + def add(self, ui, match, dryrun, prefix): return [] def status(self, rev2, **opts): @@ -351,11 +317,8 @@ class abstractsubrepo(object): """return file flags""" return '' - def archive(self, ui, archiver, prefix, match=None): - if match is not None: - files = [f for f in self.files() if match(f)] - else: - files = self.files() + def archive(self, ui, archiver, prefix): + files = self.files() total = len(files) relpath = subrelpath(self) ui.progress(_('archiving (%s)') % relpath, 0, @@ -370,20 +333,6 @@ class abstractsubrepo(object): unit=_('files'), total=total) ui.progress(_('archiving (%s)') % relpath, None) - def walk(self, match): - ''' - walk recursively through the directory tree, finding all files - matched by the match function - ''' - pass - - def forget(self, ui, match, prefix): - return ([], []) - - def revert(self, ui, substate, *pats, **opts): - ui.warn('%s: reverting %s subrepos is unsupported\n' \ - % (substate[0], substate[2])) - return [] class hgsubrepo(abstractsubrepo): def __init__(self, ctx, path, state): @@ -418,9 +367,9 @@ class hgsubrepo(abstractsubrepo): addpathconfig('default-push', defpushpath) fp.close() - def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly): - return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos, - os.path.join(prefix, self._path), explicitonly) + def add(self, ui, match, dryrun, prefix): + return cmdutil.add(ui, self._repo, match, dryrun, True, + os.path.join(prefix, self._path)) def status(self, rev2, **opts): try: @@ -448,16 +397,14 @@ class hgsubrepo(abstractsubrepo): self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n') % (inst, subrelpath(self))) - def archive(self, ui, archiver, prefix, match=None): - self._get(self._state + ('hg',)) - abstractsubrepo.archive(self, ui, archiver, prefix, match) + def archive(self, ui, archiver, prefix): + abstractsubrepo.archive(self, ui, archiver, prefix) rev = self._state[1] ctx = self._repo[rev] for subpath in ctx.substate: s = subrepo(ctx, subpath) - submatch = matchmod.narrowmatcher(subpath, match) - s.archive(ui, archiver, os.path.join(prefix, self._path), submatch) + s.archive(ui, archiver, os.path.join(prefix, self._path)) def dirty(self, ignoreupdate=False): r = self._state[1] @@ -469,9 +416,6 @@ class hgsubrepo(abstractsubrepo): return True return w.dirty() # working directory changed - def basestate(self): - return self._repo['.'].hex() - def checknested(self, path): return self._repo._checknested(self._repo.wjoin(path)) @@ -502,18 +446,15 @@ class hgsubrepo(abstractsubrepo): self._repo.ui.status(_('cloning subrepo %s from %s\n') % (subrelpath(self), srcurl)) parentrepo = self._repo._subparent - shutil.rmtree(self._repo.path) - other, cloned = hg.clone(self._repo._subparent.ui, {}, - other, self._repo.root, - update=False) - self._repo = cloned.local() + shutil.rmtree(self._repo.root) + other, self._repo = hg.clone(self._repo._subparent.ui, {}, other, + self._repo.root, update=False) self._initrepo(parentrepo, source, create=True) else: self._repo.ui.status(_('pulling subrepo %s from %s\n') % (subrelpath(self), srcurl)) self._repo.pull(other) - bookmarks.updatefromremote(self._repo.ui, self._repo, other, - srcurl) + bookmarks.updatefromremote(self._repo.ui, self._repo, other) def get(self, state, overwrite=False): self._get(state) @@ -528,7 +469,7 @@ class hgsubrepo(abstractsubrepo): anc = dst.ancestor(cur) def mergefunc(): - if anc == cur and dst.branch() == cur.branch(): + if anc == cur: self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self)) hg.update(self._repo, state[1]) elif anc == dst: @@ -547,23 +488,19 @@ class hgsubrepo(abstractsubrepo): else: mergefunc() - def push(self, opts): - force = opts.get('force') - newbranch = opts.get('new_branch') - ssh = opts.get('ssh') - + def push(self, force): # push subrepos depth-first for coherent ordering c = self._repo[''] subs = c.substate # only repos that are committed for s in sorted(subs): - if c.sub(s).push(opts) == 0: + if not c.sub(s).push(force): return False dsturl = _abssource(self._repo, True) self._repo.ui.status(_('pushing subrepo %s to %s\n') % (subrelpath(self), dsturl)) - other = hg.peer(self._repo.ui, {'ssh': ssh}, dsturl) - return self._repo.push(other, force, newbranch=newbranch) + other = hg.peer(self._repo.ui, {}, dsturl) + return self._repo.push(other, force) def outgoing(self, ui, dest, opts): return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts) @@ -585,45 +522,6 @@ class hgsubrepo(abstractsubrepo): ctx = self._repo[rev] return ctx.flags(name) - def walk(self, match): - ctx = self._repo[None] - return ctx.walk(match) - - def forget(self, ui, match, prefix): - return cmdutil.forget(ui, self._repo, match, - os.path.join(prefix, self._path), True) - - def revert(self, ui, substate, *pats, **opts): - # reverting a subrepo is a 2 step process: - # 1. if the no_backup is not set, revert all modified - # files inside the subrepo - # 2. update the subrepo to the revision specified in - # the corresponding substate dictionary - ui.status(_('reverting subrepo %s\n') % substate[0]) - if not opts.get('no_backup'): - # Revert all files on the subrepo, creating backups - # Note that this will not recursively revert subrepos - # We could do it if there was a set:subrepos() predicate - opts = opts.copy() - opts['date'] = None - opts['rev'] = substate[1] - - pats = [] - if not opts['all']: - pats = ['set:modified()'] - self.filerevert(ui, *pats, **opts) - - # Update the repo to the revision specified in the given substate - self.get(substate, overwrite=True) - - def filerevert(self, ui, *pats, **opts): - ctx = self._repo[opts['rev']] - parents = self._repo.dirstate.parents() - if opts['all']: - pats = ['set:modified()'] - else: - pats = [] - cmdutil.revert(ui, self._repo, ctx, parents, *pats, **opts) class svnsubrepo(abstractsubrepo): def __init__(self, ctx, path, state): @@ -631,13 +529,9 @@ class svnsubrepo(abstractsubrepo): self._state = state self._ctx = ctx self._ui = ctx._repo.ui - self._exe = util.findexe('svn') - if not self._exe: - raise util.Abort(_("'svn' executable not found for subrepo '%s'") - % self._path) def _svncommand(self, commands, filename='', failok=False): - cmd = [self._exe] + cmd = ['svn'] extrakw = {} if not self._ui.interactive(): # Making stdin be a pipe should prevent svn from behaving @@ -695,13 +589,12 @@ class svnsubrepo(abstractsubrepo): return self._wcrevs()[0] def _wcchanged(self): - """Return (changes, extchanges, missing) where changes is True - if the working directory was changed, extchanges is - True if any of these changes concern an external entry and missing - is True if any change is a missing entry. + """Return (changes, extchanges) where changes is True + if the working directory was changed, and extchanges is + True if any of these changes concern an external entry. """ output, err = self._svncommand(['status', '--xml']) - externals, changes, missing = [], [], [] + externals, changes = [], [] doc = xml.dom.minidom.parseString(output) for e in doc.getElementsByTagName('entry'): s = e.getElementsByTagName('wc-status') @@ -712,16 +605,14 @@ class svnsubrepo(abstractsubrepo): path = e.getAttribute('path') if item == 'external': externals.append(path) - elif item == 'missing': - missing.append(path) if (item not in ('', 'normal', 'unversioned', 'external') or props not in ('', 'none', 'normal')): changes.append(path) for path in changes: for ext in externals: if path == ext or path.startswith(ext + os.sep): - return True, True, bool(missing) - return bool(changes), False, bool(missing) + return True, True + return bool(changes), False def dirty(self, ignoreupdate=False): if not self._wcchanged()[0]: @@ -729,42 +620,18 @@ class svnsubrepo(abstractsubrepo): return False return True - def basestate(self): - lastrev, rev = self._wcrevs() - if lastrev != rev: - # Last committed rev is not the same than rev. We would - # like to take lastrev but we do not know if the subrepo - # URL exists at lastrev. Test it and fallback to rev it - # is not there. - try: - self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)]) - return lastrev - except error.Abort: - pass - return rev - def commit(self, text, user, date): # user and date are out of our hands since svn is centralized - changed, extchanged, missing = self._wcchanged() + changed, extchanged = self._wcchanged() if not changed: - return self.basestate() + return self._wcrev() if extchanged: # Do not try to commit externals raise util.Abort(_('cannot commit svn externals')) - if missing: - # svn can commit with missing entries but aborting like hg - # seems a better approach. - raise util.Abort(_('cannot commit missing svn entries')) commitinfo, err = self._svncommand(['commit', '-m', text]) self._ui.status(commitinfo) newrev = re.search('Committed revision ([0-9]+).', commitinfo) if not newrev: - if not commitinfo.strip(): - # Sometimes, our definition of "changed" differs from - # svn one. For instance, svn ignores missing files - # when committing. If there are only missing files, no - # commit is made, no output and no error code. - raise util.Abort(_('failed to commit svn changes')) raise util.Abort(commitinfo.splitlines()[-1]) newrev = newrev.groups()[0] self._ui.status(self._svncommand(['update', '-r', newrev])[0]) @@ -806,7 +673,7 @@ class svnsubrepo(abstractsubrepo): status, err = self._svncommand(args, failok=True) if not re.search('Checked out revision [0-9]+.', status): if ('is already a working copy for a different URL' in err - and (self._wcchanged()[:2] == (False, False))): + and (self._wcchanged() == (False, False))): # obstructed but clean working copy, so just blow it away. self.remove() self.get(state, overwrite=False) @@ -817,36 +684,27 @@ class svnsubrepo(abstractsubrepo): def merge(self, state): old = self._state[1] new = state[1] - wcrev = self._wcrev() - if new != wcrev: - dirty = old == wcrev or self._wcchanged()[0] - if _updateprompt(self._ui, self, dirty, wcrev, new): + if new != self._wcrev(): + dirty = old == self._wcrev() or self._wcchanged()[0] + if _updateprompt(self._ui, self, dirty, self._wcrev(), new): self.get(state, False) - def push(self, opts): + def push(self, force): # push is a no-op for SVN return True def files(self): - output = self._svncommand(['list', '--recursive', '--xml'])[0] - doc = xml.dom.minidom.parseString(output) - paths = [] - for e in doc.getElementsByTagName('entry'): - kind = str(e.getAttribute('kind')) - if kind != 'file': - continue - name = ''.join(c.data for c - in e.getElementsByTagName('name')[0].childNodes - if c.nodeType == c.TEXT_NODE) - paths.append(name) - return paths + output = self._svncommand(['list']) + # This works because svn forbids \n in filenames. + return output.splitlines() def filedata(self, name): - return self._svncommand(['cat'], name)[0] + return self._svncommand(['cat'], name) class gitsubrepo(abstractsubrepo): def __init__(self, ctx, path, state): + # TODO add git version check. self._state = state self._ctx = ctx self._path = path @@ -854,29 +712,6 @@ class gitsubrepo(abstractsubrepo): self._abspath = ctx._repo.wjoin(path) self._subparent = ctx._repo self._ui = ctx._repo.ui - self._ensuregit() - - def _ensuregit(self): - try: - self._gitexecutable = 'git' - out, err = self._gitnodir(['--version']) - except OSError, e: - if e.errno != 2 or os.name != 'nt': - raise - self._gitexecutable = 'git.cmd' - out, err = self._gitnodir(['--version']) - m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out) - if not m: - self._ui.warn(_('cannot retrieve git version')) - return - version = (int(m.group(1)), m.group(2), m.group(3)) - # git 1.4.0 can't work at all, but 1.5.X can in at least some cases, - # despite the docstring comment. For now, error on 1.4.0, warn on - # 1.5.0 but attempt to continue. - if version < (1, 5, 0): - raise util.Abort(_('git subrepo requires at least 1.6.0 or later')) - elif version < (1, 6, 0): - self._ui.warn(_('git subrepo requires at least 1.6.0 or later')) def _gitcommand(self, commands, env=None, stream=False): return self._gitdir(commands, env=env, stream=stream)[0] @@ -897,8 +732,8 @@ class gitsubrepo(abstractsubrepo): errpipe = None if self._ui.quiet: errpipe = open(os.devnull, 'w') - p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1, - cwd=cwd, env=env, close_fds=util.closefds, + p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env, + close_fds=util.closefds, stdout=subprocess.PIPE, stderr=errpipe) if stream: return p.stdout, None @@ -947,12 +782,6 @@ class gitsubrepo(abstractsubrepo): def _gitisbare(self): return self._gitcommand(['config', '--bool', 'core.bare']) == 'true' - def _gitupdatestat(self): - """This must be run before git diff-index. - diff-index only looks at changes to file stat; - this command looks at file contents and updates the stat.""" - self._gitcommand(['update-index', '-q', '--refresh']) - def _gitbranchmap(self): '''returns 2 things: a map from git branch to revision @@ -980,10 +809,9 @@ class gitsubrepo(abstractsubrepo): for b in branches: if b.startswith('refs/remotes/'): continue - bname = b.split('/', 2)[2] - remote = self._gitcommand(['config', 'branch.%s.remote' % bname]) + remote = self._gitcommand(['config', 'branch.%s.remote' % b]) if remote: - ref = self._gitcommand(['config', 'branch.%s.merge' % bname]) + ref = self._gitcommand(['config', 'branch.%s.merge' % b]) tracking['refs/remotes/%s/%s' % (remote, ref.split('/', 2)[2])] = b return tracking @@ -1022,13 +850,9 @@ class gitsubrepo(abstractsubrepo): # different version checked out return True # check for staged changes or modified files; ignore untracked files - self._gitupdatestat() out, code = self._gitdir(['diff-index', '--quiet', 'HEAD']) return code == 1 - def basestate(self): - return self._gitstate() - def get(self, state, overwrite=False): source, revision, kind = state if not revision: @@ -1133,7 +957,6 @@ class gitsubrepo(abstractsubrepo): source, revision, kind = state self._fetch(source, revision) base = self._gitcommand(['merge-base', revision, self._state[1]]) - self._gitupdatestat() out, code = self._gitdir(['diff-index', '--quiet', 'HEAD']) def mergefunc(): @@ -1151,9 +974,7 @@ class gitsubrepo(abstractsubrepo): else: mergefunc() - def push(self, opts): - force = opts.get('force') - + def push(self, force): if not self._state[1]: return True if self._gitmissing(): @@ -1186,7 +1007,7 @@ class gitsubrepo(abstractsubrepo): return True else: self._ui.warn(_('no branch checked out in subrepo %s\n' - 'cannot push revision %s\n') % + 'cannot push revision %s') % (self._relpath, self._state[1])) return False @@ -1210,7 +1031,7 @@ class gitsubrepo(abstractsubrepo): else: os.remove(path) - def archive(self, ui, archiver, prefix, match=None): + def archive(self, ui, archiver, prefix): source, revision = self._state if not revision: return @@ -1226,8 +1047,6 @@ class gitsubrepo(abstractsubrepo): for i, info in enumerate(tar): if info.isdir(): continue - if match and not match(info.name): - continue if info.issym(): data = info.linkname else: @@ -1245,7 +1064,6 @@ class gitsubrepo(abstractsubrepo): # if the repo is missing, return no results return [], [], [], [], [], [], [] modified, added, removed = [], [], [] - self._gitupdatestat() if rev2: command = ['diff-tree', rev1, rev2] else: |