##// END OF EJS Templates
dispatch: don't mangle ImportError abort messages...
dispatch: don't mangle ImportError abort messages Previously, Mercurial assumed that the last word of the string representation was the name of the moduled that was imported. This assmption is incorrect, despite being true for the common case of an exception raised by the Python VM. For example, hgsubversion raises an ImportError with a helpful message if the Subversion bindings were not found. The final word of this message is not meaningful on its own, and is never the name of a module. This patch changes the output printed to be a simple stringification of the exception instance. In most cases, this will be `abort: No module named X!' rather than `abort: could not import module X!'. No functionality change; all tests pass.

File last commit:

r10962:8d5f5122 merge default
r11053:59d0d715 stable
Show More
subrepo.py
373 lines | 13.0 KiB | text/x-python | PythonLexer
Matt Mackall
subrepo: introduce basic state parsing
r8812 # subrepo.py - sub-repository handling for Mercurial
#
David Soria Parra
subrepo: correct copyright
r10324 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
Matt Mackall
subrepo: introduce basic state parsing
r8812 #
# This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Matt Mackall
subrepo: introduce basic state parsing
r8812
Benoit Boissinot
subrepo: fix errors reported by pylint
r10299 import errno, os, re, xml.dom.minidom, shutil
Matt Mackall
subrepo: add update/merge logic
r8814 from i18n import _
Matt Mackall
commit: recurse into subrepositories
r8813 import config, util, node, error
Abderrahim Kitouni
subrepo: use hg.repository instead of creating localrepo directly...
r9092 hg = None
Matt Mackall
commit: recurse into subrepositories
r8813
Augie Fackler
subrepo: add table-based dispatch for subrepo types
r10177 nullstate = ('', '', 'empty')
Matt Mackall
subrepo: introduce basic state parsing
r8812
def state(ctx):
p = config.config()
def read(f, sections=None, remap=None):
if f in ctx:
Matt Mackall
subrepo: fix includes support in .hgsub
r10174 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')
Matt Mackall
subrepo: introduce basic state parsing
r8812
rev = {}
if '.hgsubstate' in ctx:
try:
for l in ctx['.hgsubstate'].data().splitlines():
Matt Mackall
subrepo: more robust split for .hgsubstate parsing
r9752 revision, path = l.split(" ", 1)
Matt Mackall
subrepo: introduce basic state parsing
r8812 rev[path] = revision
except IOError, err:
if err.errno != errno.ENOENT:
raise
state = {}
for path, src in p[''].items():
Augie Fackler
subrepo: add table-based dispatch for subrepo types
r10177 kind = 'hg'
if src.startswith('['):
if ']' not in src:
raise util.Abort(_('missing ] in subrepo source'))
kind, src = src.split(']', 1)
kind = kind[1:]
David Soria Parra
subrepo: make sure that the source path is stripped...
r10457 state[path] = (src.strip(), rev.get(path, ''), kind)
Matt Mackall
subrepo: introduce basic state parsing
r8812
return state
Matt Mackall
commit: recurse into subrepositories
r8813
def writestate(repo, state):
repo.wwrite('.hgsubstate',
''.join(['%s %s\n' % (state[s][1], s)
for s in sorted(state)]), '')
Matt Mackall
subrepo: add update/merge logic
r8814 def submerge(repo, wctx, mctx, actx):
Augie Fackler
subrepo: load from a context where the subrepo exists
r10175 # working context, merging context, ancestor context
Matt Mackall
subrepo: add update/merge logic
r8814 if mctx == actx: # backwards?
actx = wctx.p1()
s1 = wctx.substate
s2 = mctx.substate
sa = actx.substate
sm = {}
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
Matt Mackall
subrepo: add some debug output to submerge
r9779 def debug(s, msg, r=""):
if r:
Augie Fackler
subrepo: add table-based dispatch for subrepo types
r10177 r = "%s:%s:%s" % r
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
Matt Mackall
subrepo: add some debug output to submerge
r9779
Matt Mackall
subrepo: add update/merge logic
r8814 for s, l in s1.items():
Matt Mackall
submerge: properly deal with overwrites...
r9783 if wctx != actx and wctx.sub(s).dirty():
Matt Mackall
subrepo: notice dirty subrepo states when merging
r9780 l = (l[0], l[1] + "+")
Matt Mackall
subrepo: add update/merge logic
r8814 a = sa.get(s, nullstate)
if s in s2:
r = s2[s]
if l == r or r == a: # no change or local is newer
sm[s] = l
continue
elif l == a: # other side changed
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 debug(s, "other changed, get", r)
Matt Mackall
subrepo: add update/merge logic
r8814 wctx.sub(s).get(r)
sm[s] = r
elif l[0] != r[0]: # sources differ
Simon Heimberg
ui: extract choice from prompt...
r9048 if repo.ui.promptchoice(
Matt Mackall
subrepo: add update/merge logic
r8814 _(' subrepository sources for %s differ\n'
Dongsheng Song
Fix warning: Seen unexpected token "%"
r8908 'use (l)ocal source (%s) or (r)emote source (%s)?')
Matt Mackall
subrepo: add update/merge logic
r8814 % (s, l[0], r[0]),
Simon Heimberg
ui: extract choice from prompt...
r9048 (_('&Local'), _('&Remote')), 0):
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 debug(s, "prompt changed, get", r)
Matt Mackall
subrepo: add update/merge logic
r8814 wctx.sub(s).get(r)
sm[s] = r
elif l[1] == a[1]: # local side is unchanged
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 debug(s, "other side changed, get", r)
Matt Mackall
subrepo: add update/merge logic
r8814 wctx.sub(s).get(r)
sm[s] = r
else:
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 debug(s, "both sides changed, merge with", r)
Matt Mackall
subrepo: add update/merge logic
r8814 wctx.sub(s).merge(r)
sm[s] = l
elif l == a: # remote removed, local unchanged
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 debug(s, "remote removed, remove")
Matt Mackall
subrepo: add update/merge logic
r8814 wctx.sub(s).remove()
else:
Simon Heimberg
ui: extract choice from prompt...
r9048 if repo.ui.promptchoice(
Matt Mackall
subrepo: add update/merge logic
r8814 _(' local changed subrepository %s which remote removed\n'
Dongsheng Song
Fix warning: Seen unexpected token "%"
r8908 'use (c)hanged version or (d)elete?') % s,
Martin Geisler
filemerge, subrepo: correct indention
r9049 (_('&Changed'), _('&Delete')), 0):
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 debug(s, "prompt remove")
Matt Mackall
subrepo: add update/merge logic
r8814 wctx.sub(s).remove()
for s, r in s2.items():
if s in s1:
continue
elif s not in sa:
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 debug(s, "remote added, get", r)
Augie Fackler
subrepo: load from a context where the subrepo exists
r10175 mctx.sub(s).get(r)
Matt Mackall
subrepo: add update/merge logic
r8814 sm[s] = r
elif r != sa[s]:
Simon Heimberg
ui: extract choice from prompt...
r9048 if repo.ui.promptchoice(
Matt Mackall
subrepo: add update/merge logic
r8814 _(' remote changed subrepository %s which local removed\n'
Dongsheng Song
Fix warning: Seen unexpected token "%"
r8908 'use (c)hanged version or (d)elete?') % s,
Martin Geisler
filemerge, subrepo: correct indention
r9049 (_('&Changed'), _('&Delete')), 0) == 0:
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 debug(s, "prompt recreate", r)
Matt Mackall
subrepo: add update/merge logic
r8814 wctx.sub(s).get(r)
sm[s] = r
# record merged .hgsubstate
writestate(repo, sm)
Matt Mackall
subrepo: basic push support
r8815 def _abssource(repo, push=False):
Matt Mackall
subrepo: add update/merge logic
r8814 if hasattr(repo, '_subparent'):
source = repo._subsource
if source.startswith('/') or '://' in source:
return source
Edouard Gomez
subrepo: forward the push to argument into _abssource
r10665 parent = _abssource(repo._subparent, push)
Matt Mackall
subrepo: use '/' for joining non-local paths
r9186 if '://' in parent:
if parent[-1] == '/':
parent = parent[:-1]
return parent + '/' + source
return os.path.join(parent, repo._subsource)
Matt Mackall
subrepo: basic push support
r8815 if push and repo.ui.config('paths', 'default-push'):
return repo.ui.config('paths', 'default-push', repo.root)
Matt Mackall
subrepo: add update/merge logic
r8814 return repo.ui.config('paths', 'default', repo.root)
Matt Mackall
commit: recurse into subrepositories
r8813 def subrepo(ctx, path):
# subrepo inherently violates our import layering rules
# because it wants to make repo objects from deep inside the stack
# so we manually delay the circular imports to not break
# scripts that don't use our demand-loading
Abderrahim Kitouni
subrepo: use hg.repository instead of creating localrepo directly...
r9092 global hg
import hg as h
Matt Mackall
subrepo: add update/merge logic
r8814 hg = h
Matt Mackall
commit: recurse into subrepositories
r8813
Matt Mackall
subrepo: audit subrepo paths
r8997 util.path_auditor(ctx._repo.root)(path)
Matt Mackall
commit: recurse into subrepositories
r8813 state = ctx.substate.get(path, nullstate)
Augie Fackler
subrepo: add table-based dispatch for subrepo types
r10177 if state[2] not in types:
Benoit Boissinot
subrepo: fix errors reported by pylint
r10299 raise util.Abort(_('unknown subrepo type %s') % state[2])
Augie Fackler
subrepo: add table-based dispatch for subrepo types
r10177 return types[state[2]](ctx, path, state[:2])
Matt Mackall
commit: recurse into subrepositories
r8813
Augie Fackler
subrepo: document necessary methods for a subrepo class
r10027 # subrepo classes need to implement the following methods:
# __init__(self, ctx, path, state)
# dirty(self): returns true if the dirstate of the subrepo
# does not match current stored state
# commit(self, text, user, date): commit the current changes
# to the subrepo with the given log message. Use given
# user and date if possible. Return the new state of the subrepo.
# remove(self): remove the subrepo (should verify the dirstate
# is not dirty first)
# get(self, state): run whatever commands are needed to put the
# subrepo into this state
# merge(self, state): merge currently-saved state with the new state.
# push(self, force): perform whatever action is analagous to 'hg push'
# This may be a no-op on some systems.
Matt Mackall
commit: recurse into subrepositories
r8813
class hgsubrepo(object):
def __init__(self, ctx, path, state):
self._path = path
self._state = state
r = ctx._repo
root = r.wjoin(path)
Benoit Boissinot
subrepo: keep ui and hgrc in sync when creating new repo
r10666 create = False
if not os.path.exists(os.path.join(root, '.hg')):
create = True
Matt Mackall
subrepo: add update/merge logic
r8814 util.makedirs(root)
Benoit Boissinot
subrepo: keep ui and hgrc in sync when creating new repo
r10666 self._repo = hg.repository(r.ui, root, create=create)
Matt Mackall
subrepo: add update/merge logic
r8814 self._repo._subparent = r
self._repo._subsource = state[0]
Matt Mackall
commit: recurse into subrepositories
r8813
Benoit Boissinot
subrepo: keep ui and hgrc in sync when creating new repo
r10666 if create:
fp = self._repo.opener("hgrc", "w", text=True)
fp.write('[paths]\n')
def addpathconfig(key, value):
fp.write('%s = %s\n' % (key, value))
self._repo.ui.setconfig('paths', key, value)
Edouard Gomez
subrepo: fix hgrc paths section during subrepo pulling...
r10697 defpath = _abssource(self._repo)
defpushpath = _abssource(self._repo, True)
Benoit Boissinot
subrepo: keep ui and hgrc in sync when creating new repo
r10666 addpathconfig('default', defpath)
Edouard Gomez
subrepo: fix hgrc paths section during subrepo pulling...
r10697 if defpath != defpushpath:
addpathconfig('default-push', defpushpath)
Benoit Boissinot
subrepo: keep ui and hgrc in sync when creating new repo
r10666 fp.close()
Matt Mackall
commit: recurse into subrepositories
r8813 def dirty(self):
r = self._state[1]
if r == '':
return True
w = self._repo[None]
Benoit Boissinot
subrepo: keep ui and hgrc in sync when creating new repo
r10666 if w.p1() != self._repo[r]: # version checked out change
Matt Mackall
commit: recurse into subrepositories
r8813 return True
return w.dirty() # working directory changed
def commit(self, text, user, date):
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 self._repo.ui.debug("committing subrepo %s\n" % self._path)
Matt Mackall
commit: recurse into subrepositories
r8813 n = self._repo.commit(text, user, date)
if not n:
return self._repo['.'].hex() # different version checked out
return node.hex(n)
Matt Mackall
subrepo: add update/merge logic
r8814
def remove(self):
# we can't fully delete the repository as it may contain
# local-only history
self._repo.ui.note(_('removing subrepo %s\n') % self._path)
hg.clean(self._repo, node.nullid, False)
Matt Mackall
subrepo: add auto-pull for merge
r9507 def _get(self, state):
Augie Fackler
subrepo: add table-based dispatch for subrepo types
r10177 source, revision, kind = state
Matt Mackall
subrepo: add update/merge logic
r8814 try:
self._repo.lookup(revision)
except error.RepoError:
self._repo._subsource = source
self._repo.ui.status(_('pulling subrepo %s\n') % self._path)
srcurl = _abssource(self._repo)
other = hg.repository(self._repo.ui, srcurl)
self._repo.pull(other)
Matt Mackall
subrepo: add auto-pull for merge
r9507 def get(self, state):
self._get(state)
Augie Fackler
subrepo: add table-based dispatch for subrepo types
r10177 source, revision, kind = state
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 self._repo.ui.debug("getting subrepo %s\n" % self._path)
Matt Mackall
subrepo: add update/merge logic
r8814 hg.clean(self._repo, revision, False)
def merge(self, state):
Matt Mackall
subrepo: add auto-pull for merge
r9507 self._get(state)
Matt Mackall
subrepo: do a linear update when appropriate
r9781 cur = self._repo['.']
dst = self._repo[state[1]]
Benoit Boissinot
subrepo: fix merging of already merged subrepos (issue1986)...
r10251 anc = dst.ancestor(cur)
if anc == cur:
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 self._repo.ui.debug("updating subrepo %s\n" % self._path)
Matt Mackall
subrepo: do a linear update when appropriate
r9781 hg.update(self._repo, state[1])
Benoit Boissinot
subrepo: fix merging of already merged subrepos (issue1986)...
r10251 elif anc == dst:
self._repo.ui.debug("skipping subrepo %s\n" % self._path)
Matt Mackall
subrepo: do a linear update when appropriate
r9781 else:
Matt Mackall
subrepo: add more debugging output, lose _ markers
r9782 self._repo.ui.debug("merging subrepo %s\n" % self._path)
Matt Mackall
subrepo: do a linear update when appropriate
r9781 hg.merge(self._repo, state[1], remind=False)
Matt Mackall
subrepo: basic push support
r8815
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):
c.sub(s).push(force)
self._repo.ui.status(_('pushing subrepo %s\n') % self._path)
dsturl = _abssource(self._repo, True)
other = hg.repository(self._repo.ui, dsturl)
self._repo.push(other, force)
Augie Fackler
subrepo: add table-based dispatch for subrepo types
r10177
Augie Fackler
subrepo: Subversion support
r10178 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):
Brett Cannon
subrepo: fix repo root path handling in svn subrepo
r10954 path = os.path.join(self._ctx._repo.origroot, self._path)
cmd = ['svn'] + commands + [path]
Augie Fackler
subrepo: Subversion support
r10178 cmd = [util.shellquote(arg) for arg in cmd]
cmd = util.quotecommand(' '.join(cmd))
Patrick Mezard
subrepo: force en_US.UTF-8 locale when calling svn...
r10199 env = dict(os.environ)
Patrick Mezard
subrepo: make svn use C locale for portability...
r10271 # Avoid localized output, preserve current locale for everything else.
env['LC_MESSAGES'] = 'C'
Patrick Mezard
subrepo: force en_US.UTF-8 locale when calling svn...
r10199 write, read, err = util.popen3(cmd, env=env, newlines=True)
Augie Fackler
subrepo: Subversion support
r10178 retdata = read.read()
err = err.read().strip()
if err:
raise util.Abort(err)
return retdata
def _wcrev(self):
Patrick Mezard
subrepo: svn xml output is much easier to parse...
r10272 output = self._svncommand(['info', '--xml'])
doc = xml.dom.minidom.parseString(output)
entries = doc.getElementsByTagName('entry')
if not entries:
Augie Fackler
subrepo: Subversion support
r10178 return 0
Patrick Mezard
subrepo: svn xml output is much easier to parse...
r10272 return int(entries[0].getAttribute('revision') or 0)
Augie Fackler
subrepo: Subversion support
r10178
Patrick Mezard
subrepo: handle svn externals and meta changes (issue1982)...
r10273 def _wcchanged(self):
"""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.
"""
Patrick Mezard
subrepo: svn xml output is much easier to parse...
r10272 output = self._svncommand(['status', '--xml'])
Patrick Mezard
subrepo: handle svn externals and meta changes (issue1982)...
r10273 externals, changes = [], []
Patrick Mezard
subrepo: svn xml output is much easier to parse...
r10272 doc = xml.dom.minidom.parseString(output)
Patrick Mezard
subrepo: handle svn externals and meta changes (issue1982)...
r10273 for e in doc.getElementsByTagName('entry'):
s = e.getElementsByTagName('wc-status')
if not s:
continue
item = s[0].getAttribute('item')
props = s[0].getAttribute('props')
path = e.getAttribute('path')
if item == 'external':
externals.append(path)
if (item not in ('', 'normal', 'unversioned', 'external')
or props not in ('', 'none')):
changes.append(path)
for path in changes:
for ext in externals:
if path == ext or path.startswith(ext + os.sep):
return True, True
return bool(changes), False
Augie Fackler
subrepo: Subversion support
r10178
def dirty(self):
Patrick Mezard
subrepo: handle svn externals and meta changes (issue1982)...
r10273 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
Augie Fackler
subrepo: Subversion support
r10178 return False
return True
def commit(self, text, user, date):
# user and date are out of our hands since svn is centralized
Patrick Mezard
subrepo: handle svn externals and meta changes (issue1982)...
r10273 changed, extchanged = self._wcchanged()
if not changed:
Augie Fackler
subrepo: Subversion support
r10178 return self._wcrev()
Patrick Mezard
subrepo: handle svn externals and meta changes (issue1982)...
r10273 if extchanged:
# Do not try to commit externals
raise util.Abort(_('cannot commit svn externals'))
Augie Fackler
subrepo: Subversion support
r10178 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():
Benoit Boissinot
subrepo: fix errors reported by pylint
r10299 self._ui.warn(_('not removing repo %s because '
'it has changes.\n' % self._path))
Augie Fackler
subrepo: Subversion support
r10178 return
Benoit Boissinot
i18n: mark more strings for translation
r10510 self._ui.note(_('removing subrepo %s\n') % self._path)
Augie Fackler
subrepo: Subversion support
r10178 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
Augie Fackler
subrepo: add table-based dispatch for subrepo types
r10177 types = {
'hg': hgsubrepo,
Augie Fackler
subrepo: Subversion support
r10178 'svn': svnsubrepo,
Augie Fackler
subrepo: add table-based dispatch for subrepo types
r10177 }