##// END OF EJS Templates
subrepo: lazier git push logic...
subrepo: lazier git push logic Avoids calls to git push when the revision is already known to be in the remote repository. Now, when using a read-only git subrepo, git will never need to talk to its upstream repository.

File last commit:

r13029:f930032a default
r13029:f930032a default
Show More
subrepo.py
837 lines | 30.1 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
Edouard Gomez
subrepo: normalize path part of URLs so that pulling subrepos from webdir works...
r11109 import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
Eric Eisner
subrepo: archive git subrepos
r13027 import stat, subprocess, tarfile
Matt Mackall
subrepo: add update/merge logic
r8814 from i18n import _
Martin Geisler
diff: recurse into subrepositories with --subrepos/-S flag
r12167 import config, util, node, error, cmdutil
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
Martin Geisler
subrepos: support remapping of .hgsub source paths...
r11775 def state(ctx, ui):
Mads Kiilerich
subrepo: docstrings
r11571 """return a state dict, mapping subrepo paths configured in .hgsub
to tuple: (source from .hgsub, revision from .hgsubstate, kind
(key in types dict))
"""
Matt Mackall
subrepo: introduce basic state parsing
r8812 p = config.config()
def read(f, sections=None, remap=None):
if f in ctx:
Patrick Mezard
subrepo: handle missing subrepo spec file as removed...
r13017 try:
data = ctx[f].data()
except IOError, err:
if err.errno != errno.ENOENT:
raise
# handle missing subrepo spec files as removed
ui.warn(_("warning: subrepo spec file %s not found\n") % f)
return
p.parse(f, data, sections, remap, read)
Matt Mackall
subrepo: fix includes support in .hgsub
r10174 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
Martin Geisler
subrepos: support remapping of .hgsub source paths...
r11775 for path, src in ui.configitems('subpaths'):
p.set('subpaths', path, src, ui.configsource('subpaths', path))
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:]
Martin Geisler
subrepos: support remapping of .hgsub source paths...
r11775
for pattern, repl in p.items('subpaths'):
Martin Geisler
subrepos: handle backslashes in subpaths
r11961 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
# does a string decode.
repl = repl.encode('string-escape')
# However, we still want to allow back references to go
# through unharmed, so we turn r'\\1' into r'\1'. Again,
# extra escapes are needed because re.sub string decodes.
repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
Martin Geisler
subrepos: support remapping of .hgsub source paths...
r11775 try:
src = re.sub(pattern, repl, src, 1)
except re.error, e:
raise util.Abort(_("bad subrepository pattern in %s: %s")
% (p.source('subpaths', pattern), e))
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):
Mads Kiilerich
subrepo: docstrings
r11571 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
Matt Mackall
commit: recurse into subrepositories
r8813 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):
Mads Kiilerich
subrepo: docstrings
r11571 """delegated from merge.applyupdates: merging of .hgsubstate file
in working context, merging context and 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
subrepo: correctly handle update -C with modified subrepos (issue2022)...
r11470 a = sa.get(s, nullstate)
Matt Mackall
subrepo: fix recording of + in .hgsubstate (issue2217)
r11463 ld = l # local state with possible dirty flag for compares
Matt Mackall
subrepo: correctly handle update -C with modified subrepos (issue2022)...
r11470 if wctx.sub(s).dirty():
Matt Mackall
subrepo: fix recording of + in .hgsubstate (issue2217)
r11463 ld = (l[0], l[1] + "+")
Matt Mackall
subrepo: correctly handle update -C with modified subrepos (issue2022)...
r11470 if wctx == actx: # overwrite
a = ld
Matt Mackall
subrepo: fix recording of + in .hgsubstate (issue2217)
r11463
Matt Mackall
subrepo: add update/merge logic
r8814 if s in s2:
r = s2[s]
Matt Mackall
subrepo: fix recording of + in .hgsubstate (issue2217)
r11463 if ld == r or r == a: # no change or local is newer
Matt Mackall
subrepo: add update/merge logic
r8814 sm[s] = l
continue
Matt Mackall
subrepo: fix recording of + in .hgsubstate (issue2217)
r11463 elif ld == 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
Matt Mackall
subrepo: fix recording of + in .hgsubstate (issue2217)
r11463 elif ld[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
Matt Mackall
subrepo: fix recording of + in .hgsubstate (issue2217)
r11463 elif ld[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
Matt Mackall
subrepo: fix recording of + in .hgsubstate (issue2217)
r11463 elif ld == 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)
Mads Kiilerich
subrepo: rename relpath to subrelpath and introduce reporelpath
r12752 def reporelpath(repo):
"""return path to this (sub)repo as seen from outermost repo"""
parent = repo
while hasattr(parent, '_subparent'):
parent = parent._subparent
return repo.root[len(parent.root)+1:]
def subrelpath(sub):
Mads Kiilerich
subrepo: docstrings
r11571 """return path to this subrepo as seen from outermost repo"""
Edouard Gomez
subrepo: print paths relative to upper repo root for push/pull/commit...
r11112 if not hasattr(sub, '_repo'):
return sub._path
Mads Kiilerich
subrepo: rename relpath to subrelpath and introduce reporelpath
r12752 return reporelpath(sub._repo)
Edouard Gomez
subrepo: print paths relative to upper repo root for push/pull/commit...
r11112
Mads Kiilerich
subrepo: abort instead of pushing/pulling to the repo itself...
r12753 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."""
Matt Mackall
subrepo: add update/merge logic
r8814 if hasattr(repo, '_subparent'):
source = repo._subsource
if source.startswith('/') or '://' in source:
return source
Mads Kiilerich
subrepo: abort instead of pushing/pulling to the repo itself...
r12753 parent = _abssource(repo._subparent, push, abort=False)
if parent:
if '://' in parent:
if parent[-1] == '/':
parent = parent[:-1]
r = urlparse.urlparse(parent + '/' + source)
r = urlparse.urlunparse((r[0], r[1],
posixpath.normpath(r[2]),
r[3], r[4], r[5]))
return r
else: # plain file system path
return posixpath.normpath(os.path.join(parent, repo._subsource))
else: # recursion reached top repo
Mads Kiilerich
subrepo: propagate non-default pull/push path to relative subrepos (issue1852)
r12852 if hasattr(repo, '_subtoppath'):
return repo._subtoppath
Mads Kiilerich
subrepo: abort instead of pushing/pulling to the repo itself...
r12753 if push and repo.ui.config('paths', 'default-push'):
return repo.ui.config('paths', 'default-push')
if repo.ui.config('paths', 'default'):
return repo.ui.config('paths', 'default')
if abort:
Martin Geisler
check-code: find trailing whitespace
r12770 raise util.Abort(_("default path for subrepository %s not found") %
Mads Kiilerich
subrepo: abort instead of pushing/pulling to the repo itself...
r12753 reporelpath(repo))
Matt Mackall
subrepo: add update/merge logic
r8814
Martin Geisler
subrepos: add function for iterating over ctx subrepos
r12176 def itersubrepos(ctx1, ctx2):
"""find subrepos in ctx1 or ctx2"""
# Create a (subpath, ctx) mapping where we prefer subpaths from
# ctx1. The subpaths from ctx2 are important when the .hgsub file
# has been modified (in ctx2) but not yet committed (in ctx1).
subpaths = dict.fromkeys(ctx2.substate, ctx2)
subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
for subpath, ctx in sorted(subpaths.iteritems()):
yield subpath, ctx.sub(subpath)
Matt Mackall
commit: recurse into subrepositories
r8813 def subrepo(ctx, path):
Mads Kiilerich
subrepo: docstrings
r11571 """return instance of the right subrepo class for subrepo in path"""
Matt Mackall
commit: recurse into subrepositories
r8813 # 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
Martin Geisler
subrepo: add abstract superclass for subrepo classes
r11559 # subrepo classes need to implement the following abstract class:
class abstractsubrepo(object):
def dirty(self):
"""returns true if the dirstate of the subrepo does not match
current stored state
"""
raise NotImplementedError
Brodie Rao
subrepos: add missing self argument to abstractsubrepo.checknested
r12506 def checknested(self, path):
Martin Geisler
localrepo: add auditor attribute which knows about subrepos
r12162 """check if path is a subrepository within this repository"""
return False
Martin Geisler
subrepo: add abstract superclass for subrepo classes
r11559 def 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.
"""
raise NotImplementedError
def remove(self):
"""remove the subrepo
Matt Mackall
commit: recurse into subrepositories
r8813
Martin Geisler
subrepo: add abstract superclass for subrepo classes
r11559 (should verify the dirstate is not dirty first)
"""
raise NotImplementedError
def get(self, state):
"""run whatever commands are needed to put the subrepo into
this state
"""
raise NotImplementedError
def merge(self, state):
"""merge currently-saved state with the new state."""
raise NotImplementedError
def push(self, force):
Martin Geisler
Merge with stable
r11572 """perform whatever action is analogous to 'hg push'
Martin Geisler
subrepo: add abstract superclass for subrepo classes
r11559
This may be a no-op on some systems.
"""
raise NotImplementedError
Martin Geisler
add: recurse into subrepositories with --subrepos/-S flag
r12270 def add(self, ui, match, dryrun, prefix):
return []
Martin Geisler
subrepo: add abstract superclass for subrepo classes
r11559
Martin Geisler
status: recurse into subrepositories with --subrepos/-S flag
r12166 def status(self, rev2, **opts):
return [], [], [], [], [], [], []
Martin Geisler
diff: recurse into subrepositories with --subrepos/-S flag
r12167 def diff(self, diffopts, node2, match, prefix, **opts):
pass
Martin Geisler
outgoing: recurse into subrepositories with --subrepos/-S flag...
r12272 def outgoing(self, ui, dest, opts):
return 1
Martin Geisler
incoming: recurse into subrepositories with --subrepos/-S flag...
r12274 def incoming(self, ui, source, opts):
return 1
Martin Geisler
subrepo: introduce files and filedata methods for subrepo classes
r12322 def files(self):
"""return filename iterator"""
raise NotImplementedError
def filedata(self, name):
"""return file data"""
raise NotImplementedError
def fileflags(self, name):
"""return file flags"""
return ''
Martin Geisler
subrepo: add support for 'hg archive'
r12323 def archive(self, archiver, prefix):
for name in self.files():
flags = self.fileflags(name)
mode = 'x' in flags and 0755 or 0644
symlink = 'l' in flags
archiver.addfile(os.path.join(prefix, self._path, name),
mode, symlink, self.filedata(name))
Martin Geisler
subrepo: add abstract superclass for subrepo classes
r11559 class hgsubrepo(abstractsubrepo):
Matt Mackall
commit: recurse into subrepositories
r8813 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):
Mads Kiilerich
subrepo: abort instead of pushing/pulling to the repo itself...
r12753 if value:
fp.write('%s = %s\n' % (key, value))
self._repo.ui.setconfig('paths', key, value)
Benoit Boissinot
subrepo: keep ui and hgrc in sync when creating new repo
r10666
Mads Kiilerich
subrepo: abort instead of pushing/pulling to the repo itself...
r12753 defpath = _abssource(self._repo, abort=False)
defpushpath = _abssource(self._repo, True, abort=False)
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()
Martin Geisler
add: recurse into subrepositories with --subrepos/-S flag
r12270 def add(self, ui, match, dryrun, prefix):
return cmdutil.add(ui, self._repo, match, dryrun, True,
os.path.join(prefix, self._path))
Martin Geisler
status: recurse into subrepositories with --subrepos/-S flag
r12166 def status(self, rev2, **opts):
try:
rev1 = self._state[1]
ctx1 = self._repo[rev1]
ctx2 = self._repo[rev2]
return self._repo.status(ctx1, ctx2, **opts)
except error.RepoLookupError, inst:
Wagner Bruna
subrepo: improve lookup error messages
r12503 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
Mads Kiilerich
subrepo: rename relpath to subrelpath and introduce reporelpath
r12752 % (inst, subrelpath(self)))
Martin Geisler
status: recurse into subrepositories with --subrepos/-S flag
r12166 return [], [], [], [], [], [], []
Martin Geisler
diff: recurse into subrepositories with --subrepos/-S flag
r12167 def diff(self, diffopts, node2, match, prefix, **opts):
try:
node1 = node.bin(self._state[1])
Patrick Mezard
subrepos: handle diff nodeids in subrepos, not before...
r12209 # We currently expect node2 to come from substate and be
# in hex format
Martin Geisler
subrepo: handle diff with working copy...
r12210 if node2 is not None:
node2 = node.bin(node2)
Martin Geisler
diff: recurse into subrepositories with --subrepos/-S flag
r12167 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
node1, node2, match,
prefix=os.path.join(prefix, self._path),
listsubrepos=True, **opts)
except error.RepoLookupError, inst:
Wagner Bruna
subrepo: improve lookup error messages
r12503 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
Mads Kiilerich
subrepo: rename relpath to subrelpath and introduce reporelpath
r12752 % (inst, subrelpath(self)))
Martin Geisler
diff: recurse into subrepositories with --subrepos/-S flag
r12167
Martin Geisler
subrepo: add support for 'hg archive'
r12323 def archive(self, archiver, prefix):
abstractsubrepo.archive(self, archiver, prefix)
rev = self._state[1]
ctx = self._repo[rev]
for subpath in ctx.substate:
s = subrepo(ctx, subpath)
s.archive(archiver, os.path.join(prefix, self._path))
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
Martin Geisler
localrepo: add auditor attribute which knows about subrepos
r12162 def checknested(self, path):
return self._repo._checknested(self._repo.wjoin(path))
Matt Mackall
commit: recurse into subrepositories
r8813 def commit(self, text, user, date):
Mads Kiilerich
subrepo: rename relpath to subrelpath and introduce reporelpath
r12752 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
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
Mads Kiilerich
subrepo: rename relpath to subrelpath and introduce reporelpath
r12752 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
Matt Mackall
subrepo: add update/merge logic
r8814 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
srcurl = _abssource(self._repo)
Martin Geisler
subrepo: wrap long line
r10671 self._repo.ui.status(_('pulling subrepo %s from %s\n')
Mads Kiilerich
subrepo: rename relpath to subrelpath and introduce reporelpath
r12752 % (subrelpath(self), srcurl))
Matt Mackall
subrepo: add update/merge logic
r8814 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:
Mads Kiilerich
subrepo: rename relpath to subrelpath and introduce reporelpath
r12752 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
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:
Mads Kiilerich
subrepo: rename relpath to subrelpath and introduce reporelpath
r12752 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
Matt Mackall
subrepo: do a linear update when appropriate
r9781 else:
Mads Kiilerich
subrepo: rename relpath to subrelpath and introduce reporelpath
r12752 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
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):
Matt Mackall
subrepo: propagate and catch push failures
r11067 if not c.sub(s).push(force):
return False
Matt Mackall
subrepo: basic push support
r8815
dsturl = _abssource(self._repo, True)
Edouard Gomez
subrepo: print pushing url
r11111 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
Mads Kiilerich
subrepo: rename relpath to subrelpath and introduce reporelpath
r12752 (subrelpath(self), dsturl))
Matt Mackall
subrepo: basic push support
r8815 other = hg.repository(self._repo.ui, dsturl)
Matt Mackall
subrepo: propagate and catch push failures
r11067 return self._repo.push(other, force)
Augie Fackler
subrepo: add table-based dispatch for subrepo types
r10177
Martin Geisler
outgoing: recurse into subrepositories with --subrepos/-S flag...
r12272 def outgoing(self, ui, dest, opts):
return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
Martin Geisler
incoming: recurse into subrepositories with --subrepos/-S flag...
r12274 def incoming(self, ui, source, opts):
return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
Martin Geisler
subrepo: introduce files and filedata methods for subrepo classes
r12322 def files(self):
rev = self._state[1]
ctx = self._repo[rev]
return ctx.manifest()
def filedata(self, name):
rev = self._state[1]
return self._repo[rev][name].data()
def fileflags(self, name):
rev = self._state[1]
ctx = self._repo[rev]
return ctx.flags(name)
Martin Geisler
subrepo: add abstract superclass for subrepo classes
r11559 class svnsubrepo(abstractsubrepo):
Augie Fackler
subrepo: Subversion support
r10178 def __init__(self, ctx, path, state):
self._path = path
self._state = state
self._ctx = ctx
self._ui = ctx._repo.ui
Martin Geisler
subrepos: let caller specify a filename for SVN commands
r11560 def _svncommand(self, commands, filename=''):
path = os.path.join(self._ctx._repo.origroot, self._path, filename)
Brett Cannon
subrepo: fix repo root path handling in svn subrepo
r10954 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: use subprocess directly to avoid python 2.6 bug...
r13014 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
close_fds=util.closefds,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True, env=env)
stdout, stderr = p.communicate()
stderr = stderr.strip()
if stderr:
raise util.Abort(stderr)
return stdout
Augie Fackler
subrepo: Subversion support
r10178
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:
Martin Geisler
subrepo: svnsubrepo._wcrev should return str after 3d6ba8c2b1b8
r12799 return '0'
Matt Mackall
subrepo: fix status check on SVN subrepos (issue2445)
r12798 return str(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)
Martin Geisler
subrepo: use [0-9] instead of [\d] in svn subrepo regex...
r12060 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
Augie Fackler
subrepo: Subversion support
r10178 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)
Patrick Mezard
subrepo: fix removing read-only svn files on Windows
r13013
def onerror(function, path, excinfo):
if function is not os.remove:
raise
# read-only files cannot be unlinked under Windows
s = os.stat(path)
if (s.st_mode & stat.S_IWRITE) != 0:
raise
os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
os.remove(path)
Patrick Mezard
subrepo: prune empty directories when removing svn subrepo
r13015 path = self._ctx._repo.wjoin(self._path)
shutil.rmtree(path, onerror=onerror)
try:
os.removedirs(os.path.dirname(path))
except OSError:
pass
Augie Fackler
subrepo: Subversion support
r10178
def get(self, state):
status = self._svncommand(['checkout', state[0], '--revision', state[1]])
Martin Geisler
subrepo: use [0-9] instead of [\d] in svn subrepo regex...
r12060 if not re.search('Checked out revision [0-9]+.', status):
Augie Fackler
subrepo: Subversion support
r10178 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):
Matt Mackall
subrepo: fix silent push failure for SVN (issue2241)
r11455 # push is a no-op for SVN
return True
Augie Fackler
subrepo: Subversion support
r10178
Martin Geisler
subrepo: introduce files and filedata methods for subrepo classes
r12322 def files(self):
output = self._svncommand(['list'])
# This works because svn forbids \n in filenames.
return output.splitlines()
def filedata(self, name):
return self._svncommand(['cat'], name)
Eric Eisner
subrepo: support for adding a git subrepo...
r12992 class gitsubrepo(object):
def __init__(self, ctx, path, state):
# TODO add git version check.
self._state = state
self._ctx = ctx
Eric Eisner
subrepo: cloning and updating of git subrepos...
r12993 self._relpath = path
Eric Eisner
subrepo: support for adding a git subrepo...
r12992 self._path = ctx._repo.wjoin(path)
self._ui = ctx._repo.ui
Eric Eisner
subrepo: archive git subrepos
r13027 def _gitcommand(self, commands, stream=False):
return self._gitdir(commands, stream=stream)[0]
Eric Eisner
subrepo: support for adding a git subrepo...
r12992
Eric Eisner
subrepo: archive git subrepos
r13027 def _gitdir(self, commands, stream=False):
Eric Eisner
subrepo: support for adding a git subrepo...
r12992 commands = ['--no-pager', '--git-dir=%s/.git' % self._path,
'--work-tree=%s' % self._path] + commands
Eric Eisner
subrepo: archive git subrepos
r13027 return self._gitnodir(commands, stream=stream)
Eric Eisner
subrepo: support for adding a git subrepo...
r12992
Eric Eisner
subrepo: archive git subrepos
r13027 def _gitnodir(self, commands, stream=False):
Eric Eisner
subrepo: support for adding a git subrepo...
r12992 """Calls the git command
The methods tries to call the git command. versions previor to 1.6.0
are not supported and very probably fail.
"""
cmd = ['git'] + commands
cmd = [util.shellquote(arg) for arg in cmd]
cmd = util.quotecommand(' '.join(cmd))
# print git's stderr, which is mostly progress and useful info
p = subprocess.Popen(cmd, shell=True, bufsize=-1,
Eric Eisner
subrepo: archive git subrepos
r13027 close_fds=util.closefds,
Eric Eisner
subrepo: support for adding a git subrepo...
r12992 stdout=subprocess.PIPE)
Eric Eisner
subrepo: archive git subrepos
r13027 if stream:
return p.stdout, None
Eric Eisner
subrepo: support for adding a git subrepo...
r12992 retdata = p.stdout.read()
# wait for the child to exit to avoid race condition.
p.wait()
if p.returncode != 0:
# there are certain error codes that are ok
command = None
for arg in commands:
if not arg.startswith('-'):
command = arg
break
if command == 'cat-file':
return retdata, p.returncode
if command in ('commit', 'status') and p.returncode == 1:
return retdata, p.returncode
# for all others, abort
raise util.Abort('git %s error %d' % (command, p.returncode))
return retdata, p.returncode
def _gitstate(self):
return self._gitcommand(['rev-parse', 'HEAD']).strip()
def _githavelocally(self, revision):
out, code = self._gitdir(['cat-file', '-e', revision])
return code == 0
Eric Eisner
subrepo: lazier git push logic...
r13029 def _gitisancestor(self, r1, r2):
base = self._gitcommand(['merge-base', r1, r2]).strip()
return base == r1
Eric Eisner
subrepo: update and merge works with any git branch
r12995 def _gitbranchmap(self):
'returns the current branch and a map from git revision to branch[es]'
bm = {}
redirects = {}
current = None
out = self._gitcommand(['branch', '-a', '--no-color',
'--verbose', '--abbrev=40'])
for line in out.split('\n'):
if not line:
continue
if line[2:].startswith('(no branch)'):
continue
branch, revision = line[2:].split()[:2]
if revision == '->':
continue # ignore remote/HEAD redirects
if line[0] == '*':
current = branch
bm.setdefault(revision, []).append(branch)
return current, bm
Eric Eisner
subrepo: cloning and updating of git subrepos...
r12993 def _fetch(self, source, revision):
if not os.path.exists('%s/.git' % self._path):
self._ui.status(_('cloning subrepo %s\n') % self._relpath)
self._gitnodir(['clone', source, self._path])
if self._githavelocally(revision):
return
self._ui.status(_('pulling subrepo %s\n') % self._relpath)
self._gitcommand(['fetch', '--all', '-q'])
if not self._githavelocally(revision):
raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
(revision, self._path))
Eric Eisner
subrepo: support for adding a git subrepo...
r12992 def dirty(self):
if self._state[1] != self._gitstate(): # version checked out changed?
return True
# check for staged changes or modified files; ignore untracked files
# docs say --porcelain flag is future-proof format
changed = self._gitcommand(['status', '--porcelain',
'--untracked-files=no'])
return bool(changed.strip())
Eric Eisner
subrepo: cloning and updating of git subrepos...
r12993 def get(self, state):
source, revision, kind = state
self._fetch(source, revision)
Eric Eisner
subrepo: removing (and restoring) git subrepo state
r12996 # if the repo was set to be bare, unbare it
if self._gitcommand(['config', '--get', 'core.bare']
).strip() == 'true':
self._gitcommand(['config', 'core.bare', 'false'])
if self._gitstate() == revision:
self._gitcommand(['reset', '--hard', 'HEAD'])
return
elif self._gitstate() == revision:
Eric Eisner
subrepo: update and merge works with any git branch
r12995 return
current, bm = self._gitbranchmap()
if revision not in bm:
# no branch to checkout, check it out with no branch
Eric Eisner
subrepo: cloning and updating of git subrepos...
r12993 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
self._relpath)
self._ui.warn(_('check out a git branch if you intend '
'to make changes\n'))
self._gitcommand(['checkout', '-q', revision])
Eric Eisner
subrepo: update and merge works with any git branch
r12995 return
branches = bm[revision]
firstlocalbranch = None
for b in branches:
if b == 'master':
# master trumps all other branches
self._gitcommand(['checkout', 'master'])
return
if not firstlocalbranch and not b.startswith('remotes/'):
firstlocalbranch = b
if firstlocalbranch:
self._gitcommand(['checkout', firstlocalbranch])
else:
remote = branches[0]
local = remote.split('/')[-1]
self._gitcommand(['checkout', '-b', local, remote])
Eric Eisner
subrepo: cloning and updating of git subrepos...
r12993
Eric Eisner
subrepo: support for adding a git subrepo...
r12992 def commit(self, text, user, date):
cmd = ['commit', '-a', '-m', text]
if user:
cmd += ['--author', user]
if date:
# git's date parser silently ignores when seconds < 1e9
# convert to ISO8601
cmd += ['--date', util.datestr(date, '%Y-%m-%dT%H:%M:%S %1%2')]
self._gitcommand(cmd)
# make sure commit works otherwise HEAD might not exist under certain
# circumstances
return self._gitstate()
Eric Eisner
subrepo: allow git subrepos to push and merge...
r12994 def merge(self, state):
source, revision, kind = state
self._fetch(source, revision)
base = self._gitcommand(['merge-base', revision,
self._state[1]]).strip()
if base == revision:
self.get(state) # fast forward merge
elif base != self._state[1]:
self._gitcommand(['merge', '--no-commit', revision])
def push(self, force):
Eric Eisner
subrepo: lazier git push logic...
r13029 # if a branch in origin contains the revision, nothing to do
current, bm = self._gitbranchmap()
for revision, branches in bm.iteritems():
for b in branches:
if b.startswith('remotes/origin'):
if self._gitisancestor(self._state[1], revision):
return True
# otherwise, try to push the currently checked out branch
Eric Eisner
subrepo: allow git subrepos to push and merge...
r12994 cmd = ['push']
if force:
cmd.append('--force')
Eric Eisner
subrepo: update and merge works with any git branch
r12995 if current:
Eric Eisner
subrepo: lazier git push logic...
r13029 # determine if the current branch is even useful
if not self._gitisancestor(self._state[1], current):
self._ui.warn(_('unrelated git branch checked out '
'in subrepo %s\n') % self._relpath)
return False
self._ui.status(_('pushing branch %s of subrepo %s\n') %
(current, self._relpath))
Eric Eisner
subrepo: update and merge works with any git branch
r12995 self._gitcommand(cmd + ['origin', current, '-q'])
return True
else:
self._ui.warn(_('no branch checked out in subrepo %s\n'
Eric Eisner
subrepo: lazier git push logic...
r13029 'cannot push revision %s') %
(self._relpath, self._state[1]))
Eric Eisner
subrepo: update and merge works with any git branch
r12995 return False
Eric Eisner
subrepo: allow git subrepos to push and merge...
r12994
Eric Eisner
subrepo: removing (and restoring) git subrepo state
r12996 def remove(self):
if self.dirty():
self._ui.warn(_('not removing repo %s because '
'it has changes.\n') % self._path)
return
# we can't fully delete the repository as it may contain
# local-only history
self._ui.note(_('removing subrepo %s\n') % self._path)
self._gitcommand(['config', 'core.bare', 'true'])
for f in os.listdir(self._path):
if f == '.git':
continue
path = os.path.join(self._path, f)
if os.path.isdir(path) and not os.path.islink(path):
shutil.rmtree(path)
else:
os.remove(path)
Eric Eisner
subrepo: archive git subrepos
r13027 def archive(self, archiver, prefix):
source, revision = self._state
self._fetch(source, revision)
# Parse git's native archive command.
# This should be much faster than manually traversing the trees
# and objects with many subprocess calls.
tarstream = self._gitcommand(['archive', revision], stream=True)
tar = tarfile.open(fileobj=tarstream, mode='r|')
for info in tar:
archiver.addfile(os.path.join(prefix, self._relpath, info.name),
info.mode, info.issym(),
tar.extractfile(info).read())
Augie Fackler
subrepo: add table-based dispatch for subrepo types
r10177 types = {
'hg': hgsubrepo,
Augie Fackler
subrepo: Subversion support
r10178 'svn': svnsubrepo,
Eric Eisner
subrepo: support for adding a git subrepo...
r12992 'git': gitsubrepo,
Augie Fackler
subrepo: add table-based dispatch for subrepo types
r10177 }