[PATCH 1 of 2] subrepo: split non-core functions to new module

Yuya Nishihara yuya at tcha.org
Thu Feb 8 07:52:35 EST 2018


# HG changeset patch
# User Yuya Nishihara <yuya at tcha.org>
# Date 1517924198 -32400
#      Tue Feb 06 22:36:38 2018 +0900
# Node ID 64230c22a92152b70f41dfa429184cad84ac8eac
# Parent  3e64e5f4d038708b258cec15c17a78d9d49adad3
subrepo: split non-core functions to new module

Resolves import cycle caused by subrepo -> cmdutil. Still we have another
cycle, cmdutil -> context -> subrepo, but where I think importing context
is wrong. Perhaps we'll need repo.makememctx().

diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -94,7 +94,7 @@ from mercurial import (
     revsetlang,
     scmutil,
     smartset,
-    subrepo,
+    subrepoutil,
     util,
     vfs as vfsmod,
 )
@@ -970,8 +970,8 @@ class queue(object):
                 wctx = repo[None]
                 pctx = repo['.']
                 overwrite = False
-                mergedsubstate = subrepo.submerge(repo, pctx, wctx, wctx,
-                    overwrite)
+                mergedsubstate = subrepoutil.submerge(repo, pctx, wctx, wctx,
+                                                      overwrite)
                 files += mergedsubstate.keys()
 
             match = scmutil.matchfiles(repo, files or [])
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -40,6 +40,7 @@ from . import (
     rewriteutil,
     scmutil,
     smartset,
+    subrepoutil,
     templater,
     util,
     vfs as vfsmod,
@@ -2307,13 +2308,12 @@ def amend(ui, repo, old, extra, pats, op
         # subrepo.precommit(). To minimize the risk of this hack, we do
         # nothing if .hgsub does not exist.
         if '.hgsub' in wctx or '.hgsub' in old:
-            from . import subrepo  # avoid cycle: cmdutil -> subrepo -> cmdutil
-            subs, commitsubs, newsubstate = subrepo.precommit(
+            subs, commitsubs, newsubstate = subrepoutil.precommit(
                 ui, wctx, wctx._status, matcher)
             # amend should abort if commitsubrepos is enabled
             assert not commitsubs
             if subs:
-                subrepo.writestate(repo, newsubstate)
+                subrepoutil.writestate(repo, newsubstate)
 
         filestoamend = set(f for f in wctx.files() if matcher(f))
 
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -46,6 +46,7 @@ from . import (
     scmutil,
     sparse,
     subrepo,
+    subrepoutil,
     util,
 )
 
@@ -173,7 +174,7 @@ class basectx(object):
 
     @propertycache
     def substate(self):
-        return subrepo.state(self, self._repo.ui)
+        return subrepoutil.state(self, self._repo.ui)
 
     def subrev(self, subpath):
         return self.substate[subpath][1]
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -57,7 +57,7 @@ from . import (
     scmutil,
     sparse,
     store,
-    subrepo,
+    subrepoutil,
     tags as tagsmod,
     transaction,
     txnutil,
@@ -1833,7 +1833,7 @@ class localrepository(object):
                 status.modified.extend(status.clean) # mq may commit clean files
 
             # check subrepos
-            subs, commitsubs, newstate = subrepo.precommit(
+            subs, commitsubs, newstate = subrepoutil.precommit(
                 self.ui, wctx, status, match, force=force)
 
             # make sure all explicit patterns are matched
@@ -1870,10 +1870,10 @@ class localrepository(object):
                 for s in sorted(commitsubs):
                     sub = wctx.sub(s)
                     self.ui.status(_('committing subrepository %s\n') %
-                        subrepo.subrelpath(sub))
+                                   subrepoutil.subrelpath(sub))
                     sr = sub.commit(cctx._text, user, date)
                     newstate[s] = (newstate[s][0], sr)
-                subrepo.writestate(self, newstate)
+                subrepoutil.writestate(self, newstate)
 
             p1, p2 = self.dirstate.parents()
             hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
@@ -1983,7 +1983,7 @@ class localrepository(object):
             self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
                       parent2=xp2)
             # set the new commit is proper phase
-            targetphase = subrepo.newcommitphase(self.ui, ctx)
+            targetphase = subrepoutil.newcommitphase(self.ui, ctx)
             if targetphase:
                 # retract boundary do not alter parent changeset.
                 # if a parent have higher the resulting phase will
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -31,7 +31,7 @@ from . import (
     obsutil,
     pycompat,
     scmutil,
-    subrepo,
+    subrepoutil,
     util,
     worker,
 )
@@ -1445,7 +1445,7 @@ def applyupdates(repo, actions, wctx, mc
     z = 0
 
     if [a for a in actions['r'] if a[0] == '.hgsubstate']:
-        subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
+        subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
 
     # record path conflicts
     for f, args, msg in actions['p']:
@@ -1495,7 +1495,7 @@ def applyupdates(repo, actions, wctx, mc
     updated = len(actions['g'])
 
     if [a for a in actions['g'] if a[0] == '.hgsubstate']:
-        subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
+        subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
 
     # forget (manifest only, just log it) (must come first)
     for f, args, msg in actions['f']:
@@ -1583,8 +1583,8 @@ def applyupdates(repo, actions, wctx, mc
             z += 1
             progress(_updating, z, item=f, total=numupdates, unit=_files)
             if f == '.hgsubstate': # subrepo states need updating
-                subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
-                                 overwrite, labels)
+                subrepoutil.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
+                                     overwrite, labels)
                 continue
             wctx[f].audit()
             complete, r = ms.preresolve(f, wctx)
@@ -1913,7 +1913,7 @@ def update(repo, node, branchmerge, forc
 
         # Prompt and create actions. Most of this is in the resolve phase
         # already, but we can't handle .hgsubstate in filemerge or
-        # subrepo.submerge yet so we have to keep prompting for it.
+        # subrepoutil.submerge yet so we have to keep prompting for it.
         if '.hgsubstate' in actionbyfile:
             f = '.hgsubstate'
             m, args, msg = actionbyfile[f]
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -1,4 +1,4 @@
-# subrepo.py - sub-repository handling for Mercurial
+# subrepo.py - sub-repository classes and factory
 #
 # Copyright 2009-2010 Matt Mackall <mpm at selenic.com>
 #
@@ -19,15 +19,12 @@ import sys
 import tarfile
 import xml.dom.minidom
 
-
 from .i18n import _
 from . import (
     cmdutil,
-    config,
     encoding,
     error,
     exchange,
-    filemerge,
     logcmdutil,
     match as matchmod,
     node,
@@ -35,15 +32,17 @@ from . import (
     phases,
     pycompat,
     scmutil,
+    subrepoutil,
     util,
     vfs as vfsmod,
 )
 
 hg = None
+reporelpath = subrepoutil.reporelpath
+subrelpath = subrepoutil.subrelpath
+_abssource = subrepoutil._abssource
 propertycache = util.propertycache
 
-nullstate = ('', '', 'empty')
-
 def _expandedabspath(path):
     '''
     get a path or url and if it is a path expand it and return an absolute path
@@ -81,284 +80,6 @@ def annotatesubrepoerror(func):
         return res
     return decoratedmethod
 
-def state(ctx, ui):
-    """return a state dict, mapping subrepo paths configured in .hgsub
-    to tuple: (source from .hgsub, revision from .hgsubstate, kind
-    (key in types dict))
-    """
-    p = config.config()
-    repo = ctx.repo()
-    def read(f, sections=None, remap=None):
-        if f in ctx:
-            try:
-                data = ctx[f].data()
-            except IOError as err:
-                if err.errno != errno.ENOENT:
-                    raise
-                # handle missing subrepo spec files as removed
-                ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
-                        repo.pathto(f))
-                return
-            p.parse(f, data, sections, remap, read)
-        else:
-            raise error.Abort(_("subrepo spec file \'%s\' not found") %
-                             repo.pathto(f))
-    if '.hgsub' in ctx:
-        read('.hgsub')
-
-    for path, src in ui.configitems('subpaths'):
-        p.set('subpaths', path, src, ui.configsource('subpaths', path))
-
-    rev = {}
-    if '.hgsubstate' in ctx:
-        try:
-            for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
-                l = l.lstrip()
-                if not l:
-                    continue
-                try:
-                    revision, path = l.split(" ", 1)
-                except ValueError:
-                    raise error.Abort(_("invalid subrepository revision "
-                                       "specifier in \'%s\' line %d")
-                                     % (repo.pathto('.hgsubstate'), (i + 1)))
-                rev[path] = revision
-        except IOError as err:
-            if err.errno != errno.ENOENT:
-                raise
-
-    def remap(src):
-        for pattern, repl in p.items('subpaths'):
-            # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
-            # does a string decode.
-            repl = util.escapestr(repl)
-            # 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(br'\\\\([0-9]+)', br'\\\1', repl)
-            try:
-                src = re.sub(pattern, repl, src, 1)
-            except re.error as e:
-                raise error.Abort(_("bad subrepository pattern in %s: %s")
-                                 % (p.source('subpaths', pattern), e))
-        return src
-
-    state = {}
-    for path, src in p[''].items():
-        kind = 'hg'
-        if src.startswith('['):
-            if ']' not in src:
-                raise error.Abort(_('missing ] in subrepository source'))
-            kind, src = src.split(']', 1)
-            kind = kind[1:]
-            src = src.lstrip() # strip any extra whitespace after ']'
-
-        if not util.url(src).isabs():
-            parent = _abssource(repo, abort=False)
-            if parent:
-                parent = util.url(parent)
-                parent.path = posixpath.join(parent.path or '', src)
-                parent.path = posixpath.normpath(parent.path)
-                joined = str(parent)
-                # Remap the full joined path and use it if it changes,
-                # else remap the original source.
-                remapped = remap(joined)
-                if remapped == joined:
-                    src = remap(src)
-                else:
-                    src = remapped
-
-        src = remap(src)
-        state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
-
-    return state
-
-def writestate(repo, state):
-    """rewrite .hgsubstate in (outer) repo with these subrepo states"""
-    lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
-                                                if state[s][1] != nullstate[1]]
-    repo.wwrite('.hgsubstate', ''.join(lines), '')
-
-def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
-    """delegated from merge.applyupdates: merging of .hgsubstate file
-    in working context, merging context and ancestor context"""
-    if mctx == actx: # backwards?
-        actx = wctx.p1()
-    s1 = wctx.substate
-    s2 = mctx.substate
-    sa = actx.substate
-    sm = {}
-
-    repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
-
-    def debug(s, msg, r=""):
-        if r:
-            r = "%s:%s:%s" % r
-        repo.ui.debug("  subrepo %s: %s %s\n" % (s, msg, r))
-
-    promptssrc = filemerge.partextras(labels)
-    for s, l in sorted(s1.iteritems()):
-        prompts = None
-        a = sa.get(s, nullstate)
-        ld = l # local state with possible dirty flag for compares
-        if wctx.sub(s).dirty():
-            ld = (l[0], l[1] + "+")
-        if wctx == actx: # overwrite
-            a = ld
-
-        prompts = promptssrc.copy()
-        prompts['s'] = s
-        if s in s2:
-            r = s2[s]
-            if ld == r or r == a: # no change or local is newer
-                sm[s] = l
-                continue
-            elif ld == a: # other side changed
-                debug(s, "other changed, get", r)
-                wctx.sub(s).get(r, overwrite)
-                sm[s] = r
-            elif ld[0] != r[0]: # sources differ
-                prompts['lo'] = l[0]
-                prompts['ro'] = r[0]
-                if repo.ui.promptchoice(
-                    _(' subrepository sources for %(s)s differ\n'
-                      'use (l)ocal%(l)s source (%(lo)s)'
-                      ' or (r)emote%(o)s source (%(ro)s)?'
-                      '$$ &Local $$ &Remote') % prompts, 0):
-                    debug(s, "prompt changed, get", r)
-                    wctx.sub(s).get(r, overwrite)
-                    sm[s] = r
-            elif ld[1] == a[1]: # local side is unchanged
-                debug(s, "other side changed, get", r)
-                wctx.sub(s).get(r, overwrite)
-                sm[s] = r
-            else:
-                debug(s, "both sides changed")
-                srepo = wctx.sub(s)
-                prompts['sl'] = srepo.shortid(l[1])
-                prompts['sr'] = srepo.shortid(r[1])
-                option = repo.ui.promptchoice(
-                    _(' subrepository %(s)s diverged (local revision: %(sl)s, '
-                      'remote revision: %(sr)s)\n'
-                      '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?'
-                      '$$ &Merge $$ &Local $$ &Remote')
-                    % prompts, 0)
-                if option == 0:
-                    wctx.sub(s).merge(r)
-                    sm[s] = l
-                    debug(s, "merge with", r)
-                elif option == 1:
-                    sm[s] = l
-                    debug(s, "keep local subrepo revision", l)
-                else:
-                    wctx.sub(s).get(r, overwrite)
-                    sm[s] = r
-                    debug(s, "get remote subrepo revision", r)
-        elif ld == a: # remote removed, local unchanged
-            debug(s, "remote removed, remove")
-            wctx.sub(s).remove()
-        elif a == nullstate: # not present in remote or ancestor
-            debug(s, "local added, keep")
-            sm[s] = l
-            continue
-        else:
-            if repo.ui.promptchoice(
-                _(' local%(l)s changed subrepository %(s)s'
-                  ' which remote%(o)s removed\n'
-                  'use (c)hanged version or (d)elete?'
-                  '$$ &Changed $$ &Delete') % prompts, 0):
-                debug(s, "prompt remove")
-                wctx.sub(s).remove()
-
-    for s, r in sorted(s2.items()):
-        prompts = None
-        if s in s1:
-            continue
-        elif s not in sa:
-            debug(s, "remote added, get", r)
-            mctx.sub(s).get(r)
-            sm[s] = r
-        elif r != sa[s]:
-            prompts = promptssrc.copy()
-            prompts['s'] = s
-            if repo.ui.promptchoice(
-                _(' remote%(o)s changed subrepository %(s)s'
-                  ' which local%(l)s removed\n'
-                  'use (c)hanged version or (d)elete?'
-                  '$$ &Changed $$ &Delete') % prompts, 0) == 0:
-                debug(s, "prompt recreate", r)
-                mctx.sub(s).get(r)
-                sm[s] = r
-
-    # record merged .hgsubstate
-    writestate(repo, sm)
-    return sm
-
-def precommit(ui, wctx, status, match, force=False):
-    """Calculate .hgsubstate changes that should be applied before committing
-
-    Returns (subs, commitsubs, newstate) where
-    - subs: changed subrepos (including dirty ones)
-    - commitsubs: dirty subrepos which the caller needs to commit recursively
-    - newstate: new state dict which the caller must write to .hgsubstate
-
-    This also updates the given status argument.
-    """
-    subs = []
-    commitsubs = set()
-    newstate = wctx.substate.copy()
-
-    # only manage subrepos and .hgsubstate if .hgsub is present
-    if '.hgsub' in wctx:
-        # we'll decide whether to track this ourselves, thanks
-        for c in status.modified, status.added, status.removed:
-            if '.hgsubstate' in c:
-                c.remove('.hgsubstate')
-
-        # compare current state to last committed state
-        # build new substate based on last committed state
-        oldstate = wctx.p1().substate
-        for s in sorted(newstate.keys()):
-            if not match(s):
-                # ignore working copy, use old state if present
-                if s in oldstate:
-                    newstate[s] = oldstate[s]
-                    continue
-                if not force:
-                    raise error.Abort(
-                        _("commit with new subrepo %s excluded") % s)
-            dirtyreason = wctx.sub(s).dirtyreason(True)
-            if dirtyreason:
-                if not ui.configbool('ui', 'commitsubrepos'):
-                    raise error.Abort(dirtyreason,
-                        hint=_("use --subrepos for recursive commit"))
-                subs.append(s)
-                commitsubs.add(s)
-            else:
-                bs = wctx.sub(s).basestate()
-                newstate[s] = (newstate[s][0], bs, newstate[s][2])
-                if oldstate.get(s, (None, None, None))[1] != bs:
-                    subs.append(s)
-
-        # check for removed subrepos
-        for p in wctx.parents():
-            r = [s for s in p.substate if s not in newstate]
-            subs += [s for s in r if match(s)]
-        if subs:
-            if (not match('.hgsub') and
-                '.hgsub' in (wctx.modified() + wctx.added())):
-                raise error.Abort(_("can't commit subrepos without .hgsub"))
-            status.modified.insert(0, '.hgsubstate')
-
-    elif '.hgsub' in status.removed:
-        # clean up .hgsubstate when .hgsub is removed
-        if ('.hgsubstate' in wctx and
-            '.hgsubstate' not in (status.modified + status.added +
-                                  status.removed)):
-            status.removed.insert(0, '.hgsubstate')
-
-    return subs, commitsubs, newstate
-
 def _updateprompt(ui, sub, dirty, local, remote):
     if dirty:
         msg = (_(' subrepository sources for %s differ\n'
@@ -373,64 +94,6 @@ def _updateprompt(ui, sub, dirty, local,
                % (subrelpath(sub), local, remote))
     return ui.promptchoice(msg, 0)
 
-def reporelpath(repo):
-    """return path to this (sub)repo as seen from outermost repo"""
-    parent = repo
-    while util.safehasattr(parent, '_subparent'):
-        parent = parent._subparent
-    return repo.root[len(pathutil.normasprefix(parent.root)):]
-
-def subrelpath(sub):
-    """return path to this subrepo as seen from outermost repo"""
-    return sub._relpath
-
-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."""
-    if util.safehasattr(repo, '_subparent'):
-        source = util.url(repo._subsource)
-        if source.isabs():
-            return bytes(source)
-        source.path = posixpath.normpath(source.path)
-        parent = _abssource(repo._subparent, push, abort=False)
-        if parent:
-            parent = util.url(util.pconvert(parent))
-            parent.path = posixpath.join(parent.path or '', source.path)
-            parent.path = posixpath.normpath(parent.path)
-            return bytes(parent)
-    else: # recursion reached top repo
-        path = None
-        if util.safehasattr(repo, '_subtoppath'):
-            path = repo._subtoppath
-        elif push and repo.ui.config('paths', 'default-push'):
-            path = repo.ui.config('paths', 'default-push')
-        elif repo.ui.config('paths', 'default'):
-            path = repo.ui.config('paths', 'default')
-        elif repo.shared():
-            # chop off the .hg component to get the default path form.  This has
-            # already run through vfsmod.vfs(..., realpath=True), so it doesn't
-            # have problems with 'C:'
-            return os.path.dirname(repo.sharedpath)
-        if path:
-            # issue5770: 'C:\' and 'C:' are not equivalent paths.  The former is
-            # as expected: an absolute path to the root of the C: drive.  The
-            # latter is a relative path, and works like so:
-            #
-            #   C:\>cd C:\some\path
-            #   C:\>D:
-            #   D:\>python -c "import os; print os.path.abspath('C:')"
-            #   C:\some\path
-            #
-            #   D:\>python -c "import os; print os.path.abspath('C:relative')"
-            #   C:\some\path\relative
-            if util.hasdriveletter(path):
-                if len(path) == 2 or path[2:3] not in br'\/':
-                    path = os.path.abspath(path)
-            return path
-
-    if abort:
-        raise error.Abort(_("default path for subrepository not found"))
-
 def _sanitize(ui, vfs, ignore):
     for dirname, dirs, names in vfs.walk():
         for i, d in enumerate(dirs):
@@ -509,37 +172,6 @@ def nullsubrepo(ctx, path, pctx):
         subrev = "0" * 40
     return types[state[2]](pctx, path, (state[0], subrev), True)
 
-def newcommitphase(ui, ctx):
-    commitphase = phases.newcommitphase(ui)
-    substate = getattr(ctx, "substate", None)
-    if not substate:
-        return commitphase
-    check = ui.config('phases', 'checksubrepos')
-    if check not in ('ignore', 'follow', 'abort'):
-        raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
-                         % (check))
-    if check == 'ignore':
-        return commitphase
-    maxphase = phases.public
-    maxsub = None
-    for s in sorted(substate):
-        sub = ctx.sub(s)
-        subphase = sub.phase(substate[s][1])
-        if maxphase < subphase:
-            maxphase = subphase
-            maxsub = s
-    if commitphase < maxphase:
-        if check == 'abort':
-            raise error.Abort(_("can't commit in %s phase"
-                               " conflicting %s from subrepository %s") %
-                             (phases.phasenames[commitphase],
-                              phases.phasenames[maxphase], maxsub))
-        ui.warn(_("warning: changes are committed in"
-                  " %s phase from subrepository %s\n") %
-                (phases.phasenames[maxphase], maxsub))
-        return maxphase
-    return commitphase
-
 # subrepo classes need to implement the following abstract class:
 
 class abstractsubrepo(object):
diff --git a/mercurial/subrepo.py b/mercurial/subrepoutil.py
copy from mercurial/subrepo.py
copy to mercurial/subrepoutil.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepoutil.py
@@ -1,4 +1,4 @@
-# subrepo.py - sub-repository handling for Mercurial
+# subrepoutil.py - sub-repository operations and substate handling
 #
 # Copyright 2009-2010 Matt Mackall <mpm at selenic.com>
 #
@@ -7,80 +7,23 @@
 
 from __future__ import absolute_import
 
-import copy
 import errno
-import hashlib
 import os
 import posixpath
 import re
-import stat
-import subprocess
-import sys
-import tarfile
-import xml.dom.minidom
-
 
 from .i18n import _
 from . import (
-    cmdutil,
     config,
-    encoding,
     error,
-    exchange,
     filemerge,
-    logcmdutil,
-    match as matchmod,
-    node,
     pathutil,
     phases,
-    pycompat,
-    scmutil,
     util,
-    vfs as vfsmod,
 )
 
-hg = None
-propertycache = util.propertycache
-
 nullstate = ('', '', 'empty')
 
-def _expandedabspath(path):
-    '''
-    get a path or url and if it is a path expand it and return an absolute path
-    '''
-    expandedpath = util.urllocalpath(util.expandpath(path))
-    u = util.url(expandedpath)
-    if not u.scheme:
-        path = util.normpath(os.path.abspath(u.path))
-    return path
-
-def _getstorehashcachename(remotepath):
-    '''get a unique filename for the store hash cache of a remote repository'''
-    return node.hex(hashlib.sha1(_expandedabspath(remotepath)).digest())[0:12]
-
-class SubrepoAbort(error.Abort):
-    """Exception class used to avoid handling a subrepo error more than once"""
-    def __init__(self, *args, **kw):
-        self.subrepo = kw.pop(r'subrepo', None)
-        self.cause = kw.pop(r'cause', None)
-        error.Abort.__init__(self, *args, **kw)
-
-def annotatesubrepoerror(func):
-    def decoratedmethod(self, *args, **kargs):
-        try:
-            res = func(self, *args, **kargs)
-        except SubrepoAbort as ex:
-            # This exception has already been handled
-            raise ex
-        except error.Abort as ex:
-            subrepo = subrelpath(self)
-            errormsg = str(ex) + ' ' + _('(in subrepository "%s")') % subrepo
-            # avoid handling this exception by raising a SubrepoAbort exception
-            raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
-                               cause=sys.exc_info())
-        return res
-    return decoratedmethod
-
 def state(ctx, ui):
     """return a state dict, mapping subrepo paths configured in .hgsub
     to tuple: (source from .hgsub, revision from .hgsubstate, kind
@@ -359,20 +302,6 @@ def precommit(ui, wctx, status, match, f
 
     return subs, commitsubs, newstate
 
-def _updateprompt(ui, sub, dirty, local, remote):
-    if dirty:
-        msg = (_(' subrepository sources for %s differ\n'
-                 'use (l)ocal source (%s) or (r)emote source (%s)?'
-                 '$$ &Local $$ &Remote')
-               % (subrelpath(sub), local, remote))
-    else:
-        msg = (_(' subrepository sources for %s differ (in checked out '
-                 'version)\n'
-                 'use (l)ocal source (%s) or (r)emote source (%s)?'
-                 '$$ &Local $$ &Remote')
-               % (subrelpath(sub), local, remote))
-    return ui.promptchoice(msg, 0)
-
 def reporelpath(repo):
     """return path to this (sub)repo as seen from outermost repo"""
     parent = repo
@@ -431,84 +360,6 @@ def _abssource(repo, push=False, abort=T
     if abort:
         raise error.Abort(_("default path for subrepository not found"))
 
-def _sanitize(ui, vfs, ignore):
-    for dirname, dirs, names in vfs.walk():
-        for i, d in enumerate(dirs):
-            if d.lower() == ignore:
-                del dirs[i]
-                break
-        if vfs.basename(dirname).lower() != '.hg':
-            continue
-        for f in names:
-            if f.lower() == 'hgrc':
-                ui.warn(_("warning: removing potentially hostile 'hgrc' "
-                          "in '%s'\n") % vfs.join(dirname))
-                vfs.unlink(vfs.reljoin(dirname, f))
-
-def _auditsubrepopath(repo, path):
-    # auditor doesn't check if the path itself is a symlink
-    pathutil.pathauditor(repo.root)(path)
-    if repo.wvfs.islink(path):
-        raise error.Abort(_("subrepo '%s' traverses symbolic link") % path)
-
-SUBREPO_ALLOWED_DEFAULTS = {
-    'hg': True,
-    'git': False,
-    'svn': False,
-}
-
-def _checktype(ui, kind):
-    # subrepos.allowed is a master kill switch. If disabled, subrepos are
-    # disabled period.
-    if not ui.configbool('subrepos', 'allowed', True):
-        raise error.Abort(_('subrepos not enabled'),
-                          hint=_("see 'hg help config.subrepos' for details"))
-
-    default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
-    if not ui.configbool('subrepos', '%s:allowed' % kind, default):
-        raise error.Abort(_('%s subrepos not allowed') % kind,
-                          hint=_("see 'hg help config.subrepos' for details"))
-
-    if kind not in types:
-        raise error.Abort(_('unknown subrepo type %s') % kind)
-
-def subrepo(ctx, path, allowwdir=False, allowcreate=True):
-    """return instance of the right subrepo class for subrepo in 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
-    global hg
-    from . import hg as h
-    hg = h
-
-    repo = ctx.repo()
-    _auditsubrepopath(repo, path)
-    state = ctx.substate[path]
-    _checktype(repo.ui, state[2])
-    if allowwdir:
-        state = (state[0], ctx.subrev(path), state[2])
-    return types[state[2]](ctx, path, state[:2], allowcreate)
-
-def nullsubrepo(ctx, path, pctx):
-    """return an empty subrepo in pctx for the extant subrepo in ctx"""
-    # 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
-    global hg
-    from . import hg as h
-    hg = h
-
-    repo = ctx.repo()
-    _auditsubrepopath(repo, path)
-    state = ctx.substate[path]
-    _checktype(repo.ui, state[2])
-    subrev = ''
-    if state[2] == 'hg':
-        subrev = "0" * 40
-    return types[state[2]](pctx, path, (state[0], subrev), True)
-
 def newcommitphase(ui, ctx):
     commitphase = phases.newcommitphase(ui)
     substate = getattr(ctx, "substate", None)
@@ -539,1617 +390,3 @@ def newcommitphase(ui, ctx):
                 (phases.phasenames[maxphase], maxsub))
         return maxphase
     return commitphase
-
-# subrepo classes need to implement the following abstract class:
-
-class abstractsubrepo(object):
-
-    def __init__(self, ctx, path):
-        """Initialize abstractsubrepo part
-
-        ``ctx`` is the context referring this subrepository in the
-        parent repository.
-
-        ``path`` is the path to this subrepository as seen from
-        innermost repository.
-        """
-        self.ui = ctx.repo().ui
-        self._ctx = ctx
-        self._path = path
-
-    def addwebdirpath(self, serverpath, webconf):
-        """Add the hgwebdir entries for this subrepo, and any of its subrepos.
-
-        ``serverpath`` is the path component of the URL for this repo.
-
-        ``webconf`` is the dictionary of hgwebdir entries.
-        """
-        pass
-
-    def storeclean(self, path):
-        """
-        returns true if the repository has not changed since it was last
-        cloned from or pushed to a given repository.
-        """
-        return False
-
-    def dirty(self, ignoreupdate=False, missing=False):
-        """returns true if the dirstate of the subrepo is dirty or does not
-        match current stored state. If ignoreupdate is true, only check
-        whether the subrepo has uncommitted changes in its dirstate.  If missing
-        is true, check for deleted files.
-        """
-        raise NotImplementedError
-
-    def dirtyreason(self, ignoreupdate=False, missing=False):
-        """return reason string if it is ``dirty()``
-
-        Returned string should have enough information for the message
-        of exception.
-
-        This returns None, otherwise.
-        """
-        if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
-            return _('uncommitted changes in subrepository "%s"'
-                     ) % subrelpath(self)
-
-    def bailifchanged(self, ignoreupdate=False, hint=None):
-        """raise Abort if subrepository is ``dirty()``
-        """
-        dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate,
-                                       missing=True)
-        if dirtyreason:
-            raise error.Abort(dirtyreason, hint=hint)
-
-    def basestate(self):
-        """current working directory base state, disregarding .hgsubstate
-        state and working directory modifications"""
-        raise NotImplementedError
-
-    def checknested(self, path):
-        """check if path is a subrepository within this repository"""
-        return False
-
-    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 phase(self, state):
-        """returns phase of specified state in the subrepository.
-        """
-        return phases.public
-
-    def remove(self):
-        """remove the subrepo
-
-        (should verify the dirstate is not dirty first)
-        """
-        raise NotImplementedError
-
-    def get(self, state, overwrite=False):
-        """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, opts):
-        """perform whatever action is analogous to 'hg push'
-
-        This may be a no-op on some systems.
-        """
-        raise NotImplementedError
-
-    def add(self, ui, match, prefix, explicitonly, **opts):
-        return []
-
-    def addremove(self, matcher, prefix, opts, dry_run, similarity):
-        self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
-        return 1
-
-    def cat(self, match, fm, fntemplate, prefix, **opts):
-        return 1
-
-    def status(self, rev2, **opts):
-        return scmutil.status([], [], [], [], [], [], [])
-
-    def diff(self, ui, diffopts, node2, match, prefix, **opts):
-        pass
-
-    def outgoing(self, ui, dest, opts):
-        return 1
-
-    def incoming(self, ui, source, opts):
-        return 1
-
-    def files(self):
-        """return filename iterator"""
-        raise NotImplementedError
-
-    def filedata(self, name, decode):
-        """return file data, optionally passed through repo decoders"""
-        raise NotImplementedError
-
-    def fileflags(self, name):
-        """return file flags"""
-        return ''
-
-    def getfileset(self, expr):
-        """Resolve the fileset expression for this repo"""
-        return set()
-
-    def printfiles(self, ui, m, fm, fmt, subrepos):
-        """handle the files command for this subrepo"""
-        return 1
-
-    def archive(self, archiver, prefix, match=None, decode=True):
-        if match is not None:
-            files = [f for f in self.files() if match(f)]
-        else:
-            files = self.files()
-        total = len(files)
-        relpath = subrelpath(self)
-        self.ui.progress(_('archiving (%s)') % relpath, 0,
-                         unit=_('files'), total=total)
-        for i, name in enumerate(files):
-            flags = self.fileflags(name)
-            mode = 'x' in flags and 0o755 or 0o644
-            symlink = 'l' in flags
-            archiver.addfile(prefix + self._path + '/' + name,
-                             mode, symlink, self.filedata(name, decode))
-            self.ui.progress(_('archiving (%s)') % relpath, i + 1,
-                             unit=_('files'), total=total)
-        self.ui.progress(_('archiving (%s)') % relpath, None)
-        return total
-
-    def walk(self, match):
-        '''
-        walk recursively through the directory tree, finding all files
-        matched by the match function
-        '''
-
-    def forget(self, match, prefix):
-        return ([], [])
-
-    def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
-        """remove the matched files from the subrepository and the filesystem,
-        possibly by force and/or after the file has been removed from the
-        filesystem.  Return 0 on success, 1 on any warning.
-        """
-        warnings.append(_("warning: removefiles not implemented (%s)")
-                        % self._path)
-        return 1
-
-    def revert(self, substate, *pats, **opts):
-        self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
-            % (substate[0], substate[2]))
-        return []
-
-    def shortid(self, revid):
-        return revid
-
-    def unshare(self):
-        '''
-        convert this repository from shared to normal storage.
-        '''
-
-    def verify(self):
-        '''verify the integrity of the repository.  Return 0 on success or
-        warning, 1 on any error.
-        '''
-        return 0
-
-    @propertycache
-    def wvfs(self):
-        """return vfs to access the working directory of this subrepository
-        """
-        return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
-
-    @propertycache
-    def _relpath(self):
-        """return path to this subrepository as seen from outermost repository
-        """
-        return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
-
-class hgsubrepo(abstractsubrepo):
-    def __init__(self, ctx, path, state, allowcreate):
-        super(hgsubrepo, self).__init__(ctx, path)
-        self._state = state
-        r = ctx.repo()
-        root = r.wjoin(path)
-        create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
-        self._repo = hg.repository(r.baseui, root, create=create)
-
-        # Propagate the parent's --hidden option
-        if r is r.unfiltered():
-            self._repo = self._repo.unfiltered()
-
-        self.ui = self._repo.ui
-        for s, k in [('ui', 'commitsubrepos')]:
-            v = r.ui.config(s, k)
-            if v:
-                self.ui.setconfig(s, k, v, 'subrepo')
-        # internal config: ui._usedassubrepo
-        self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
-        self._initrepo(r, state[0], create)
-
-    @annotatesubrepoerror
-    def addwebdirpath(self, serverpath, webconf):
-        cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
-
-    def storeclean(self, path):
-        with self._repo.lock():
-            return self._storeclean(path)
-
-    def _storeclean(self, path):
-        clean = True
-        itercache = self._calcstorehash(path)
-        for filehash in self._readstorehashcache(path):
-            if filehash != next(itercache, None):
-                clean = False
-                break
-        if clean:
-            # if not empty:
-            # the cached and current pull states have a different size
-            clean = next(itercache, None) is None
-        return clean
-
-    def _calcstorehash(self, remotepath):
-        '''calculate a unique "store hash"
-
-        This method is used to to detect when there are changes that may
-        require a push to a given remote path.'''
-        # sort the files that will be hashed in increasing (likely) file size
-        filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
-        yield '# %s\n' % _expandedabspath(remotepath)
-        vfs = self._repo.vfs
-        for relname in filelist:
-            filehash = node.hex(hashlib.sha1(vfs.tryread(relname)).digest())
-            yield '%s = %s\n' % (relname, filehash)
-
-    @propertycache
-    def _cachestorehashvfs(self):
-        return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
-
-    def _readstorehashcache(self, remotepath):
-        '''read the store hash cache for a given remote repository'''
-        cachefile = _getstorehashcachename(remotepath)
-        return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
-
-    def _cachestorehash(self, remotepath):
-        '''cache the current store hash
-
-        Each remote repo requires its own store hash cache, because a subrepo
-        store may be "clean" versus a given remote repo, but not versus another
-        '''
-        cachefile = _getstorehashcachename(remotepath)
-        with self._repo.lock():
-            storehash = list(self._calcstorehash(remotepath))
-            vfs = self._cachestorehashvfs
-            vfs.writelines(cachefile, storehash, mode='wb', notindexed=True)
-
-    def _getctx(self):
-        '''fetch the context for this subrepo revision, possibly a workingctx
-        '''
-        if self._ctx.rev() is None:
-            return self._repo[None]  # workingctx if parent is workingctx
-        else:
-            rev = self._state[1]
-            return self._repo[rev]
-
-    @annotatesubrepoerror
-    def _initrepo(self, parentrepo, source, create):
-        self._repo._subparent = parentrepo
-        self._repo._subsource = source
-
-        if create:
-            lines = ['[paths]\n']
-
-            def addpathconfig(key, value):
-                if value:
-                    lines.append('%s = %s\n' % (key, value))
-                    self.ui.setconfig('paths', key, value, 'subrepo')
-
-            defpath = _abssource(self._repo, abort=False)
-            defpushpath = _abssource(self._repo, True, abort=False)
-            addpathconfig('default', defpath)
-            if defpath != defpushpath:
-                addpathconfig('default-push', defpushpath)
-
-            self._repo.vfs.write('hgrc', util.tonativeeol(''.join(lines)))
-
-    @annotatesubrepoerror
-    def add(self, ui, match, prefix, explicitonly, **opts):
-        return cmdutil.add(ui, self._repo, match,
-                           self.wvfs.reljoin(prefix, self._path),
-                           explicitonly, **opts)
-
-    @annotatesubrepoerror
-    def addremove(self, m, prefix, opts, dry_run, similarity):
-        # In the same way as sub directories are processed, once in a subrepo,
-        # always entry any of its subrepos.  Don't corrupt the options that will
-        # be used to process sibling subrepos however.
-        opts = copy.copy(opts)
-        opts['subrepos'] = True
-        return scmutil.addremove(self._repo, m,
-                                 self.wvfs.reljoin(prefix, self._path), opts,
-                                 dry_run, similarity)
-
-    @annotatesubrepoerror
-    def cat(self, match, fm, fntemplate, prefix, **opts):
-        rev = self._state[1]
-        ctx = self._repo[rev]
-        return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate,
-                           prefix, **opts)
-
-    @annotatesubrepoerror
-    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 as inst:
-            self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
-                         % (inst, subrelpath(self)))
-            return scmutil.status([], [], [], [], [], [], [])
-
-    @annotatesubrepoerror
-    def diff(self, ui, diffopts, node2, match, prefix, **opts):
-        try:
-            node1 = node.bin(self._state[1])
-            # We currently expect node2 to come from substate and be
-            # in hex format
-            if node2 is not None:
-                node2 = node.bin(node2)
-            logcmdutil.diffordiffstat(ui, self._repo, diffopts,
-                                      node1, node2, match,
-                                      prefix=posixpath.join(prefix, self._path),
-                                      listsubrepos=True, **opts)
-        except error.RepoLookupError as inst:
-            self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
-                          % (inst, subrelpath(self)))
-
-    @annotatesubrepoerror
-    def archive(self, archiver, prefix, match=None, decode=True):
-        self._get(self._state + ('hg',))
-        files = self.files()
-        if match:
-            files = [f for f in files if match(f)]
-        rev = self._state[1]
-        ctx = self._repo[rev]
-        cmdutil._prefetchfiles(self._repo, ctx, files)
-        total = abstractsubrepo.archive(self, archiver, prefix, match)
-        for subpath in ctx.substate:
-            s = subrepo(ctx, subpath, True)
-            submatch = matchmod.subdirmatcher(subpath, match)
-            total += s.archive(archiver, prefix + self._path + '/', submatch,
-                               decode)
-        return total
-
-    @annotatesubrepoerror
-    def dirty(self, ignoreupdate=False, missing=False):
-        r = self._state[1]
-        if r == '' and not ignoreupdate: # no state recorded
-            return True
-        w = self._repo[None]
-        if r != w.p1().hex() and not ignoreupdate:
-            # different version checked out
-            return True
-        return w.dirty(missing=missing) # working directory changed
-
-    def basestate(self):
-        return self._repo['.'].hex()
-
-    def checknested(self, path):
-        return self._repo._checknested(self._repo.wjoin(path))
-
-    @annotatesubrepoerror
-    def commit(self, text, user, date):
-        # don't bother committing in the subrepo if it's only been
-        # updated
-        if not self.dirty(True):
-            return self._repo['.'].hex()
-        self.ui.debug("committing subrepo %s\n" % subrelpath(self))
-        n = self._repo.commit(text, user, date)
-        if not n:
-            return self._repo['.'].hex() # different version checked out
-        return node.hex(n)
-
-    @annotatesubrepoerror
-    def phase(self, state):
-        return self._repo[state].phase()
-
-    @annotatesubrepoerror
-    def remove(self):
-        # we can't fully delete the repository as it may contain
-        # local-only history
-        self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
-        hg.clean(self._repo, node.nullid, False)
-
-    def _get(self, state):
-        source, revision, kind = state
-        parentrepo = self._repo._subparent
-
-        if revision in self._repo.unfiltered():
-            # Allow shared subrepos tracked at null to setup the sharedpath
-            if len(self._repo) != 0 or not parentrepo.shared():
-                return True
-        self._repo._subsource = source
-        srcurl = _abssource(self._repo)
-        other = hg.peer(self._repo, {}, srcurl)
-        if len(self._repo) == 0:
-            # use self._repo.vfs instead of self.wvfs to remove .hg only
-            self._repo.vfs.rmtree()
-            if parentrepo.shared():
-                self.ui.status(_('sharing subrepo %s from %s\n')
-                               % (subrelpath(self), srcurl))
-                shared = hg.share(self._repo._subparent.baseui,
-                                  other, self._repo.root,
-                                  update=False, bookmarks=False)
-                self._repo = shared.local()
-            else:
-                self.ui.status(_('cloning subrepo %s from %s\n')
-                               % (subrelpath(self), srcurl))
-                other, cloned = hg.clone(self._repo._subparent.baseui, {},
-                                         other, self._repo.root,
-                                         update=False)
-                self._repo = cloned.local()
-            self._initrepo(parentrepo, source, create=True)
-            self._cachestorehash(srcurl)
-        else:
-            self.ui.status(_('pulling subrepo %s from %s\n')
-                           % (subrelpath(self), srcurl))
-            cleansub = self.storeclean(srcurl)
-            exchange.pull(self._repo, other)
-            if cleansub:
-                # keep the repo clean after pull
-                self._cachestorehash(srcurl)
-        return False
-
-    @annotatesubrepoerror
-    def get(self, state, overwrite=False):
-        inrepo = self._get(state)
-        source, revision, kind = state
-        repo = self._repo
-        repo.ui.debug("getting subrepo %s\n" % self._path)
-        if inrepo:
-            urepo = repo.unfiltered()
-            ctx = urepo[revision]
-            if ctx.hidden():
-                urepo.ui.warn(
-                    _('revision %s in subrepository "%s" is hidden\n') \
-                    % (revision[0:12], self._path))
-                repo = urepo
-        hg.updaterepo(repo, revision, overwrite)
-
-    @annotatesubrepoerror
-    def merge(self, state):
-        self._get(state)
-        cur = self._repo['.']
-        dst = self._repo[state[1]]
-        anc = dst.ancestor(cur)
-
-        def mergefunc():
-            if anc == cur and dst.branch() == cur.branch():
-                self.ui.debug('updating subrepository "%s"\n'
-                              % subrelpath(self))
-                hg.update(self._repo, state[1])
-            elif anc == dst:
-                self.ui.debug('skipping subrepository "%s"\n'
-                              % subrelpath(self))
-            else:
-                self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
-                hg.merge(self._repo, state[1], remind=False)
-
-        wctx = self._repo[None]
-        if self.dirty():
-            if anc != dst:
-                if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
-                    mergefunc()
-            else:
-                mergefunc()
-        else:
-            mergefunc()
-
-    @annotatesubrepoerror
-    def push(self, opts):
-        force = opts.get('force')
-        newbranch = opts.get('new_branch')
-        ssh = opts.get('ssh')
-
-        # push subrepos depth-first for coherent ordering
-        c = self._repo['']
-        subs = c.substate # only repos that are committed
-        for s in sorted(subs):
-            if c.sub(s).push(opts) == 0:
-                return False
-
-        dsturl = _abssource(self._repo, True)
-        if not force:
-            if self.storeclean(dsturl):
-                self.ui.status(
-                    _('no changes made to subrepo %s since last push to %s\n')
-                    % (subrelpath(self), dsturl))
-                return None
-        self.ui.status(_('pushing subrepo %s to %s\n') %
-            (subrelpath(self), dsturl))
-        other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
-        res = exchange.push(self._repo, other, force, newbranch=newbranch)
-
-        # the repo is now clean
-        self._cachestorehash(dsturl)
-        return res.cgresult
-
-    @annotatesubrepoerror
-    def outgoing(self, ui, dest, opts):
-        if 'rev' in opts or 'branch' in opts:
-            opts = copy.copy(opts)
-            opts.pop('rev', None)
-            opts.pop('branch', None)
-        return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
-
-    @annotatesubrepoerror
-    def incoming(self, ui, source, opts):
-        if 'rev' in opts or 'branch' in opts:
-            opts = copy.copy(opts)
-            opts.pop('rev', None)
-            opts.pop('branch', None)
-        return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
-
-    @annotatesubrepoerror
-    def files(self):
-        rev = self._state[1]
-        ctx = self._repo[rev]
-        return ctx.manifest().keys()
-
-    def filedata(self, name, decode):
-        rev = self._state[1]
-        data = self._repo[rev][name].data()
-        if decode:
-            data = self._repo.wwritedata(name, data)
-        return data
-
-    def fileflags(self, name):
-        rev = self._state[1]
-        ctx = self._repo[rev]
-        return ctx.flags(name)
-
-    @annotatesubrepoerror
-    def printfiles(self, ui, m, fm, fmt, subrepos):
-        # If the parent context is a workingctx, use the workingctx here for
-        # consistency.
-        if self._ctx.rev() is None:
-            ctx = self._repo[None]
-        else:
-            rev = self._state[1]
-            ctx = self._repo[rev]
-        return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
-
-    @annotatesubrepoerror
-    def getfileset(self, expr):
-        if self._ctx.rev() is None:
-            ctx = self._repo[None]
-        else:
-            rev = self._state[1]
-            ctx = self._repo[rev]
-
-        files = ctx.getfileset(expr)
-
-        for subpath in ctx.substate:
-            sub = ctx.sub(subpath)
-
-            try:
-                files.extend(subpath + '/' + f for f in sub.getfileset(expr))
-            except error.LookupError:
-                self.ui.status(_("skipping missing subrepository: %s\n")
-                               % self.wvfs.reljoin(reporelpath(self), subpath))
-        return files
-
-    def walk(self, match):
-        ctx = self._repo[None]
-        return ctx.walk(match)
-
-    @annotatesubrepoerror
-    def forget(self, match, prefix):
-        return cmdutil.forget(self.ui, self._repo, match,
-                              self.wvfs.reljoin(prefix, self._path), True)
-
-    @annotatesubrepoerror
-    def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
-        return cmdutil.remove(self.ui, self._repo, matcher,
-                              self.wvfs.reljoin(prefix, self._path),
-                              after, force, subrepos)
-
-    @annotatesubrepoerror
-    def revert(self, substate, *pats, **opts):
-        # reverting a subrepo is a 2 step process:
-        # 1. if the no_backup is not set, revert all modified
-        #    files inside the subrepo
-        # 2. update the subrepo to the revision specified in
-        #    the corresponding substate dictionary
-        self.ui.status(_('reverting subrepo %s\n') % substate[0])
-        if not opts.get(r'no_backup'):
-            # Revert all files on the subrepo, creating backups
-            # Note that this will not recursively revert subrepos
-            # We could do it if there was a set:subrepos() predicate
-            opts = opts.copy()
-            opts[r'date'] = None
-            opts[r'rev'] = substate[1]
-
-            self.filerevert(*pats, **opts)
-
-        # Update the repo to the revision specified in the given substate
-        if not opts.get(r'dry_run'):
-            self.get(substate, overwrite=True)
-
-    def filerevert(self, *pats, **opts):
-        ctx = self._repo[opts[r'rev']]
-        parents = self._repo.dirstate.parents()
-        if opts.get(r'all'):
-            pats = ['set:modified()']
-        else:
-            pats = []
-        cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
-
-    def shortid(self, revid):
-        return revid[:12]
-
-    @annotatesubrepoerror
-    def unshare(self):
-        # 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
-        global hg
-        from . import hg as h
-        hg = h
-
-        # Nothing prevents a user from sharing in a repo, and then making that a
-        # subrepo.  Alternately, the previous unshare attempt may have failed
-        # part way through.  So recurse whether or not this layer is shared.
-        if self._repo.shared():
-            self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath)
-
-        hg.unshare(self.ui, self._repo)
-
-    def verify(self):
-        try:
-            rev = self._state[1]
-            ctx = self._repo.unfiltered()[rev]
-            if ctx.hidden():
-                # Since hidden revisions aren't pushed/pulled, it seems worth an
-                # explicit warning.
-                ui = self._repo.ui
-                ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
-                        (self._relpath, node.short(self._ctx.node())))
-            return 0
-        except error.RepoLookupError:
-            # A missing subrepo revision may be a case of needing to pull it, so
-            # don't treat this as an error.
-            self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
-                               (self._relpath, node.short(self._ctx.node())))
-            return 0
-
-    @propertycache
-    def wvfs(self):
-        """return own wvfs for efficiency and consistency
-        """
-        return self._repo.wvfs
-
-    @propertycache
-    def _relpath(self):
-        """return path to this subrepository as seen from outermost repository
-        """
-        # Keep consistent dir separators by avoiding vfs.join(self._path)
-        return reporelpath(self._repo)
-
-class svnsubrepo(abstractsubrepo):
-    def __init__(self, ctx, path, state, allowcreate):
-        super(svnsubrepo, self).__init__(ctx, path)
-        self._state = state
-        self._exe = util.findexe('svn')
-        if not self._exe:
-            raise error.Abort(_("'svn' executable not found for subrepo '%s'")
-                             % self._path)
-
-    def _svncommand(self, commands, filename='', failok=False):
-        cmd = [self._exe]
-        extrakw = {}
-        if not self.ui.interactive():
-            # Making stdin be a pipe should prevent svn from behaving
-            # interactively even if we can't pass --non-interactive.
-            extrakw[r'stdin'] = subprocess.PIPE
-            # Starting in svn 1.5 --non-interactive is a global flag
-            # instead of being per-command, but we need to support 1.4 so
-            # we have to be intelligent about what commands take
-            # --non-interactive.
-            if commands[0] in ('update', 'checkout', 'commit'):
-                cmd.append('--non-interactive')
-        cmd.extend(commands)
-        if filename is not None:
-            path = self.wvfs.reljoin(self._ctx.repo().origroot,
-                                     self._path, filename)
-            cmd.append(path)
-        env = dict(encoding.environ)
-        # Avoid localized output, preserve current locale for everything else.
-        lc_all = env.get('LC_ALL')
-        if lc_all:
-            env['LANG'] = lc_all
-            del env['LC_ALL']
-        env['LC_MESSAGES'] = 'C'
-        p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
-                             stdout=subprocess.PIPE, stderr=subprocess.PIPE,
-                              universal_newlines=True, env=env, **extrakw)
-        stdout, stderr = p.communicate()
-        stderr = stderr.strip()
-        if not failok:
-            if p.returncode:
-                raise error.Abort(stderr or 'exited with code %d'
-                                  % p.returncode)
-            if stderr:
-                self.ui.warn(stderr + '\n')
-        return stdout, stderr
-
-    @propertycache
-    def _svnversion(self):
-        output, err = self._svncommand(['--version', '--quiet'], filename=None)
-        m = re.search(br'^(\d+)\.(\d+)', output)
-        if not m:
-            raise error.Abort(_('cannot retrieve svn tool version'))
-        return (int(m.group(1)), int(m.group(2)))
-
-    def _svnmissing(self):
-        return not self.wvfs.exists('.svn')
-
-    def _wcrevs(self):
-        # Get the working directory revision as well as the last
-        # commit revision so we can compare the subrepo state with
-        # both. We used to store the working directory one.
-        output, err = self._svncommand(['info', '--xml'])
-        doc = xml.dom.minidom.parseString(output)
-        entries = doc.getElementsByTagName('entry')
-        lastrev, rev = '0', '0'
-        if entries:
-            rev = str(entries[0].getAttribute('revision')) or '0'
-            commits = entries[0].getElementsByTagName('commit')
-            if commits:
-                lastrev = str(commits[0].getAttribute('revision')) or '0'
-        return (lastrev, rev)
-
-    def _wcrev(self):
-        return self._wcrevs()[0]
-
-    def _wcchanged(self):
-        """Return (changes, extchanges, missing) where changes is True
-        if the working directory was changed, extchanges is
-        True if any of these changes concern an external entry and missing
-        is True if any change is a missing entry.
-        """
-        output, err = self._svncommand(['status', '--xml'])
-        externals, changes, missing = [], [], []
-        doc = xml.dom.minidom.parseString(output)
-        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)
-            elif item == 'missing':
-                missing.append(path)
-            if (item not in ('', 'normal', 'unversioned', 'external')
-                or props not in ('', 'none', 'normal')):
-                changes.append(path)
-        for path in changes:
-            for ext in externals:
-                if path == ext or path.startswith(ext + pycompat.ossep):
-                    return True, True, bool(missing)
-        return bool(changes), False, bool(missing)
-
-    @annotatesubrepoerror
-    def dirty(self, ignoreupdate=False, missing=False):
-        if self._svnmissing():
-            return self._state[1] != ''
-        wcchanged = self._wcchanged()
-        changed = wcchanged[0] or (missing and wcchanged[2])
-        if not changed:
-            if self._state[1] in self._wcrevs() or ignoreupdate:
-                return False
-        return True
-
-    def basestate(self):
-        lastrev, rev = self._wcrevs()
-        if lastrev != rev:
-            # Last committed rev is not the same than rev. We would
-            # like to take lastrev but we do not know if the subrepo
-            # URL exists at lastrev.  Test it and fallback to rev it
-            # is not there.
-            try:
-                self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
-                return lastrev
-            except error.Abort:
-                pass
-        return rev
-
-    @annotatesubrepoerror
-    def commit(self, text, user, date):
-        # user and date are out of our hands since svn is centralized
-        changed, extchanged, missing = self._wcchanged()
-        if not changed:
-            return self.basestate()
-        if extchanged:
-            # Do not try to commit externals
-            raise error.Abort(_('cannot commit svn externals'))
-        if missing:
-            # svn can commit with missing entries but aborting like hg
-            # seems a better approach.
-            raise error.Abort(_('cannot commit missing svn entries'))
-        commitinfo, err = self._svncommand(['commit', '-m', text])
-        self.ui.status(commitinfo)
-        newrev = re.search('Committed revision ([0-9]+).', commitinfo)
-        if not newrev:
-            if not commitinfo.strip():
-                # Sometimes, our definition of "changed" differs from
-                # svn one. For instance, svn ignores missing files
-                # when committing. If there are only missing files, no
-                # commit is made, no output and no error code.
-                raise error.Abort(_('failed to commit svn changes'))
-            raise error.Abort(commitinfo.splitlines()[-1])
-        newrev = newrev.groups()[0]
-        self.ui.status(self._svncommand(['update', '-r', newrev])[0])
-        return newrev
-
-    @annotatesubrepoerror
-    def remove(self):
-        if self.dirty():
-            self.ui.warn(_('not removing repo %s because '
-                           'it has changes.\n') % self._path)
-            return
-        self.ui.note(_('removing subrepo %s\n') % self._path)
-
-        self.wvfs.rmtree(forcibly=True)
-        try:
-            pwvfs = self._ctx.repo().wvfs
-            pwvfs.removedirs(pwvfs.dirname(self._path))
-        except OSError:
-            pass
-
-    @annotatesubrepoerror
-    def get(self, state, overwrite=False):
-        if overwrite:
-            self._svncommand(['revert', '--recursive'])
-        args = ['checkout']
-        if self._svnversion >= (1, 5):
-            args.append('--force')
-        # The revision must be specified at the end of the URL to properly
-        # update to a directory which has since been deleted and recreated.
-        args.append('%s@%s' % (state[0], state[1]))
-
-        # SEC: check that the ssh url is safe
-        util.checksafessh(state[0])
-
-        status, err = self._svncommand(args, failok=True)
-        _sanitize(self.ui, self.wvfs, '.svn')
-        if not re.search('Checked out revision [0-9]+.', status):
-            if ('is already a working copy for a different URL' in err
-                and (self._wcchanged()[:2] == (False, False))):
-                # obstructed but clean working copy, so just blow it away.
-                self.remove()
-                self.get(state, overwrite=False)
-                return
-            raise error.Abort((status or err).splitlines()[-1])
-        self.ui.status(status)
-
-    @annotatesubrepoerror
-    def merge(self, state):
-        old = self._state[1]
-        new = state[1]
-        wcrev = self._wcrev()
-        if new != wcrev:
-            dirty = old == wcrev or self._wcchanged()[0]
-            if _updateprompt(self.ui, self, dirty, wcrev, new):
-                self.get(state, False)
-
-    def push(self, opts):
-        # push is a no-op for SVN
-        return True
-
-    @annotatesubrepoerror
-    def files(self):
-        output = self._svncommand(['list', '--recursive', '--xml'])[0]
-        doc = xml.dom.minidom.parseString(output)
-        paths = []
-        for e in doc.getElementsByTagName('entry'):
-            kind = str(e.getAttribute('kind'))
-            if kind != 'file':
-                continue
-            name = ''.join(c.data for c
-                           in e.getElementsByTagName('name')[0].childNodes
-                           if c.nodeType == c.TEXT_NODE)
-            paths.append(name.encode('utf-8'))
-        return paths
-
-    def filedata(self, name, decode):
-        return self._svncommand(['cat'], name)[0]
-
-
-class gitsubrepo(abstractsubrepo):
-    def __init__(self, ctx, path, state, allowcreate):
-        super(gitsubrepo, self).__init__(ctx, path)
-        self._state = state
-        self._abspath = ctx.repo().wjoin(path)
-        self._subparent = ctx.repo()
-        self._ensuregit()
-
-    def _ensuregit(self):
-        try:
-            self._gitexecutable = 'git'
-            out, err = self._gitnodir(['--version'])
-        except OSError as e:
-            genericerror = _("error executing git for subrepo '%s': %s")
-            notfoundhint = _("check git is installed and in your PATH")
-            if e.errno != errno.ENOENT:
-                raise error.Abort(genericerror % (
-                    self._path, encoding.strtolocal(e.strerror)))
-            elif pycompat.iswindows:
-                try:
-                    self._gitexecutable = 'git.cmd'
-                    out, err = self._gitnodir(['--version'])
-                except OSError as e2:
-                    if e2.errno == errno.ENOENT:
-                        raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
-                            " for subrepo '%s'") % self._path,
-                            hint=notfoundhint)
-                    else:
-                        raise error.Abort(genericerror % (self._path,
-                            encoding.strtolocal(e2.strerror)))
-            else:
-                raise error.Abort(_("couldn't find git for subrepo '%s'")
-                    % self._path, hint=notfoundhint)
-        versionstatus = self._checkversion(out)
-        if versionstatus == 'unknown':
-            self.ui.warn(_('cannot retrieve git version\n'))
-        elif versionstatus == 'abort':
-            raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
-        elif versionstatus == 'warning':
-            self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
-
-    @staticmethod
-    def _gitversion(out):
-        m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
-        if m:
-            return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
-
-        m = re.search(br'^git version (\d+)\.(\d+)', out)
-        if m:
-            return (int(m.group(1)), int(m.group(2)), 0)
-
-        return -1
-
-    @staticmethod
-    def _checkversion(out):
-        '''ensure git version is new enough
-
-        >>> _checkversion = gitsubrepo._checkversion
-        >>> _checkversion(b'git version 1.6.0')
-        'ok'
-        >>> _checkversion(b'git version 1.8.5')
-        'ok'
-        >>> _checkversion(b'git version 1.4.0')
-        'abort'
-        >>> _checkversion(b'git version 1.5.0')
-        'warning'
-        >>> _checkversion(b'git version 1.9-rc0')
-        'ok'
-        >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
-        'ok'
-        >>> _checkversion(b'git version 1.9.0.GIT')
-        'ok'
-        >>> _checkversion(b'git version 12345')
-        'unknown'
-        >>> _checkversion(b'no')
-        'unknown'
-        '''
-        version = gitsubrepo._gitversion(out)
-        # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
-        # despite the docstring comment.  For now, error on 1.4.0, warn on
-        # 1.5.0 but attempt to continue.
-        if version == -1:
-            return 'unknown'
-        if version < (1, 5, 0):
-            return 'abort'
-        elif version < (1, 6, 0):
-            return 'warning'
-        return 'ok'
-
-    def _gitcommand(self, commands, env=None, stream=False):
-        return self._gitdir(commands, env=env, stream=stream)[0]
-
-    def _gitdir(self, commands, env=None, stream=False):
-        return self._gitnodir(commands, env=env, stream=stream,
-                              cwd=self._abspath)
-
-    def _gitnodir(self, commands, env=None, stream=False, cwd=None):
-        """Calls the git command
-
-        The methods tries to call the git command. versions prior to 1.6.0
-        are not supported and very probably fail.
-        """
-        self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
-        if env is None:
-            env = encoding.environ.copy()
-        # disable localization for Git output (issue5176)
-        env['LC_ALL'] = 'C'
-        # fix for Git CVE-2015-7545
-        if 'GIT_ALLOW_PROTOCOL' not in env:
-            env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
-        # unless ui.quiet is set, print git's stderr,
-        # which is mostly progress and useful info
-        errpipe = None
-        if self.ui.quiet:
-            errpipe = open(os.devnull, 'w')
-        if self.ui._colormode and len(commands) and commands[0] == "diff":
-            # insert the argument in the front,
-            # the end of git diff arguments is used for paths
-            commands.insert(1, '--color')
-        p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
-                             cwd=cwd, env=env, close_fds=util.closefds,
-                             stdout=subprocess.PIPE, stderr=errpipe)
-        if stream:
-            return p.stdout, None
-
-        retdata = p.stdout.read().strip()
-        # wait for the child to exit to avoid race condition.
-        p.wait()
-
-        if p.returncode != 0 and p.returncode != 1:
-            # there are certain error codes that are ok
-            command = commands[0]
-            if command in ('cat-file', 'symbolic-ref'):
-                return retdata, p.returncode
-            # for all others, abort
-            raise error.Abort(_('git %s error %d in %s') %
-                             (command, p.returncode, self._relpath))
-
-        return retdata, p.returncode
-
-    def _gitmissing(self):
-        return not self.wvfs.exists('.git')
-
-    def _gitstate(self):
-        return self._gitcommand(['rev-parse', 'HEAD'])
-
-    def _gitcurrentbranch(self):
-        current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
-        if err:
-            current = None
-        return current
-
-    def _gitremote(self, remote):
-        out = self._gitcommand(['remote', 'show', '-n', remote])
-        line = out.split('\n')[1]
-        i = line.index('URL: ') + len('URL: ')
-        return line[i:]
-
-    def _githavelocally(self, revision):
-        out, code = self._gitdir(['cat-file', '-e', revision])
-        return code == 0
-
-    def _gitisancestor(self, r1, r2):
-        base = self._gitcommand(['merge-base', r1, r2])
-        return base == r1
-
-    def _gitisbare(self):
-        return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
-
-    def _gitupdatestat(self):
-        """This must be run before git diff-index.
-        diff-index only looks at changes to file stat;
-        this command looks at file contents and updates the stat."""
-        self._gitcommand(['update-index', '-q', '--refresh'])
-
-    def _gitbranchmap(self):
-        '''returns 2 things:
-        a map from git branch to revision
-        a map from revision to branches'''
-        branch2rev = {}
-        rev2branch = {}
-
-        out = self._gitcommand(['for-each-ref', '--format',
-                                '%(objectname) %(refname)'])
-        for line in out.split('\n'):
-            revision, ref = line.split(' ')
-            if (not ref.startswith('refs/heads/') and
-                not ref.startswith('refs/remotes/')):
-                continue
-            if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
-                continue # ignore remote/HEAD redirects
-            branch2rev[ref] = revision
-            rev2branch.setdefault(revision, []).append(ref)
-        return branch2rev, rev2branch
-
-    def _gittracking(self, branches):
-        'return map of remote branch to local tracking branch'
-        # assumes no more than one local tracking branch for each remote
-        tracking = {}
-        for b in branches:
-            if b.startswith('refs/remotes/'):
-                continue
-            bname = b.split('/', 2)[2]
-            remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
-            if remote:
-                ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
-                tracking['refs/remotes/%s/%s' %
-                         (remote, ref.split('/', 2)[2])] = b
-        return tracking
-
-    def _abssource(self, source):
-        if '://' not in source:
-            # recognize the scp syntax as an absolute source
-            colon = source.find(':')
-            if colon != -1 and '/' not in source[:colon]:
-                return source
-        self._subsource = source
-        return _abssource(self)
-
-    def _fetch(self, source, revision):
-        if self._gitmissing():
-            # SEC: check for safe ssh url
-            util.checksafessh(source)
-
-            source = self._abssource(source)
-            self.ui.status(_('cloning subrepo %s from %s\n') %
-                            (self._relpath, source))
-            self._gitnodir(['clone', source, self._abspath])
-        if self._githavelocally(revision):
-            return
-        self.ui.status(_('pulling subrepo %s from %s\n') %
-                        (self._relpath, self._gitremote('origin')))
-        # try only origin: the originally cloned repo
-        self._gitcommand(['fetch'])
-        if not self._githavelocally(revision):
-            raise error.Abort(_('revision %s does not exist in subrepository '
-                                '"%s"\n') % (revision, self._relpath))
-
-    @annotatesubrepoerror
-    def dirty(self, ignoreupdate=False, missing=False):
-        if self._gitmissing():
-            return self._state[1] != ''
-        if self._gitisbare():
-            return True
-        if not ignoreupdate and self._state[1] != self._gitstate():
-            # different version checked out
-            return True
-        # check for staged changes or modified files; ignore untracked files
-        self._gitupdatestat()
-        out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
-        return code == 1
-
-    def basestate(self):
-        return self._gitstate()
-
-    @annotatesubrepoerror
-    def get(self, state, overwrite=False):
-        source, revision, kind = state
-        if not revision:
-            self.remove()
-            return
-        self._fetch(source, revision)
-        # if the repo was set to be bare, unbare it
-        if self._gitisbare():
-            self._gitcommand(['config', 'core.bare', 'false'])
-            if self._gitstate() == revision:
-                self._gitcommand(['reset', '--hard', 'HEAD'])
-                return
-        elif self._gitstate() == revision:
-            if overwrite:
-                # first reset the index to unmark new files for commit, because
-                # reset --hard will otherwise throw away files added for commit,
-                # not just unmark them.
-                self._gitcommand(['reset', 'HEAD'])
-                self._gitcommand(['reset', '--hard', 'HEAD'])
-            return
-        branch2rev, rev2branch = self._gitbranchmap()
-
-        def checkout(args):
-            cmd = ['checkout']
-            if overwrite:
-                # first reset the index to unmark new files for commit, because
-                # the -f option will otherwise throw away files added for
-                # commit, not just unmark them.
-                self._gitcommand(['reset', 'HEAD'])
-                cmd.append('-f')
-            self._gitcommand(cmd + args)
-            _sanitize(self.ui, self.wvfs, '.git')
-
-        def rawcheckout():
-            # no branch to checkout, check it out with no branch
-            self.ui.warn(_('checking out detached HEAD in '
-                           'subrepository "%s"\n') % self._relpath)
-            self.ui.warn(_('check out a git branch if you intend '
-                            'to make changes\n'))
-            checkout(['-q', revision])
-
-        if revision not in rev2branch:
-            rawcheckout()
-            return
-        branches = rev2branch[revision]
-        firstlocalbranch = None
-        for b in branches:
-            if b == 'refs/heads/master':
-                # master trumps all other branches
-                checkout(['refs/heads/master'])
-                return
-            if not firstlocalbranch and not b.startswith('refs/remotes/'):
-                firstlocalbranch = b
-        if firstlocalbranch:
-            checkout([firstlocalbranch])
-            return
-
-        tracking = self._gittracking(branch2rev.keys())
-        # choose a remote branch already tracked if possible
-        remote = branches[0]
-        if remote not in tracking:
-            for b in branches:
-                if b in tracking:
-                    remote = b
-                    break
-
-        if remote not in tracking:
-            # create a new local tracking branch
-            local = remote.split('/', 3)[3]
-            checkout(['-b', local, remote])
-        elif self._gitisancestor(branch2rev[tracking[remote]], remote):
-            # When updating to a tracked remote branch,
-            # if the local tracking branch is downstream of it,
-            # a normal `git pull` would have performed a "fast-forward merge"
-            # which is equivalent to updating the local branch to the remote.
-            # Since we are only looking at branching at update, we need to
-            # detect this situation and perform this action lazily.
-            if tracking[remote] != self._gitcurrentbranch():
-                checkout([tracking[remote]])
-            self._gitcommand(['merge', '--ff', remote])
-            _sanitize(self.ui, self.wvfs, '.git')
-        else:
-            # a real merge would be required, just checkout the revision
-            rawcheckout()
-
-    @annotatesubrepoerror
-    def commit(self, text, user, date):
-        if self._gitmissing():
-            raise error.Abort(_("subrepo %s is missing") % self._relpath)
-        cmd = ['commit', '-a', '-m', text]
-        env = encoding.environ.copy()
-        if user:
-            cmd += ['--author', user]
-        if date:
-            # git's date parser silently ignores when seconds < 1e9
-            # convert to ISO8601
-            env['GIT_AUTHOR_DATE'] = util.datestr(date,
-                                                  '%Y-%m-%dT%H:%M:%S %1%2')
-        self._gitcommand(cmd, env=env)
-        # make sure commit works otherwise HEAD might not exist under certain
-        # circumstances
-        return self._gitstate()
-
-    @annotatesubrepoerror
-    def merge(self, state):
-        source, revision, kind = state
-        self._fetch(source, revision)
-        base = self._gitcommand(['merge-base', revision, self._state[1]])
-        self._gitupdatestat()
-        out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
-
-        def mergefunc():
-            if base == revision:
-                self.get(state) # fast forward merge
-            elif base != self._state[1]:
-                self._gitcommand(['merge', '--no-commit', revision])
-            _sanitize(self.ui, self.wvfs, '.git')
-
-        if self.dirty():
-            if self._gitstate() != revision:
-                dirty = self._gitstate() == self._state[1] or code != 0
-                if _updateprompt(self.ui, self, dirty,
-                                 self._state[1][:7], revision[:7]):
-                    mergefunc()
-        else:
-            mergefunc()
-
-    @annotatesubrepoerror
-    def push(self, opts):
-        force = opts.get('force')
-
-        if not self._state[1]:
-            return True
-        if self._gitmissing():
-            raise error.Abort(_("subrepo %s is missing") % self._relpath)
-        # if a branch in origin contains the revision, nothing to do
-        branch2rev, rev2branch = self._gitbranchmap()
-        if self._state[1] in rev2branch:
-            for b in rev2branch[self._state[1]]:
-                if b.startswith('refs/remotes/origin/'):
-                    return True
-        for b, revision in branch2rev.iteritems():
-            if b.startswith('refs/remotes/origin/'):
-                if self._gitisancestor(self._state[1], revision):
-                    return True
-        # otherwise, try to push the currently checked out branch
-        cmd = ['push']
-        if force:
-            cmd.append('--force')
-
-        current = self._gitcurrentbranch()
-        if current:
-            # 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 subrepository "%s"\n') % self._relpath)
-                return False
-            self.ui.status(_('pushing branch %s of subrepository "%s"\n') %
-                           (current.split('/', 2)[2], self._relpath))
-            ret = self._gitdir(cmd + ['origin', current])
-            return ret[1] == 0
-        else:
-            self.ui.warn(_('no branch checked out in subrepository "%s"\n'
-                           'cannot push revision %s\n') %
-                          (self._relpath, self._state[1]))
-            return False
-
-    @annotatesubrepoerror
-    def add(self, ui, match, prefix, explicitonly, **opts):
-        if self._gitmissing():
-            return []
-
-        (modified, added, removed,
-         deleted, unknown, ignored, clean) = self.status(None, unknown=True,
-                                                         clean=True)
-
-        tracked = set()
-        # dirstates 'amn' warn, 'r' is added again
-        for l in (modified, added, deleted, clean):
-            tracked.update(l)
-
-        # Unknown files not of interest will be rejected by the matcher
-        files = unknown
-        files.extend(match.files())
-
-        rejected = []
-
-        files = [f for f in sorted(set(files)) if match(f)]
-        for f in files:
-            exact = match.exact(f)
-            command = ["add"]
-            if exact:
-                command.append("-f") #should be added, even if ignored
-            if ui.verbose or not exact:
-                ui.status(_('adding %s\n') % match.rel(f))
-
-            if f in tracked:  # hg prints 'adding' even if already tracked
-                if exact:
-                    rejected.append(f)
-                continue
-            if not opts.get(r'dry_run'):
-                self._gitcommand(command + [f])
-
-        for f in rejected:
-            ui.warn(_("%s already tracked!\n") % match.abs(f))
-
-        return rejected
-
-    @annotatesubrepoerror
-    def remove(self):
-        if self._gitmissing():
-            return
-        if self.dirty():
-            self.ui.warn(_('not removing repo %s because '
-                           'it has changes.\n') % self._relpath)
-            return
-        # we can't fully delete the repository as it may contain
-        # local-only history
-        self.ui.note(_('removing subrepo %s\n') % self._relpath)
-        self._gitcommand(['config', 'core.bare', 'true'])
-        for f, kind in self.wvfs.readdir():
-            if f == '.git':
-                continue
-            if kind == stat.S_IFDIR:
-                self.wvfs.rmtree(f)
-            else:
-                self.wvfs.unlink(f)
-
-    def archive(self, archiver, prefix, match=None, decode=True):
-        total = 0
-        source, revision = self._state
-        if not revision:
-            return total
-        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|')
-        relpath = subrelpath(self)
-        self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
-        for i, info in enumerate(tar):
-            if info.isdir():
-                continue
-            if match and not match(info.name):
-                continue
-            if info.issym():
-                data = info.linkname
-            else:
-                data = tar.extractfile(info).read()
-            archiver.addfile(prefix + self._path + '/' + info.name,
-                             info.mode, info.issym(), data)
-            total += 1
-            self.ui.progress(_('archiving (%s)') % relpath, i + 1,
-                             unit=_('files'))
-        self.ui.progress(_('archiving (%s)') % relpath, None)
-        return total
-
-
-    @annotatesubrepoerror
-    def cat(self, match, fm, fntemplate, prefix, **opts):
-        rev = self._state[1]
-        if match.anypats():
-            return 1 #No support for include/exclude yet
-
-        if not match.files():
-            return 1
-
-        # TODO: add support for non-plain formatter (see cmdutil.cat())
-        for f in match.files():
-            output = self._gitcommand(["show", "%s:%s" % (rev, f)])
-            fp = cmdutil.makefileobj(self._subparent, fntemplate,
-                                     self._ctx.node(),
-                                     pathname=self.wvfs.reljoin(prefix, f))
-            fp.write(output)
-            fp.close()
-        return 0
-
-
-    @annotatesubrepoerror
-    def status(self, rev2, **opts):
-        rev1 = self._state[1]
-        if self._gitmissing() or not rev1:
-            # if the repo is missing, return no results
-            return scmutil.status([], [], [], [], [], [], [])
-        modified, added, removed = [], [], []
-        self._gitupdatestat()
-        if rev2:
-            command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
-        else:
-            command = ['diff-index', '--no-renames', rev1]
-        out = self._gitcommand(command)
-        for line in out.split('\n'):
-            tab = line.find('\t')
-            if tab == -1:
-                continue
-            status, f = line[tab - 1], line[tab + 1:]
-            if status == 'M':
-                modified.append(f)
-            elif status == 'A':
-                added.append(f)
-            elif status == 'D':
-                removed.append(f)
-
-        deleted, unknown, ignored, clean = [], [], [], []
-
-        command = ['status', '--porcelain', '-z']
-        if opts.get(r'unknown'):
-            command += ['--untracked-files=all']
-        if opts.get(r'ignored'):
-            command += ['--ignored']
-        out = self._gitcommand(command)
-
-        changedfiles = set()
-        changedfiles.update(modified)
-        changedfiles.update(added)
-        changedfiles.update(removed)
-        for line in out.split('\0'):
-            if not line:
-                continue
-            st = line[0:2]
-            #moves and copies show 2 files on one line
-            if line.find('\0') >= 0:
-                filename1, filename2 = line[3:].split('\0')
-            else:
-                filename1 = line[3:]
-                filename2 = None
-
-            changedfiles.add(filename1)
-            if filename2:
-                changedfiles.add(filename2)
-
-            if st == '??':
-                unknown.append(filename1)
-            elif st == '!!':
-                ignored.append(filename1)
-
-        if opts.get(r'clean'):
-            out = self._gitcommand(['ls-files'])
-            for f in out.split('\n'):
-                if not f in changedfiles:
-                    clean.append(f)
-
-        return scmutil.status(modified, added, removed, deleted,
-                              unknown, ignored, clean)
-
-    @annotatesubrepoerror
-    def diff(self, ui, diffopts, node2, match, prefix, **opts):
-        node1 = self._state[1]
-        cmd = ['diff', '--no-renames']
-        if opts[r'stat']:
-            cmd.append('--stat')
-        else:
-            # for Git, this also implies '-p'
-            cmd.append('-U%d' % diffopts.context)
-
-        gitprefix = self.wvfs.reljoin(prefix, self._path)
-
-        if diffopts.noprefix:
-            cmd.extend(['--src-prefix=%s/' % gitprefix,
-                        '--dst-prefix=%s/' % gitprefix])
-        else:
-            cmd.extend(['--src-prefix=a/%s/' % gitprefix,
-                        '--dst-prefix=b/%s/' % gitprefix])
-
-        if diffopts.ignorews:
-            cmd.append('--ignore-all-space')
-        if diffopts.ignorewsamount:
-            cmd.append('--ignore-space-change')
-        if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
-                and diffopts.ignoreblanklines:
-            cmd.append('--ignore-blank-lines')
-
-        cmd.append(node1)
-        if node2:
-            cmd.append(node2)
-
-        output = ""
-        if match.always():
-            output += self._gitcommand(cmd) + '\n'
-        else:
-            st = self.status(node2)[:3]
-            files = [f for sublist in st for f in sublist]
-            for f in files:
-                if match(f):
-                    output += self._gitcommand(cmd + ['--', f]) + '\n'
-
-        if output.strip():
-            ui.write(output)
-
-    @annotatesubrepoerror
-    def revert(self, substate, *pats, **opts):
-        self.ui.status(_('reverting subrepo %s\n') % substate[0])
-        if not opts.get(r'no_backup'):
-            status = self.status(None)
-            names = status.modified
-            for name in names:
-                bakname = scmutil.origpath(self.ui, self._subparent, name)
-                self.ui.note(_('saving current version of %s as %s\n') %
-                        (name, bakname))
-                self.wvfs.rename(name, bakname)
-
-        if not opts.get(r'dry_run'):
-            self.get(substate, overwrite=True)
-        return []
-
-    def shortid(self, revid):
-        return revid[:7]
-
-types = {
-    'hg': hgsubrepo,
-    'svn': svnsubrepo,
-    'git': gitsubrepo,
-    }



More information about the Mercurial-devel mailing list