##// END OF EJS Templates
patch: support diff data loss detection and upgrade...
patch: support diff data loss detection and upgrade In worst case, generating diff in upgrade mode can be two times more expensive than generating it in git mode directly: we may have to regenerate the whole diff again whenever a git feature is detected. Also, the first diff attempt is completely buffered instead of being streamed. That said, even without having profiled it yet, I am convinced we can fast-path the upgrade mode if necessary were it to be used in regular diff commands, and not only in mq where avoiding data loss is worth the price.

File last commit:

r10183:572dd10f default
r10189:e451e599 default
Show More
subrepo.py
337 lines | 11.3 KiB | text/x-python | PythonLexer
Matt Mackall
subrepo: introduce basic state parsing
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.
Augie Fackler
subrepo: Subversion support
r10178 import errno, os, re
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:]
state[path] = (src, 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
Matt Mackall
subrepo: use '/' for joining non-local paths
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
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:
raise util.Abort(_('unknown subrepo type %s') % t)
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)
Matt Mackall
subrepo: add update/merge logic
r8814 if os.path.exists(os.path.join(root, '.hg')):
Abderrahim Kitouni
subrepo: use hg.repository instead of creating localrepo directly...
r9092 self._repo = hg.repository(r.ui, root)
Matt Mackall
subrepo: add update/merge logic
r8814 else:
util.makedirs(root)
Abderrahim Kitouni
subrepo: use hg.repository instead of creating localrepo directly...
r9092 self._repo = hg.repository(r.ui, root, create=True)
Matt Mackall
subrepo: add default path to new clones
r10068 f = file(os.path.join(root, '.hg', 'hgrc'), 'w')
f.write('[paths]\ndefault = %s\n' % state[0])
f.close()
Matt Mackall
subrepo: add update/merge logic
r8814 self._repo._subparent = r
self._repo._subsource = state[0]
Matt Mackall
commit: recurse into subrepositories
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
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]]
if dst.ancestor(cur) == 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])
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):
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():
Martin Geisler
subrepo: mark string for translation and fix case
r10183 self._repo.ui.warn(_('not removing repo %s because'
'it has changes.\n' % self._path))
Augie Fackler
subrepo: Subversion support
r10178 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
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 }