comparison mercurial/subrepo.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents f6540aba8e3e
children 687b865b95ad
comparison
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 }