mercurial/subrepo.py
changeset 43076 2372284d9457
parent 42588 f6540aba8e3e
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    45 reporelpath = subrepoutil.reporelpath
    45 reporelpath = subrepoutil.reporelpath
    46 subrelpath = subrepoutil.subrelpath
    46 subrelpath = subrepoutil.subrelpath
    47 _abssource = subrepoutil._abssource
    47 _abssource = subrepoutil._abssource
    48 propertycache = util.propertycache
    48 propertycache = util.propertycache
    49 
    49 
       
    50 
    50 def _expandedabspath(path):
    51 def _expandedabspath(path):
    51     '''
    52     '''
    52     get a path or url and if it is a path expand it and return an absolute path
    53     get a path or url and if it is a path expand it and return an absolute path
    53     '''
    54     '''
    54     expandedpath = util.urllocalpath(util.expandpath(path))
    55     expandedpath = util.urllocalpath(util.expandpath(path))
    55     u = util.url(expandedpath)
    56     u = util.url(expandedpath)
    56     if not u.scheme:
    57     if not u.scheme:
    57         path = util.normpath(os.path.abspath(u.path))
    58         path = util.normpath(os.path.abspath(u.path))
    58     return path
    59     return path
    59 
    60 
       
    61 
    60 def _getstorehashcachename(remotepath):
    62 def _getstorehashcachename(remotepath):
    61     '''get a unique filename for the store hash cache of a remote repository'''
    63     '''get a unique filename for the store hash cache of a remote repository'''
    62     return node.hex(hashlib.sha1(_expandedabspath(remotepath)).digest())[0:12]
    64     return node.hex(hashlib.sha1(_expandedabspath(remotepath)).digest())[0:12]
    63 
    65 
       
    66 
    64 class SubrepoAbort(error.Abort):
    67 class SubrepoAbort(error.Abort):
    65     """Exception class used to avoid handling a subrepo error more than once"""
    68     """Exception class used to avoid handling a subrepo error more than once"""
       
    69 
    66     def __init__(self, *args, **kw):
    70     def __init__(self, *args, **kw):
    67         self.subrepo = kw.pop(r'subrepo', None)
    71         self.subrepo = kw.pop(r'subrepo', None)
    68         self.cause = kw.pop(r'cause', None)
    72         self.cause = kw.pop(r'cause', None)
    69         error.Abort.__init__(self, *args, **kw)
    73         error.Abort.__init__(self, *args, **kw)
       
    74 
    70 
    75 
    71 def annotatesubrepoerror(func):
    76 def annotatesubrepoerror(func):
    72     def decoratedmethod(self, *args, **kargs):
    77     def decoratedmethod(self, *args, **kargs):
    73         try:
    78         try:
    74             res = func(self, *args, **kargs)
    79             res = func(self, *args, **kargs)
    75         except SubrepoAbort as ex:
    80         except SubrepoAbort as ex:
    76             # This exception has already been handled
    81             # This exception has already been handled
    77             raise ex
    82             raise ex
    78         except error.Abort as ex:
    83         except error.Abort as ex:
    79             subrepo = subrelpath(self)
    84             subrepo = subrelpath(self)
    80             errormsg = (stringutil.forcebytestr(ex) + ' '
    85             errormsg = (
    81                         + _('(in subrepository "%s")') % subrepo)
    86                 stringutil.forcebytestr(ex)
       
    87                 + ' '
       
    88                 + _('(in subrepository "%s")') % subrepo
       
    89             )
    82             # avoid handling this exception by raising a SubrepoAbort exception
    90             # avoid handling this exception by raising a SubrepoAbort exception
    83             raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
    91             raise SubrepoAbort(
    84                                cause=sys.exc_info())
    92                 errormsg, hint=ex.hint, subrepo=subrepo, cause=sys.exc_info()
       
    93             )
    85         return res
    94         return res
       
    95 
    86     return decoratedmethod
    96     return decoratedmethod
       
    97 
    87 
    98 
    88 def _updateprompt(ui, sub, dirty, local, remote):
    99 def _updateprompt(ui, sub, dirty, local, remote):
    89     if dirty:
   100     if dirty:
    90         msg = (_(' subrepository sources for %s differ\n'
   101         msg = _(
    91                  'you can use (l)ocal source (%s) or (r)emote source (%s).\n'
   102             ' subrepository sources for %s differ\n'
    92                  'what do you want to do?'
   103             'you can use (l)ocal source (%s) or (r)emote source (%s).\n'
    93                  '$$ &Local $$ &Remote')
   104             'what do you want to do?'
    94                % (subrelpath(sub), local, remote))
   105             '$$ &Local $$ &Remote'
       
   106         ) % (subrelpath(sub), local, remote)
    95     else:
   107     else:
    96         msg = (_(' subrepository sources for %s differ (in checked out '
   108         msg = _(
    97                  'version)\n'
   109             ' subrepository sources for %s differ (in checked out '
    98                  'you can use (l)ocal source (%s) or (r)emote source (%s).\n'
   110             'version)\n'
    99                  'what do you want to do?'
   111             'you can use (l)ocal source (%s) or (r)emote source (%s).\n'
   100                  '$$ &Local $$ &Remote')
   112             'what do you want to do?'
   101                % (subrelpath(sub), local, remote))
   113             '$$ &Local $$ &Remote'
       
   114         ) % (subrelpath(sub), local, remote)
   102     return ui.promptchoice(msg, 0)
   115     return ui.promptchoice(msg, 0)
       
   116 
   103 
   117 
   104 def _sanitize(ui, vfs, ignore):
   118 def _sanitize(ui, vfs, ignore):
   105     for dirname, dirs, names in vfs.walk():
   119     for dirname, dirs, names in vfs.walk():
   106         for i, d in enumerate(dirs):
   120         for i, d in enumerate(dirs):
   107             if d.lower() == ignore:
   121             if d.lower() == ignore:
   109                 break
   123                 break
   110         if vfs.basename(dirname).lower() != '.hg':
   124         if vfs.basename(dirname).lower() != '.hg':
   111             continue
   125             continue
   112         for f in names:
   126         for f in names:
   113             if f.lower() == 'hgrc':
   127             if f.lower() == 'hgrc':
   114                 ui.warn(_("warning: removing potentially hostile 'hgrc' "
   128                 ui.warn(
   115                           "in '%s'\n") % vfs.join(dirname))
   129                     _(
       
   130                         "warning: removing potentially hostile 'hgrc' "
       
   131                         "in '%s'\n"
       
   132                     )
       
   133                     % vfs.join(dirname)
       
   134                 )
   116                 vfs.unlink(vfs.reljoin(dirname, f))
   135                 vfs.unlink(vfs.reljoin(dirname, f))
       
   136 
   117 
   137 
   118 def _auditsubrepopath(repo, path):
   138 def _auditsubrepopath(repo, path):
   119     # sanity check for potentially unsafe paths such as '~' and '$FOO'
   139     # sanity check for potentially unsafe paths such as '~' and '$FOO'
   120     if path.startswith('~') or '$' in path or util.expandpath(path) != path:
   140     if path.startswith('~') or '$' in path or util.expandpath(path) != path:
   121         raise error.Abort(_('subrepo path contains illegal component: %s')
   141         raise error.Abort(
   122                           % path)
   142             _('subrepo path contains illegal component: %s') % path
       
   143         )
   123     # auditor doesn't check if the path itself is a symlink
   144     # auditor doesn't check if the path itself is a symlink
   124     pathutil.pathauditor(repo.root)(path)
   145     pathutil.pathauditor(repo.root)(path)
   125     if repo.wvfs.islink(path):
   146     if repo.wvfs.islink(path):
   126         raise error.Abort(_("subrepo '%s' traverses symbolic link") % path)
   147         raise error.Abort(_("subrepo '%s' traverses symbolic link") % path)
       
   148 
   127 
   149 
   128 SUBREPO_ALLOWED_DEFAULTS = {
   150 SUBREPO_ALLOWED_DEFAULTS = {
   129     'hg': True,
   151     'hg': True,
   130     'git': False,
   152     'git': False,
   131     'svn': False,
   153     'svn': False,
   132 }
   154 }
   133 
   155 
       
   156 
   134 def _checktype(ui, kind):
   157 def _checktype(ui, kind):
   135     # subrepos.allowed is a master kill switch. If disabled, subrepos are
   158     # subrepos.allowed is a master kill switch. If disabled, subrepos are
   136     # disabled period.
   159     # disabled period.
   137     if not ui.configbool('subrepos', 'allowed', True):
   160     if not ui.configbool('subrepos', 'allowed', True):
   138         raise error.Abort(_('subrepos not enabled'),
   161         raise error.Abort(
   139                           hint=_("see 'hg help config.subrepos' for details"))
   162             _('subrepos not enabled'),
       
   163             hint=_("see 'hg help config.subrepos' for details"),
       
   164         )
   140 
   165 
   141     default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
   166     default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
   142     if not ui.configbool('subrepos', '%s:allowed' % kind, default):
   167     if not ui.configbool('subrepos', '%s:allowed' % kind, default):
   143         raise error.Abort(_('%s subrepos not allowed') % kind,
   168         raise error.Abort(
   144                           hint=_("see 'hg help config.subrepos' for details"))
   169             _('%s subrepos not allowed') % kind,
       
   170             hint=_("see 'hg help config.subrepos' for details"),
       
   171         )
   145 
   172 
   146     if kind not in types:
   173     if kind not in types:
   147         raise error.Abort(_('unknown subrepo type %s') % kind)
   174         raise error.Abort(_('unknown subrepo type %s') % kind)
       
   175 
   148 
   176 
   149 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
   177 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
   150     """return instance of the right subrepo class for subrepo in path"""
   178     """return instance of the right subrepo class for subrepo in path"""
   151     # subrepo inherently violates our import layering rules
   179     # subrepo inherently violates our import layering rules
   152     # because it wants to make repo objects from deep inside the stack
   180     # because it wants to make repo objects from deep inside the stack
   153     # so we manually delay the circular imports to not break
   181     # so we manually delay the circular imports to not break
   154     # scripts that don't use our demand-loading
   182     # scripts that don't use our demand-loading
   155     global hg
   183     global hg
   156     from . import hg as h
   184     from . import hg as h
       
   185 
   157     hg = h
   186     hg = h
   158 
   187 
   159     repo = ctx.repo()
   188     repo = ctx.repo()
   160     _auditsubrepopath(repo, path)
   189     _auditsubrepopath(repo, path)
   161     state = ctx.substate[path]
   190     state = ctx.substate[path]
   162     _checktype(repo.ui, state[2])
   191     _checktype(repo.ui, state[2])
   163     if allowwdir:
   192     if allowwdir:
   164         state = (state[0], ctx.subrev(path), state[2])
   193         state = (state[0], ctx.subrev(path), state[2])
   165     return types[state[2]](ctx, path, state[:2], allowcreate)
   194     return types[state[2]](ctx, path, state[:2], allowcreate)
       
   195 
   166 
   196 
   167 def nullsubrepo(ctx, path, pctx):
   197 def nullsubrepo(ctx, path, pctx):
   168     """return an empty subrepo in pctx for the extant subrepo in ctx"""
   198     """return an empty subrepo in pctx for the extant subrepo in ctx"""
   169     # subrepo inherently violates our import layering rules
   199     # subrepo inherently violates our import layering rules
   170     # because it wants to make repo objects from deep inside the stack
   200     # because it wants to make repo objects from deep inside the stack
   171     # so we manually delay the circular imports to not break
   201     # so we manually delay the circular imports to not break
   172     # scripts that don't use our demand-loading
   202     # scripts that don't use our demand-loading
   173     global hg
   203     global hg
   174     from . import hg as h
   204     from . import hg as h
       
   205 
   175     hg = h
   206     hg = h
   176 
   207 
   177     repo = ctx.repo()
   208     repo = ctx.repo()
   178     _auditsubrepopath(repo, path)
   209     _auditsubrepopath(repo, path)
   179     state = ctx.substate[path]
   210     state = ctx.substate[path]
   181     subrev = ''
   212     subrev = ''
   182     if state[2] == 'hg':
   213     if state[2] == 'hg':
   183         subrev = "0" * 40
   214         subrev = "0" * 40
   184     return types[state[2]](pctx, path, (state[0], subrev), True)
   215     return types[state[2]](pctx, path, (state[0], subrev), True)
   185 
   216 
       
   217 
   186 # subrepo classes need to implement the following abstract class:
   218 # subrepo classes need to implement the following abstract class:
   187 
   219 
       
   220 
   188 class abstractsubrepo(object):
   221 class abstractsubrepo(object):
   189 
       
   190     def __init__(self, ctx, path):
   222     def __init__(self, ctx, path):
   191         """Initialize abstractsubrepo part
   223         """Initialize abstractsubrepo part
   192 
   224 
   193         ``ctx`` is the context referring this subrepository in the
   225         ``ctx`` is the context referring this subrepository in the
   194         parent repository.
   226         parent repository.
   231         of exception.
   263         of exception.
   232 
   264 
   233         This returns None, otherwise.
   265         This returns None, otherwise.
   234         """
   266         """
   235         if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
   267         if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
   236             return _('uncommitted changes in subrepository "%s"'
   268             return _('uncommitted changes in subrepository "%s"') % subrelpath(
   237                      ) % subrelpath(self)
   269                 self
       
   270             )
   238 
   271 
   239     def bailifchanged(self, ignoreupdate=False, hint=None):
   272     def bailifchanged(self, ignoreupdate=False, hint=None):
   240         """raise Abort if subrepository is ``dirty()``
   273         """raise Abort if subrepository is ``dirty()``
   241         """
   274         """
   242         dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate,
   275         dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate, missing=True)
   243                                        missing=True)
       
   244         if dirtyreason:
   276         if dirtyreason:
   245             raise error.Abort(dirtyreason, hint=hint)
   277             raise error.Abort(dirtyreason, hint=hint)
   246 
   278 
   247     def basestate(self):
   279     def basestate(self):
   248         """current working directory base state, disregarding .hgsubstate
   280         """current working directory base state, disregarding .hgsubstate
   336             files = [f for f in self.files() if match(f)]
   368             files = [f for f in self.files() if match(f)]
   337         else:
   369         else:
   338             files = self.files()
   370             files = self.files()
   339         total = len(files)
   371         total = len(files)
   340         relpath = subrelpath(self)
   372         relpath = subrelpath(self)
   341         progress = self.ui.makeprogress(_('archiving (%s)') % relpath,
   373         progress = self.ui.makeprogress(
   342                                         unit=_('files'), total=total)
   374             _('archiving (%s)') % relpath, unit=_('files'), total=total
       
   375         )
   343         progress.update(0)
   376         progress.update(0)
   344         for name in files:
   377         for name in files:
   345             flags = self.fileflags(name)
   378             flags = self.fileflags(name)
   346             mode = 'x' in flags and 0o755 or 0o644
   379             mode = 'x' in flags and 0o755 or 0o644
   347             symlink = 'l' in flags
   380             symlink = 'l' in flags
   348             archiver.addfile(prefix + name, mode, symlink,
   381             archiver.addfile(
   349                              self.filedata(name, decode))
   382                 prefix + name, mode, symlink, self.filedata(name, decode)
       
   383             )
   350             progress.increment()
   384             progress.increment()
   351         progress.complete()
   385         progress.complete()
   352         return total
   386         return total
   353 
   387 
   354     def walk(self, match):
   388     def walk(self, match):
   358         '''
   392         '''
   359 
   393 
   360     def forget(self, match, prefix, uipathfn, dryrun, interactive):
   394     def forget(self, match, prefix, uipathfn, dryrun, interactive):
   361         return ([], [])
   395         return ([], [])
   362 
   396 
   363     def removefiles(self, matcher, prefix, uipathfn, after, force, subrepos,
   397     def removefiles(
   364                     dryrun, warnings):
   398         self,
       
   399         matcher,
       
   400         prefix,
       
   401         uipathfn,
       
   402         after,
       
   403         force,
       
   404         subrepos,
       
   405         dryrun,
       
   406         warnings,
       
   407     ):
   365         """remove the matched files from the subrepository and the filesystem,
   408         """remove the matched files from the subrepository and the filesystem,
   366         possibly by force and/or after the file has been removed from the
   409         possibly by force and/or after the file has been removed from the
   367         filesystem.  Return 0 on success, 1 on any warning.
   410         filesystem.  Return 0 on success, 1 on any warning.
   368         """
   411         """
   369         warnings.append(_("warning: removefiles not implemented (%s)")
   412         warnings.append(
   370                         % self._path)
   413             _("warning: removefiles not implemented (%s)") % self._path
       
   414         )
   371         return 1
   415         return 1
   372 
   416 
   373     def revert(self, substate, *pats, **opts):
   417     def revert(self, substate, *pats, **opts):
   374         self.ui.warn(_('%s: reverting %s subrepos is unsupported\n')
   418         self.ui.warn(
   375                      % (substate[0], substate[2]))
   419             _('%s: reverting %s subrepos is unsupported\n')
       
   420             % (substate[0], substate[2])
       
   421         )
   376         return []
   422         return []
   377 
   423 
   378     def shortid(self, revid):
   424     def shortid(self, revid):
   379         return revid
   425         return revid
   380 
   426 
   398     @propertycache
   444     @propertycache
   399     def _relpath(self):
   445     def _relpath(self):
   400         """return path to this subrepository as seen from outermost repository
   446         """return path to this subrepository as seen from outermost repository
   401         """
   447         """
   402         return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
   448         return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
       
   449 
   403 
   450 
   404 class hgsubrepo(abstractsubrepo):
   451 class hgsubrepo(abstractsubrepo):
   405     def __init__(self, ctx, path, state, allowcreate):
   452     def __init__(self, ctx, path, state, allowcreate):
   406         super(hgsubrepo, self).__init__(ctx, path)
   453         super(hgsubrepo, self).__init__(ctx, path)
   407         self._state = state
   454         self._state = state
   409         root = r.wjoin(util.localpath(path))
   456         root = r.wjoin(util.localpath(path))
   410         create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
   457         create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
   411         # repository constructor does expand variables in path, which is
   458         # repository constructor does expand variables in path, which is
   412         # unsafe since subrepo path might come from untrusted source.
   459         # unsafe since subrepo path might come from untrusted source.
   413         if os.path.realpath(util.expandpath(root)) != root:
   460         if os.path.realpath(util.expandpath(root)) != root:
   414             raise error.Abort(_('subrepo path contains illegal component: %s')
   461             raise error.Abort(
   415                               % path)
   462                 _('subrepo path contains illegal component: %s') % path
       
   463             )
   416         self._repo = hg.repository(r.baseui, root, create=create)
   464         self._repo = hg.repository(r.baseui, root, create=create)
   417         if self._repo.root != root:
   465         if self._repo.root != root:
   418             raise error.ProgrammingError('failed to reject unsafe subrepo '
   466             raise error.ProgrammingError(
   419                                          'path: %s (expanded to %s)'
   467                 'failed to reject unsafe subrepo '
   420                                          % (root, self._repo.root))
   468                 'path: %s (expanded to %s)' % (root, self._repo.root)
       
   469             )
   421 
   470 
   422         # Propagate the parent's --hidden option
   471         # Propagate the parent's --hidden option
   423         if r is r.unfiltered():
   472         if r is r.unfiltered():
   424             self._repo = self._repo.unfiltered()
   473             self._repo = self._repo.unfiltered()
   425 
   474 
   517 
   566 
   518             self._repo.vfs.write('hgrc', util.tonativeeol(''.join(lines)))
   567             self._repo.vfs.write('hgrc', util.tonativeeol(''.join(lines)))
   519 
   568 
   520     @annotatesubrepoerror
   569     @annotatesubrepoerror
   521     def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
   570     def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
   522         return cmdutil.add(ui, self._repo, match, prefix, uipathfn,
   571         return cmdutil.add(
   523                            explicitonly, **opts)
   572             ui, self._repo, match, prefix, uipathfn, explicitonly, **opts
       
   573         )
   524 
   574 
   525     @annotatesubrepoerror
   575     @annotatesubrepoerror
   526     def addremove(self, m, prefix, uipathfn, opts):
   576     def addremove(self, m, prefix, uipathfn, opts):
   527         # In the same way as sub directories are processed, once in a subrepo,
   577         # In the same way as sub directories are processed, once in a subrepo,
   528         # always entry any of its subrepos.  Don't corrupt the options that will
   578         # always entry any of its subrepos.  Don't corrupt the options that will
   533 
   583 
   534     @annotatesubrepoerror
   584     @annotatesubrepoerror
   535     def cat(self, match, fm, fntemplate, prefix, **opts):
   585     def cat(self, match, fm, fntemplate, prefix, **opts):
   536         rev = self._state[1]
   586         rev = self._state[1]
   537         ctx = self._repo[rev]
   587         ctx = self._repo[rev]
   538         return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate,
   588         return cmdutil.cat(
   539                            prefix, **opts)
   589             self.ui, self._repo, ctx, match, fm, fntemplate, prefix, **opts
       
   590         )
   540 
   591 
   541     @annotatesubrepoerror
   592     @annotatesubrepoerror
   542     def status(self, rev2, **opts):
   593     def status(self, rev2, **opts):
   543         try:
   594         try:
   544             rev1 = self._state[1]
   595             rev1 = self._state[1]
   545             ctx1 = self._repo[rev1]
   596             ctx1 = self._repo[rev1]
   546             ctx2 = self._repo[rev2]
   597             ctx2 = self._repo[rev2]
   547             return self._repo.status(ctx1, ctx2, **opts)
   598             return self._repo.status(ctx1, ctx2, **opts)
   548         except error.RepoLookupError as inst:
   599         except error.RepoLookupError as inst:
   549             self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
   600             self.ui.warn(
   550                          % (inst, subrelpath(self)))
   601                 _('warning: error "%s" in subrepository "%s"\n')
       
   602                 % (inst, subrelpath(self))
       
   603             )
   551             return scmutil.status([], [], [], [], [], [], [])
   604             return scmutil.status([], [], [], [], [], [], [])
   552 
   605 
   553     @annotatesubrepoerror
   606     @annotatesubrepoerror
   554     def diff(self, ui, diffopts, node2, match, prefix, **opts):
   607     def diff(self, ui, diffopts, node2, match, prefix, **opts):
   555         try:
   608         try:
   556             node1 = node.bin(self._state[1])
   609             node1 = node.bin(self._state[1])
   557             # We currently expect node2 to come from substate and be
   610             # We currently expect node2 to come from substate and be
   558             # in hex format
   611             # in hex format
   559             if node2 is not None:
   612             if node2 is not None:
   560                 node2 = node.bin(node2)
   613                 node2 = node.bin(node2)
   561             logcmdutil.diffordiffstat(ui, self._repo, diffopts, node1, node2,
   614             logcmdutil.diffordiffstat(
   562                                       match, prefix=prefix, listsubrepos=True,
   615                 ui,
   563                                       **opts)
   616                 self._repo,
       
   617                 diffopts,
       
   618                 node1,
       
   619                 node2,
       
   620                 match,
       
   621                 prefix=prefix,
       
   622                 listsubrepos=True,
       
   623                 **opts
       
   624             )
   564         except error.RepoLookupError as inst:
   625         except error.RepoLookupError as inst:
   565             self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
   626             self.ui.warn(
   566                           % (inst, subrelpath(self)))
   627                 _('warning: error "%s" in subrepository "%s"\n')
       
   628                 % (inst, subrelpath(self))
       
   629             )
   567 
   630 
   568     @annotatesubrepoerror
   631     @annotatesubrepoerror
   569     def archive(self, archiver, prefix, match=None, decode=True):
   632     def archive(self, archiver, prefix, match=None, decode=True):
   570         self._get(self._state + ('hg',))
   633         self._get(self._state + ('hg',))
   571         files = self.files()
   634         files = self.files()
   572         if match:
   635         if match:
   573             files = [f for f in files if match(f)]
   636             files = [f for f in files if match(f)]
   574         rev = self._state[1]
   637         rev = self._state[1]
   575         ctx = self._repo[rev]
   638         ctx = self._repo[rev]
   576         scmutil.prefetchfiles(self._repo, [ctx.rev()],
   639         scmutil.prefetchfiles(
   577                               scmutil.matchfiles(self._repo, files))
   640             self._repo, [ctx.rev()], scmutil.matchfiles(self._repo, files)
       
   641         )
   578         total = abstractsubrepo.archive(self, archiver, prefix, match)
   642         total = abstractsubrepo.archive(self, archiver, prefix, match)
   579         for subpath in ctx.substate:
   643         for subpath in ctx.substate:
   580             s = subrepo(ctx, subpath, True)
   644             s = subrepo(ctx, subpath, True)
   581             submatch = matchmod.subdirmatcher(subpath, match)
   645             submatch = matchmod.subdirmatcher(subpath, match)
   582             subprefix = prefix + subpath + '/'
   646             subprefix = prefix + subpath + '/'
   583             total += s.archive(archiver, subprefix, submatch,
   647             total += s.archive(archiver, subprefix, submatch, decode)
   584                                decode)
       
   585         return total
   648         return total
   586 
   649 
   587     @annotatesubrepoerror
   650     @annotatesubrepoerror
   588     def dirty(self, ignoreupdate=False, missing=False):
   651     def dirty(self, ignoreupdate=False, missing=False):
   589         r = self._state[1]
   652         r = self._state[1]
   590         if r == '' and not ignoreupdate: # no state recorded
   653         if r == '' and not ignoreupdate:  # no state recorded
   591             return True
   654             return True
   592         w = self._repo[None]
   655         w = self._repo[None]
   593         if r != w.p1().hex() and not ignoreupdate:
   656         if r != w.p1().hex() and not ignoreupdate:
   594             # different version checked out
   657             # different version checked out
   595             return True
   658             return True
   596         return w.dirty(missing=missing) # working directory changed
   659         return w.dirty(missing=missing)  # working directory changed
   597 
   660 
   598     def basestate(self):
   661     def basestate(self):
   599         return self._repo['.'].hex()
   662         return self._repo['.'].hex()
   600 
   663 
   601     def checknested(self, path):
   664     def checknested(self, path):
   608         if not self.dirty(True):
   671         if not self.dirty(True):
   609             return self._repo['.'].hex()
   672             return self._repo['.'].hex()
   610         self.ui.debug("committing subrepo %s\n" % subrelpath(self))
   673         self.ui.debug("committing subrepo %s\n" % subrelpath(self))
   611         n = self._repo.commit(text, user, date)
   674         n = self._repo.commit(text, user, date)
   612         if not n:
   675         if not n:
   613             return self._repo['.'].hex() # different version checked out
   676             return self._repo['.'].hex()  # different version checked out
   614         return node.hex(n)
   677         return node.hex(n)
   615 
   678 
   616     @annotatesubrepoerror
   679     @annotatesubrepoerror
   617     def phase(self, state):
   680     def phase(self, state):
   618         return self._repo[state or '.'].phase()
   681         return self._repo[state or '.'].phase()
   647             # relative to the parent's share source.  But clone pooling doesn't
   710             # relative to the parent's share source.  But clone pooling doesn't
   648             # assemble the repos in a tree, so that can't be consistently done.
   711             # assemble the repos in a tree, so that can't be consistently done.
   649             # A simpler option is for the user to configure clone pooling, and
   712             # A simpler option is for the user to configure clone pooling, and
   650             # work with that.
   713             # work with that.
   651             if parentrepo.shared() and hg.islocal(srcurl):
   714             if parentrepo.shared() and hg.islocal(srcurl):
   652                 self.ui.status(_('sharing subrepo %s from %s\n')
   715                 self.ui.status(
   653                                % (subrelpath(self), srcurl))
   716                     _('sharing subrepo %s from %s\n')
   654                 shared = hg.share(self._repo._subparent.baseui,
   717                     % (subrelpath(self), srcurl)
   655                                   getpeer(), self._repo.root,
   718                 )
   656                                   update=False, bookmarks=False)
   719                 shared = hg.share(
       
   720                     self._repo._subparent.baseui,
       
   721                     getpeer(),
       
   722                     self._repo.root,
       
   723                     update=False,
       
   724                     bookmarks=False,
       
   725                 )
   657                 self._repo = shared.local()
   726                 self._repo = shared.local()
   658             else:
   727             else:
   659                 # TODO: find a common place for this and this code in the
   728                 # TODO: find a common place for this and this code in the
   660                 # share.py wrap of the clone command.
   729                 # share.py wrap of the clone command.
   661                 if parentrepo.shared():
   730                 if parentrepo.shared():
   668                         'mode': self.ui.config('share', 'poolnaming'),
   737                         'mode': self.ui.config('share', 'poolnaming'),
   669                     }
   738                     }
   670                 else:
   739                 else:
   671                     shareopts = {}
   740                     shareopts = {}
   672 
   741 
   673                 self.ui.status(_('cloning subrepo %s from %s\n')
   742                 self.ui.status(
   674                                % (subrelpath(self), util.hidepassword(srcurl)))
   743                     _('cloning subrepo %s from %s\n')
   675                 other, cloned = hg.clone(self._repo._subparent.baseui, {},
   744                     % (subrelpath(self), util.hidepassword(srcurl))
   676                                          getpeer(), self._repo.root,
   745                 )
   677                                          update=False, shareopts=shareopts)
   746                 other, cloned = hg.clone(
       
   747                     self._repo._subparent.baseui,
       
   748                     {},
       
   749                     getpeer(),
       
   750                     self._repo.root,
       
   751                     update=False,
       
   752                     shareopts=shareopts,
       
   753                 )
   678                 self._repo = cloned.local()
   754                 self._repo = cloned.local()
   679             self._initrepo(parentrepo, source, create=True)
   755             self._initrepo(parentrepo, source, create=True)
   680             self._cachestorehash(srcurl)
   756             self._cachestorehash(srcurl)
   681         else:
   757         else:
   682             self.ui.status(_('pulling subrepo %s from %s\n')
   758             self.ui.status(
   683                            % (subrelpath(self), util.hidepassword(srcurl)))
   759                 _('pulling subrepo %s from %s\n')
       
   760                 % (subrelpath(self), util.hidepassword(srcurl))
       
   761             )
   684             cleansub = self.storeclean(srcurl)
   762             cleansub = self.storeclean(srcurl)
   685             exchange.pull(self._repo, getpeer())
   763             exchange.pull(self._repo, getpeer())
   686             if cleansub:
   764             if cleansub:
   687                 # keep the repo clean after pull
   765                 # keep the repo clean after pull
   688                 self._cachestorehash(srcurl)
   766                 self._cachestorehash(srcurl)
   698             urepo = repo.unfiltered()
   776             urepo = repo.unfiltered()
   699             ctx = urepo[revision]
   777             ctx = urepo[revision]
   700             if ctx.hidden():
   778             if ctx.hidden():
   701                 urepo.ui.warn(
   779                 urepo.ui.warn(
   702                     _('revision %s in subrepository "%s" is hidden\n')
   780                     _('revision %s in subrepository "%s" is hidden\n')
   703                     % (revision[0:12], self._path))
   781                     % (revision[0:12], self._path)
       
   782                 )
   704                 repo = urepo
   783                 repo = urepo
   705         hg.updaterepo(repo, revision, overwrite)
   784         hg.updaterepo(repo, revision, overwrite)
   706 
   785 
   707     @annotatesubrepoerror
   786     @annotatesubrepoerror
   708     def merge(self, state):
   787     def merge(self, state):
   711         dst = self._repo[state[1]]
   790         dst = self._repo[state[1]]
   712         anc = dst.ancestor(cur)
   791         anc = dst.ancestor(cur)
   713 
   792 
   714         def mergefunc():
   793         def mergefunc():
   715             if anc == cur and dst.branch() == cur.branch():
   794             if anc == cur and dst.branch() == cur.branch():
   716                 self.ui.debug('updating subrepository "%s"\n'
   795                 self.ui.debug(
   717                               % subrelpath(self))
   796                     'updating subrepository "%s"\n' % subrelpath(self)
       
   797                 )
   718                 hg.update(self._repo, state[1])
   798                 hg.update(self._repo, state[1])
   719             elif anc == dst:
   799             elif anc == dst:
   720                 self.ui.debug('skipping subrepository "%s"\n'
   800                 self.ui.debug(
   721                               % subrelpath(self))
   801                     'skipping subrepository "%s"\n' % subrelpath(self)
       
   802                 )
   722             else:
   803             else:
   723                 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
   804                 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
   724                 hg.merge(self._repo, state[1], remind=False)
   805                 hg.merge(self._repo, state[1], remind=False)
   725 
   806 
   726         wctx = self._repo[None]
   807         wctx = self._repo[None]
   739         newbranch = opts.get('new_branch')
   820         newbranch = opts.get('new_branch')
   740         ssh = opts.get('ssh')
   821         ssh = opts.get('ssh')
   741 
   822 
   742         # push subrepos depth-first for coherent ordering
   823         # push subrepos depth-first for coherent ordering
   743         c = self._repo['.']
   824         c = self._repo['.']
   744         subs = c.substate # only repos that are committed
   825         subs = c.substate  # only repos that are committed
   745         for s in sorted(subs):
   826         for s in sorted(subs):
   746             if c.sub(s).push(opts) == 0:
   827             if c.sub(s).push(opts) == 0:
   747                 return False
   828                 return False
   748 
   829 
   749         dsturl = _abssource(self._repo, True)
   830         dsturl = _abssource(self._repo, True)
   750         if not force:
   831         if not force:
   751             if self.storeclean(dsturl):
   832             if self.storeclean(dsturl):
   752                 self.ui.status(
   833                 self.ui.status(
   753                     _('no changes made to subrepo %s since last push to %s\n')
   834                     _('no changes made to subrepo %s since last push to %s\n')
   754                     % (subrelpath(self), util.hidepassword(dsturl)))
   835                     % (subrelpath(self), util.hidepassword(dsturl))
       
   836                 )
   755                 return None
   837                 return None
   756         self.ui.status(_('pushing subrepo %s to %s\n') %
   838         self.ui.status(
   757             (subrelpath(self), util.hidepassword(dsturl)))
   839             _('pushing subrepo %s to %s\n')
       
   840             % (subrelpath(self), util.hidepassword(dsturl))
       
   841         )
   758         other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
   842         other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
   759         res = exchange.push(self._repo, other, force, newbranch=newbranch)
   843         res = exchange.push(self._repo, other, force, newbranch=newbranch)
   760 
   844 
   761         # the repo is now clean
   845         # the repo is now clean
   762         self._cachestorehash(dsturl)
   846         self._cachestorehash(dsturl)
   823             try:
   907             try:
   824                 sm = sub.matchfileset(expr, badfn=badfn)
   908                 sm = sub.matchfileset(expr, badfn=badfn)
   825                 pm = matchmod.prefixdirmatcher(subpath, sm, badfn=badfn)
   909                 pm = matchmod.prefixdirmatcher(subpath, sm, badfn=badfn)
   826                 matchers.append(pm)
   910                 matchers.append(pm)
   827             except error.LookupError:
   911             except error.LookupError:
   828                 self.ui.status(_("skipping missing subrepository: %s\n")
   912                 self.ui.status(
   829                                % self.wvfs.reljoin(reporelpath(self), subpath))
   913                     _("skipping missing subrepository: %s\n")
       
   914                     % self.wvfs.reljoin(reporelpath(self), subpath)
       
   915                 )
   830         if len(matchers) == 1:
   916         if len(matchers) == 1:
   831             return matchers[0]
   917             return matchers[0]
   832         return matchmod.unionmatcher(matchers)
   918         return matchmod.unionmatcher(matchers)
   833 
   919 
   834     def walk(self, match):
   920     def walk(self, match):
   835         ctx = self._repo[None]
   921         ctx = self._repo[None]
   836         return ctx.walk(match)
   922         return ctx.walk(match)
   837 
   923 
   838     @annotatesubrepoerror
   924     @annotatesubrepoerror
   839     def forget(self, match, prefix, uipathfn, dryrun, interactive):
   925     def forget(self, match, prefix, uipathfn, dryrun, interactive):
   840         return cmdutil.forget(self.ui, self._repo, match, prefix, uipathfn,
   926         return cmdutil.forget(
   841                               True, dryrun=dryrun, interactive=interactive)
   927             self.ui,
   842 
   928             self._repo,
   843     @annotatesubrepoerror
   929             match,
   844     def removefiles(self, matcher, prefix, uipathfn, after, force, subrepos,
   930             prefix,
   845                     dryrun, warnings):
   931             uipathfn,
   846         return cmdutil.remove(self.ui, self._repo, matcher, prefix, uipathfn,
   932             True,
   847                               after, force, subrepos, dryrun)
   933             dryrun=dryrun,
       
   934             interactive=interactive,
       
   935         )
       
   936 
       
   937     @annotatesubrepoerror
       
   938     def removefiles(
       
   939         self,
       
   940         matcher,
       
   941         prefix,
       
   942         uipathfn,
       
   943         after,
       
   944         force,
       
   945         subrepos,
       
   946         dryrun,
       
   947         warnings,
       
   948     ):
       
   949         return cmdutil.remove(
       
   950             self.ui,
       
   951             self._repo,
       
   952             matcher,
       
   953             prefix,
       
   954             uipathfn,
       
   955             after,
       
   956             force,
       
   957             subrepos,
       
   958             dryrun,
       
   959         )
   848 
   960 
   849     @annotatesubrepoerror
   961     @annotatesubrepoerror
   850     def revert(self, substate, *pats, **opts):
   962     def revert(self, substate, *pats, **opts):
   851         # reverting a subrepo is a 2 step process:
   963         # reverting a subrepo is a 2 step process:
   852         # 1. if the no_backup is not set, revert all modified
   964         # 1. if the no_backup is not set, revert all modified
   886         # because it wants to make repo objects from deep inside the stack
   998         # because it wants to make repo objects from deep inside the stack
   887         # so we manually delay the circular imports to not break
   999         # so we manually delay the circular imports to not break
   888         # scripts that don't use our demand-loading
  1000         # scripts that don't use our demand-loading
   889         global hg
  1001         global hg
   890         from . import hg as h
  1002         from . import hg as h
       
  1003 
   891         hg = h
  1004         hg = h
   892 
  1005 
   893         # Nothing prevents a user from sharing in a repo, and then making that a
  1006         # Nothing prevents a user from sharing in a repo, and then making that a
   894         # subrepo.  Alternately, the previous unshare attempt may have failed
  1007         # subrepo.  Alternately, the previous unshare attempt may have failed
   895         # part way through.  So recurse whether or not this layer is shared.
  1008         # part way through.  So recurse whether or not this layer is shared.
   904             ctx = self._repo.unfiltered()[rev]
  1017             ctx = self._repo.unfiltered()[rev]
   905             if ctx.hidden():
  1018             if ctx.hidden():
   906                 # Since hidden revisions aren't pushed/pulled, it seems worth an
  1019                 # Since hidden revisions aren't pushed/pulled, it seems worth an
   907                 # explicit warning.
  1020                 # explicit warning.
   908                 ui = self._repo.ui
  1021                 ui = self._repo.ui
   909                 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
  1022                 ui.warn(
   910                         (self._relpath, node.short(self._ctx.node())))
  1023                     _("subrepo '%s' is hidden in revision %s\n")
       
  1024                     % (self._relpath, node.short(self._ctx.node()))
       
  1025                 )
   911             return 0
  1026             return 0
   912         except error.RepoLookupError:
  1027         except error.RepoLookupError:
   913             # A missing subrepo revision may be a case of needing to pull it, so
  1028             # A missing subrepo revision may be a case of needing to pull it, so
   914             # don't treat this as an error.
  1029             # don't treat this as an error.
   915             self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
  1030             self._repo.ui.warn(
   916                                (self._relpath, node.short(self._ctx.node())))
  1031                 _("subrepo '%s' not found in revision %s\n")
       
  1032                 % (self._relpath, node.short(self._ctx.node()))
       
  1033             )
   917             return 0
  1034             return 0
   918 
  1035 
   919     @propertycache
  1036     @propertycache
   920     def wvfs(self):
  1037     def wvfs(self):
   921         """return own wvfs for efficiency and consistency
  1038         """return own wvfs for efficiency and consistency
   927         """return path to this subrepository as seen from outermost repository
  1044         """return path to this subrepository as seen from outermost repository
   928         """
  1045         """
   929         # Keep consistent dir separators by avoiding vfs.join(self._path)
  1046         # Keep consistent dir separators by avoiding vfs.join(self._path)
   930         return reporelpath(self._repo)
  1047         return reporelpath(self._repo)
   931 
  1048 
       
  1049 
   932 class svnsubrepo(abstractsubrepo):
  1050 class svnsubrepo(abstractsubrepo):
   933     def __init__(self, ctx, path, state, allowcreate):
  1051     def __init__(self, ctx, path, state, allowcreate):
   934         super(svnsubrepo, self).__init__(ctx, path)
  1052         super(svnsubrepo, self).__init__(ctx, path)
   935         self._state = state
  1053         self._state = state
   936         self._exe = procutil.findexe('svn')
  1054         self._exe = procutil.findexe('svn')
   937         if not self._exe:
  1055         if not self._exe:
   938             raise error.Abort(_("'svn' executable not found for subrepo '%s'")
  1056             raise error.Abort(
   939                              % self._path)
  1057                 _("'svn' executable not found for subrepo '%s'") % self._path
       
  1058             )
   940 
  1059 
   941     def _svncommand(self, commands, filename='', failok=False):
  1060     def _svncommand(self, commands, filename='', failok=False):
   942         cmd = [self._exe]
  1061         cmd = [self._exe]
   943         extrakw = {}
  1062         extrakw = {}
   944         if not self.ui.interactive():
  1063         if not self.ui.interactive():
   951             # --non-interactive.
  1070             # --non-interactive.
   952             if commands[0] in ('update', 'checkout', 'commit'):
  1071             if commands[0] in ('update', 'checkout', 'commit'):
   953                 cmd.append('--non-interactive')
  1072                 cmd.append('--non-interactive')
   954         cmd.extend(commands)
  1073         cmd.extend(commands)
   955         if filename is not None:
  1074         if filename is not None:
   956             path = self.wvfs.reljoin(self._ctx.repo().origroot,
  1075             path = self.wvfs.reljoin(
   957                                      self._path, filename)
  1076                 self._ctx.repo().origroot, self._path, filename
       
  1077             )
   958             cmd.append(path)
  1078             cmd.append(path)
   959         env = dict(encoding.environ)
  1079         env = dict(encoding.environ)
   960         # Avoid localized output, preserve current locale for everything else.
  1080         # Avoid localized output, preserve current locale for everything else.
   961         lc_all = env.get('LC_ALL')
  1081         lc_all = env.get('LC_ALL')
   962         if lc_all:
  1082         if lc_all:
   963             env['LANG'] = lc_all
  1083             env['LANG'] = lc_all
   964             del env['LC_ALL']
  1084             del env['LC_ALL']
   965         env['LC_MESSAGES'] = 'C'
  1085         env['LC_MESSAGES'] = 'C'
   966         p = subprocess.Popen(pycompat.rapply(procutil.tonativestr, cmd),
  1086         p = subprocess.Popen(
   967                              bufsize=-1, close_fds=procutil.closefds,
  1087             pycompat.rapply(procutil.tonativestr, cmd),
   968                              stdout=subprocess.PIPE, stderr=subprocess.PIPE,
  1088             bufsize=-1,
   969                              env=procutil.tonativeenv(env), **extrakw)
  1089             close_fds=procutil.closefds,
       
  1090             stdout=subprocess.PIPE,
       
  1091             stderr=subprocess.PIPE,
       
  1092             env=procutil.tonativeenv(env),
       
  1093             **extrakw
       
  1094         )
   970         stdout, stderr = map(util.fromnativeeol, p.communicate())
  1095         stdout, stderr = map(util.fromnativeeol, p.communicate())
   971         stderr = stderr.strip()
  1096         stderr = stderr.strip()
   972         if not failok:
  1097         if not failok:
   973             if p.returncode:
  1098             if p.returncode:
   974                 raise error.Abort(stderr or 'exited with code %d'
  1099                 raise error.Abort(
   975                                   % p.returncode)
  1100                     stderr or 'exited with code %d' % p.returncode
       
  1101                 )
   976             if stderr:
  1102             if stderr:
   977                 self.ui.warn(stderr + '\n')
  1103                 self.ui.warn(stderr + '\n')
   978         return stdout, stderr
  1104         return stdout, stderr
   979 
  1105 
   980     @propertycache
  1106     @propertycache
   998         lastrev, rev = '0', '0'
  1124         lastrev, rev = '0', '0'
   999         if entries:
  1125         if entries:
  1000             rev = pycompat.bytestr(entries[0].getAttribute(r'revision')) or '0'
  1126             rev = pycompat.bytestr(entries[0].getAttribute(r'revision')) or '0'
  1001             commits = entries[0].getElementsByTagName(r'commit')
  1127             commits = entries[0].getElementsByTagName(r'commit')
  1002             if commits:
  1128             if commits:
  1003                 lastrev = pycompat.bytestr(
  1129                 lastrev = (
  1004                     commits[0].getAttribute(r'revision')) or '0'
  1130                     pycompat.bytestr(commits[0].getAttribute(r'revision'))
       
  1131                     or '0'
       
  1132                 )
  1005         return (lastrev, rev)
  1133         return (lastrev, rev)
  1006 
  1134 
  1007     def _wcrev(self):
  1135     def _wcrev(self):
  1008         return self._wcrevs()[0]
  1136         return self._wcrevs()[0]
  1009 
  1137 
  1025             path = e.getAttribute(r'path').encode('utf8')
  1153             path = e.getAttribute(r'path').encode('utf8')
  1026             if item == r'external':
  1154             if item == r'external':
  1027                 externals.append(path)
  1155                 externals.append(path)
  1028             elif item == r'missing':
  1156             elif item == r'missing':
  1029                 missing.append(path)
  1157                 missing.append(path)
  1030             if (item not in (r'', r'normal', r'unversioned', r'external')
  1158             if item not in (
  1031                 or props not in (r'', r'none', r'normal')):
  1159                 r'',
       
  1160                 r'normal',
       
  1161                 r'unversioned',
       
  1162                 r'external',
       
  1163             ) or props not in (r'', r'none', r'normal'):
  1032                 changes.append(path)
  1164                 changes.append(path)
  1033         for path in changes:
  1165         for path in changes:
  1034             for ext in externals:
  1166             for ext in externals:
  1035                 if path == ext or path.startswith(ext + pycompat.ossep):
  1167                 if path == ext or path.startswith(ext + pycompat.ossep):
  1036                     return True, True, bool(missing)
  1168                     return True, True, bool(missing)
  1090         return newrev
  1222         return newrev
  1091 
  1223 
  1092     @annotatesubrepoerror
  1224     @annotatesubrepoerror
  1093     def remove(self):
  1225     def remove(self):
  1094         if self.dirty():
  1226         if self.dirty():
  1095             self.ui.warn(_('not removing repo %s because '
  1227             self.ui.warn(
  1096                            'it has changes.\n') % self._path)
  1228                 _('not removing repo %s because ' 'it has changes.\n')
       
  1229                 % self._path
       
  1230             )
  1097             return
  1231             return
  1098         self.ui.note(_('removing subrepo %s\n') % self._path)
  1232         self.ui.note(_('removing subrepo %s\n') % self._path)
  1099 
  1233 
  1100         self.wvfs.rmtree(forcibly=True)
  1234         self.wvfs.rmtree(forcibly=True)
  1101         try:
  1235         try:
  1119         util.checksafessh(state[0])
  1253         util.checksafessh(state[0])
  1120 
  1254 
  1121         status, err = self._svncommand(args, failok=True)
  1255         status, err = self._svncommand(args, failok=True)
  1122         _sanitize(self.ui, self.wvfs, '.svn')
  1256         _sanitize(self.ui, self.wvfs, '.svn')
  1123         if not re.search('Checked out revision [0-9]+.', status):
  1257         if not re.search('Checked out revision [0-9]+.', status):
  1124             if ('is already a working copy for a different URL' in err
  1258             if 'is already a working copy for a different URL' in err and (
  1125                 and (self._wcchanged()[:2] == (False, False))):
  1259                 self._wcchanged()[:2] == (False, False)
       
  1260             ):
  1126                 # obstructed but clean working copy, so just blow it away.
  1261                 # obstructed but clean working copy, so just blow it away.
  1127                 self.remove()
  1262                 self.remove()
  1128                 self.get(state, overwrite=False)
  1263                 self.get(state, overwrite=False)
  1129                 return
  1264                 return
  1130             raise error.Abort((status or err).splitlines()[-1])
  1265             raise error.Abort((status or err).splitlines()[-1])
  1151         paths = []
  1286         paths = []
  1152         for e in doc.getElementsByTagName(r'entry'):
  1287         for e in doc.getElementsByTagName(r'entry'):
  1153             kind = pycompat.bytestr(e.getAttribute(r'kind'))
  1288             kind = pycompat.bytestr(e.getAttribute(r'kind'))
  1154             if kind != 'file':
  1289             if kind != 'file':
  1155                 continue
  1290                 continue
  1156             name = r''.join(c.data for c
  1291             name = r''.join(
  1157                             in e.getElementsByTagName(r'name')[0].childNodes
  1292                 c.data
  1158                             if c.nodeType == c.TEXT_NODE)
  1293                 for c in e.getElementsByTagName(r'name')[0].childNodes
       
  1294                 if c.nodeType == c.TEXT_NODE
       
  1295             )
  1159             paths.append(name.encode('utf8'))
  1296             paths.append(name.encode('utf8'))
  1160         return paths
  1297         return paths
  1161 
  1298 
  1162     def filedata(self, name, decode):
  1299     def filedata(self, name, decode):
  1163         return self._svncommand(['cat'], name)[0]
  1300         return self._svncommand(['cat'], name)[0]
  1177             out, err = self._gitnodir(['--version'])
  1314             out, err = self._gitnodir(['--version'])
  1178         except OSError as e:
  1315         except OSError as e:
  1179             genericerror = _("error executing git for subrepo '%s': %s")
  1316             genericerror = _("error executing git for subrepo '%s': %s")
  1180             notfoundhint = _("check git is installed and in your PATH")
  1317             notfoundhint = _("check git is installed and in your PATH")
  1181             if e.errno != errno.ENOENT:
  1318             if e.errno != errno.ENOENT:
  1182                 raise error.Abort(genericerror % (
  1319                 raise error.Abort(
  1183                     self._path, encoding.strtolocal(e.strerror)))
  1320                     genericerror % (self._path, encoding.strtolocal(e.strerror))
       
  1321                 )
  1184             elif pycompat.iswindows:
  1322             elif pycompat.iswindows:
  1185                 try:
  1323                 try:
  1186                     self._gitexecutable = 'git.cmd'
  1324                     self._gitexecutable = 'git.cmd'
  1187                     out, err = self._gitnodir(['--version'])
  1325                     out, err = self._gitnodir(['--version'])
  1188                 except OSError as e2:
  1326                 except OSError as e2:
  1189                     if e2.errno == errno.ENOENT:
  1327                     if e2.errno == errno.ENOENT:
  1190                         raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
  1328                         raise error.Abort(
  1191                             " for subrepo '%s'") % self._path,
  1329                             _(
  1192                             hint=notfoundhint)
  1330                                 "couldn't find 'git' or 'git.cmd'"
       
  1331                                 " for subrepo '%s'"
       
  1332                             )
       
  1333                             % self._path,
       
  1334                             hint=notfoundhint,
       
  1335                         )
  1193                     else:
  1336                     else:
  1194                         raise error.Abort(genericerror % (self._path,
  1337                         raise error.Abort(
  1195                             encoding.strtolocal(e2.strerror)))
  1338                             genericerror
       
  1339                             % (self._path, encoding.strtolocal(e2.strerror))
       
  1340                         )
  1196             else:
  1341             else:
  1197                 raise error.Abort(_("couldn't find git for subrepo '%s'")
  1342                 raise error.Abort(
  1198                     % self._path, hint=notfoundhint)
  1343                     _("couldn't find git for subrepo '%s'") % self._path,
       
  1344                     hint=notfoundhint,
       
  1345                 )
  1199         versionstatus = self._checkversion(out)
  1346         versionstatus = self._checkversion(out)
  1200         if versionstatus == 'unknown':
  1347         if versionstatus == 'unknown':
  1201             self.ui.warn(_('cannot retrieve git version\n'))
  1348             self.ui.warn(_('cannot retrieve git version\n'))
  1202         elif versionstatus == 'abort':
  1349         elif versionstatus == 'abort':
  1203             raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
  1350             raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
  1254 
  1401 
  1255     def _gitcommand(self, commands, env=None, stream=False):
  1402     def _gitcommand(self, commands, env=None, stream=False):
  1256         return self._gitdir(commands, env=env, stream=stream)[0]
  1403         return self._gitdir(commands, env=env, stream=stream)[0]
  1257 
  1404 
  1258     def _gitdir(self, commands, env=None, stream=False):
  1405     def _gitdir(self, commands, env=None, stream=False):
  1259         return self._gitnodir(commands, env=env, stream=stream,
  1406         return self._gitnodir(
  1260                               cwd=self._abspath)
  1407             commands, env=env, stream=stream, cwd=self._abspath
       
  1408         )
  1261 
  1409 
  1262     def _gitnodir(self, commands, env=None, stream=False, cwd=None):
  1410     def _gitnodir(self, commands, env=None, stream=False, cwd=None):
  1263         """Calls the git command
  1411         """Calls the git command
  1264 
  1412 
  1265         The methods tries to call the git command. versions prior to 1.6.0
  1413         The methods tries to call the git command. versions prior to 1.6.0
  1280             errpipe = open(os.devnull, 'w')
  1428             errpipe = open(os.devnull, 'w')
  1281         if self.ui._colormode and len(commands) and commands[0] == "diff":
  1429         if self.ui._colormode and len(commands) and commands[0] == "diff":
  1282             # insert the argument in the front,
  1430             # insert the argument in the front,
  1283             # the end of git diff arguments is used for paths
  1431             # the end of git diff arguments is used for paths
  1284             commands.insert(1, '--color')
  1432             commands.insert(1, '--color')
  1285         p = subprocess.Popen(pycompat.rapply(procutil.tonativestr,
  1433         p = subprocess.Popen(
  1286                                              [self._gitexecutable] + commands),
  1434             pycompat.rapply(
  1287                              bufsize=-1,
  1435                 procutil.tonativestr, [self._gitexecutable] + commands
  1288                              cwd=pycompat.rapply(procutil.tonativestr, cwd),
  1436             ),
  1289                              env=procutil.tonativeenv(env),
  1437             bufsize=-1,
  1290                              close_fds=procutil.closefds,
  1438             cwd=pycompat.rapply(procutil.tonativestr, cwd),
  1291                              stdout=subprocess.PIPE, stderr=errpipe)
  1439             env=procutil.tonativeenv(env),
       
  1440             close_fds=procutil.closefds,
       
  1441             stdout=subprocess.PIPE,
       
  1442             stderr=errpipe,
       
  1443         )
  1292         if stream:
  1444         if stream:
  1293             return p.stdout, None
  1445             return p.stdout, None
  1294 
  1446 
  1295         retdata = p.stdout.read().strip()
  1447         retdata = p.stdout.read().strip()
  1296         # wait for the child to exit to avoid race condition.
  1448         # wait for the child to exit to avoid race condition.
  1300             # there are certain error codes that are ok
  1452             # there are certain error codes that are ok
  1301             command = commands[0]
  1453             command = commands[0]
  1302             if command in ('cat-file', 'symbolic-ref'):
  1454             if command in ('cat-file', 'symbolic-ref'):
  1303                 return retdata, p.returncode
  1455                 return retdata, p.returncode
  1304             # for all others, abort
  1456             # for all others, abort
  1305             raise error.Abort(_('git %s error %d in %s') %
  1457             raise error.Abort(
  1306                              (command, p.returncode, self._relpath))
  1458                 _('git %s error %d in %s')
       
  1459                 % (command, p.returncode, self._relpath)
       
  1460             )
  1307 
  1461 
  1308         return retdata, p.returncode
  1462         return retdata, p.returncode
  1309 
  1463 
  1310     def _gitmissing(self):
  1464     def _gitmissing(self):
  1311         return not self.wvfs.exists('.git')
  1465         return not self.wvfs.exists('.git')
  1347         a map from git branch to revision
  1501         a map from git branch to revision
  1348         a map from revision to branches'''
  1502         a map from revision to branches'''
  1349         branch2rev = {}
  1503         branch2rev = {}
  1350         rev2branch = {}
  1504         rev2branch = {}
  1351 
  1505 
  1352         out = self._gitcommand(['for-each-ref', '--format',
  1506         out = self._gitcommand(
  1353                                 '%(objectname) %(refname)'])
  1507             ['for-each-ref', '--format', '%(objectname) %(refname)']
       
  1508         )
  1354         for line in out.split('\n'):
  1509         for line in out.split('\n'):
  1355             revision, ref = line.split(' ')
  1510             revision, ref = line.split(' ')
  1356             if (not ref.startswith('refs/heads/') and
  1511             if not ref.startswith('refs/heads/') and not ref.startswith(
  1357                 not ref.startswith('refs/remotes/')):
  1512                 'refs/remotes/'
       
  1513             ):
  1358                 continue
  1514                 continue
  1359             if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
  1515             if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
  1360                 continue # ignore remote/HEAD redirects
  1516                 continue  # ignore remote/HEAD redirects
  1361             branch2rev[ref] = revision
  1517             branch2rev[ref] = revision
  1362             rev2branch.setdefault(revision, []).append(ref)
  1518             rev2branch.setdefault(revision, []).append(ref)
  1363         return branch2rev, rev2branch
  1519         return branch2rev, rev2branch
  1364 
  1520 
  1365     def _gittracking(self, branches):
  1521     def _gittracking(self, branches):
  1371                 continue
  1527                 continue
  1372             bname = b.split('/', 2)[2]
  1528             bname = b.split('/', 2)[2]
  1373             remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
  1529             remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
  1374             if remote:
  1530             if remote:
  1375                 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
  1531                 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
  1376                 tracking['refs/remotes/%s/%s' %
  1532                 tracking[
  1377                          (remote, ref.split('/', 2)[2])] = b
  1533                     'refs/remotes/%s/%s' % (remote, ref.split('/', 2)[2])
       
  1534                 ] = b
  1378         return tracking
  1535         return tracking
  1379 
  1536 
  1380     def _abssource(self, source):
  1537     def _abssource(self, source):
  1381         if '://' not in source:
  1538         if '://' not in source:
  1382             # recognize the scp syntax as an absolute source
  1539             # recognize the scp syntax as an absolute source
  1390         if self._gitmissing():
  1547         if self._gitmissing():
  1391             # SEC: check for safe ssh url
  1548             # SEC: check for safe ssh url
  1392             util.checksafessh(source)
  1549             util.checksafessh(source)
  1393 
  1550 
  1394             source = self._abssource(source)
  1551             source = self._abssource(source)
  1395             self.ui.status(_('cloning subrepo %s from %s\n') %
  1552             self.ui.status(
  1396                             (self._relpath, source))
  1553                 _('cloning subrepo %s from %s\n') % (self._relpath, source)
       
  1554             )
  1397             self._gitnodir(['clone', source, self._abspath])
  1555             self._gitnodir(['clone', source, self._abspath])
  1398         if self._githavelocally(revision):
  1556         if self._githavelocally(revision):
  1399             return
  1557             return
  1400         self.ui.status(_('pulling subrepo %s from %s\n') %
  1558         self.ui.status(
  1401                         (self._relpath, self._gitremote('origin')))
  1559             _('pulling subrepo %s from %s\n')
       
  1560             % (self._relpath, self._gitremote('origin'))
       
  1561         )
  1402         # try only origin: the originally cloned repo
  1562         # try only origin: the originally cloned repo
  1403         self._gitcommand(['fetch'])
  1563         self._gitcommand(['fetch'])
  1404         if not self._githavelocally(revision):
  1564         if not self._githavelocally(revision):
  1405             raise error.Abort(_('revision %s does not exist in subrepository '
  1565             raise error.Abort(
  1406                                 '"%s"\n') % (revision, self._relpath))
  1566                 _('revision %s does not exist in subrepository ' '"%s"\n')
       
  1567                 % (revision, self._relpath)
       
  1568             )
  1407 
  1569 
  1408     @annotatesubrepoerror
  1570     @annotatesubrepoerror
  1409     def dirty(self, ignoreupdate=False, missing=False):
  1571     def dirty(self, ignoreupdate=False, missing=False):
  1410         if self._gitmissing():
  1572         if self._gitmissing():
  1411             return self._state[1] != ''
  1573             return self._state[1] != ''
  1456             self._gitcommand(cmd + args)
  1618             self._gitcommand(cmd + args)
  1457             _sanitize(self.ui, self.wvfs, '.git')
  1619             _sanitize(self.ui, self.wvfs, '.git')
  1458 
  1620 
  1459         def rawcheckout():
  1621         def rawcheckout():
  1460             # no branch to checkout, check it out with no branch
  1622             # no branch to checkout, check it out with no branch
  1461             self.ui.warn(_('checking out detached HEAD in '
  1623             self.ui.warn(
  1462                            'subrepository "%s"\n') % self._relpath)
  1624                 _('checking out detached HEAD in ' 'subrepository "%s"\n')
  1463             self.ui.warn(_('check out a git branch if you intend '
  1625                 % self._relpath
  1464                             'to make changes\n'))
  1626             )
       
  1627             self.ui.warn(
       
  1628                 _('check out a git branch if you intend ' 'to make changes\n')
       
  1629             )
  1465             checkout(['-q', revision])
  1630             checkout(['-q', revision])
  1466 
  1631 
  1467         if revision not in rev2branch:
  1632         if revision not in rev2branch:
  1468             rawcheckout()
  1633             rawcheckout()
  1469             return
  1634             return
  1517         if user:
  1682         if user:
  1518             cmd += ['--author', user]
  1683             cmd += ['--author', user]
  1519         if date:
  1684         if date:
  1520             # git's date parser silently ignores when seconds < 1e9
  1685             # git's date parser silently ignores when seconds < 1e9
  1521             # convert to ISO8601
  1686             # convert to ISO8601
  1522             env['GIT_AUTHOR_DATE'] = dateutil.datestr(date,
  1687             env['GIT_AUTHOR_DATE'] = dateutil.datestr(
  1523                                                   '%Y-%m-%dT%H:%M:%S %1%2')
  1688                 date, '%Y-%m-%dT%H:%M:%S %1%2'
       
  1689             )
  1524         self._gitcommand(cmd, env=env)
  1690         self._gitcommand(cmd, env=env)
  1525         # make sure commit works otherwise HEAD might not exist under certain
  1691         # make sure commit works otherwise HEAD might not exist under certain
  1526         # circumstances
  1692         # circumstances
  1527         return self._gitstate()
  1693         return self._gitstate()
  1528 
  1694 
  1534         self._gitupdatestat()
  1700         self._gitupdatestat()
  1535         out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
  1701         out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
  1536 
  1702 
  1537         def mergefunc():
  1703         def mergefunc():
  1538             if base == revision:
  1704             if base == revision:
  1539                 self.get(state) # fast forward merge
  1705                 self.get(state)  # fast forward merge
  1540             elif base != self._state[1]:
  1706             elif base != self._state[1]:
  1541                 self._gitcommand(['merge', '--no-commit', revision])
  1707                 self._gitcommand(['merge', '--no-commit', revision])
  1542             _sanitize(self.ui, self.wvfs, '.git')
  1708             _sanitize(self.ui, self.wvfs, '.git')
  1543 
  1709 
  1544         if self.dirty():
  1710         if self.dirty():
  1545             if self._gitstate() != revision:
  1711             if self._gitstate() != revision:
  1546                 dirty = self._gitstate() == self._state[1] or code != 0
  1712                 dirty = self._gitstate() == self._state[1] or code != 0
  1547                 if _updateprompt(self.ui, self, dirty,
  1713                 if _updateprompt(
  1548                                  self._state[1][:7], revision[:7]):
  1714                     self.ui, self, dirty, self._state[1][:7], revision[:7]
       
  1715                 ):
  1549                     mergefunc()
  1716                     mergefunc()
  1550         else:
  1717         else:
  1551             mergefunc()
  1718             mergefunc()
  1552 
  1719 
  1553     @annotatesubrepoerror
  1720     @annotatesubrepoerror
  1575 
  1742 
  1576         current = self._gitcurrentbranch()
  1743         current = self._gitcurrentbranch()
  1577         if current:
  1744         if current:
  1578             # determine if the current branch is even useful
  1745             # determine if the current branch is even useful
  1579             if not self._gitisancestor(self._state[1], current):
  1746             if not self._gitisancestor(self._state[1], current):
  1580                 self.ui.warn(_('unrelated git branch checked out '
  1747                 self.ui.warn(
  1581                                'in subrepository "%s"\n') % self._relpath)
  1748                     _(
       
  1749                         'unrelated git branch checked out '
       
  1750                         'in subrepository "%s"\n'
       
  1751                     )
       
  1752                     % self._relpath
       
  1753                 )
  1582                 return False
  1754                 return False
  1583             self.ui.status(_('pushing branch %s of subrepository "%s"\n') %
  1755             self.ui.status(
  1584                            (current.split('/', 2)[2], self._relpath))
  1756                 _('pushing branch %s of subrepository "%s"\n')
       
  1757                 % (current.split('/', 2)[2], self._relpath)
       
  1758             )
  1585             ret = self._gitdir(cmd + ['origin', current])
  1759             ret = self._gitdir(cmd + ['origin', current])
  1586             return ret[1] == 0
  1760             return ret[1] == 0
  1587         else:
  1761         else:
  1588             self.ui.warn(_('no branch checked out in subrepository "%s"\n'
  1762             self.ui.warn(
  1589                            'cannot push revision %s\n') %
  1763                 _(
  1590                           (self._relpath, self._state[1]))
  1764                     'no branch checked out in subrepository "%s"\n'
       
  1765                     'cannot push revision %s\n'
       
  1766                 )
       
  1767                 % (self._relpath, self._state[1])
       
  1768             )
  1591             return False
  1769             return False
  1592 
  1770 
  1593     @annotatesubrepoerror
  1771     @annotatesubrepoerror
  1594     def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
  1772     def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
  1595         if self._gitmissing():
  1773         if self._gitmissing():
  1611         files = [f for f in sorted(set(files)) if match(f)]
  1789         files = [f for f in sorted(set(files)) if match(f)]
  1612         for f in files:
  1790         for f in files:
  1613             exact = match.exact(f)
  1791             exact = match.exact(f)
  1614             command = ["add"]
  1792             command = ["add"]
  1615             if exact:
  1793             if exact:
  1616                 command.append("-f") #should be added, even if ignored
  1794                 command.append("-f")  # should be added, even if ignored
  1617             if ui.verbose or not exact:
  1795             if ui.verbose or not exact:
  1618                 ui.status(_('adding %s\n') % uipathfn(f))
  1796                 ui.status(_('adding %s\n') % uipathfn(f))
  1619 
  1797 
  1620             if f in tracked:  # hg prints 'adding' even if already tracked
  1798             if f in tracked:  # hg prints 'adding' even if already tracked
  1621                 if exact:
  1799                 if exact:
  1632     @annotatesubrepoerror
  1810     @annotatesubrepoerror
  1633     def remove(self):
  1811     def remove(self):
  1634         if self._gitmissing():
  1812         if self._gitmissing():
  1635             return
  1813             return
  1636         if self.dirty():
  1814         if self.dirty():
  1637             self.ui.warn(_('not removing repo %s because '
  1815             self.ui.warn(
  1638                            'it has changes.\n') % self._relpath)
  1816                 _('not removing repo %s because ' 'it has changes.\n')
       
  1817                 % self._relpath
       
  1818             )
  1639             return
  1819             return
  1640         # we can't fully delete the repository as it may contain
  1820         # we can't fully delete the repository as it may contain
  1641         # local-only history
  1821         # local-only history
  1642         self.ui.note(_('removing subrepo %s\n') % self._relpath)
  1822         self.ui.note(_('removing subrepo %s\n') % self._relpath)
  1643         self._gitcommand(['config', 'core.bare', 'true'])
  1823         self._gitcommand(['config', 'core.bare', 'true'])
  1660         # This should be much faster than manually traversing the trees
  1840         # This should be much faster than manually traversing the trees
  1661         # and objects with many subprocess calls.
  1841         # and objects with many subprocess calls.
  1662         tarstream = self._gitcommand(['archive', revision], stream=True)
  1842         tarstream = self._gitcommand(['archive', revision], stream=True)
  1663         tar = tarfile.open(fileobj=tarstream, mode=r'r|')
  1843         tar = tarfile.open(fileobj=tarstream, mode=r'r|')
  1664         relpath = subrelpath(self)
  1844         relpath = subrelpath(self)
  1665         progress = self.ui.makeprogress(_('archiving (%s)') % relpath,
  1845         progress = self.ui.makeprogress(
  1666                                         unit=_('files'))
  1846             _('archiving (%s)') % relpath, unit=_('files')
       
  1847         )
  1667         progress.update(0)
  1848         progress.update(0)
  1668         for info in tar:
  1849         for info in tar:
  1669             if info.isdir():
  1850             if info.isdir():
  1670                 continue
  1851                 continue
  1671             bname = pycompat.fsencode(info.name)
  1852             bname = pycompat.fsencode(info.name)
  1679             total += 1
  1860             total += 1
  1680             progress.increment()
  1861             progress.increment()
  1681         progress.complete()
  1862         progress.complete()
  1682         return total
  1863         return total
  1683 
  1864 
  1684 
       
  1685     @annotatesubrepoerror
  1865     @annotatesubrepoerror
  1686     def cat(self, match, fm, fntemplate, prefix, **opts):
  1866     def cat(self, match, fm, fntemplate, prefix, **opts):
  1687         rev = self._state[1]
  1867         rev = self._state[1]
  1688         if match.anypats():
  1868         if match.anypats():
  1689             return 1 #No support for include/exclude yet
  1869             return 1  # No support for include/exclude yet
  1690 
  1870 
  1691         if not match.files():
  1871         if not match.files():
  1692             return 1
  1872             return 1
  1693 
  1873 
  1694         # TODO: add support for non-plain formatter (see cmdutil.cat())
  1874         # TODO: add support for non-plain formatter (see cmdutil.cat())
  1695         for f in match.files():
  1875         for f in match.files():
  1696             output = self._gitcommand(["show", "%s:%s" % (rev, f)])
  1876             output = self._gitcommand(["show", "%s:%s" % (rev, f)])
  1697             fp = cmdutil.makefileobj(self._ctx, fntemplate,
  1877             fp = cmdutil.makefileobj(
  1698                                      pathname=self.wvfs.reljoin(prefix, f))
  1878                 self._ctx, fntemplate, pathname=self.wvfs.reljoin(prefix, f)
       
  1879             )
  1699             fp.write(output)
  1880             fp.write(output)
  1700             fp.close()
  1881             fp.close()
  1701         return 0
  1882         return 0
  1702 
       
  1703 
  1883 
  1704     @annotatesubrepoerror
  1884     @annotatesubrepoerror
  1705     def status(self, rev2, **opts):
  1885     def status(self, rev2, **opts):
  1706         rev1 = self._state[1]
  1886         rev1 = self._state[1]
  1707         if self._gitmissing() or not rev1:
  1887         if self._gitmissing() or not rev1:
  1716         out = self._gitcommand(command)
  1896         out = self._gitcommand(command)
  1717         for line in out.split('\n'):
  1897         for line in out.split('\n'):
  1718             tab = line.find('\t')
  1898             tab = line.find('\t')
  1719             if tab == -1:
  1899             if tab == -1:
  1720                 continue
  1900                 continue
  1721             status, f = line[tab - 1:tab], line[tab + 1:]
  1901             status, f = line[tab - 1 : tab], line[tab + 1 :]
  1722             if status == 'M':
  1902             if status == 'M':
  1723                 modified.append(f)
  1903                 modified.append(f)
  1724             elif status == 'A':
  1904             elif status == 'A':
  1725                 added.append(f)
  1905                 added.append(f)
  1726             elif status == 'D':
  1906             elif status == 'D':
  1741         changedfiles.update(removed)
  1921         changedfiles.update(removed)
  1742         for line in out.split('\0'):
  1922         for line in out.split('\0'):
  1743             if not line:
  1923             if not line:
  1744                 continue
  1924                 continue
  1745             st = line[0:2]
  1925             st = line[0:2]
  1746             #moves and copies show 2 files on one line
  1926             # moves and copies show 2 files on one line
  1747             if line.find('\0') >= 0:
  1927             if line.find('\0') >= 0:
  1748                 filename1, filename2 = line[3:].split('\0')
  1928                 filename1, filename2 = line[3:].split('\0')
  1749             else:
  1929             else:
  1750                 filename1 = line[3:]
  1930                 filename1 = line[3:]
  1751                 filename2 = None
  1931                 filename2 = None
  1763             out = self._gitcommand(['ls-files'])
  1943             out = self._gitcommand(['ls-files'])
  1764             for f in out.split('\n'):
  1944             for f in out.split('\n'):
  1765                 if not f in changedfiles:
  1945                 if not f in changedfiles:
  1766                     clean.append(f)
  1946                     clean.append(f)
  1767 
  1947 
  1768         return scmutil.status(modified, added, removed, deleted,
  1948         return scmutil.status(
  1769                               unknown, ignored, clean)
  1949             modified, added, removed, deleted, unknown, ignored, clean
       
  1950         )
  1770 
  1951 
  1771     @annotatesubrepoerror
  1952     @annotatesubrepoerror
  1772     def diff(self, ui, diffopts, node2, match, prefix, **opts):
  1953     def diff(self, ui, diffopts, node2, match, prefix, **opts):
  1773         node1 = self._state[1]
  1954         node1 = self._state[1]
  1774         cmd = ['diff', '--no-renames']
  1955         cmd = ['diff', '--no-renames']
  1777         else:
  1958         else:
  1778             # for Git, this also implies '-p'
  1959             # for Git, this also implies '-p'
  1779             cmd.append('-U%d' % diffopts.context)
  1960             cmd.append('-U%d' % diffopts.context)
  1780 
  1961 
  1781         if diffopts.noprefix:
  1962         if diffopts.noprefix:
  1782             cmd.extend(['--src-prefix=%s/' % prefix,
  1963             cmd.extend(
  1783                         '--dst-prefix=%s/' % prefix])
  1964                 ['--src-prefix=%s/' % prefix, '--dst-prefix=%s/' % prefix]
       
  1965             )
  1784         else:
  1966         else:
  1785             cmd.extend(['--src-prefix=a/%s/' % prefix,
  1967             cmd.extend(
  1786                         '--dst-prefix=b/%s/' % prefix])
  1968                 ['--src-prefix=a/%s/' % prefix, '--dst-prefix=b/%s/' % prefix]
       
  1969             )
  1787 
  1970 
  1788         if diffopts.ignorews:
  1971         if diffopts.ignorews:
  1789             cmd.append('--ignore-all-space')
  1972             cmd.append('--ignore-all-space')
  1790         if diffopts.ignorewsamount:
  1973         if diffopts.ignorewsamount:
  1791             cmd.append('--ignore-space-change')
  1974             cmd.append('--ignore-space-change')
  1792         if (self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4)
  1975         if (
  1793             and diffopts.ignoreblanklines):
  1976             self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4)
       
  1977             and diffopts.ignoreblanklines
       
  1978         ):
  1794             cmd.append('--ignore-blank-lines')
  1979             cmd.append('--ignore-blank-lines')
  1795 
  1980 
  1796         cmd.append(node1)
  1981         cmd.append(node1)
  1797         if node2:
  1982         if node2:
  1798             cmd.append(node2)
  1983             cmd.append(node2)
  1818             names = status.modified
  2003             names = status.modified
  1819             for name in names:
  2004             for name in names:
  1820                 # backuppath() expects a path relative to the parent repo (the
  2005                 # backuppath() expects a path relative to the parent repo (the
  1821                 # repo that ui.origbackuppath is relative to)
  2006                 # repo that ui.origbackuppath is relative to)
  1822                 parentname = os.path.join(self._path, name)
  2007                 parentname = os.path.join(self._path, name)
  1823                 bakname = scmutil.backuppath(self.ui, self._subparent,
  2008                 bakname = scmutil.backuppath(
  1824                                              parentname)
  2009                     self.ui, self._subparent, parentname
  1825                 self.ui.note(_('saving current version of %s as %s\n') %
  2010                 )
  1826                         (name, os.path.relpath(bakname)))
  2011                 self.ui.note(
       
  2012                     _('saving current version of %s as %s\n')
       
  2013                     % (name, os.path.relpath(bakname))
       
  2014                 )
  1827                 util.rename(self.wvfs.join(name), bakname)
  2015                 util.rename(self.wvfs.join(name), bakname)
  1828 
  2016 
  1829         if not opts.get(r'dry_run'):
  2017         if not opts.get(r'dry_run'):
  1830             self.get(substate, overwrite=True)
  2018             self.get(substate, overwrite=True)
  1831         return []
  2019         return []
  1832 
  2020 
  1833     def shortid(self, revid):
  2021     def shortid(self, revid):
  1834         return revid[:7]
  2022         return revid[:7]
       
  2023 
  1835 
  2024 
  1836 types = {
  2025 types = {
  1837     'hg': hgsubrepo,
  2026     'hg': hgsubrepo,
  1838     'svn': svnsubrepo,
  2027     'svn': svnsubrepo,
  1839     'git': gitsubrepo,
  2028     'git': gitsubrepo,
  1840     }
  2029 }