subrepo.py
241 lines
| 8.3 KiB
| text/x-python
|
PythonLexer
/ mercurial / subrepo.py
Matt Mackall
|
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. | ||||
Matt Mackall
|
r8813 | import errno, os | ||
Matt Mackall
|
r8814 | from i18n import _ | ||
Matt Mackall
|
r8813 | import config, util, node, error | ||
Abderrahim Kitouni
|
r9092 | hg = None | ||
Matt Mackall
|
r8813 | |||
nullstate = ('', '') | ||||
Matt Mackall
|
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(): | ||||
Matt Mackall
|
r9752 | revision, path = l.split(" ", 1) | ||
Matt Mackall
|
r8812 | 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 | ||||
Matt Mackall
|
r8813 | |||
def writestate(repo, state): | ||||
repo.wwrite('.hgsubstate', | ||||
''.join(['%s %s\n' % (state[s][1], s) | ||||
for s in sorted(state)]), '') | ||||
Matt Mackall
|
r8814 | def submerge(repo, wctx, mctx, actx): | ||
if mctx == actx: # backwards? | ||||
actx = wctx.p1() | ||||
s1 = wctx.substate | ||||
s2 = mctx.substate | ||||
sa = actx.substate | ||||
sm = {} | ||||
Matt Mackall
|
r9782 | repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx)) | ||
Matt Mackall
|
r9779 | def debug(s, msg, r=""): | ||
if r: | ||||
r = "%s:%s" % r | ||||
Matt Mackall
|
r9782 | repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r)) | ||
Matt Mackall
|
r9779 | |||
Matt Mackall
|
r8814 | for s, l in s1.items(): | ||
Matt Mackall
|
r9783 | if wctx != actx and wctx.sub(s).dirty(): | ||
Matt Mackall
|
r9780 | l = (l[0], l[1] + "+") | ||
Matt Mackall
|
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
|
r9782 | debug(s, "other changed, get", r) | ||
Matt Mackall
|
r8814 | wctx.sub(s).get(r) | ||
sm[s] = r | ||||
elif l[0] != r[0]: # sources differ | ||||
Simon Heimberg
|
r9048 | if repo.ui.promptchoice( | ||
Matt Mackall
|
r8814 | _(' subrepository sources for %s differ\n' | ||
Dongsheng Song
|
r8908 | 'use (l)ocal source (%s) or (r)emote source (%s)?') | ||
Matt Mackall
|
r8814 | % (s, l[0], r[0]), | ||
Simon Heimberg
|
r9048 | (_('&Local'), _('&Remote')), 0): | ||
Matt Mackall
|
r9782 | debug(s, "prompt changed, get", r) | ||
Matt Mackall
|
r8814 | wctx.sub(s).get(r) | ||
sm[s] = r | ||||
elif l[1] == a[1]: # local side is unchanged | ||||
Matt Mackall
|
r9782 | debug(s, "other side changed, get", r) | ||
Matt Mackall
|
r8814 | wctx.sub(s).get(r) | ||
sm[s] = r | ||||
else: | ||||
Matt Mackall
|
r9782 | debug(s, "both sides changed, merge with", r) | ||
Matt Mackall
|
r8814 | wctx.sub(s).merge(r) | ||
sm[s] = l | ||||
elif l == a: # remote removed, local unchanged | ||||
Matt Mackall
|
r9782 | debug(s, "remote removed, remove") | ||
Matt Mackall
|
r8814 | wctx.sub(s).remove() | ||
else: | ||||
Simon Heimberg
|
r9048 | if repo.ui.promptchoice( | ||
Matt Mackall
|
r8814 | _(' local changed subrepository %s which remote removed\n' | ||
Dongsheng Song
|
r8908 | 'use (c)hanged version or (d)elete?') % s, | ||
Martin Geisler
|
r9049 | (_('&Changed'), _('&Delete')), 0): | ||
Matt Mackall
|
r9782 | debug(s, "prompt remove") | ||
Matt Mackall
|
r8814 | wctx.sub(s).remove() | ||
for s, r in s2.items(): | ||||
if s in s1: | ||||
continue | ||||
elif s not in sa: | ||||
Matt Mackall
|
r9782 | debug(s, "remote added, get", r) | ||
Matt Mackall
|
r8814 | wctx.sub(s).get(r) | ||
sm[s] = r | ||||
elif r != sa[s]: | ||||
Simon Heimberg
|
r9048 | if repo.ui.promptchoice( | ||
Matt Mackall
|
r8814 | _(' remote changed subrepository %s which local removed\n' | ||
Dongsheng Song
|
r8908 | 'use (c)hanged version or (d)elete?') % s, | ||
Martin Geisler
|
r9049 | (_('&Changed'), _('&Delete')), 0) == 0: | ||
Matt Mackall
|
r9782 | debug(s, "prompt recreate", r) | ||
Matt Mackall
|
r8814 | wctx.sub(s).get(r) | ||
sm[s] = r | ||||
# record merged .hgsubstate | ||||
writestate(repo, sm) | ||||
Matt Mackall
|
r8815 | def _abssource(repo, push=False): | ||
Matt Mackall
|
r8814 | if hasattr(repo, '_subparent'): | ||
source = repo._subsource | ||||
if source.startswith('/') or '://' in source: | ||||
return source | ||||
Matt Mackall
|
r9186 | parent = _abssource(repo._subparent) | ||
if '://' in parent: | ||||
if parent[-1] == '/': | ||||
parent = parent[:-1] | ||||
return parent + '/' + source | ||||
return os.path.join(parent, repo._subsource) | ||||
Matt Mackall
|
r8815 | if push and repo.ui.config('paths', 'default-push'): | ||
return repo.ui.config('paths', 'default-push', repo.root) | ||||
Matt Mackall
|
r8814 | return repo.ui.config('paths', 'default', repo.root) | ||
Matt Mackall
|
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
|
r9092 | global hg | ||
import hg as h | ||||
Matt Mackall
|
r8814 | hg = h | ||
Matt Mackall
|
r8813 | |||
Matt Mackall
|
r8997 | util.path_auditor(ctx._repo.root)(path) | ||
Matt Mackall
|
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) | ||||
Augie Fackler
|
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
|
r8813 | class hgsubrepo(object): | ||
def __init__(self, ctx, path, state): | ||||
self._path = path | ||||
self._state = state | ||||
r = ctx._repo | ||||
root = r.wjoin(path) | ||||
Matt Mackall
|
r8814 | if os.path.exists(os.path.join(root, '.hg')): | ||
Abderrahim Kitouni
|
r9092 | self._repo = hg.repository(r.ui, root) | ||
Matt Mackall
|
r8814 | else: | ||
util.makedirs(root) | ||||
Abderrahim Kitouni
|
r9092 | self._repo = hg.repository(r.ui, root, create=True) | ||
Matt Mackall
|
r8814 | self._repo._subparent = r | ||
self._repo._subsource = state[0] | ||||
Matt Mackall
|
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): | ||||
Matt Mackall
|
r9782 | self._repo.ui.debug("committing subrepo %s\n" % self._path) | ||
Matt Mackall
|
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
|
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
|
r9507 | def _get(self, state): | ||
Matt Mackall
|
r8814 | 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) | ||||
Matt Mackall
|
r9507 | def get(self, state): | ||
self._get(state) | ||||
source, revision = state | ||||
Matt Mackall
|
r9782 | self._repo.ui.debug("getting subrepo %s\n" % self._path) | ||
Matt Mackall
|
r8814 | hg.clean(self._repo, revision, False) | ||
def merge(self, state): | ||||
Matt Mackall
|
r9507 | self._get(state) | ||
Matt Mackall
|
r9781 | cur = self._repo['.'] | ||
dst = self._repo[state[1]] | ||||
if dst.ancestor(cur) == cur: | ||||
Matt Mackall
|
r9782 | self._repo.ui.debug("updating subrepo %s\n" % self._path) | ||
Matt Mackall
|
r9781 | hg.update(self._repo, state[1]) | ||
else: | ||||
Matt Mackall
|
r9782 | self._repo.ui.debug("merging subrepo %s\n" % self._path) | ||
Matt Mackall
|
r9781 | hg.merge(self._repo, state[1], remind=False) | ||
Matt Mackall
|
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) | ||||