comparison mercurial/subrepo.py @ 43077:687b865b95ad

formatting: byteify all mercurial/ and hgext/ string literals Done with python3.7 contrib/byteify-strings.py -i $(hg files 'set:mercurial/**.py - mercurial/thirdparty/** + hgext/**.py - hgext/fsmonitor/pywatchman/** - mercurial/__init__.py') black -l 80 -t py33 -S $(hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**" - hgext/fsmonitor/pywatchman/**') # skip-blame mass-reformatting only Differential Revision: https://phab.mercurial-scm.org/D6972
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:48:39 -0400
parents 2372284d9457
children eef9a2d67051
comparison
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
82 raise ex 82 raise ex
83 except error.Abort as ex: 83 except error.Abort as ex:
84 subrepo = subrelpath(self) 84 subrepo = subrelpath(self)
85 errormsg = ( 85 errormsg = (
86 stringutil.forcebytestr(ex) 86 stringutil.forcebytestr(ex)
87 + ' ' 87 + b' '
88 + _('(in subrepository "%s")') % subrepo 88 + _(b'(in subrepository "%s")') % subrepo
89 ) 89 )
90 # avoid handling this exception by raising a SubrepoAbort exception 90 # avoid handling this exception by raising a SubrepoAbort exception
91 raise SubrepoAbort( 91 raise SubrepoAbort(
92 errormsg, hint=ex.hint, subrepo=subrepo, cause=sys.exc_info() 92 errormsg, hint=ex.hint, subrepo=subrepo, cause=sys.exc_info()
93 ) 93 )
97 97
98 98
99 def _updateprompt(ui, sub, dirty, local, remote): 99 def _updateprompt(ui, sub, dirty, local, remote):
100 if dirty: 100 if dirty:
101 msg = _( 101 msg = _(
102 ' subrepository sources for %s differ\n' 102 b' subrepository sources for %s differ\n'
103 'you can use (l)ocal source (%s) or (r)emote source (%s).\n' 103 b'you can use (l)ocal source (%s) or (r)emote source (%s).\n'
104 'what do you want to do?' 104 b'what do you want to do?'
105 '$$ &Local $$ &Remote' 105 b'$$ &Local $$ &Remote'
106 ) % (subrelpath(sub), local, remote) 106 ) % (subrelpath(sub), local, remote)
107 else: 107 else:
108 msg = _( 108 msg = _(
109 ' subrepository sources for %s differ (in checked out ' 109 b' subrepository sources for %s differ (in checked out '
110 'version)\n' 110 b'version)\n'
111 'you can use (l)ocal source (%s) or (r)emote source (%s).\n' 111 b'you can use (l)ocal source (%s) or (r)emote source (%s).\n'
112 'what do you want to do?' 112 b'what do you want to do?'
113 '$$ &Local $$ &Remote' 113 b'$$ &Local $$ &Remote'
114 ) % (subrelpath(sub), local, remote) 114 ) % (subrelpath(sub), local, remote)
115 return ui.promptchoice(msg, 0) 115 return ui.promptchoice(msg, 0)
116 116
117 117
118 def _sanitize(ui, vfs, ignore): 118 def _sanitize(ui, vfs, ignore):
119 for dirname, dirs, names in vfs.walk(): 119 for dirname, dirs, names in vfs.walk():
120 for i, d in enumerate(dirs): 120 for i, d in enumerate(dirs):
121 if d.lower() == ignore: 121 if d.lower() == ignore:
122 del dirs[i] 122 del dirs[i]
123 break 123 break
124 if vfs.basename(dirname).lower() != '.hg': 124 if vfs.basename(dirname).lower() != b'.hg':
125 continue 125 continue
126 for f in names: 126 for f in names:
127 if f.lower() == 'hgrc': 127 if f.lower() == b'hgrc':
128 ui.warn( 128 ui.warn(
129 _( 129 _(
130 "warning: removing potentially hostile 'hgrc' " 130 b"warning: removing potentially hostile 'hgrc' "
131 "in '%s'\n" 131 b"in '%s'\n"
132 ) 132 )
133 % vfs.join(dirname) 133 % vfs.join(dirname)
134 ) 134 )
135 vfs.unlink(vfs.reljoin(dirname, f)) 135 vfs.unlink(vfs.reljoin(dirname, f))
136 136
137 137
138 def _auditsubrepopath(repo, path): 138 def _auditsubrepopath(repo, path):
139 # sanity check for potentially unsafe paths such as '~' and '$FOO' 139 # sanity check for potentially unsafe paths such as '~' and '$FOO'
140 if path.startswith('~') or '$' in path or util.expandpath(path) != path: 140 if path.startswith(b'~') or b'$' in path or util.expandpath(path) != path:
141 raise error.Abort( 141 raise error.Abort(
142 _('subrepo path contains illegal component: %s') % path 142 _(b'subrepo path contains illegal component: %s') % path
143 ) 143 )
144 # auditor doesn't check if the path itself is a symlink 144 # auditor doesn't check if the path itself is a symlink
145 pathutil.pathauditor(repo.root)(path) 145 pathutil.pathauditor(repo.root)(path)
146 if repo.wvfs.islink(path): 146 if repo.wvfs.islink(path):
147 raise error.Abort(_("subrepo '%s' traverses symbolic link") % path) 147 raise error.Abort(_(b"subrepo '%s' traverses symbolic link") % path)
148 148
149 149
150 SUBREPO_ALLOWED_DEFAULTS = { 150 SUBREPO_ALLOWED_DEFAULTS = {
151 'hg': True, 151 b'hg': True,
152 'git': False, 152 b'git': False,
153 'svn': False, 153 b'svn': False,
154 } 154 }
155 155
156 156
157 def _checktype(ui, kind): 157 def _checktype(ui, kind):
158 # subrepos.allowed is a master kill switch. If disabled, subrepos are 158 # subrepos.allowed is a master kill switch. If disabled, subrepos are
159 # disabled period. 159 # disabled period.
160 if not ui.configbool('subrepos', 'allowed', True): 160 if not ui.configbool(b'subrepos', b'allowed', True):
161 raise error.Abort( 161 raise error.Abort(
162 _('subrepos not enabled'), 162 _(b'subrepos not enabled'),
163 hint=_("see 'hg help config.subrepos' for details"), 163 hint=_(b"see 'hg help config.subrepos' for details"),
164 ) 164 )
165 165
166 default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False) 166 default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
167 if not ui.configbool('subrepos', '%s:allowed' % kind, default): 167 if not ui.configbool(b'subrepos', b'%s:allowed' % kind, default):
168 raise error.Abort( 168 raise error.Abort(
169 _('%s subrepos not allowed') % kind, 169 _(b'%s subrepos not allowed') % kind,
170 hint=_("see 'hg help config.subrepos' for details"), 170 hint=_(b"see 'hg help config.subrepos' for details"),
171 ) 171 )
172 172
173 if kind not in types: 173 if kind not in types:
174 raise error.Abort(_('unknown subrepo type %s') % kind) 174 raise error.Abort(_(b'unknown subrepo type %s') % kind)
175 175
176 176
177 def subrepo(ctx, path, allowwdir=False, allowcreate=True): 177 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
178 """return instance of the right subrepo class for subrepo in path""" 178 """return instance of the right subrepo class for subrepo in path"""
179 # subrepo inherently violates our import layering rules 179 # subrepo inherently violates our import layering rules
207 207
208 repo = ctx.repo() 208 repo = ctx.repo()
209 _auditsubrepopath(repo, path) 209 _auditsubrepopath(repo, path)
210 state = ctx.substate[path] 210 state = ctx.substate[path]
211 _checktype(repo.ui, state[2]) 211 _checktype(repo.ui, state[2])
212 subrev = '' 212 subrev = b''
213 if state[2] == 'hg': 213 if state[2] == b'hg':
214 subrev = "0" * 40 214 subrev = b"0" * 40
215 return types[state[2]](pctx, path, (state[0], subrev), True) 215 return types[state[2]](pctx, path, (state[0], subrev), True)
216 216
217 217
218 # subrepo classes need to implement the following abstract class: 218 # subrepo classes need to implement the following abstract class:
219 219
263 of exception. 263 of exception.
264 264
265 This returns None, otherwise. 265 This returns None, otherwise.
266 """ 266 """
267 if self.dirty(ignoreupdate=ignoreupdate, missing=missing): 267 if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
268 return _('uncommitted changes in subrepository "%s"') % subrelpath( 268 return _(b'uncommitted changes in subrepository "%s"') % subrelpath(
269 self 269 self
270 ) 270 )
271 271
272 def bailifchanged(self, ignoreupdate=False, hint=None): 272 def bailifchanged(self, ignoreupdate=False, hint=None):
273 """raise Abort if subrepository is ``dirty()`` 273 """raise Abort if subrepository is ``dirty()``
323 323
324 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts): 324 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
325 return [] 325 return []
326 326
327 def addremove(self, matcher, prefix, uipathfn, opts): 327 def addremove(self, matcher, prefix, uipathfn, opts):
328 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported"))) 328 self.ui.warn(b"%s: %s" % (prefix, _(b"addremove is not supported")))
329 return 1 329 return 1
330 330
331 def cat(self, match, fm, fntemplate, prefix, **opts): 331 def cat(self, match, fm, fntemplate, prefix, **opts):
332 return 1 332 return 1
333 333
351 """return file data, optionally passed through repo decoders""" 351 """return file data, optionally passed through repo decoders"""
352 raise NotImplementedError 352 raise NotImplementedError
353 353
354 def fileflags(self, name): 354 def fileflags(self, name):
355 """return file flags""" 355 """return file flags"""
356 return '' 356 return b''
357 357
358 def matchfileset(self, expr, badfn=None): 358 def matchfileset(self, expr, badfn=None):
359 """Resolve the fileset expression for this repo""" 359 """Resolve the fileset expression for this repo"""
360 return matchmod.never(badfn=badfn) 360 return matchmod.never(badfn=badfn)
361 361
369 else: 369 else:
370 files = self.files() 370 files = self.files()
371 total = len(files) 371 total = len(files)
372 relpath = subrelpath(self) 372 relpath = subrelpath(self)
373 progress = self.ui.makeprogress( 373 progress = self.ui.makeprogress(
374 _('archiving (%s)') % relpath, unit=_('files'), total=total 374 _(b'archiving (%s)') % relpath, unit=_(b'files'), total=total
375 ) 375 )
376 progress.update(0) 376 progress.update(0)
377 for name in files: 377 for name in files:
378 flags = self.fileflags(name) 378 flags = self.fileflags(name)
379 mode = 'x' in flags and 0o755 or 0o644 379 mode = b'x' in flags and 0o755 or 0o644
380 symlink = 'l' in flags 380 symlink = b'l' in flags
381 archiver.addfile( 381 archiver.addfile(
382 prefix + name, mode, symlink, self.filedata(name, decode) 382 prefix + name, mode, symlink, self.filedata(name, decode)
383 ) 383 )
384 progress.increment() 384 progress.increment()
385 progress.complete() 385 progress.complete()
408 """remove the matched files from the subrepository and the filesystem, 408 """remove the matched files from the subrepository and the filesystem,
409 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
410 filesystem. Return 0 on success, 1 on any warning. 410 filesystem. Return 0 on success, 1 on any warning.
411 """ 411 """
412 warnings.append( 412 warnings.append(
413 _("warning: removefiles not implemented (%s)") % self._path 413 _(b"warning: removefiles not implemented (%s)") % self._path
414 ) 414 )
415 return 1 415 return 1
416 416
417 def revert(self, substate, *pats, **opts): 417 def revert(self, substate, *pats, **opts):
418 self.ui.warn( 418 self.ui.warn(
419 _('%s: reverting %s subrepos is unsupported\n') 419 _(b'%s: reverting %s subrepos is unsupported\n')
420 % (substate[0], substate[2]) 420 % (substate[0], substate[2])
421 ) 421 )
422 return [] 422 return []
423 423
424 def shortid(self, revid): 424 def shortid(self, revid):
452 def __init__(self, ctx, path, state, allowcreate): 452 def __init__(self, ctx, path, state, allowcreate):
453 super(hgsubrepo, self).__init__(ctx, path) 453 super(hgsubrepo, self).__init__(ctx, path)
454 self._state = state 454 self._state = state
455 r = ctx.repo() 455 r = ctx.repo()
456 root = r.wjoin(util.localpath(path)) 456 root = r.wjoin(util.localpath(path))
457 create = allowcreate and not r.wvfs.exists('%s/.hg' % path) 457 create = allowcreate and not r.wvfs.exists(b'%s/.hg' % path)
458 # repository constructor does expand variables in path, which is 458 # repository constructor does expand variables in path, which is
459 # unsafe since subrepo path might come from untrusted source. 459 # unsafe since subrepo path might come from untrusted source.
460 if os.path.realpath(util.expandpath(root)) != root: 460 if os.path.realpath(util.expandpath(root)) != root:
461 raise error.Abort( 461 raise error.Abort(
462 _('subrepo path contains illegal component: %s') % path 462 _(b'subrepo path contains illegal component: %s') % path
463 ) 463 )
464 self._repo = hg.repository(r.baseui, root, create=create) 464 self._repo = hg.repository(r.baseui, root, create=create)
465 if self._repo.root != root: 465 if self._repo.root != root:
466 raise error.ProgrammingError( 466 raise error.ProgrammingError(
467 'failed to reject unsafe subrepo ' 467 b'failed to reject unsafe subrepo '
468 'path: %s (expanded to %s)' % (root, self._repo.root) 468 b'path: %s (expanded to %s)' % (root, self._repo.root)
469 ) 469 )
470 470
471 # Propagate the parent's --hidden option 471 # Propagate the parent's --hidden option
472 if r is r.unfiltered(): 472 if r is r.unfiltered():
473 self._repo = self._repo.unfiltered() 473 self._repo = self._repo.unfiltered()
474 474
475 self.ui = self._repo.ui 475 self.ui = self._repo.ui
476 for s, k in [('ui', 'commitsubrepos')]: 476 for s, k in [(b'ui', b'commitsubrepos')]:
477 v = r.ui.config(s, k) 477 v = r.ui.config(s, k)
478 if v: 478 if v:
479 self.ui.setconfig(s, k, v, 'subrepo') 479 self.ui.setconfig(s, k, v, b'subrepo')
480 # internal config: ui._usedassubrepo 480 # internal config: ui._usedassubrepo
481 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo') 481 self.ui.setconfig(b'ui', b'_usedassubrepo', b'True', b'subrepo')
482 self._initrepo(r, state[0], create) 482 self._initrepo(r, state[0], create)
483 483
484 @annotatesubrepoerror 484 @annotatesubrepoerror
485 def addwebdirpath(self, serverpath, webconf): 485 def addwebdirpath(self, serverpath, webconf):
486 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf) 486 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
506 '''calculate a unique "store hash" 506 '''calculate a unique "store hash"
507 507
508 This method is used to to detect when there are changes that may 508 This method is used to to detect when there are changes that may
509 require a push to a given remote path.''' 509 require a push to a given remote path.'''
510 # sort the files that will be hashed in increasing (likely) file size 510 # sort the files that will be hashed in increasing (likely) file size
511 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i') 511 filelist = (b'bookmarks', b'store/phaseroots', b'store/00changelog.i')
512 yield '# %s\n' % _expandedabspath(remotepath) 512 yield b'# %s\n' % _expandedabspath(remotepath)
513 vfs = self._repo.vfs 513 vfs = self._repo.vfs
514 for relname in filelist: 514 for relname in filelist:
515 filehash = node.hex(hashlib.sha1(vfs.tryread(relname)).digest()) 515 filehash = node.hex(hashlib.sha1(vfs.tryread(relname)).digest())
516 yield '%s = %s\n' % (relname, filehash) 516 yield b'%s = %s\n' % (relname, filehash)
517 517
518 @propertycache 518 @propertycache
519 def _cachestorehashvfs(self): 519 def _cachestorehashvfs(self):
520 return vfsmod.vfs(self._repo.vfs.join('cache/storehash')) 520 return vfsmod.vfs(self._repo.vfs.join(b'cache/storehash'))
521 521
522 def _readstorehashcache(self, remotepath): 522 def _readstorehashcache(self, remotepath):
523 '''read the store hash cache for a given remote repository''' 523 '''read the store hash cache for a given remote repository'''
524 cachefile = _getstorehashcachename(remotepath) 524 cachefile = _getstorehashcachename(remotepath)
525 return self._cachestorehashvfs.tryreadlines(cachefile, 'r') 525 return self._cachestorehashvfs.tryreadlines(cachefile, b'r')
526 526
527 def _cachestorehash(self, remotepath): 527 def _cachestorehash(self, remotepath):
528 '''cache the current store hash 528 '''cache the current store hash
529 529
530 Each remote repo requires its own store hash cache, because a subrepo 530 Each remote repo requires its own store hash cache, because a subrepo
532 ''' 532 '''
533 cachefile = _getstorehashcachename(remotepath) 533 cachefile = _getstorehashcachename(remotepath)
534 with self._repo.lock(): 534 with self._repo.lock():
535 storehash = list(self._calcstorehash(remotepath)) 535 storehash = list(self._calcstorehash(remotepath))
536 vfs = self._cachestorehashvfs 536 vfs = self._cachestorehashvfs
537 vfs.writelines(cachefile, storehash, mode='wb', notindexed=True) 537 vfs.writelines(cachefile, storehash, mode=b'wb', notindexed=True)
538 538
539 def _getctx(self): 539 def _getctx(self):
540 '''fetch the context for this subrepo revision, possibly a workingctx 540 '''fetch the context for this subrepo revision, possibly a workingctx
541 ''' 541 '''
542 if self._ctx.rev() is None: 542 if self._ctx.rev() is None:
549 def _initrepo(self, parentrepo, source, create): 549 def _initrepo(self, parentrepo, source, create):
550 self._repo._subparent = parentrepo 550 self._repo._subparent = parentrepo
551 self._repo._subsource = source 551 self._repo._subsource = source
552 552
553 if create: 553 if create:
554 lines = ['[paths]\n'] 554 lines = [b'[paths]\n']
555 555
556 def addpathconfig(key, value): 556 def addpathconfig(key, value):
557 if value: 557 if value:
558 lines.append('%s = %s\n' % (key, value)) 558 lines.append(b'%s = %s\n' % (key, value))
559 self.ui.setconfig('paths', key, value, 'subrepo') 559 self.ui.setconfig(b'paths', key, value, b'subrepo')
560 560
561 defpath = _abssource(self._repo, abort=False) 561 defpath = _abssource(self._repo, abort=False)
562 defpushpath = _abssource(self._repo, True, abort=False) 562 defpushpath = _abssource(self._repo, True, abort=False)
563 addpathconfig('default', defpath) 563 addpathconfig(b'default', defpath)
564 if defpath != defpushpath: 564 if defpath != defpushpath:
565 addpathconfig('default-push', defpushpath) 565 addpathconfig(b'default-push', defpushpath)
566 566
567 self._repo.vfs.write('hgrc', util.tonativeeol(''.join(lines))) 567 self._repo.vfs.write(b'hgrc', util.tonativeeol(b''.join(lines)))
568 568
569 @annotatesubrepoerror 569 @annotatesubrepoerror
570 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts): 570 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
571 return cmdutil.add( 571 return cmdutil.add(
572 ui, self._repo, match, prefix, uipathfn, explicitonly, **opts 572 ui, self._repo, match, prefix, uipathfn, explicitonly, **opts
576 def addremove(self, m, prefix, uipathfn, opts): 576 def addremove(self, m, prefix, uipathfn, opts):
577 # 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,
578 # 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
579 # be used to process sibling subrepos however. 579 # be used to process sibling subrepos however.
580 opts = copy.copy(opts) 580 opts = copy.copy(opts)
581 opts['subrepos'] = True 581 opts[b'subrepos'] = True
582 return scmutil.addremove(self._repo, m, prefix, uipathfn, opts) 582 return scmutil.addremove(self._repo, m, prefix, uipathfn, opts)
583 583
584 @annotatesubrepoerror 584 @annotatesubrepoerror
585 def cat(self, match, fm, fntemplate, prefix, **opts): 585 def cat(self, match, fm, fntemplate, prefix, **opts):
586 rev = self._state[1] 586 rev = self._state[1]
596 ctx1 = self._repo[rev1] 596 ctx1 = self._repo[rev1]
597 ctx2 = self._repo[rev2] 597 ctx2 = self._repo[rev2]
598 return self._repo.status(ctx1, ctx2, **opts) 598 return self._repo.status(ctx1, ctx2, **opts)
599 except error.RepoLookupError as inst: 599 except error.RepoLookupError as inst:
600 self.ui.warn( 600 self.ui.warn(
601 _('warning: error "%s" in subrepository "%s"\n') 601 _(b'warning: error "%s" in subrepository "%s"\n')
602 % (inst, subrelpath(self)) 602 % (inst, subrelpath(self))
603 ) 603 )
604 return scmutil.status([], [], [], [], [], [], []) 604 return scmutil.status([], [], [], [], [], [], [])
605 605
606 @annotatesubrepoerror 606 @annotatesubrepoerror
622 listsubrepos=True, 622 listsubrepos=True,
623 **opts 623 **opts
624 ) 624 )
625 except error.RepoLookupError as inst: 625 except error.RepoLookupError as inst:
626 self.ui.warn( 626 self.ui.warn(
627 _('warning: error "%s" in subrepository "%s"\n') 627 _(b'warning: error "%s" in subrepository "%s"\n')
628 % (inst, subrelpath(self)) 628 % (inst, subrelpath(self))
629 ) 629 )
630 630
631 @annotatesubrepoerror 631 @annotatesubrepoerror
632 def archive(self, archiver, prefix, match=None, decode=True): 632 def archive(self, archiver, prefix, match=None, decode=True):
633 self._get(self._state + ('hg',)) 633 self._get(self._state + (b'hg',))
634 files = self.files() 634 files = self.files()
635 if match: 635 if match:
636 files = [f for f in files if match(f)] 636 files = [f for f in files if match(f)]
637 rev = self._state[1] 637 rev = self._state[1]
638 ctx = self._repo[rev] 638 ctx = self._repo[rev]
641 ) 641 )
642 total = abstractsubrepo.archive(self, archiver, prefix, match) 642 total = abstractsubrepo.archive(self, archiver, prefix, match)
643 for subpath in ctx.substate: 643 for subpath in ctx.substate:
644 s = subrepo(ctx, subpath, True) 644 s = subrepo(ctx, subpath, True)
645 submatch = matchmod.subdirmatcher(subpath, match) 645 submatch = matchmod.subdirmatcher(subpath, match)
646 subprefix = prefix + subpath + '/' 646 subprefix = prefix + subpath + b'/'
647 total += s.archive(archiver, subprefix, submatch, decode) 647 total += s.archive(archiver, subprefix, submatch, decode)
648 return total 648 return total
649 649
650 @annotatesubrepoerror 650 @annotatesubrepoerror
651 def dirty(self, ignoreupdate=False, missing=False): 651 def dirty(self, ignoreupdate=False, missing=False):
652 r = self._state[1] 652 r = self._state[1]
653 if r == '' and not ignoreupdate: # no state recorded 653 if r == b'' and not ignoreupdate: # no state recorded
654 return True 654 return True
655 w = self._repo[None] 655 w = self._repo[None]
656 if r != w.p1().hex() and not ignoreupdate: 656 if r != w.p1().hex() and not ignoreupdate:
657 # different version checked out 657 # different version checked out
658 return True 658 return True
659 return w.dirty(missing=missing) # working directory changed 659 return w.dirty(missing=missing) # working directory changed
660 660
661 def basestate(self): 661 def basestate(self):
662 return self._repo['.'].hex() 662 return self._repo[b'.'].hex()
663 663
664 def checknested(self, path): 664 def checknested(self, path):
665 return self._repo._checknested(self._repo.wjoin(path)) 665 return self._repo._checknested(self._repo.wjoin(path))
666 666
667 @annotatesubrepoerror 667 @annotatesubrepoerror
668 def commit(self, text, user, date): 668 def commit(self, text, user, date):
669 # don't bother committing in the subrepo if it's only been 669 # don't bother committing in the subrepo if it's only been
670 # updated 670 # updated
671 if not self.dirty(True): 671 if not self.dirty(True):
672 return self._repo['.'].hex() 672 return self._repo[b'.'].hex()
673 self.ui.debug("committing subrepo %s\n" % subrelpath(self)) 673 self.ui.debug(b"committing subrepo %s\n" % subrelpath(self))
674 n = self._repo.commit(text, user, date) 674 n = self._repo.commit(text, user, date)
675 if not n: 675 if not n:
676 return self._repo['.'].hex() # different version checked out 676 return self._repo[b'.'].hex() # different version checked out
677 return node.hex(n) 677 return node.hex(n)
678 678
679 @annotatesubrepoerror 679 @annotatesubrepoerror
680 def phase(self, state): 680 def phase(self, state):
681 return self._repo[state or '.'].phase() 681 return self._repo[state or b'.'].phase()
682 682
683 @annotatesubrepoerror 683 @annotatesubrepoerror
684 def remove(self): 684 def remove(self):
685 # we can't fully delete the repository as it may contain 685 # we can't fully delete the repository as it may contain
686 # local-only history 686 # local-only history
687 self.ui.note(_('removing subrepo %s\n') % subrelpath(self)) 687 self.ui.note(_(b'removing subrepo %s\n') % subrelpath(self))
688 hg.clean(self._repo, node.nullid, False) 688 hg.clean(self._repo, node.nullid, False)
689 689
690 def _get(self, state): 690 def _get(self, state):
691 source, revision, kind = state 691 source, revision, kind = state
692 parentrepo = self._repo._subparent 692 parentrepo = self._repo._subparent
711 # 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.
712 # 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
713 # work with that. 713 # work with that.
714 if parentrepo.shared() and hg.islocal(srcurl): 714 if parentrepo.shared() and hg.islocal(srcurl):
715 self.ui.status( 715 self.ui.status(
716 _('sharing subrepo %s from %s\n') 716 _(b'sharing subrepo %s from %s\n')
717 % (subrelpath(self), srcurl) 717 % (subrelpath(self), srcurl)
718 ) 718 )
719 shared = hg.share( 719 shared = hg.share(
720 self._repo._subparent.baseui, 720 self._repo._subparent.baseui,
721 getpeer(), 721 getpeer(),
726 self._repo = shared.local() 726 self._repo = shared.local()
727 else: 727 else:
728 # 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
729 # share.py wrap of the clone command. 729 # share.py wrap of the clone command.
730 if parentrepo.shared(): 730 if parentrepo.shared():
731 pool = self.ui.config('share', 'pool') 731 pool = self.ui.config(b'share', b'pool')
732 if pool: 732 if pool:
733 pool = util.expandpath(pool) 733 pool = util.expandpath(pool)
734 734
735 shareopts = { 735 shareopts = {
736 'pool': pool, 736 b'pool': pool,
737 'mode': self.ui.config('share', 'poolnaming'), 737 b'mode': self.ui.config(b'share', b'poolnaming'),
738 } 738 }
739 else: 739 else:
740 shareopts = {} 740 shareopts = {}
741 741
742 self.ui.status( 742 self.ui.status(
743 _('cloning subrepo %s from %s\n') 743 _(b'cloning subrepo %s from %s\n')
744 % (subrelpath(self), util.hidepassword(srcurl)) 744 % (subrelpath(self), util.hidepassword(srcurl))
745 ) 745 )
746 other, cloned = hg.clone( 746 other, cloned = hg.clone(
747 self._repo._subparent.baseui, 747 self._repo._subparent.baseui,
748 {}, 748 {},
754 self._repo = cloned.local() 754 self._repo = cloned.local()
755 self._initrepo(parentrepo, source, create=True) 755 self._initrepo(parentrepo, source, create=True)
756 self._cachestorehash(srcurl) 756 self._cachestorehash(srcurl)
757 else: 757 else:
758 self.ui.status( 758 self.ui.status(
759 _('pulling subrepo %s from %s\n') 759 _(b'pulling subrepo %s from %s\n')
760 % (subrelpath(self), util.hidepassword(srcurl)) 760 % (subrelpath(self), util.hidepassword(srcurl))
761 ) 761 )
762 cleansub = self.storeclean(srcurl) 762 cleansub = self.storeclean(srcurl)
763 exchange.pull(self._repo, getpeer()) 763 exchange.pull(self._repo, getpeer())
764 if cleansub: 764 if cleansub:
769 @annotatesubrepoerror 769 @annotatesubrepoerror
770 def get(self, state, overwrite=False): 770 def get(self, state, overwrite=False):
771 inrepo = self._get(state) 771 inrepo = self._get(state)
772 source, revision, kind = state 772 source, revision, kind = state
773 repo = self._repo 773 repo = self._repo
774 repo.ui.debug("getting subrepo %s\n" % self._path) 774 repo.ui.debug(b"getting subrepo %s\n" % self._path)
775 if inrepo: 775 if inrepo:
776 urepo = repo.unfiltered() 776 urepo = repo.unfiltered()
777 ctx = urepo[revision] 777 ctx = urepo[revision]
778 if ctx.hidden(): 778 if ctx.hidden():
779 urepo.ui.warn( 779 urepo.ui.warn(
780 _('revision %s in subrepository "%s" is hidden\n') 780 _(b'revision %s in subrepository "%s" is hidden\n')
781 % (revision[0:12], self._path) 781 % (revision[0:12], self._path)
782 ) 782 )
783 repo = urepo 783 repo = urepo
784 hg.updaterepo(repo, revision, overwrite) 784 hg.updaterepo(repo, revision, overwrite)
785 785
786 @annotatesubrepoerror 786 @annotatesubrepoerror
787 def merge(self, state): 787 def merge(self, state):
788 self._get(state) 788 self._get(state)
789 cur = self._repo['.'] 789 cur = self._repo[b'.']
790 dst = self._repo[state[1]] 790 dst = self._repo[state[1]]
791 anc = dst.ancestor(cur) 791 anc = dst.ancestor(cur)
792 792
793 def mergefunc(): 793 def mergefunc():
794 if anc == cur and dst.branch() == cur.branch(): 794 if anc == cur and dst.branch() == cur.branch():
795 self.ui.debug( 795 self.ui.debug(
796 'updating subrepository "%s"\n' % subrelpath(self) 796 b'updating subrepository "%s"\n' % subrelpath(self)
797 ) 797 )
798 hg.update(self._repo, state[1]) 798 hg.update(self._repo, state[1])
799 elif anc == dst: 799 elif anc == dst:
800 self.ui.debug( 800 self.ui.debug(
801 'skipping subrepository "%s"\n' % subrelpath(self) 801 b'skipping subrepository "%s"\n' % subrelpath(self)
802 ) 802 )
803 else: 803 else:
804 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self)) 804 self.ui.debug(
805 b'merging subrepository "%s"\n' % subrelpath(self)
806 )
805 hg.merge(self._repo, state[1], remind=False) 807 hg.merge(self._repo, state[1], remind=False)
806 808
807 wctx = self._repo[None] 809 wctx = self._repo[None]
808 if self.dirty(): 810 if self.dirty():
809 if anc != dst: 811 if anc != dst:
814 else: 816 else:
815 mergefunc() 817 mergefunc()
816 818
817 @annotatesubrepoerror 819 @annotatesubrepoerror
818 def push(self, opts): 820 def push(self, opts):
819 force = opts.get('force') 821 force = opts.get(b'force')
820 newbranch = opts.get('new_branch') 822 newbranch = opts.get(b'new_branch')
821 ssh = opts.get('ssh') 823 ssh = opts.get(b'ssh')
822 824
823 # push subrepos depth-first for coherent ordering 825 # push subrepos depth-first for coherent ordering
824 c = self._repo['.'] 826 c = self._repo[b'.']
825 subs = c.substate # only repos that are committed 827 subs = c.substate # only repos that are committed
826 for s in sorted(subs): 828 for s in sorted(subs):
827 if c.sub(s).push(opts) == 0: 829 if c.sub(s).push(opts) == 0:
828 return False 830 return False
829 831
830 dsturl = _abssource(self._repo, True) 832 dsturl = _abssource(self._repo, True)
831 if not force: 833 if not force:
832 if self.storeclean(dsturl): 834 if self.storeclean(dsturl):
833 self.ui.status( 835 self.ui.status(
834 _('no changes made to subrepo %s since last push to %s\n') 836 _(b'no changes made to subrepo %s since last push to %s\n')
835 % (subrelpath(self), util.hidepassword(dsturl)) 837 % (subrelpath(self), util.hidepassword(dsturl))
836 ) 838 )
837 return None 839 return None
838 self.ui.status( 840 self.ui.status(
839 _('pushing subrepo %s to %s\n') 841 _(b'pushing subrepo %s to %s\n')
840 % (subrelpath(self), util.hidepassword(dsturl)) 842 % (subrelpath(self), util.hidepassword(dsturl))
841 ) 843 )
842 other = hg.peer(self._repo, {'ssh': ssh}, dsturl) 844 other = hg.peer(self._repo, {b'ssh': ssh}, dsturl)
843 res = exchange.push(self._repo, other, force, newbranch=newbranch) 845 res = exchange.push(self._repo, other, force, newbranch=newbranch)
844 846
845 # the repo is now clean 847 # the repo is now clean
846 self._cachestorehash(dsturl) 848 self._cachestorehash(dsturl)
847 return res.cgresult 849 return res.cgresult
848 850
849 @annotatesubrepoerror 851 @annotatesubrepoerror
850 def outgoing(self, ui, dest, opts): 852 def outgoing(self, ui, dest, opts):
851 if 'rev' in opts or 'branch' in opts: 853 if b'rev' in opts or b'branch' in opts:
852 opts = copy.copy(opts) 854 opts = copy.copy(opts)
853 opts.pop('rev', None) 855 opts.pop(b'rev', None)
854 opts.pop('branch', None) 856 opts.pop(b'branch', None)
855 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts) 857 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
856 858
857 @annotatesubrepoerror 859 @annotatesubrepoerror
858 def incoming(self, ui, source, opts): 860 def incoming(self, ui, source, opts):
859 if 'rev' in opts or 'branch' in opts: 861 if b'rev' in opts or b'branch' in opts:
860 opts = copy.copy(opts) 862 opts = copy.copy(opts)
861 opts.pop('rev', None) 863 opts.pop(b'rev', None)
862 opts.pop('branch', None) 864 opts.pop(b'branch', None)
863 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts) 865 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
864 866
865 @annotatesubrepoerror 867 @annotatesubrepoerror
866 def files(self): 868 def files(self):
867 rev = self._state[1] 869 rev = self._state[1]
908 sm = sub.matchfileset(expr, badfn=badfn) 910 sm = sub.matchfileset(expr, badfn=badfn)
909 pm = matchmod.prefixdirmatcher(subpath, sm, badfn=badfn) 911 pm = matchmod.prefixdirmatcher(subpath, sm, badfn=badfn)
910 matchers.append(pm) 912 matchers.append(pm)
911 except error.LookupError: 913 except error.LookupError:
912 self.ui.status( 914 self.ui.status(
913 _("skipping missing subrepository: %s\n") 915 _(b"skipping missing subrepository: %s\n")
914 % self.wvfs.reljoin(reporelpath(self), subpath) 916 % self.wvfs.reljoin(reporelpath(self), subpath)
915 ) 917 )
916 if len(matchers) == 1: 918 if len(matchers) == 1:
917 return matchers[0] 919 return matchers[0]
918 return matchmod.unionmatcher(matchers) 920 return matchmod.unionmatcher(matchers)
963 # reverting a subrepo is a 2 step process: 965 # reverting a subrepo is a 2 step process:
964 # 1. if the no_backup is not set, revert all modified 966 # 1. if the no_backup is not set, revert all modified
965 # files inside the subrepo 967 # files inside the subrepo
966 # 2. update the subrepo to the revision specified in 968 # 2. update the subrepo to the revision specified in
967 # the corresponding substate dictionary 969 # the corresponding substate dictionary
968 self.ui.status(_('reverting subrepo %s\n') % substate[0]) 970 self.ui.status(_(b'reverting subrepo %s\n') % substate[0])
969 if not opts.get(r'no_backup'): 971 if not opts.get(r'no_backup'):
970 # Revert all files on the subrepo, creating backups 972 # Revert all files on the subrepo, creating backups
971 # Note that this will not recursively revert subrepos 973 # Note that this will not recursively revert subrepos
972 # We could do it if there was a set:subrepos() predicate 974 # We could do it if there was a set:subrepos() predicate
973 opts = opts.copy() 975 opts = opts.copy()
982 984
983 def filerevert(self, *pats, **opts): 985 def filerevert(self, *pats, **opts):
984 ctx = self._repo[opts[r'rev']] 986 ctx = self._repo[opts[r'rev']]
985 parents = self._repo.dirstate.parents() 987 parents = self._repo.dirstate.parents()
986 if opts.get(r'all'): 988 if opts.get(r'all'):
987 pats = ['set:modified()'] 989 pats = [b'set:modified()']
988 else: 990 else:
989 pats = [] 991 pats = []
990 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts) 992 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
991 993
992 def shortid(self, revid): 994 def shortid(self, revid):
1005 1007
1006 # Nothing prevents a user from sharing in a repo, and then making that a 1008 # Nothing prevents a user from sharing in a repo, and then making that a
1007 # subrepo. Alternately, the previous unshare attempt may have failed 1009 # subrepo. Alternately, the previous unshare attempt may have failed
1008 # part way through. So recurse whether or not this layer is shared. 1010 # part way through. So recurse whether or not this layer is shared.
1009 if self._repo.shared(): 1011 if self._repo.shared():
1010 self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath) 1012 self.ui.status(_(b"unsharing subrepo '%s'\n") % self._relpath)
1011 1013
1012 hg.unshare(self.ui, self._repo) 1014 hg.unshare(self.ui, self._repo)
1013 1015
1014 def verify(self): 1016 def verify(self):
1015 try: 1017 try:
1018 if ctx.hidden(): 1020 if ctx.hidden():
1019 # Since hidden revisions aren't pushed/pulled, it seems worth an 1021 # Since hidden revisions aren't pushed/pulled, it seems worth an
1020 # explicit warning. 1022 # explicit warning.
1021 ui = self._repo.ui 1023 ui = self._repo.ui
1022 ui.warn( 1024 ui.warn(
1023 _("subrepo '%s' is hidden in revision %s\n") 1025 _(b"subrepo '%s' is hidden in revision %s\n")
1024 % (self._relpath, node.short(self._ctx.node())) 1026 % (self._relpath, node.short(self._ctx.node()))
1025 ) 1027 )
1026 return 0 1028 return 0
1027 except error.RepoLookupError: 1029 except error.RepoLookupError:
1028 # A missing subrepo revision may be a case of needing to pull it, so 1030 # A missing subrepo revision may be a case of needing to pull it, so
1029 # don't treat this as an error. 1031 # don't treat this as an error.
1030 self._repo.ui.warn( 1032 self._repo.ui.warn(
1031 _("subrepo '%s' not found in revision %s\n") 1033 _(b"subrepo '%s' not found in revision %s\n")
1032 % (self._relpath, node.short(self._ctx.node())) 1034 % (self._relpath, node.short(self._ctx.node()))
1033 ) 1035 )
1034 return 0 1036 return 0
1035 1037
1036 @propertycache 1038 @propertycache
1049 1051
1050 class svnsubrepo(abstractsubrepo): 1052 class svnsubrepo(abstractsubrepo):
1051 def __init__(self, ctx, path, state, allowcreate): 1053 def __init__(self, ctx, path, state, allowcreate):
1052 super(svnsubrepo, self).__init__(ctx, path) 1054 super(svnsubrepo, self).__init__(ctx, path)
1053 self._state = state 1055 self._state = state
1054 self._exe = procutil.findexe('svn') 1056 self._exe = procutil.findexe(b'svn')
1055 if not self._exe: 1057 if not self._exe:
1056 raise error.Abort( 1058 raise error.Abort(
1057 _("'svn' executable not found for subrepo '%s'") % self._path 1059 _(b"'svn' executable not found for subrepo '%s'") % self._path
1058 ) 1060 )
1059 1061
1060 def _svncommand(self, commands, filename='', failok=False): 1062 def _svncommand(self, commands, filename=b'', failok=False):
1061 cmd = [self._exe] 1063 cmd = [self._exe]
1062 extrakw = {} 1064 extrakw = {}
1063 if not self.ui.interactive(): 1065 if not self.ui.interactive():
1064 # Making stdin be a pipe should prevent svn from behaving 1066 # Making stdin be a pipe should prevent svn from behaving
1065 # interactively even if we can't pass --non-interactive. 1067 # interactively even if we can't pass --non-interactive.
1066 extrakw[r'stdin'] = subprocess.PIPE 1068 extrakw[r'stdin'] = subprocess.PIPE
1067 # Starting in svn 1.5 --non-interactive is a global flag 1069 # Starting in svn 1.5 --non-interactive is a global flag
1068 # instead of being per-command, but we need to support 1.4 so 1070 # instead of being per-command, but we need to support 1.4 so
1069 # we have to be intelligent about what commands take 1071 # we have to be intelligent about what commands take
1070 # --non-interactive. 1072 # --non-interactive.
1071 if commands[0] in ('update', 'checkout', 'commit'): 1073 if commands[0] in (b'update', b'checkout', b'commit'):
1072 cmd.append('--non-interactive') 1074 cmd.append(b'--non-interactive')
1073 cmd.extend(commands) 1075 cmd.extend(commands)
1074 if filename is not None: 1076 if filename is not None:
1075 path = self.wvfs.reljoin( 1077 path = self.wvfs.reljoin(
1076 self._ctx.repo().origroot, self._path, filename 1078 self._ctx.repo().origroot, self._path, filename
1077 ) 1079 )
1078 cmd.append(path) 1080 cmd.append(path)
1079 env = dict(encoding.environ) 1081 env = dict(encoding.environ)
1080 # Avoid localized output, preserve current locale for everything else. 1082 # Avoid localized output, preserve current locale for everything else.
1081 lc_all = env.get('LC_ALL') 1083 lc_all = env.get(b'LC_ALL')
1082 if lc_all: 1084 if lc_all:
1083 env['LANG'] = lc_all 1085 env[b'LANG'] = lc_all
1084 del env['LC_ALL'] 1086 del env[b'LC_ALL']
1085 env['LC_MESSAGES'] = 'C' 1087 env[b'LC_MESSAGES'] = b'C'
1086 p = subprocess.Popen( 1088 p = subprocess.Popen(
1087 pycompat.rapply(procutil.tonativestr, cmd), 1089 pycompat.rapply(procutil.tonativestr, cmd),
1088 bufsize=-1, 1090 bufsize=-1,
1089 close_fds=procutil.closefds, 1091 close_fds=procutil.closefds,
1090 stdout=subprocess.PIPE, 1092 stdout=subprocess.PIPE,
1095 stdout, stderr = map(util.fromnativeeol, p.communicate()) 1097 stdout, stderr = map(util.fromnativeeol, p.communicate())
1096 stderr = stderr.strip() 1098 stderr = stderr.strip()
1097 if not failok: 1099 if not failok:
1098 if p.returncode: 1100 if p.returncode:
1099 raise error.Abort( 1101 raise error.Abort(
1100 stderr or 'exited with code %d' % p.returncode 1102 stderr or b'exited with code %d' % p.returncode
1101 ) 1103 )
1102 if stderr: 1104 if stderr:
1103 self.ui.warn(stderr + '\n') 1105 self.ui.warn(stderr + b'\n')
1104 return stdout, stderr 1106 return stdout, stderr
1105 1107
1106 @propertycache 1108 @propertycache
1107 def _svnversion(self): 1109 def _svnversion(self):
1108 output, err = self._svncommand(['--version', '--quiet'], filename=None) 1110 output, err = self._svncommand(
1111 [b'--version', b'--quiet'], filename=None
1112 )
1109 m = re.search(br'^(\d+)\.(\d+)', output) 1113 m = re.search(br'^(\d+)\.(\d+)', output)
1110 if not m: 1114 if not m:
1111 raise error.Abort(_('cannot retrieve svn tool version')) 1115 raise error.Abort(_(b'cannot retrieve svn tool version'))
1112 return (int(m.group(1)), int(m.group(2))) 1116 return (int(m.group(1)), int(m.group(2)))
1113 1117
1114 def _svnmissing(self): 1118 def _svnmissing(self):
1115 return not self.wvfs.exists('.svn') 1119 return not self.wvfs.exists(b'.svn')
1116 1120
1117 def _wcrevs(self): 1121 def _wcrevs(self):
1118 # Get the working directory revision as well as the last 1122 # Get the working directory revision as well as the last
1119 # commit revision so we can compare the subrepo state with 1123 # commit revision so we can compare the subrepo state with
1120 # both. We used to store the working directory one. 1124 # both. We used to store the working directory one.
1121 output, err = self._svncommand(['info', '--xml']) 1125 output, err = self._svncommand([b'info', b'--xml'])
1122 doc = xml.dom.minidom.parseString(output) 1126 doc = xml.dom.minidom.parseString(output)
1123 entries = doc.getElementsByTagName(r'entry') 1127 entries = doc.getElementsByTagName(r'entry')
1124 lastrev, rev = '0', '0' 1128 lastrev, rev = b'0', b'0'
1125 if entries: 1129 if entries:
1126 rev = pycompat.bytestr(entries[0].getAttribute(r'revision')) or '0' 1130 rev = pycompat.bytestr(entries[0].getAttribute(r'revision')) or b'0'
1127 commits = entries[0].getElementsByTagName(r'commit') 1131 commits = entries[0].getElementsByTagName(r'commit')
1128 if commits: 1132 if commits:
1129 lastrev = ( 1133 lastrev = (
1130 pycompat.bytestr(commits[0].getAttribute(r'revision')) 1134 pycompat.bytestr(commits[0].getAttribute(r'revision'))
1131 or '0' 1135 or b'0'
1132 ) 1136 )
1133 return (lastrev, rev) 1137 return (lastrev, rev)
1134 1138
1135 def _wcrev(self): 1139 def _wcrev(self):
1136 return self._wcrevs()[0] 1140 return self._wcrevs()[0]
1139 """Return (changes, extchanges, missing) where changes is True 1143 """Return (changes, extchanges, missing) where changes is True
1140 if the working directory was changed, extchanges is 1144 if the working directory was changed, extchanges is
1141 True if any of these changes concern an external entry and missing 1145 True if any of these changes concern an external entry and missing
1142 is True if any change is a missing entry. 1146 is True if any change is a missing entry.
1143 """ 1147 """
1144 output, err = self._svncommand(['status', '--xml']) 1148 output, err = self._svncommand([b'status', b'--xml'])
1145 externals, changes, missing = [], [], [] 1149 externals, changes, missing = [], [], []
1146 doc = xml.dom.minidom.parseString(output) 1150 doc = xml.dom.minidom.parseString(output)
1147 for e in doc.getElementsByTagName(r'entry'): 1151 for e in doc.getElementsByTagName(r'entry'):
1148 s = e.getElementsByTagName(r'wc-status') 1152 s = e.getElementsByTagName(r'wc-status')
1149 if not s: 1153 if not s:
1169 return bool(changes), False, bool(missing) 1173 return bool(changes), False, bool(missing)
1170 1174
1171 @annotatesubrepoerror 1175 @annotatesubrepoerror
1172 def dirty(self, ignoreupdate=False, missing=False): 1176 def dirty(self, ignoreupdate=False, missing=False):
1173 if self._svnmissing(): 1177 if self._svnmissing():
1174 return self._state[1] != '' 1178 return self._state[1] != b''
1175 wcchanged = self._wcchanged() 1179 wcchanged = self._wcchanged()
1176 changed = wcchanged[0] or (missing and wcchanged[2]) 1180 changed = wcchanged[0] or (missing and wcchanged[2])
1177 if not changed: 1181 if not changed:
1178 if self._state[1] in self._wcrevs() or ignoreupdate: 1182 if self._state[1] in self._wcrevs() or ignoreupdate:
1179 return False 1183 return False
1185 # Last committed rev is not the same than rev. We would 1189 # Last committed rev is not the same than rev. We would
1186 # like to take lastrev but we do not know if the subrepo 1190 # like to take lastrev but we do not know if the subrepo
1187 # URL exists at lastrev. Test it and fallback to rev it 1191 # URL exists at lastrev. Test it and fallback to rev it
1188 # is not there. 1192 # is not there.
1189 try: 1193 try:
1190 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)]) 1194 self._svncommand(
1195 [b'list', b'%s@%s' % (self._state[0], lastrev)]
1196 )
1191 return lastrev 1197 return lastrev
1192 except error.Abort: 1198 except error.Abort:
1193 pass 1199 pass
1194 return rev 1200 return rev
1195 1201
1199 changed, extchanged, missing = self._wcchanged() 1205 changed, extchanged, missing = self._wcchanged()
1200 if not changed: 1206 if not changed:
1201 return self.basestate() 1207 return self.basestate()
1202 if extchanged: 1208 if extchanged:
1203 # Do not try to commit externals 1209 # Do not try to commit externals
1204 raise error.Abort(_('cannot commit svn externals')) 1210 raise error.Abort(_(b'cannot commit svn externals'))
1205 if missing: 1211 if missing:
1206 # svn can commit with missing entries but aborting like hg 1212 # svn can commit with missing entries but aborting like hg
1207 # seems a better approach. 1213 # seems a better approach.
1208 raise error.Abort(_('cannot commit missing svn entries')) 1214 raise error.Abort(_(b'cannot commit missing svn entries'))
1209 commitinfo, err = self._svncommand(['commit', '-m', text]) 1215 commitinfo, err = self._svncommand([b'commit', b'-m', text])
1210 self.ui.status(commitinfo) 1216 self.ui.status(commitinfo)
1211 newrev = re.search('Committed revision ([0-9]+).', commitinfo) 1217 newrev = re.search(b'Committed revision ([0-9]+).', commitinfo)
1212 if not newrev: 1218 if not newrev:
1213 if not commitinfo.strip(): 1219 if not commitinfo.strip():
1214 # Sometimes, our definition of "changed" differs from 1220 # Sometimes, our definition of "changed" differs from
1215 # svn one. For instance, svn ignores missing files 1221 # svn one. For instance, svn ignores missing files
1216 # when committing. If there are only missing files, no 1222 # when committing. If there are only missing files, no
1217 # commit is made, no output and no error code. 1223 # commit is made, no output and no error code.
1218 raise error.Abort(_('failed to commit svn changes')) 1224 raise error.Abort(_(b'failed to commit svn changes'))
1219 raise error.Abort(commitinfo.splitlines()[-1]) 1225 raise error.Abort(commitinfo.splitlines()[-1])
1220 newrev = newrev.groups()[0] 1226 newrev = newrev.groups()[0]
1221 self.ui.status(self._svncommand(['update', '-r', newrev])[0]) 1227 self.ui.status(self._svncommand([b'update', b'-r', newrev])[0])
1222 return newrev 1228 return newrev
1223 1229
1224 @annotatesubrepoerror 1230 @annotatesubrepoerror
1225 def remove(self): 1231 def remove(self):
1226 if self.dirty(): 1232 if self.dirty():
1227 self.ui.warn( 1233 self.ui.warn(
1228 _('not removing repo %s because ' 'it has changes.\n') 1234 _(b'not removing repo %s because ' b'it has changes.\n')
1229 % self._path 1235 % self._path
1230 ) 1236 )
1231 return 1237 return
1232 self.ui.note(_('removing subrepo %s\n') % self._path) 1238 self.ui.note(_(b'removing subrepo %s\n') % self._path)
1233 1239
1234 self.wvfs.rmtree(forcibly=True) 1240 self.wvfs.rmtree(forcibly=True)
1235 try: 1241 try:
1236 pwvfs = self._ctx.repo().wvfs 1242 pwvfs = self._ctx.repo().wvfs
1237 pwvfs.removedirs(pwvfs.dirname(self._path)) 1243 pwvfs.removedirs(pwvfs.dirname(self._path))
1239 pass 1245 pass
1240 1246
1241 @annotatesubrepoerror 1247 @annotatesubrepoerror
1242 def get(self, state, overwrite=False): 1248 def get(self, state, overwrite=False):
1243 if overwrite: 1249 if overwrite:
1244 self._svncommand(['revert', '--recursive']) 1250 self._svncommand([b'revert', b'--recursive'])
1245 args = ['checkout'] 1251 args = [b'checkout']
1246 if self._svnversion >= (1, 5): 1252 if self._svnversion >= (1, 5):
1247 args.append('--force') 1253 args.append(b'--force')
1248 # The revision must be specified at the end of the URL to properly 1254 # The revision must be specified at the end of the URL to properly
1249 # update to a directory which has since been deleted and recreated. 1255 # update to a directory which has since been deleted and recreated.
1250 args.append('%s@%s' % (state[0], state[1])) 1256 args.append(b'%s@%s' % (state[0], state[1]))
1251 1257
1252 # SEC: check that the ssh url is safe 1258 # SEC: check that the ssh url is safe
1253 util.checksafessh(state[0]) 1259 util.checksafessh(state[0])
1254 1260
1255 status, err = self._svncommand(args, failok=True) 1261 status, err = self._svncommand(args, failok=True)
1256 _sanitize(self.ui, self.wvfs, '.svn') 1262 _sanitize(self.ui, self.wvfs, b'.svn')
1257 if not re.search('Checked out revision [0-9]+.', status): 1263 if not re.search(b'Checked out revision [0-9]+.', status):
1258 if 'is already a working copy for a different URL' in err and ( 1264 if b'is already a working copy for a different URL' in err and (
1259 self._wcchanged()[:2] == (False, False) 1265 self._wcchanged()[:2] == (False, False)
1260 ): 1266 ):
1261 # obstructed but clean working copy, so just blow it away. 1267 # obstructed but clean working copy, so just blow it away.
1262 self.remove() 1268 self.remove()
1263 self.get(state, overwrite=False) 1269 self.get(state, overwrite=False)
1279 # push is a no-op for SVN 1285 # push is a no-op for SVN
1280 return True 1286 return True
1281 1287
1282 @annotatesubrepoerror 1288 @annotatesubrepoerror
1283 def files(self): 1289 def files(self):
1284 output = self._svncommand(['list', '--recursive', '--xml'])[0] 1290 output = self._svncommand([b'list', b'--recursive', b'--xml'])[0]
1285 doc = xml.dom.minidom.parseString(output) 1291 doc = xml.dom.minidom.parseString(output)
1286 paths = [] 1292 paths = []
1287 for e in doc.getElementsByTagName(r'entry'): 1293 for e in doc.getElementsByTagName(r'entry'):
1288 kind = pycompat.bytestr(e.getAttribute(r'kind')) 1294 kind = pycompat.bytestr(e.getAttribute(r'kind'))
1289 if kind != 'file': 1295 if kind != b'file':
1290 continue 1296 continue
1291 name = r''.join( 1297 name = r''.join(
1292 c.data 1298 c.data
1293 for c in e.getElementsByTagName(r'name')[0].childNodes 1299 for c in e.getElementsByTagName(r'name')[0].childNodes
1294 if c.nodeType == c.TEXT_NODE 1300 if c.nodeType == c.TEXT_NODE
1295 ) 1301 )
1296 paths.append(name.encode('utf8')) 1302 paths.append(name.encode('utf8'))
1297 return paths 1303 return paths
1298 1304
1299 def filedata(self, name, decode): 1305 def filedata(self, name, decode):
1300 return self._svncommand(['cat'], name)[0] 1306 return self._svncommand([b'cat'], name)[0]
1301 1307
1302 1308
1303 class gitsubrepo(abstractsubrepo): 1309 class gitsubrepo(abstractsubrepo):
1304 def __init__(self, ctx, path, state, allowcreate): 1310 def __init__(self, ctx, path, state, allowcreate):
1305 super(gitsubrepo, self).__init__(ctx, path) 1311 super(gitsubrepo, self).__init__(ctx, path)
1308 self._subparent = ctx.repo() 1314 self._subparent = ctx.repo()
1309 self._ensuregit() 1315 self._ensuregit()
1310 1316
1311 def _ensuregit(self): 1317 def _ensuregit(self):
1312 try: 1318 try:
1313 self._gitexecutable = 'git' 1319 self._gitexecutable = b'git'
1314 out, err = self._gitnodir(['--version']) 1320 out, err = self._gitnodir([b'--version'])
1315 except OSError as e: 1321 except OSError as e:
1316 genericerror = _("error executing git for subrepo '%s': %s") 1322 genericerror = _(b"error executing git for subrepo '%s': %s")
1317 notfoundhint = _("check git is installed and in your PATH") 1323 notfoundhint = _(b"check git is installed and in your PATH")
1318 if e.errno != errno.ENOENT: 1324 if e.errno != errno.ENOENT:
1319 raise error.Abort( 1325 raise error.Abort(
1320 genericerror % (self._path, encoding.strtolocal(e.strerror)) 1326 genericerror % (self._path, encoding.strtolocal(e.strerror))
1321 ) 1327 )
1322 elif pycompat.iswindows: 1328 elif pycompat.iswindows:
1323 try: 1329 try:
1324 self._gitexecutable = 'git.cmd' 1330 self._gitexecutable = b'git.cmd'
1325 out, err = self._gitnodir(['--version']) 1331 out, err = self._gitnodir([b'--version'])
1326 except OSError as e2: 1332 except OSError as e2:
1327 if e2.errno == errno.ENOENT: 1333 if e2.errno == errno.ENOENT:
1328 raise error.Abort( 1334 raise error.Abort(
1329 _( 1335 _(
1330 "couldn't find 'git' or 'git.cmd'" 1336 b"couldn't find 'git' or 'git.cmd'"
1331 " for subrepo '%s'" 1337 b" for subrepo '%s'"
1332 ) 1338 )
1333 % self._path, 1339 % self._path,
1334 hint=notfoundhint, 1340 hint=notfoundhint,
1335 ) 1341 )
1336 else: 1342 else:
1338 genericerror 1344 genericerror
1339 % (self._path, encoding.strtolocal(e2.strerror)) 1345 % (self._path, encoding.strtolocal(e2.strerror))
1340 ) 1346 )
1341 else: 1347 else:
1342 raise error.Abort( 1348 raise error.Abort(
1343 _("couldn't find git for subrepo '%s'") % self._path, 1349 _(b"couldn't find git for subrepo '%s'") % self._path,
1344 hint=notfoundhint, 1350 hint=notfoundhint,
1345 ) 1351 )
1346 versionstatus = self._checkversion(out) 1352 versionstatus = self._checkversion(out)
1347 if versionstatus == 'unknown': 1353 if versionstatus == b'unknown':
1348 self.ui.warn(_('cannot retrieve git version\n')) 1354 self.ui.warn(_(b'cannot retrieve git version\n'))
1349 elif versionstatus == 'abort': 1355 elif versionstatus == b'abort':
1350 raise error.Abort(_('git subrepo requires at least 1.6.0 or later')) 1356 raise error.Abort(
1351 elif versionstatus == 'warning': 1357 _(b'git subrepo requires at least 1.6.0 or later')
1352 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n')) 1358 )
1359 elif versionstatus == b'warning':
1360 self.ui.warn(_(b'git subrepo requires at least 1.6.0 or later\n'))
1353 1361
1354 @staticmethod 1362 @staticmethod
1355 def _gitversion(out): 1363 def _gitversion(out):
1356 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out) 1364 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1357 if m: 1365 if m:
1390 version = gitsubrepo._gitversion(out) 1398 version = gitsubrepo._gitversion(out)
1391 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases, 1399 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1392 # despite the docstring comment. For now, error on 1.4.0, warn on 1400 # despite the docstring comment. For now, error on 1.4.0, warn on
1393 # 1.5.0 but attempt to continue. 1401 # 1.5.0 but attempt to continue.
1394 if version == -1: 1402 if version == -1:
1395 return 'unknown' 1403 return b'unknown'
1396 if version < (1, 5, 0): 1404 if version < (1, 5, 0):
1397 return 'abort' 1405 return b'abort'
1398 elif version < (1, 6, 0): 1406 elif version < (1, 6, 0):
1399 return 'warning' 1407 return b'warning'
1400 return 'ok' 1408 return b'ok'
1401 1409
1402 def _gitcommand(self, commands, env=None, stream=False): 1410 def _gitcommand(self, commands, env=None, stream=False):
1403 return self._gitdir(commands, env=env, stream=stream)[0] 1411 return self._gitdir(commands, env=env, stream=stream)[0]
1404 1412
1405 def _gitdir(self, commands, env=None, stream=False): 1413 def _gitdir(self, commands, env=None, stream=False):
1411 """Calls the git command 1419 """Calls the git command
1412 1420
1413 The methods tries to call the git command. versions prior to 1.6.0 1421 The methods tries to call the git command. versions prior to 1.6.0
1414 are not supported and very probably fail. 1422 are not supported and very probably fail.
1415 """ 1423 """
1416 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands))) 1424 self.ui.debug(b'%s: git %s\n' % (self._relpath, b' '.join(commands)))
1417 if env is None: 1425 if env is None:
1418 env = encoding.environ.copy() 1426 env = encoding.environ.copy()
1419 # disable localization for Git output (issue5176) 1427 # disable localization for Git output (issue5176)
1420 env['LC_ALL'] = 'C' 1428 env[b'LC_ALL'] = b'C'
1421 # fix for Git CVE-2015-7545 1429 # fix for Git CVE-2015-7545
1422 if 'GIT_ALLOW_PROTOCOL' not in env: 1430 if b'GIT_ALLOW_PROTOCOL' not in env:
1423 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh' 1431 env[b'GIT_ALLOW_PROTOCOL'] = b'file:git:http:https:ssh'
1424 # unless ui.quiet is set, print git's stderr, 1432 # unless ui.quiet is set, print git's stderr,
1425 # which is mostly progress and useful info 1433 # which is mostly progress and useful info
1426 errpipe = None 1434 errpipe = None
1427 if self.ui.quiet: 1435 if self.ui.quiet:
1428 errpipe = open(os.devnull, 'w') 1436 errpipe = open(os.devnull, b'w')
1429 if self.ui._colormode and len(commands) and commands[0] == "diff": 1437 if self.ui._colormode and len(commands) and commands[0] == b"diff":
1430 # insert the argument in the front, 1438 # insert the argument in the front,
1431 # the end of git diff arguments is used for paths 1439 # the end of git diff arguments is used for paths
1432 commands.insert(1, '--color') 1440 commands.insert(1, b'--color')
1433 p = subprocess.Popen( 1441 p = subprocess.Popen(
1434 pycompat.rapply( 1442 pycompat.rapply(
1435 procutil.tonativestr, [self._gitexecutable] + commands 1443 procutil.tonativestr, [self._gitexecutable] + commands
1436 ), 1444 ),
1437 bufsize=-1, 1445 bufsize=-1,
1449 p.wait() 1457 p.wait()
1450 1458
1451 if p.returncode != 0 and p.returncode != 1: 1459 if p.returncode != 0 and p.returncode != 1:
1452 # there are certain error codes that are ok 1460 # there are certain error codes that are ok
1453 command = commands[0] 1461 command = commands[0]
1454 if command in ('cat-file', 'symbolic-ref'): 1462 if command in (b'cat-file', b'symbolic-ref'):
1455 return retdata, p.returncode 1463 return retdata, p.returncode
1456 # for all others, abort 1464 # for all others, abort
1457 raise error.Abort( 1465 raise error.Abort(
1458 _('git %s error %d in %s') 1466 _(b'git %s error %d in %s')
1459 % (command, p.returncode, self._relpath) 1467 % (command, p.returncode, self._relpath)
1460 ) 1468 )
1461 1469
1462 return retdata, p.returncode 1470 return retdata, p.returncode
1463 1471
1464 def _gitmissing(self): 1472 def _gitmissing(self):
1465 return not self.wvfs.exists('.git') 1473 return not self.wvfs.exists(b'.git')
1466 1474
1467 def _gitstate(self): 1475 def _gitstate(self):
1468 return self._gitcommand(['rev-parse', 'HEAD']) 1476 return self._gitcommand([b'rev-parse', b'HEAD'])
1469 1477
1470 def _gitcurrentbranch(self): 1478 def _gitcurrentbranch(self):
1471 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet']) 1479 current, err = self._gitdir([b'symbolic-ref', b'HEAD', b'--quiet'])
1472 if err: 1480 if err:
1473 current = None 1481 current = None
1474 return current 1482 return current
1475 1483
1476 def _gitremote(self, remote): 1484 def _gitremote(self, remote):
1477 out = self._gitcommand(['remote', 'show', '-n', remote]) 1485 out = self._gitcommand([b'remote', b'show', b'-n', remote])
1478 line = out.split('\n')[1] 1486 line = out.split(b'\n')[1]
1479 i = line.index('URL: ') + len('URL: ') 1487 i = line.index(b'URL: ') + len(b'URL: ')
1480 return line[i:] 1488 return line[i:]
1481 1489
1482 def _githavelocally(self, revision): 1490 def _githavelocally(self, revision):
1483 out, code = self._gitdir(['cat-file', '-e', revision]) 1491 out, code = self._gitdir([b'cat-file', b'-e', revision])
1484 return code == 0 1492 return code == 0
1485 1493
1486 def _gitisancestor(self, r1, r2): 1494 def _gitisancestor(self, r1, r2):
1487 base = self._gitcommand(['merge-base', r1, r2]) 1495 base = self._gitcommand([b'merge-base', r1, r2])
1488 return base == r1 1496 return base == r1
1489 1497
1490 def _gitisbare(self): 1498 def _gitisbare(self):
1491 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true' 1499 return self._gitcommand([b'config', b'--bool', b'core.bare']) == b'true'
1492 1500
1493 def _gitupdatestat(self): 1501 def _gitupdatestat(self):
1494 """This must be run before git diff-index. 1502 """This must be run before git diff-index.
1495 diff-index only looks at changes to file stat; 1503 diff-index only looks at changes to file stat;
1496 this command looks at file contents and updates the stat.""" 1504 this command looks at file contents and updates the stat."""
1497 self._gitcommand(['update-index', '-q', '--refresh']) 1505 self._gitcommand([b'update-index', b'-q', b'--refresh'])
1498 1506
1499 def _gitbranchmap(self): 1507 def _gitbranchmap(self):
1500 '''returns 2 things: 1508 '''returns 2 things:
1501 a map from git branch to revision 1509 a map from git branch to revision
1502 a map from revision to branches''' 1510 a map from revision to branches'''
1503 branch2rev = {} 1511 branch2rev = {}
1504 rev2branch = {} 1512 rev2branch = {}
1505 1513
1506 out = self._gitcommand( 1514 out = self._gitcommand(
1507 ['for-each-ref', '--format', '%(objectname) %(refname)'] 1515 [b'for-each-ref', b'--format', b'%(objectname) %(refname)']
1508 ) 1516 )
1509 for line in out.split('\n'): 1517 for line in out.split(b'\n'):
1510 revision, ref = line.split(' ') 1518 revision, ref = line.split(b' ')
1511 if not ref.startswith('refs/heads/') and not ref.startswith( 1519 if not ref.startswith(b'refs/heads/') and not ref.startswith(
1512 'refs/remotes/' 1520 b'refs/remotes/'
1513 ): 1521 ):
1514 continue 1522 continue
1515 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'): 1523 if ref.startswith(b'refs/remotes/') and ref.endswith(b'/HEAD'):
1516 continue # ignore remote/HEAD redirects 1524 continue # ignore remote/HEAD redirects
1517 branch2rev[ref] = revision 1525 branch2rev[ref] = revision
1518 rev2branch.setdefault(revision, []).append(ref) 1526 rev2branch.setdefault(revision, []).append(ref)
1519 return branch2rev, rev2branch 1527 return branch2rev, rev2branch
1520 1528
1521 def _gittracking(self, branches): 1529 def _gittracking(self, branches):
1522 'return map of remote branch to local tracking branch' 1530 b'return map of remote branch to local tracking branch'
1523 # assumes no more than one local tracking branch for each remote 1531 # assumes no more than one local tracking branch for each remote
1524 tracking = {} 1532 tracking = {}
1525 for b in branches: 1533 for b in branches:
1526 if b.startswith('refs/remotes/'): 1534 if b.startswith(b'refs/remotes/'):
1527 continue 1535 continue
1528 bname = b.split('/', 2)[2] 1536 bname = b.split(b'/', 2)[2]
1529 remote = self._gitcommand(['config', 'branch.%s.remote' % bname]) 1537 remote = self._gitcommand([b'config', b'branch.%s.remote' % bname])
1530 if remote: 1538 if remote:
1531 ref = self._gitcommand(['config', 'branch.%s.merge' % bname]) 1539 ref = self._gitcommand([b'config', b'branch.%s.merge' % bname])
1532 tracking[ 1540 tracking[
1533 'refs/remotes/%s/%s' % (remote, ref.split('/', 2)[2]) 1541 b'refs/remotes/%s/%s' % (remote, ref.split(b'/', 2)[2])
1534 ] = b 1542 ] = b
1535 return tracking 1543 return tracking
1536 1544
1537 def _abssource(self, source): 1545 def _abssource(self, source):
1538 if '://' not in source: 1546 if b'://' not in source:
1539 # recognize the scp syntax as an absolute source 1547 # recognize the scp syntax as an absolute source
1540 colon = source.find(':') 1548 colon = source.find(b':')
1541 if colon != -1 and '/' not in source[:colon]: 1549 if colon != -1 and b'/' not in source[:colon]:
1542 return source 1550 return source
1543 self._subsource = source 1551 self._subsource = source
1544 return _abssource(self) 1552 return _abssource(self)
1545 1553
1546 def _fetch(self, source, revision): 1554 def _fetch(self, source, revision):
1548 # SEC: check for safe ssh url 1556 # SEC: check for safe ssh url
1549 util.checksafessh(source) 1557 util.checksafessh(source)
1550 1558
1551 source = self._abssource(source) 1559 source = self._abssource(source)
1552 self.ui.status( 1560 self.ui.status(
1553 _('cloning subrepo %s from %s\n') % (self._relpath, source) 1561 _(b'cloning subrepo %s from %s\n') % (self._relpath, source)
1554 ) 1562 )
1555 self._gitnodir(['clone', source, self._abspath]) 1563 self._gitnodir([b'clone', source, self._abspath])
1556 if self._githavelocally(revision): 1564 if self._githavelocally(revision):
1557 return 1565 return
1558 self.ui.status( 1566 self.ui.status(
1559 _('pulling subrepo %s from %s\n') 1567 _(b'pulling subrepo %s from %s\n')
1560 % (self._relpath, self._gitremote('origin')) 1568 % (self._relpath, self._gitremote(b'origin'))
1561 ) 1569 )
1562 # try only origin: the originally cloned repo 1570 # try only origin: the originally cloned repo
1563 self._gitcommand(['fetch']) 1571 self._gitcommand([b'fetch'])
1564 if not self._githavelocally(revision): 1572 if not self._githavelocally(revision):
1565 raise error.Abort( 1573 raise error.Abort(
1566 _('revision %s does not exist in subrepository ' '"%s"\n') 1574 _(b'revision %s does not exist in subrepository ' b'"%s"\n')
1567 % (revision, self._relpath) 1575 % (revision, self._relpath)
1568 ) 1576 )
1569 1577
1570 @annotatesubrepoerror 1578 @annotatesubrepoerror
1571 def dirty(self, ignoreupdate=False, missing=False): 1579 def dirty(self, ignoreupdate=False, missing=False):
1572 if self._gitmissing(): 1580 if self._gitmissing():
1573 return self._state[1] != '' 1581 return self._state[1] != b''
1574 if self._gitisbare(): 1582 if self._gitisbare():
1575 return True 1583 return True
1576 if not ignoreupdate and self._state[1] != self._gitstate(): 1584 if not ignoreupdate and self._state[1] != self._gitstate():
1577 # different version checked out 1585 # different version checked out
1578 return True 1586 return True
1579 # check for staged changes or modified files; ignore untracked files 1587 # check for staged changes or modified files; ignore untracked files
1580 self._gitupdatestat() 1588 self._gitupdatestat()
1581 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD']) 1589 out, code = self._gitdir([b'diff-index', b'--quiet', b'HEAD'])
1582 return code == 1 1590 return code == 1
1583 1591
1584 def basestate(self): 1592 def basestate(self):
1585 return self._gitstate() 1593 return self._gitstate()
1586 1594
1591 self.remove() 1599 self.remove()
1592 return 1600 return
1593 self._fetch(source, revision) 1601 self._fetch(source, revision)
1594 # if the repo was set to be bare, unbare it 1602 # if the repo was set to be bare, unbare it
1595 if self._gitisbare(): 1603 if self._gitisbare():
1596 self._gitcommand(['config', 'core.bare', 'false']) 1604 self._gitcommand([b'config', b'core.bare', b'false'])
1597 if self._gitstate() == revision: 1605 if self._gitstate() == revision:
1598 self._gitcommand(['reset', '--hard', 'HEAD']) 1606 self._gitcommand([b'reset', b'--hard', b'HEAD'])
1599 return 1607 return
1600 elif self._gitstate() == revision: 1608 elif self._gitstate() == revision:
1601 if overwrite: 1609 if overwrite:
1602 # first reset the index to unmark new files for commit, because 1610 # first reset the index to unmark new files for commit, because
1603 # reset --hard will otherwise throw away files added for commit, 1611 # reset --hard will otherwise throw away files added for commit,
1604 # not just unmark them. 1612 # not just unmark them.
1605 self._gitcommand(['reset', 'HEAD']) 1613 self._gitcommand([b'reset', b'HEAD'])
1606 self._gitcommand(['reset', '--hard', 'HEAD']) 1614 self._gitcommand([b'reset', b'--hard', b'HEAD'])
1607 return 1615 return
1608 branch2rev, rev2branch = self._gitbranchmap() 1616 branch2rev, rev2branch = self._gitbranchmap()
1609 1617
1610 def checkout(args): 1618 def checkout(args):
1611 cmd = ['checkout'] 1619 cmd = [b'checkout']
1612 if overwrite: 1620 if overwrite:
1613 # first reset the index to unmark new files for commit, because 1621 # first reset the index to unmark new files for commit, because
1614 # the -f option will otherwise throw away files added for 1622 # the -f option will otherwise throw away files added for
1615 # commit, not just unmark them. 1623 # commit, not just unmark them.
1616 self._gitcommand(['reset', 'HEAD']) 1624 self._gitcommand([b'reset', b'HEAD'])
1617 cmd.append('-f') 1625 cmd.append(b'-f')
1618 self._gitcommand(cmd + args) 1626 self._gitcommand(cmd + args)
1619 _sanitize(self.ui, self.wvfs, '.git') 1627 _sanitize(self.ui, self.wvfs, b'.git')
1620 1628
1621 def rawcheckout(): 1629 def rawcheckout():
1622 # no branch to checkout, check it out with no branch 1630 # no branch to checkout, check it out with no branch
1623 self.ui.warn( 1631 self.ui.warn(
1624 _('checking out detached HEAD in ' 'subrepository "%s"\n') 1632 _(b'checking out detached HEAD in ' b'subrepository "%s"\n')
1625 % self._relpath 1633 % self._relpath
1626 ) 1634 )
1627 self.ui.warn( 1635 self.ui.warn(
1628 _('check out a git branch if you intend ' 'to make changes\n') 1636 _(b'check out a git branch if you intend ' b'to make changes\n')
1629 ) 1637 )
1630 checkout(['-q', revision]) 1638 checkout([b'-q', revision])
1631 1639
1632 if revision not in rev2branch: 1640 if revision not in rev2branch:
1633 rawcheckout() 1641 rawcheckout()
1634 return 1642 return
1635 branches = rev2branch[revision] 1643 branches = rev2branch[revision]
1636 firstlocalbranch = None 1644 firstlocalbranch = None
1637 for b in branches: 1645 for b in branches:
1638 if b == 'refs/heads/master': 1646 if b == b'refs/heads/master':
1639 # master trumps all other branches 1647 # master trumps all other branches
1640 checkout(['refs/heads/master']) 1648 checkout([b'refs/heads/master'])
1641 return 1649 return
1642 if not firstlocalbranch and not b.startswith('refs/remotes/'): 1650 if not firstlocalbranch and not b.startswith(b'refs/remotes/'):
1643 firstlocalbranch = b 1651 firstlocalbranch = b
1644 if firstlocalbranch: 1652 if firstlocalbranch:
1645 checkout([firstlocalbranch]) 1653 checkout([firstlocalbranch])
1646 return 1654 return
1647 1655
1654 remote = b 1662 remote = b
1655 break 1663 break
1656 1664
1657 if remote not in tracking: 1665 if remote not in tracking:
1658 # create a new local tracking branch 1666 # create a new local tracking branch
1659 local = remote.split('/', 3)[3] 1667 local = remote.split(b'/', 3)[3]
1660 checkout(['-b', local, remote]) 1668 checkout([b'-b', local, remote])
1661 elif self._gitisancestor(branch2rev[tracking[remote]], remote): 1669 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1662 # When updating to a tracked remote branch, 1670 # When updating to a tracked remote branch,
1663 # if the local tracking branch is downstream of it, 1671 # if the local tracking branch is downstream of it,
1664 # a normal `git pull` would have performed a "fast-forward merge" 1672 # a normal `git pull` would have performed a "fast-forward merge"
1665 # which is equivalent to updating the local branch to the remote. 1673 # which is equivalent to updating the local branch to the remote.
1666 # Since we are only looking at branching at update, we need to 1674 # Since we are only looking at branching at update, we need to
1667 # detect this situation and perform this action lazily. 1675 # detect this situation and perform this action lazily.
1668 if tracking[remote] != self._gitcurrentbranch(): 1676 if tracking[remote] != self._gitcurrentbranch():
1669 checkout([tracking[remote]]) 1677 checkout([tracking[remote]])
1670 self._gitcommand(['merge', '--ff', remote]) 1678 self._gitcommand([b'merge', b'--ff', remote])
1671 _sanitize(self.ui, self.wvfs, '.git') 1679 _sanitize(self.ui, self.wvfs, b'.git')
1672 else: 1680 else:
1673 # a real merge would be required, just checkout the revision 1681 # a real merge would be required, just checkout the revision
1674 rawcheckout() 1682 rawcheckout()
1675 1683
1676 @annotatesubrepoerror 1684 @annotatesubrepoerror
1677 def commit(self, text, user, date): 1685 def commit(self, text, user, date):
1678 if self._gitmissing(): 1686 if self._gitmissing():
1679 raise error.Abort(_("subrepo %s is missing") % self._relpath) 1687 raise error.Abort(_(b"subrepo %s is missing") % self._relpath)
1680 cmd = ['commit', '-a', '-m', text] 1688 cmd = [b'commit', b'-a', b'-m', text]
1681 env = encoding.environ.copy() 1689 env = encoding.environ.copy()
1682 if user: 1690 if user:
1683 cmd += ['--author', user] 1691 cmd += [b'--author', user]
1684 if date: 1692 if date:
1685 # git's date parser silently ignores when seconds < 1e9 1693 # git's date parser silently ignores when seconds < 1e9
1686 # convert to ISO8601 1694 # convert to ISO8601
1687 env['GIT_AUTHOR_DATE'] = dateutil.datestr( 1695 env[b'GIT_AUTHOR_DATE'] = dateutil.datestr(
1688 date, '%Y-%m-%dT%H:%M:%S %1%2' 1696 date, b'%Y-%m-%dT%H:%M:%S %1%2'
1689 ) 1697 )
1690 self._gitcommand(cmd, env=env) 1698 self._gitcommand(cmd, env=env)
1691 # make sure commit works otherwise HEAD might not exist under certain 1699 # make sure commit works otherwise HEAD might not exist under certain
1692 # circumstances 1700 # circumstances
1693 return self._gitstate() 1701 return self._gitstate()
1694 1702
1695 @annotatesubrepoerror 1703 @annotatesubrepoerror
1696 def merge(self, state): 1704 def merge(self, state):
1697 source, revision, kind = state 1705 source, revision, kind = state
1698 self._fetch(source, revision) 1706 self._fetch(source, revision)
1699 base = self._gitcommand(['merge-base', revision, self._state[1]]) 1707 base = self._gitcommand([b'merge-base', revision, self._state[1]])
1700 self._gitupdatestat() 1708 self._gitupdatestat()
1701 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD']) 1709 out, code = self._gitdir([b'diff-index', b'--quiet', b'HEAD'])
1702 1710
1703 def mergefunc(): 1711 def mergefunc():
1704 if base == revision: 1712 if base == revision:
1705 self.get(state) # fast forward merge 1713 self.get(state) # fast forward merge
1706 elif base != self._state[1]: 1714 elif base != self._state[1]:
1707 self._gitcommand(['merge', '--no-commit', revision]) 1715 self._gitcommand([b'merge', b'--no-commit', revision])
1708 _sanitize(self.ui, self.wvfs, '.git') 1716 _sanitize(self.ui, self.wvfs, b'.git')
1709 1717
1710 if self.dirty(): 1718 if self.dirty():
1711 if self._gitstate() != revision: 1719 if self._gitstate() != revision:
1712 dirty = self._gitstate() == self._state[1] or code != 0 1720 dirty = self._gitstate() == self._state[1] or code != 0
1713 if _updateprompt( 1721 if _updateprompt(
1717 else: 1725 else:
1718 mergefunc() 1726 mergefunc()
1719 1727
1720 @annotatesubrepoerror 1728 @annotatesubrepoerror
1721 def push(self, opts): 1729 def push(self, opts):
1722 force = opts.get('force') 1730 force = opts.get(b'force')
1723 1731
1724 if not self._state[1]: 1732 if not self._state[1]:
1725 return True 1733 return True
1726 if self._gitmissing(): 1734 if self._gitmissing():
1727 raise error.Abort(_("subrepo %s is missing") % self._relpath) 1735 raise error.Abort(_(b"subrepo %s is missing") % self._relpath)
1728 # if a branch in origin contains the revision, nothing to do 1736 # if a branch in origin contains the revision, nothing to do
1729 branch2rev, rev2branch = self._gitbranchmap() 1737 branch2rev, rev2branch = self._gitbranchmap()
1730 if self._state[1] in rev2branch: 1738 if self._state[1] in rev2branch:
1731 for b in rev2branch[self._state[1]]: 1739 for b in rev2branch[self._state[1]]:
1732 if b.startswith('refs/remotes/origin/'): 1740 if b.startswith(b'refs/remotes/origin/'):
1733 return True 1741 return True
1734 for b, revision in branch2rev.iteritems(): 1742 for b, revision in branch2rev.iteritems():
1735 if b.startswith('refs/remotes/origin/'): 1743 if b.startswith(b'refs/remotes/origin/'):
1736 if self._gitisancestor(self._state[1], revision): 1744 if self._gitisancestor(self._state[1], revision):
1737 return True 1745 return True
1738 # otherwise, try to push the currently checked out branch 1746 # otherwise, try to push the currently checked out branch
1739 cmd = ['push'] 1747 cmd = [b'push']
1740 if force: 1748 if force:
1741 cmd.append('--force') 1749 cmd.append(b'--force')
1742 1750
1743 current = self._gitcurrentbranch() 1751 current = self._gitcurrentbranch()
1744 if current: 1752 if current:
1745 # determine if the current branch is even useful 1753 # determine if the current branch is even useful
1746 if not self._gitisancestor(self._state[1], current): 1754 if not self._gitisancestor(self._state[1], current):
1747 self.ui.warn( 1755 self.ui.warn(
1748 _( 1756 _(
1749 'unrelated git branch checked out ' 1757 b'unrelated git branch checked out '
1750 'in subrepository "%s"\n' 1758 b'in subrepository "%s"\n'
1751 ) 1759 )
1752 % self._relpath 1760 % self._relpath
1753 ) 1761 )
1754 return False 1762 return False
1755 self.ui.status( 1763 self.ui.status(
1756 _('pushing branch %s of subrepository "%s"\n') 1764 _(b'pushing branch %s of subrepository "%s"\n')
1757 % (current.split('/', 2)[2], self._relpath) 1765 % (current.split(b'/', 2)[2], self._relpath)
1758 ) 1766 )
1759 ret = self._gitdir(cmd + ['origin', current]) 1767 ret = self._gitdir(cmd + [b'origin', current])
1760 return ret[1] == 0 1768 return ret[1] == 0
1761 else: 1769 else:
1762 self.ui.warn( 1770 self.ui.warn(
1763 _( 1771 _(
1764 'no branch checked out in subrepository "%s"\n' 1772 b'no branch checked out in subrepository "%s"\n'
1765 'cannot push revision %s\n' 1773 b'cannot push revision %s\n'
1766 ) 1774 )
1767 % (self._relpath, self._state[1]) 1775 % (self._relpath, self._state[1])
1768 ) 1776 )
1769 return False 1777 return False
1770 1778
1787 rejected = [] 1795 rejected = []
1788 1796
1789 files = [f for f in sorted(set(files)) if match(f)] 1797 files = [f for f in sorted(set(files)) if match(f)]
1790 for f in files: 1798 for f in files:
1791 exact = match.exact(f) 1799 exact = match.exact(f)
1792 command = ["add"] 1800 command = [b"add"]
1793 if exact: 1801 if exact:
1794 command.append("-f") # should be added, even if ignored 1802 command.append(b"-f") # should be added, even if ignored
1795 if ui.verbose or not exact: 1803 if ui.verbose or not exact:
1796 ui.status(_('adding %s\n') % uipathfn(f)) 1804 ui.status(_(b'adding %s\n') % uipathfn(f))
1797 1805
1798 if f in tracked: # hg prints 'adding' even if already tracked 1806 if f in tracked: # hg prints 'adding' even if already tracked
1799 if exact: 1807 if exact:
1800 rejected.append(f) 1808 rejected.append(f)
1801 continue 1809 continue
1802 if not opts.get(r'dry_run'): 1810 if not opts.get(r'dry_run'):
1803 self._gitcommand(command + [f]) 1811 self._gitcommand(command + [f])
1804 1812
1805 for f in rejected: 1813 for f in rejected:
1806 ui.warn(_("%s already tracked!\n") % uipathfn(f)) 1814 ui.warn(_(b"%s already tracked!\n") % uipathfn(f))
1807 1815
1808 return rejected 1816 return rejected
1809 1817
1810 @annotatesubrepoerror 1818 @annotatesubrepoerror
1811 def remove(self): 1819 def remove(self):
1812 if self._gitmissing(): 1820 if self._gitmissing():
1813 return 1821 return
1814 if self.dirty(): 1822 if self.dirty():
1815 self.ui.warn( 1823 self.ui.warn(
1816 _('not removing repo %s because ' 'it has changes.\n') 1824 _(b'not removing repo %s because ' b'it has changes.\n')
1817 % self._relpath 1825 % self._relpath
1818 ) 1826 )
1819 return 1827 return
1820 # we can't fully delete the repository as it may contain 1828 # we can't fully delete the repository as it may contain
1821 # local-only history 1829 # local-only history
1822 self.ui.note(_('removing subrepo %s\n') % self._relpath) 1830 self.ui.note(_(b'removing subrepo %s\n') % self._relpath)
1823 self._gitcommand(['config', 'core.bare', 'true']) 1831 self._gitcommand([b'config', b'core.bare', b'true'])
1824 for f, kind in self.wvfs.readdir(): 1832 for f, kind in self.wvfs.readdir():
1825 if f == '.git': 1833 if f == b'.git':
1826 continue 1834 continue
1827 if kind == stat.S_IFDIR: 1835 if kind == stat.S_IFDIR:
1828 self.wvfs.rmtree(f) 1836 self.wvfs.rmtree(f)
1829 else: 1837 else:
1830 self.wvfs.unlink(f) 1838 self.wvfs.unlink(f)
1837 self._fetch(source, revision) 1845 self._fetch(source, revision)
1838 1846
1839 # Parse git's native archive command. 1847 # Parse git's native archive command.
1840 # This should be much faster than manually traversing the trees 1848 # This should be much faster than manually traversing the trees
1841 # and objects with many subprocess calls. 1849 # and objects with many subprocess calls.
1842 tarstream = self._gitcommand(['archive', revision], stream=True) 1850 tarstream = self._gitcommand([b'archive', revision], stream=True)
1843 tar = tarfile.open(fileobj=tarstream, mode=r'r|') 1851 tar = tarfile.open(fileobj=tarstream, mode=r'r|')
1844 relpath = subrelpath(self) 1852 relpath = subrelpath(self)
1845 progress = self.ui.makeprogress( 1853 progress = self.ui.makeprogress(
1846 _('archiving (%s)') % relpath, unit=_('files') 1854 _(b'archiving (%s)') % relpath, unit=_(b'files')
1847 ) 1855 )
1848 progress.update(0) 1856 progress.update(0)
1849 for info in tar: 1857 for info in tar:
1850 if info.isdir(): 1858 if info.isdir():
1851 continue 1859 continue
1871 if not match.files(): 1879 if not match.files():
1872 return 1 1880 return 1
1873 1881
1874 # TODO: add support for non-plain formatter (see cmdutil.cat()) 1882 # TODO: add support for non-plain formatter (see cmdutil.cat())
1875 for f in match.files(): 1883 for f in match.files():
1876 output = self._gitcommand(["show", "%s:%s" % (rev, f)]) 1884 output = self._gitcommand([b"show", b"%s:%s" % (rev, f)])
1877 fp = cmdutil.makefileobj( 1885 fp = cmdutil.makefileobj(
1878 self._ctx, fntemplate, pathname=self.wvfs.reljoin(prefix, f) 1886 self._ctx, fntemplate, pathname=self.wvfs.reljoin(prefix, f)
1879 ) 1887 )
1880 fp.write(output) 1888 fp.write(output)
1881 fp.close() 1889 fp.close()
1888 # if the repo is missing, return no results 1896 # if the repo is missing, return no results
1889 return scmutil.status([], [], [], [], [], [], []) 1897 return scmutil.status([], [], [], [], [], [], [])
1890 modified, added, removed = [], [], [] 1898 modified, added, removed = [], [], []
1891 self._gitupdatestat() 1899 self._gitupdatestat()
1892 if rev2: 1900 if rev2:
1893 command = ['diff-tree', '--no-renames', '-r', rev1, rev2] 1901 command = [b'diff-tree', b'--no-renames', b'-r', rev1, rev2]
1894 else: 1902 else:
1895 command = ['diff-index', '--no-renames', rev1] 1903 command = [b'diff-index', b'--no-renames', rev1]
1896 out = self._gitcommand(command) 1904 out = self._gitcommand(command)
1897 for line in out.split('\n'): 1905 for line in out.split(b'\n'):
1898 tab = line.find('\t') 1906 tab = line.find(b'\t')
1899 if tab == -1: 1907 if tab == -1:
1900 continue 1908 continue
1901 status, f = line[tab - 1 : tab], line[tab + 1 :] 1909 status, f = line[tab - 1 : tab], line[tab + 1 :]
1902 if status == 'M': 1910 if status == b'M':
1903 modified.append(f) 1911 modified.append(f)
1904 elif status == 'A': 1912 elif status == b'A':
1905 added.append(f) 1913 added.append(f)
1906 elif status == 'D': 1914 elif status == b'D':
1907 removed.append(f) 1915 removed.append(f)
1908 1916
1909 deleted, unknown, ignored, clean = [], [], [], [] 1917 deleted, unknown, ignored, clean = [], [], [], []
1910 1918
1911 command = ['status', '--porcelain', '-z'] 1919 command = [b'status', b'--porcelain', b'-z']
1912 if opts.get(r'unknown'): 1920 if opts.get(r'unknown'):
1913 command += ['--untracked-files=all'] 1921 command += [b'--untracked-files=all']
1914 if opts.get(r'ignored'): 1922 if opts.get(r'ignored'):
1915 command += ['--ignored'] 1923 command += [b'--ignored']
1916 out = self._gitcommand(command) 1924 out = self._gitcommand(command)
1917 1925
1918 changedfiles = set() 1926 changedfiles = set()
1919 changedfiles.update(modified) 1927 changedfiles.update(modified)
1920 changedfiles.update(added) 1928 changedfiles.update(added)
1921 changedfiles.update(removed) 1929 changedfiles.update(removed)
1922 for line in out.split('\0'): 1930 for line in out.split(b'\0'):
1923 if not line: 1931 if not line:
1924 continue 1932 continue
1925 st = line[0:2] 1933 st = line[0:2]
1926 # moves and copies show 2 files on one line 1934 # moves and copies show 2 files on one line
1927 if line.find('\0') >= 0: 1935 if line.find(b'\0') >= 0:
1928 filename1, filename2 = line[3:].split('\0') 1936 filename1, filename2 = line[3:].split(b'\0')
1929 else: 1937 else:
1930 filename1 = line[3:] 1938 filename1 = line[3:]
1931 filename2 = None 1939 filename2 = None
1932 1940
1933 changedfiles.add(filename1) 1941 changedfiles.add(filename1)
1934 if filename2: 1942 if filename2:
1935 changedfiles.add(filename2) 1943 changedfiles.add(filename2)
1936 1944
1937 if st == '??': 1945 if st == b'??':
1938 unknown.append(filename1) 1946 unknown.append(filename1)
1939 elif st == '!!': 1947 elif st == b'!!':
1940 ignored.append(filename1) 1948 ignored.append(filename1)
1941 1949
1942 if opts.get(r'clean'): 1950 if opts.get(r'clean'):
1943 out = self._gitcommand(['ls-files']) 1951 out = self._gitcommand([b'ls-files'])
1944 for f in out.split('\n'): 1952 for f in out.split(b'\n'):
1945 if not f in changedfiles: 1953 if not f in changedfiles:
1946 clean.append(f) 1954 clean.append(f)
1947 1955
1948 return scmutil.status( 1956 return scmutil.status(
1949 modified, added, removed, deleted, unknown, ignored, clean 1957 modified, added, removed, deleted, unknown, ignored, clean
1950 ) 1958 )
1951 1959
1952 @annotatesubrepoerror 1960 @annotatesubrepoerror
1953 def diff(self, ui, diffopts, node2, match, prefix, **opts): 1961 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1954 node1 = self._state[1] 1962 node1 = self._state[1]
1955 cmd = ['diff', '--no-renames'] 1963 cmd = [b'diff', b'--no-renames']
1956 if opts[r'stat']: 1964 if opts[r'stat']:
1957 cmd.append('--stat') 1965 cmd.append(b'--stat')
1958 else: 1966 else:
1959 # for Git, this also implies '-p' 1967 # for Git, this also implies '-p'
1960 cmd.append('-U%d' % diffopts.context) 1968 cmd.append(b'-U%d' % diffopts.context)
1961 1969
1962 if diffopts.noprefix: 1970 if diffopts.noprefix:
1963 cmd.extend( 1971 cmd.extend(
1964 ['--src-prefix=%s/' % prefix, '--dst-prefix=%s/' % prefix] 1972 [b'--src-prefix=%s/' % prefix, b'--dst-prefix=%s/' % prefix]
1965 ) 1973 )
1966 else: 1974 else:
1967 cmd.extend( 1975 cmd.extend(
1968 ['--src-prefix=a/%s/' % prefix, '--dst-prefix=b/%s/' % prefix] 1976 [b'--src-prefix=a/%s/' % prefix, b'--dst-prefix=b/%s/' % prefix]
1969 ) 1977 )
1970 1978
1971 if diffopts.ignorews: 1979 if diffopts.ignorews:
1972 cmd.append('--ignore-all-space') 1980 cmd.append(b'--ignore-all-space')
1973 if diffopts.ignorewsamount: 1981 if diffopts.ignorewsamount:
1974 cmd.append('--ignore-space-change') 1982 cmd.append(b'--ignore-space-change')
1975 if ( 1983 if (
1976 self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) 1984 self._gitversion(self._gitcommand([b'--version'])) >= (1, 8, 4)
1977 and diffopts.ignoreblanklines 1985 and diffopts.ignoreblanklines
1978 ): 1986 ):
1979 cmd.append('--ignore-blank-lines') 1987 cmd.append(b'--ignore-blank-lines')
1980 1988
1981 cmd.append(node1) 1989 cmd.append(node1)
1982 if node2: 1990 if node2:
1983 cmd.append(node2) 1991 cmd.append(node2)
1984 1992
1985 output = "" 1993 output = b""
1986 if match.always(): 1994 if match.always():
1987 output += self._gitcommand(cmd) + '\n' 1995 output += self._gitcommand(cmd) + b'\n'
1988 else: 1996 else:
1989 st = self.status(node2)[:3] 1997 st = self.status(node2)[:3]
1990 files = [f for sublist in st for f in sublist] 1998 files = [f for sublist in st for f in sublist]
1991 for f in files: 1999 for f in files:
1992 if match(f): 2000 if match(f):
1993 output += self._gitcommand(cmd + ['--', f]) + '\n' 2001 output += self._gitcommand(cmd + [b'--', f]) + b'\n'
1994 2002
1995 if output.strip(): 2003 if output.strip():
1996 ui.write(output) 2004 ui.write(output)
1997 2005
1998 @annotatesubrepoerror 2006 @annotatesubrepoerror
1999 def revert(self, substate, *pats, **opts): 2007 def revert(self, substate, *pats, **opts):
2000 self.ui.status(_('reverting subrepo %s\n') % substate[0]) 2008 self.ui.status(_(b'reverting subrepo %s\n') % substate[0])
2001 if not opts.get(r'no_backup'): 2009 if not opts.get(r'no_backup'):
2002 status = self.status(None) 2010 status = self.status(None)
2003 names = status.modified 2011 names = status.modified
2004 for name in names: 2012 for name in names:
2005 # backuppath() expects a path relative to the parent repo (the 2013 # backuppath() expects a path relative to the parent repo (the
2007 parentname = os.path.join(self._path, name) 2015 parentname = os.path.join(self._path, name)
2008 bakname = scmutil.backuppath( 2016 bakname = scmutil.backuppath(
2009 self.ui, self._subparent, parentname 2017 self.ui, self._subparent, parentname
2010 ) 2018 )
2011 self.ui.note( 2019 self.ui.note(
2012 _('saving current version of %s as %s\n') 2020 _(b'saving current version of %s as %s\n')
2013 % (name, os.path.relpath(bakname)) 2021 % (name, os.path.relpath(bakname))
2014 ) 2022 )
2015 util.rename(self.wvfs.join(name), bakname) 2023 util.rename(self.wvfs.join(name), bakname)
2016 2024
2017 if not opts.get(r'dry_run'): 2025 if not opts.get(r'dry_run'):
2021 def shortid(self, revid): 2029 def shortid(self, revid):
2022 return revid[:7] 2030 return revid[:7]
2023 2031
2024 2032
2025 types = { 2033 types = {
2026 'hg': hgsubrepo, 2034 b'hg': hgsubrepo,
2027 'svn': svnsubrepo, 2035 b'svn': svnsubrepo,
2028 'git': gitsubrepo, 2036 b'git': gitsubrepo,
2029 } 2037 }