subrepo.py
193 lines
| 6.3 KiB
| text/x-python
|
PythonLexer
/ mercurial / subrepo.py
|
r8812 | # subrepo.py - sub-repository handling for Mercurial | ||
# | ||||
# Copyright 2006, 2007 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2, incorporated herein by reference. | ||||
|
r8813 | import errno, os | ||
|
r8814 | from i18n import _ | ||
|
r8813 | import config, util, node, error | ||
|
r8814 | localrepo = hg = None | ||
|
r8813 | |||
nullstate = ('', '') | ||||
|
r8812 | |||
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') | ||||
rev = {} | ||||
if '.hgsubstate' in ctx: | ||||
try: | ||||
for l in ctx['.hgsubstate'].data().splitlines(): | ||||
revision, path = l.split() | ||||
rev[path] = revision | ||||
except IOError, err: | ||||
if err.errno != errno.ENOENT: | ||||
raise | ||||
state = {} | ||||
for path, src in p[''].items(): | ||||
state[path] = (src, rev.get(path, '')) | ||||
return state | ||||
|
r8813 | |||
def writestate(repo, state): | ||||
repo.wwrite('.hgsubstate', | ||||
''.join(['%s %s\n' % (state[s][1], s) | ||||
for s in sorted(state)]), '') | ||||
|
r8814 | def submerge(repo, wctx, mctx, actx): | ||
if mctx == actx: # backwards? | ||||
actx = wctx.p1() | ||||
s1 = wctx.substate | ||||
s2 = mctx.substate | ||||
sa = actx.substate | ||||
sm = {} | ||||
for s, l in s1.items(): | ||||
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 | ||||
wctx.sub(s).get(r) | ||||
sm[s] = r | ||||
elif l[0] != r[0]: # sources differ | ||||
if repo.ui.prompt( | ||||
_(' subrepository sources for %s differ\n' | ||||
|
r8908 | 'use (l)ocal source (%s) or (r)emote source (%s)?') | ||
|
r8814 | % (s, l[0], r[0]), | ||
|
r8908 | (_('&Local'), _('&Remote')), _('l')) == _('r'): | ||
|
r8814 | wctx.sub(s).get(r) | ||
sm[s] = r | ||||
elif l[1] == a[1]: # local side is unchanged | ||||
wctx.sub(s).get(r) | ||||
sm[s] = r | ||||
else: | ||||
wctx.sub(s).merge(r) | ||||
sm[s] = l | ||||
elif l == a: # remote removed, local unchanged | ||||
wctx.sub(s).remove() | ||||
else: | ||||
if repo.ui.prompt( | ||||
_(' local changed subrepository %s which remote removed\n' | ||||
|
r8908 | 'use (c)hanged version or (d)elete?') % s, | ||
(_('&Changed'), _('&Delete')), _('c')) == _('d'): | ||||
|
r8814 | wctx.sub(s).remove() | ||
for s, r in s2.items(): | ||||
if s in s1: | ||||
continue | ||||
elif s not in sa: | ||||
wctx.sub(s).get(r) | ||||
sm[s] = r | ||||
elif r != sa[s]: | ||||
if repo.ui.prompt( | ||||
_(' remote changed subrepository %s which local removed\n' | ||||
|
r8908 | 'use (c)hanged version or (d)elete?') % s, | ||
(_('&Changed'), _('&Delete')), _('c')) == _('c'): | ||||
|
r8814 | wctx.sub(s).get(r) | ||
sm[s] = r | ||||
# record merged .hgsubstate | ||||
writestate(repo, sm) | ||||
|
r8815 | def _abssource(repo, push=False): | ||
|
r8814 | if hasattr(repo, '_subparent'): | ||
source = repo._subsource | ||||
if source.startswith('/') or '://' in source: | ||||
return source | ||||
return os.path.join(_abssource(repo._subparent), repo._subsource) | ||||
|
r8815 | if push and repo.ui.config('paths', 'default-push'): | ||
return repo.ui.config('paths', 'default-push', repo.root) | ||||
|
r8814 | return repo.ui.config('paths', 'default', repo.root) | ||
|
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 | ||||
|
r8814 | global localrepo, hg | ||
import localrepo as l, hg as h | ||||
|
r8813 | localrepo = l | ||
|
r8814 | hg = h | ||
|
r8813 | |||
|
r8997 | util.path_auditor(ctx._repo.root)(path) | ||
|
r8813 | 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) | ||||
class hgsubrepo(object): | ||||
def __init__(self, ctx, path, state): | ||||
self._path = path | ||||
self._state = state | ||||
r = ctx._repo | ||||
root = r.wjoin(path) | ||||
|
r8814 | if os.path.exists(os.path.join(root, '.hg')): | ||
self._repo = localrepo.localrepository(r.ui, root) | ||||
else: | ||||
util.makedirs(root) | ||||
self._repo = localrepo.localrepository(r.ui, root, create=True) | ||||
self._repo._subparent = r | ||||
self._repo._subsource = state[0] | ||||
|
r8813 | |||
def dirty(self): | ||||
r = self._state[1] | ||||
if r == '': | ||||
return True | ||||
w = self._repo[None] | ||||
if w.p1() != self._repo[r]: # version checked out changed | ||||
return True | ||||
return w.dirty() # working directory changed | ||||
def commit(self, text, user, date): | ||||
n = self._repo.commit(text, user, date) | ||||
if not n: | ||||
return self._repo['.'].hex() # different version checked out | ||||
return node.hex(n) | ||||
|
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) | ||||
def get(self, state): | ||||
source, revision = state | ||||
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) | ||||
hg.clean(self._repo, revision, False) | ||||
def merge(self, state): | ||||
hg.merge(self._repo, state[1], remind=False) | ||||
|
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) | ||||