##// END OF EJS Templates
formatting: byteify all mercurial/ and hgext/ string literals...
Augie Fackler -
r43732:687b865b default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -59,7 +59,7 b' from mercurial.utils import stringutil'
59 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
59 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
60 # be specifying the version(s) of Mercurial they are tested with, or
60 # be specifying the version(s) of Mercurial they are tested with, or
61 # leave the attribute unspecified.
61 # leave the attribute unspecified.
62 testedwith = 'ships-with-hg-core'
62 testedwith = b'ships-with-hg-core'
63
63
64 cmdtable = {}
64 cmdtable = {}
65 command = registrar.command(cmdtable)
65 command = registrar.command(cmdtable)
@@ -67,14 +67,14 b' command = registrar.command(cmdtable)'
67 configtable = {}
67 configtable = {}
68 configitem = registrar.configitem(configtable)
68 configitem = registrar.configitem(configtable)
69
69
70 configitem('absorb', 'add-noise', default=True)
70 configitem(b'absorb', b'add-noise', default=True)
71 configitem('absorb', 'amend-flag', default=None)
71 configitem(b'absorb', b'amend-flag', default=None)
72 configitem('absorb', 'max-stack-size', default=50)
72 configitem(b'absorb', b'max-stack-size', default=50)
73
73
74 colortable = {
74 colortable = {
75 'absorb.description': 'yellow',
75 b'absorb.description': b'yellow',
76 'absorb.node': 'blue bold',
76 b'absorb.node': b'blue bold',
77 'absorb.path': 'bold',
77 b'absorb.path': b'bold',
78 }
78 }
79
79
80 defaultdict = collections.defaultdict
80 defaultdict = collections.defaultdict
@@ -98,7 +98,7 b' class emptyfilecontext(object):'
98 """minimal filecontext representing an empty file"""
98 """minimal filecontext representing an empty file"""
99
99
100 def data(self):
100 def data(self):
101 return ''
101 return b''
102
102
103 def node(self):
103 def node(self):
104 return node.nullid
104 return node.nullid
@@ -364,11 +364,11 b' class filefixupstate(object):'
364 if self.ui.debugflag:
364 if self.ui.debugflag:
365 idx = (max(rev - 1, 0)) // 2
365 idx = (max(rev - 1, 0)) // 2
366 self.ui.write(
366 self.ui.write(
367 _('%s: chunk %d:%d -> %d lines\n')
367 _(b'%s: chunk %d:%d -> %d lines\n')
368 % (node.short(self.fctxs[idx].node()), a1, a2, len(blines))
368 % (node.short(self.fctxs[idx].node()), a1, a2, len(blines))
369 )
369 )
370 self.linelog.replacelines(rev, a1, a2, b1, b2)
370 self.linelog.replacelines(rev, a1, a2, b1, b2)
371 if self.opts.get('edit_lines', False):
371 if self.opts.get(b'edit_lines', False):
372 self.finalcontents = self._checkoutlinelogwithedits()
372 self.finalcontents = self._checkoutlinelogwithedits()
373 else:
373 else:
374 self.finalcontents = self._checkoutlinelog()
374 self.finalcontents = self._checkoutlinelog()
@@ -434,7 +434,7 b' class filefixupstate(object):'
434 """like mdiff.allblocks, but only care about differences"""
434 """like mdiff.allblocks, but only care about differences"""
435 blocks = mdiff.allblocks(a, b, lines1=alines, lines2=blines)
435 blocks = mdiff.allblocks(a, b, lines1=alines, lines2=blines)
436 for chunk, btype in blocks:
436 for chunk, btype in blocks:
437 if btype != '!':
437 if btype != b'!':
438 continue
438 continue
439 yield chunk
439 yield chunk
440
440
@@ -443,7 +443,7 b' class filefixupstate(object):'
443 this is similar to running a partial "annotate".
443 this is similar to running a partial "annotate".
444 """
444 """
445 llog = linelog.linelog()
445 llog = linelog.linelog()
446 a, alines = '', []
446 a, alines = b'', []
447 for i in pycompat.xrange(len(self.contents)):
447 for i in pycompat.xrange(len(self.contents)):
448 b, blines = self.contents[i], self.contentlines[i]
448 b, blines = self.contents[i], self.contentlines[i]
449 llrev = i * 2 + 1
449 llrev = i * 2 + 1
@@ -459,7 +459,7 b' class filefixupstate(object):'
459 for i in pycompat.xrange(len(self.contents)):
459 for i in pycompat.xrange(len(self.contents)):
460 rev = (i + 1) * 2
460 rev = (i + 1) * 2
461 self.linelog.annotate(rev)
461 self.linelog.annotate(rev)
462 content = ''.join(map(self._getline, self.linelog.annotateresult))
462 content = b''.join(map(self._getline, self.linelog.annotateresult))
463 contents.append(content)
463 contents.append(content)
464 return contents
464 return contents
465
465
@@ -469,8 +469,8 b' class filefixupstate(object):'
469 # header
469 # header
470 editortext = (
470 editortext = (
471 _(
471 _(
472 'HG: editing %s\nHG: "y" means the line to the right '
472 b'HG: editing %s\nHG: "y" means the line to the right '
473 'exists in the changeset to the top\nHG:\n'
473 b'exists in the changeset to the top\nHG:\n'
474 )
474 )
475 % self.fctxs[-1].path()
475 % self.fctxs[-1].path()
476 )
476 )
@@ -481,13 +481,13 b' class filefixupstate(object):'
481 if not isinstance(f, emptyfilecontext)
481 if not isinstance(f, emptyfilecontext)
482 ]
482 ]
483 for i, (j, f) in enumerate(visiblefctxs):
483 for i, (j, f) in enumerate(visiblefctxs):
484 editortext += _('HG: %s/%s %s %s\n') % (
484 editortext += _(b'HG: %s/%s %s %s\n') % (
485 '|' * i,
485 b'|' * i,
486 '-' * (len(visiblefctxs) - i + 1),
486 b'-' * (len(visiblefctxs) - i + 1),
487 node.short(f.node()),
487 node.short(f.node()),
488 f.description().split('\n', 1)[0],
488 f.description().split(b'\n', 1)[0],
489 )
489 )
490 editortext += _('HG: %s\n') % ('|' * len(visiblefctxs))
490 editortext += _(b'HG: %s\n') % (b'|' * len(visiblefctxs))
491 # figure out the lifetime of a line, this is relatively inefficient,
491 # figure out the lifetime of a line, this is relatively inefficient,
492 # but probably fine
492 # but probably fine
493 lineset = defaultdict(lambda: set()) # {(llrev, linenum): {llrev}}
493 lineset = defaultdict(lambda: set()) # {(llrev, linenum): {llrev}}
@@ -497,33 +497,33 b' class filefixupstate(object):'
497 lineset[l].add(i)
497 lineset[l].add(i)
498 # append lines
498 # append lines
499 for l in alllines:
499 for l in alllines:
500 editortext += ' %s : %s' % (
500 editortext += b' %s : %s' % (
501 ''.join(
501 b''.join(
502 [
502 [
503 ('y' if i in lineset[l] else ' ')
503 (b'y' if i in lineset[l] else b' ')
504 for i, _f in visiblefctxs
504 for i, _f in visiblefctxs
505 ]
505 ]
506 ),
506 ),
507 self._getline(l),
507 self._getline(l),
508 )
508 )
509 # run editor
509 # run editor
510 editedtext = self.ui.edit(editortext, '', action='absorb')
510 editedtext = self.ui.edit(editortext, b'', action=b'absorb')
511 if not editedtext:
511 if not editedtext:
512 raise error.Abort(_('empty editor text'))
512 raise error.Abort(_(b'empty editor text'))
513 # parse edited result
513 # parse edited result
514 contents = ['' for i in self.fctxs]
514 contents = [b'' for i in self.fctxs]
515 leftpadpos = 4
515 leftpadpos = 4
516 colonpos = leftpadpos + len(visiblefctxs) + 1
516 colonpos = leftpadpos + len(visiblefctxs) + 1
517 for l in mdiff.splitnewlines(editedtext):
517 for l in mdiff.splitnewlines(editedtext):
518 if l.startswith('HG:'):
518 if l.startswith(b'HG:'):
519 continue
519 continue
520 if l[colonpos - 1 : colonpos + 2] != ' : ':
520 if l[colonpos - 1 : colonpos + 2] != b' : ':
521 raise error.Abort(_('malformed line: %s') % l)
521 raise error.Abort(_(b'malformed line: %s') % l)
522 linecontent = l[colonpos + 2 :]
522 linecontent = l[colonpos + 2 :]
523 for i, ch in enumerate(
523 for i, ch in enumerate(
524 pycompat.bytestr(l[leftpadpos : colonpos - 1])
524 pycompat.bytestr(l[leftpadpos : colonpos - 1])
525 ):
525 ):
526 if ch == 'y':
526 if ch == b'y':
527 contents[visiblefctxs[i][0]] += linecontent
527 contents[visiblefctxs[i][0]] += linecontent
528 # chunkstats is hard to calculate if anything changes, therefore
528 # chunkstats is hard to calculate if anything changes, therefore
529 # set them to just a simple value (1, 1).
529 # set them to just a simple value (1, 1).
@@ -589,7 +589,7 b' class filefixupstate(object):'
589
589
590 def _showchanges(self, fm, alines, blines, chunk, fixups):
590 def _showchanges(self, fm, alines, blines, chunk, fixups):
591 def trim(line):
591 def trim(line):
592 if line.endswith('\n'):
592 if line.endswith(b'\n'):
593 line = line[:-1]
593 line = line[:-1]
594 return line
594 return line
595
595
@@ -605,25 +605,25 b' class filefixupstate(object):'
605
605
606 fm.startitem()
606 fm.startitem()
607 fm.write(
607 fm.write(
608 'hunk',
608 b'hunk',
609 ' %s\n',
609 b' %s\n',
610 '@@ -%d,%d +%d,%d @@' % (a1, a2 - a1, b1, b2 - b1),
610 b'@@ -%d,%d +%d,%d @@' % (a1, a2 - a1, b1, b2 - b1),
611 label='diff.hunk',
611 label=b'diff.hunk',
612 )
612 )
613 fm.data(path=self.path, linetype='hunk')
613 fm.data(path=self.path, linetype=b'hunk')
614
614
615 def writeline(idx, diffchar, line, linetype, linelabel):
615 def writeline(idx, diffchar, line, linetype, linelabel):
616 fm.startitem()
616 fm.startitem()
617 node = ''
617 node = b''
618 if idx:
618 if idx:
619 ctx = self.fctxs[idx]
619 ctx = self.fctxs[idx]
620 fm.context(fctx=ctx)
620 fm.context(fctx=ctx)
621 node = ctx.hex()
621 node = ctx.hex()
622 self.ctxaffected.add(ctx.changectx())
622 self.ctxaffected.add(ctx.changectx())
623 fm.write('node', '%-7.7s ', node, label='absorb.node')
623 fm.write(b'node', b'%-7.7s ', node, label=b'absorb.node')
624 fm.write(
624 fm.write(
625 'diffchar ' + linetype,
625 b'diffchar ' + linetype,
626 '%s%s\n',
626 b'%s%s\n',
627 diffchar,
627 diffchar,
628 line,
628 line,
629 label=linelabel,
629 label=linelabel,
@@ -632,11 +632,19 b' class filefixupstate(object):'
632
632
633 for i in pycompat.xrange(a1, a2):
633 for i in pycompat.xrange(a1, a2):
634 writeline(
634 writeline(
635 aidxs[i - a1], '-', trim(alines[i]), 'deleted', 'diff.deleted'
635 aidxs[i - a1],
636 b'-',
637 trim(alines[i]),
638 b'deleted',
639 b'diff.deleted',
636 )
640 )
637 for i in pycompat.xrange(b1, b2):
641 for i in pycompat.xrange(b1, b2):
638 writeline(
642 writeline(
639 bidxs[i - b1], '+', trim(blines[i]), 'inserted', 'diff.inserted'
643 bidxs[i - b1],
644 b'+',
645 trim(blines[i]),
646 b'inserted',
647 b'diff.inserted',
640 )
648 )
641
649
642
650
@@ -681,7 +689,7 b' class fixupstate(object):'
681 self.paths = []
689 self.paths = []
682 # but if --edit-lines is used, the user may want to edit files
690 # but if --edit-lines is used, the user may want to edit files
683 # even if they are not modified
691 # even if they are not modified
684 editopt = self.opts.get('edit_lines')
692 editopt = self.opts.get(b'edit_lines')
685 if not self.status.modified and editopt and match:
693 if not self.status.modified and editopt and match:
686 interestingpaths = match.files()
694 interestingpaths = match.files()
687 else:
695 else:
@@ -691,7 +699,7 b' class fixupstate(object):'
691 # sorting is necessary to eliminate ambiguity for the "double move"
699 # sorting is necessary to eliminate ambiguity for the "double move"
692 # case: "hg cp A B; hg cp A C; hg rm A", then only "B" can affect "A".
700 # case: "hg cp A B; hg cp A C; hg rm A", then only "B" can affect "A".
693 for path in sorted(interestingpaths):
701 for path in sorted(interestingpaths):
694 self.ui.debug('calculating fixups for %s\n' % path)
702 self.ui.debug(b'calculating fixups for %s\n' % path)
695 targetfctx = targetctx[path]
703 targetfctx = targetctx[path]
696 fctxs, ctx2fctx = getfilestack(self.stack, path, seenfctxs)
704 fctxs, ctx2fctx = getfilestack(self.stack, path, seenfctxs)
697 # ignore symbolic links or binary, or unchanged files
705 # ignore symbolic links or binary, or unchanged files
@@ -708,9 +716,9 b' class fixupstate(object):'
708 fstate = filefixupstate(fctxs, path, ui=self.ui, opts=self.opts)
716 fstate = filefixupstate(fctxs, path, ui=self.ui, opts=self.opts)
709 if fm is not None:
717 if fm is not None:
710 fm.startitem()
718 fm.startitem()
711 fm.plain('showing changes for ')
719 fm.plain(b'showing changes for ')
712 fm.write('path', '%s\n', path, label='absorb.path')
720 fm.write(b'path', b'%s\n', path, label=b'absorb.path')
713 fm.data(linetype='path')
721 fm.data(linetype=b'path')
714 fstate.diffwith(targetfctx, fm)
722 fstate.diffwith(targetfctx, fm)
715 self.fixupmap[path] = fstate
723 self.fixupmap[path] = fstate
716 self.paths.append(path)
724 self.paths.append(path)
@@ -720,7 +728,7 b' class fixupstate(object):'
720 """apply fixups to individual filefixupstates"""
728 """apply fixups to individual filefixupstates"""
721 for path, state in self.fixupmap.iteritems():
729 for path, state in self.fixupmap.iteritems():
722 if self.ui.debugflag:
730 if self.ui.debugflag:
723 self.ui.write(_('applying fixups to %s\n') % path)
731 self.ui.write(_(b'applying fixups to %s\n') % path)
724 state.apply()
732 state.apply()
725
733
726 @property
734 @property
@@ -733,10 +741,10 b' class fixupstate(object):'
733
741
734 def commit(self):
742 def commit(self):
735 """commit changes. update self.finalnode, self.replacemap"""
743 """commit changes. update self.finalnode, self.replacemap"""
736 with self.repo.transaction('absorb') as tr:
744 with self.repo.transaction(b'absorb') as tr:
737 self._commitstack()
745 self._commitstack()
738 self._movebookmarks(tr)
746 self._movebookmarks(tr)
739 if self.repo['.'].node() in self.replacemap:
747 if self.repo[b'.'].node() in self.replacemap:
740 self._moveworkingdirectoryparent()
748 self._moveworkingdirectoryparent()
741 self._cleanupoldcommits()
749 self._cleanupoldcommits()
742 return self.finalnode
750 return self.finalnode
@@ -750,14 +758,14 b' class fixupstate(object):'
750 for path, stat in chunkstats.iteritems():
758 for path, stat in chunkstats.iteritems():
751 if stat[0]:
759 if stat[0]:
752 ui.write(
760 ui.write(
753 _('%s: %d of %d chunk(s) applied\n')
761 _(b'%s: %d of %d chunk(s) applied\n')
754 % (path, stat[0], stat[1])
762 % (path, stat[0], stat[1])
755 )
763 )
756 elif not ui.quiet:
764 elif not ui.quiet:
757 # a summary for all files
765 # a summary for all files
758 stats = chunkstats.values()
766 stats = chunkstats.values()
759 applied, total = (sum(s[i] for s in stats) for i in (0, 1))
767 applied, total = (sum(s[i] for s in stats) for i in (0, 1))
760 ui.write(_('%d of %d chunk(s) applied\n') % (applied, total))
768 ui.write(_(b'%d of %d chunk(s) applied\n') % (applied, total))
761
769
762 def _commitstack(self):
770 def _commitstack(self):
763 """make new commits. update self.finalnode, self.replacemap.
771 """make new commits. update self.finalnode, self.replacemap.
@@ -777,7 +785,7 b' class fixupstate(object):'
777 if self._willbecomenoop(memworkingcopy, ctx, nextp1):
785 if self._willbecomenoop(memworkingcopy, ctx, nextp1):
778 # changeset is no longer necessary
786 # changeset is no longer necessary
779 self.replacemap[ctx.node()] = None
787 self.replacemap[ctx.node()] = None
780 msg = _('became empty and was dropped')
788 msg = _(b'became empty and was dropped')
781 else:
789 else:
782 # changeset needs re-commit
790 # changeset needs re-commit
783 nodestr = self._commitsingle(memworkingcopy, ctx, p1=nextp1)
791 nodestr = self._commitsingle(memworkingcopy, ctx, p1=nextp1)
@@ -785,21 +793,21 b' class fixupstate(object):'
785 nextp1 = lastcommitted
793 nextp1 = lastcommitted
786 self.replacemap[ctx.node()] = lastcommitted.node()
794 self.replacemap[ctx.node()] = lastcommitted.node()
787 if memworkingcopy:
795 if memworkingcopy:
788 msg = _('%d file(s) changed, became %s') % (
796 msg = _(b'%d file(s) changed, became %s') % (
789 len(memworkingcopy),
797 len(memworkingcopy),
790 self._ctx2str(lastcommitted),
798 self._ctx2str(lastcommitted),
791 )
799 )
792 else:
800 else:
793 msg = _('became %s') % self._ctx2str(lastcommitted)
801 msg = _(b'became %s') % self._ctx2str(lastcommitted)
794 if self.ui.verbose and msg:
802 if self.ui.verbose and msg:
795 self.ui.write(_('%s: %s\n') % (self._ctx2str(ctx), msg))
803 self.ui.write(_(b'%s: %s\n') % (self._ctx2str(ctx), msg))
796 self.finalnode = lastcommitted and lastcommitted.node()
804 self.finalnode = lastcommitted and lastcommitted.node()
797
805
798 def _ctx2str(self, ctx):
806 def _ctx2str(self, ctx):
799 if self.ui.debugflag:
807 if self.ui.debugflag:
800 return '%d:%s' % (ctx.rev(), ctx.hex())
808 return b'%d:%s' % (ctx.rev(), ctx.hex())
801 else:
809 else:
802 return '%d:%s' % (ctx.rev(), node.short(ctx.node()))
810 return b'%d:%s' % (ctx.rev(), node.short(ctx.node()))
803
811
804 def _getnewfilecontents(self, ctx):
812 def _getnewfilecontents(self, ctx):
805 """(ctx) -> {path: str}
813 """(ctx) -> {path: str}
@@ -832,18 +840,18 b' class fixupstate(object):'
832 changes.append((name, hsh))
840 changes.append((name, hsh))
833 if self.ui.verbose:
841 if self.ui.verbose:
834 self.ui.write(
842 self.ui.write(
835 _('moving bookmark %s to %s\n') % (name, node.hex(hsh))
843 _(b'moving bookmark %s to %s\n') % (name, node.hex(hsh))
836 )
844 )
837 else:
845 else:
838 changes.append((name, None))
846 changes.append((name, None))
839 if self.ui.verbose:
847 if self.ui.verbose:
840 self.ui.write(_('deleting bookmark %s\n') % name)
848 self.ui.write(_(b'deleting bookmark %s\n') % name)
841 repo._bookmarks.applychanges(repo, tr, changes)
849 repo._bookmarks.applychanges(repo, tr, changes)
842
850
843 def _moveworkingdirectoryparent(self):
851 def _moveworkingdirectoryparent(self):
844 if not self.finalnode:
852 if not self.finalnode:
845 # Find the latest not-{obsoleted,stripped} parent.
853 # Find the latest not-{obsoleted,stripped} parent.
846 revs = self.repo.revs('max(::. - %ln)', self.replacemap.keys())
854 revs = self.repo.revs(b'max(::. - %ln)', self.replacemap.keys())
847 ctx = self.repo[revs.first()]
855 ctx = self.repo[revs.first()]
848 self.finalnode = ctx.node()
856 self.finalnode = ctx.node()
849 else:
857 else:
@@ -854,7 +862,7 b' class fixupstate(object):'
854 # be slow. in absorb's case, no need to invalidate fsmonitorstate.
862 # be slow. in absorb's case, no need to invalidate fsmonitorstate.
855 noop = lambda: 0
863 noop = lambda: 0
856 restore = noop
864 restore = noop
857 if util.safehasattr(dirstate, '_fsmonitorstate'):
865 if util.safehasattr(dirstate, b'_fsmonitorstate'):
858 bak = dirstate._fsmonitorstate.invalidate
866 bak = dirstate._fsmonitorstate.invalidate
859
867
860 def restore():
868 def restore():
@@ -901,8 +909,8 b' class fixupstate(object):'
901 """
909 """
902 parents = p1 and (p1, node.nullid)
910 parents = p1 and (p1, node.nullid)
903 extra = ctx.extra()
911 extra = ctx.extra()
904 if self._useobsolete and self.ui.configbool('absorb', 'add-noise'):
912 if self._useobsolete and self.ui.configbool(b'absorb', b'add-noise'):
905 extra['absorb_source'] = ctx.hex()
913 extra[b'absorb_source'] = ctx.hex()
906 mctx = overlaycontext(memworkingcopy, ctx, parents, extra=extra)
914 mctx = overlaycontext(memworkingcopy, ctx, parents, extra=extra)
907 return mctx.commit()
915 return mctx.commit()
908
916
@@ -918,7 +926,7 b' class fixupstate(object):'
918 }
926 }
919 if replacements:
927 if replacements:
920 scmutil.cleanupnodes(
928 scmutil.cleanupnodes(
921 self.repo, replacements, operation='absorb', fixphase=True
929 self.repo, replacements, operation=b'absorb', fixphase=True
922 )
930 )
923
931
924
932
@@ -935,7 +943,7 b' def _parsechunk(hunk):'
935 patchlines = mdiff.splitnewlines(buf.getvalue())
943 patchlines = mdiff.splitnewlines(buf.getvalue())
936 # hunk.prettystr() will update hunk.removed
944 # hunk.prettystr() will update hunk.removed
937 a2 = a1 + hunk.removed
945 a2 = a1 + hunk.removed
938 blines = [l[1:] for l in patchlines[1:] if not l.startswith('-')]
946 blines = [l[1:] for l in patchlines[1:] if not l.startswith(b'-')]
939 return path, (a1, a2, blines)
947 return path, (a1, a2, blines)
940
948
941
949
@@ -967,7 +975,7 b' def overlaydiffcontext(ctx, chunks):'
967 lines = mdiff.splitnewlines(ctx[path].data())
975 lines = mdiff.splitnewlines(ctx[path].data())
968 for a1, a2, blines in patches:
976 for a1, a2, blines in patches:
969 lines[a1:a2] = blines
977 lines[a1:a2] = blines
970 memworkingcopy[path] = ''.join(lines)
978 memworkingcopy[path] = b''.join(lines)
971 return overlaycontext(memworkingcopy, ctx)
979 return overlaycontext(memworkingcopy, ctx)
972
980
973
981
@@ -979,18 +987,21 b' def absorb(ui, repo, stack=None, targetc'
979 return fixupstate.
987 return fixupstate.
980 """
988 """
981 if stack is None:
989 if stack is None:
982 limit = ui.configint('absorb', 'max-stack-size')
990 limit = ui.configint(b'absorb', b'max-stack-size')
983 headctx = repo['.']
991 headctx = repo[b'.']
984 if len(headctx.parents()) > 1:
992 if len(headctx.parents()) > 1:
985 raise error.Abort(_('cannot absorb into a merge'))
993 raise error.Abort(_(b'cannot absorb into a merge'))
986 stack = getdraftstack(headctx, limit)
994 stack = getdraftstack(headctx, limit)
987 if limit and len(stack) >= limit:
995 if limit and len(stack) >= limit:
988 ui.warn(
996 ui.warn(
989 _('absorb: only the recent %d changesets will ' 'be analysed\n')
997 _(
998 b'absorb: only the recent %d changesets will '
999 b'be analysed\n'
1000 )
990 % limit
1001 % limit
991 )
1002 )
992 if not stack:
1003 if not stack:
993 raise error.Abort(_('no mutable changeset to change'))
1004 raise error.Abort(_(b'no mutable changeset to change'))
994 if targetctx is None: # default to working copy
1005 if targetctx is None: # default to working copy
995 targetctx = repo[None]
1006 targetctx = repo[None]
996 if pats is None:
1007 if pats is None:
@@ -999,85 +1010,89 b' def absorb(ui, repo, stack=None, targetc'
999 opts = {}
1010 opts = {}
1000 state = fixupstate(stack, ui=ui, opts=opts)
1011 state = fixupstate(stack, ui=ui, opts=opts)
1001 matcher = scmutil.match(targetctx, pats, opts)
1012 matcher = scmutil.match(targetctx, pats, opts)
1002 if opts.get('interactive'):
1013 if opts.get(b'interactive'):
1003 diff = patch.diff(repo, stack[-1].node(), targetctx.node(), matcher)
1014 diff = patch.diff(repo, stack[-1].node(), targetctx.node(), matcher)
1004 origchunks = patch.parsepatch(diff)
1015 origchunks = patch.parsepatch(diff)
1005 chunks = cmdutil.recordfilter(ui, origchunks, matcher)[0]
1016 chunks = cmdutil.recordfilter(ui, origchunks, matcher)[0]
1006 targetctx = overlaydiffcontext(stack[-1], chunks)
1017 targetctx = overlaydiffcontext(stack[-1], chunks)
1007 fm = None
1018 fm = None
1008 if opts.get('print_changes') or not opts.get('apply_changes'):
1019 if opts.get(b'print_changes') or not opts.get(b'apply_changes'):
1009 fm = ui.formatter('absorb', opts)
1020 fm = ui.formatter(b'absorb', opts)
1010 state.diffwith(targetctx, matcher, fm)
1021 state.diffwith(targetctx, matcher, fm)
1011 if fm is not None:
1022 if fm is not None:
1012 fm.startitem()
1023 fm.startitem()
1013 fm.write("count", "\n%d changesets affected\n", len(state.ctxaffected))
1024 fm.write(
1014 fm.data(linetype='summary')
1025 b"count", b"\n%d changesets affected\n", len(state.ctxaffected)
1026 )
1027 fm.data(linetype=b'summary')
1015 for ctx in reversed(stack):
1028 for ctx in reversed(stack):
1016 if ctx not in state.ctxaffected:
1029 if ctx not in state.ctxaffected:
1017 continue
1030 continue
1018 fm.startitem()
1031 fm.startitem()
1019 fm.context(ctx=ctx)
1032 fm.context(ctx=ctx)
1020 fm.data(linetype='changeset')
1033 fm.data(linetype=b'changeset')
1021 fm.write('node', '%-7.7s ', ctx.hex(), label='absorb.node')
1034 fm.write(b'node', b'%-7.7s ', ctx.hex(), label=b'absorb.node')
1022 descfirstline = ctx.description().splitlines()[0]
1035 descfirstline = ctx.description().splitlines()[0]
1023 fm.write(
1036 fm.write(
1024 'descfirstline',
1037 b'descfirstline',
1025 '%s\n',
1038 b'%s\n',
1026 descfirstline,
1039 descfirstline,
1027 label='absorb.description',
1040 label=b'absorb.description',
1028 )
1041 )
1029 fm.end()
1042 fm.end()
1030 if not opts.get('dry_run'):
1043 if not opts.get(b'dry_run'):
1031 if (
1044 if (
1032 not opts.get('apply_changes')
1045 not opts.get(b'apply_changes')
1033 and state.ctxaffected
1046 and state.ctxaffected
1034 and ui.promptchoice("apply changes (yn)? $$ &Yes $$ &No", default=1)
1047 and ui.promptchoice(
1048 b"apply changes (yn)? $$ &Yes $$ &No", default=1
1049 )
1035 ):
1050 ):
1036 raise error.Abort(_('absorb cancelled\n'))
1051 raise error.Abort(_(b'absorb cancelled\n'))
1037
1052
1038 state.apply()
1053 state.apply()
1039 if state.commit():
1054 if state.commit():
1040 state.printchunkstats()
1055 state.printchunkstats()
1041 elif not ui.quiet:
1056 elif not ui.quiet:
1042 ui.write(_('nothing applied\n'))
1057 ui.write(_(b'nothing applied\n'))
1043 return state
1058 return state
1044
1059
1045
1060
1046 @command(
1061 @command(
1047 'absorb',
1062 b'absorb',
1048 [
1063 [
1049 (
1064 (
1050 'a',
1065 b'a',
1051 'apply-changes',
1066 b'apply-changes',
1052 None,
1067 None,
1053 _('apply changes without prompting for confirmation'),
1068 _(b'apply changes without prompting for confirmation'),
1054 ),
1069 ),
1055 (
1070 (
1056 'p',
1071 b'p',
1057 'print-changes',
1072 b'print-changes',
1058 None,
1073 None,
1059 _('always print which changesets are modified by which changes'),
1074 _(b'always print which changesets are modified by which changes'),
1060 ),
1075 ),
1061 (
1076 (
1062 'i',
1077 b'i',
1063 'interactive',
1078 b'interactive',
1064 None,
1079 None,
1065 _('interactively select which chunks to apply (EXPERIMENTAL)'),
1080 _(b'interactively select which chunks to apply (EXPERIMENTAL)'),
1066 ),
1081 ),
1067 (
1082 (
1068 'e',
1083 b'e',
1069 'edit-lines',
1084 b'edit-lines',
1070 None,
1085 None,
1071 _(
1086 _(
1072 'edit what lines belong to which changesets before commit '
1087 b'edit what lines belong to which changesets before commit '
1073 '(EXPERIMENTAL)'
1088 b'(EXPERIMENTAL)'
1074 ),
1089 ),
1075 ),
1090 ),
1076 ]
1091 ]
1077 + commands.dryrunopts
1092 + commands.dryrunopts
1078 + commands.templateopts
1093 + commands.templateopts
1079 + commands.walkopts,
1094 + commands.walkopts,
1080 _('hg absorb [OPTION] [FILE]...'),
1095 _(b'hg absorb [OPTION] [FILE]...'),
1081 helpcategory=command.CATEGORY_COMMITTING,
1096 helpcategory=command.CATEGORY_COMMITTING,
1082 helpbasic=True,
1097 helpbasic=True,
1083 )
1098 )
@@ -1108,7 +1123,7 b' def absorbcmd(ui, repo, *pats, **opts):'
1108 opts = pycompat.byteskwargs(opts)
1123 opts = pycompat.byteskwargs(opts)
1109
1124
1110 with repo.wlock(), repo.lock():
1125 with repo.wlock(), repo.lock():
1111 if not opts['dry_run']:
1126 if not opts[b'dry_run']:
1112 cmdutil.checkunfinished(repo)
1127 cmdutil.checkunfinished(repo)
1113
1128
1114 state = absorb(ui, repo, pats=pats, opts=opts)
1129 state = absorb(ui, repo, pats=pats, opts=opts)
@@ -232,66 +232,66 b' urlreq = util.urlreq'
232 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
232 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
233 # be specifying the version(s) of Mercurial they are tested with, or
233 # be specifying the version(s) of Mercurial they are tested with, or
234 # leave the attribute unspecified.
234 # leave the attribute unspecified.
235 testedwith = 'ships-with-hg-core'
235 testedwith = b'ships-with-hg-core'
236
236
237 configtable = {}
237 configtable = {}
238 configitem = registrar.configitem(configtable)
238 configitem = registrar.configitem(configtable)
239
239
240 # deprecated config: acl.config
240 # deprecated config: acl.config
241 configitem(
241 configitem(
242 'acl', 'config', default=None,
242 b'acl', b'config', default=None,
243 )
243 )
244 configitem(
244 configitem(
245 'acl.groups', '.*', default=None, generic=True,
245 b'acl.groups', b'.*', default=None, generic=True,
246 )
246 )
247 configitem(
247 configitem(
248 'acl.deny.branches', '.*', default=None, generic=True,
248 b'acl.deny.branches', b'.*', default=None, generic=True,
249 )
249 )
250 configitem(
250 configitem(
251 'acl.allow.branches', '.*', default=None, generic=True,
251 b'acl.allow.branches', b'.*', default=None, generic=True,
252 )
252 )
253 configitem(
253 configitem(
254 'acl.deny', '.*', default=None, generic=True,
254 b'acl.deny', b'.*', default=None, generic=True,
255 )
255 )
256 configitem(
256 configitem(
257 'acl.allow', '.*', default=None, generic=True,
257 b'acl.allow', b'.*', default=None, generic=True,
258 )
258 )
259 configitem(
259 configitem(
260 'acl', 'sources', default=lambda: ['serve'],
260 b'acl', b'sources', default=lambda: [b'serve'],
261 )
261 )
262
262
263
263
264 def _getusers(ui, group):
264 def _getusers(ui, group):
265
265
266 # First, try to use group definition from section [acl.groups]
266 # First, try to use group definition from section [acl.groups]
267 hgrcusers = ui.configlist('acl.groups', group)
267 hgrcusers = ui.configlist(b'acl.groups', group)
268 if hgrcusers:
268 if hgrcusers:
269 return hgrcusers
269 return hgrcusers
270
270
271 ui.debug('acl: "%s" not defined in [acl.groups]\n' % group)
271 ui.debug(b'acl: "%s" not defined in [acl.groups]\n' % group)
272 # If no users found in group definition, get users from OS-level group
272 # If no users found in group definition, get users from OS-level group
273 try:
273 try:
274 return util.groupmembers(group)
274 return util.groupmembers(group)
275 except KeyError:
275 except KeyError:
276 raise error.Abort(_("group '%s' is undefined") % group)
276 raise error.Abort(_(b"group '%s' is undefined") % group)
277
277
278
278
279 def _usermatch(ui, user, usersorgroups):
279 def _usermatch(ui, user, usersorgroups):
280
280
281 if usersorgroups == '*':
281 if usersorgroups == b'*':
282 return True
282 return True
283
283
284 for ug in usersorgroups.replace(',', ' ').split():
284 for ug in usersorgroups.replace(b',', b' ').split():
285
285
286 if ug.startswith('!'):
286 if ug.startswith(b'!'):
287 # Test for excluded user or group. Format:
287 # Test for excluded user or group. Format:
288 # if ug is a user name: !username
288 # if ug is a user name: !username
289 # if ug is a group name: !@groupname
289 # if ug is a group name: !@groupname
290 ug = ug[1:]
290 ug = ug[1:]
291 if (
291 if (
292 not ug.startswith('@')
292 not ug.startswith(b'@')
293 and user != ug
293 and user != ug
294 or ug.startswith('@')
294 or ug.startswith(b'@')
295 and user not in _getusers(ui, ug[1:])
295 and user not in _getusers(ui, ug[1:])
296 ):
296 ):
297 return True
297 return True
@@ -299,7 +299,9 b' def _usermatch(ui, user, usersorgroups):'
299 # Test for user or group. Format:
299 # Test for user or group. Format:
300 # if ug is a user name: username
300 # if ug is a user name: username
301 # if ug is a group name: @groupname
301 # if ug is a group name: @groupname
302 elif user == ug or ug.startswith('@') and user in _getusers(ui, ug[1:]):
302 elif (
303 user == ug or ug.startswith(b'@') and user in _getusers(ui, ug[1:])
304 ):
303 return True
305 return True
304
306
305 return False
307 return False
@@ -308,14 +310,14 b' def _usermatch(ui, user, usersorgroups):'
308 def buildmatch(ui, repo, user, key):
310 def buildmatch(ui, repo, user, key):
309 '''return tuple of (match function, list enabled).'''
311 '''return tuple of (match function, list enabled).'''
310 if not ui.has_section(key):
312 if not ui.has_section(key):
311 ui.debug('acl: %s not enabled\n' % key)
313 ui.debug(b'acl: %s not enabled\n' % key)
312 return None
314 return None
313
315
314 pats = [
316 pats = [
315 pat for pat, users in ui.configitems(key) if _usermatch(ui, user, users)
317 pat for pat, users in ui.configitems(key) if _usermatch(ui, user, users)
316 ]
318 ]
317 ui.debug(
319 ui.debug(
318 'acl: %s enabled, %d entries for user %s\n' % (key, len(pats), user)
320 b'acl: %s enabled, %d entries for user %s\n' % (key, len(pats), user)
319 )
321 )
320
322
321 # Branch-based ACL
323 # Branch-based ACL
@@ -323,14 +325,14 b' def buildmatch(ui, repo, user, key):'
323 if pats:
325 if pats:
324 # If there's an asterisk (meaning "any branch"), always return True;
326 # If there's an asterisk (meaning "any branch"), always return True;
325 # Otherwise, test if b is in pats
327 # Otherwise, test if b is in pats
326 if '*' in pats:
328 if b'*' in pats:
327 return util.always
329 return util.always
328 return lambda b: b in pats
330 return lambda b: b in pats
329 return util.never
331 return util.never
330
332
331 # Path-based ACL
333 # Path-based ACL
332 if pats:
334 if pats:
333 return match.match(repo.root, '', pats)
335 return match.match(repo.root, b'', pats)
334 return util.never
336 return util.never
335
337
336
338
@@ -342,122 +344,128 b' def ensureenabled(ui):'
342 never loaded. This function ensure the extension is enabled when running
344 never loaded. This function ensure the extension is enabled when running
343 hooks.
345 hooks.
344 """
346 """
345 if 'acl' in ui._knownconfig:
347 if b'acl' in ui._knownconfig:
346 return
348 return
347 ui.setconfig('extensions', 'acl', '', source='internal')
349 ui.setconfig(b'extensions', b'acl', b'', source=b'internal')
348 extensions.loadall(ui, ['acl'])
350 extensions.loadall(ui, [b'acl'])
349
351
350
352
351 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
353 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
352
354
353 ensureenabled(ui)
355 ensureenabled(ui)
354
356
355 if hooktype not in ['pretxnchangegroup', 'pretxncommit', 'prepushkey']:
357 if hooktype not in [b'pretxnchangegroup', b'pretxncommit', b'prepushkey']:
356 raise error.Abort(
358 raise error.Abort(
357 _(
359 _(
358 'config error - hook type "%s" cannot stop '
360 b'config error - hook type "%s" cannot stop '
359 'incoming changesets, commits, nor bookmarks'
361 b'incoming changesets, commits, nor bookmarks'
360 )
362 )
361 % hooktype
363 % hooktype
362 )
364 )
363 if hooktype == 'pretxnchangegroup' and source not in ui.configlist(
365 if hooktype == b'pretxnchangegroup' and source not in ui.configlist(
364 'acl', 'sources'
366 b'acl', b'sources'
365 ):
367 ):
366 ui.debug('acl: changes have source "%s" - skipping\n' % source)
368 ui.debug(b'acl: changes have source "%s" - skipping\n' % source)
367 return
369 return
368
370
369 user = None
371 user = None
370 if source == 'serve' and r'url' in kwargs:
372 if source == b'serve' and r'url' in kwargs:
371 url = kwargs[r'url'].split(':')
373 url = kwargs[r'url'].split(b':')
372 if url[0] == 'remote' and url[1].startswith('http'):
374 if url[0] == b'remote' and url[1].startswith(b'http'):
373 user = urlreq.unquote(url[3])
375 user = urlreq.unquote(url[3])
374
376
375 if user is None:
377 if user is None:
376 user = procutil.getuser()
378 user = procutil.getuser()
377
379
378 ui.debug('acl: checking access for user "%s"\n' % user)
380 ui.debug(b'acl: checking access for user "%s"\n' % user)
379
381
380 if hooktype == 'prepushkey':
382 if hooktype == b'prepushkey':
381 _pkhook(ui, repo, hooktype, node, source, user, **kwargs)
383 _pkhook(ui, repo, hooktype, node, source, user, **kwargs)
382 else:
384 else:
383 _txnhook(ui, repo, hooktype, node, source, user, **kwargs)
385 _txnhook(ui, repo, hooktype, node, source, user, **kwargs)
384
386
385
387
386 def _pkhook(ui, repo, hooktype, node, source, user, **kwargs):
388 def _pkhook(ui, repo, hooktype, node, source, user, **kwargs):
387 if kwargs[r'namespace'] == 'bookmarks':
389 if kwargs[r'namespace'] == b'bookmarks':
388 bookmark = kwargs[r'key']
390 bookmark = kwargs[r'key']
389 ctx = kwargs[r'new']
391 ctx = kwargs[r'new']
390 allowbookmarks = buildmatch(ui, None, user, 'acl.allow.bookmarks')
392 allowbookmarks = buildmatch(ui, None, user, b'acl.allow.bookmarks')
391 denybookmarks = buildmatch(ui, None, user, 'acl.deny.bookmarks')
393 denybookmarks = buildmatch(ui, None, user, b'acl.deny.bookmarks')
392
394
393 if denybookmarks and denybookmarks(bookmark):
395 if denybookmarks and denybookmarks(bookmark):
394 raise error.Abort(
396 raise error.Abort(
395 _('acl: user "%s" denied on bookmark "%s"' ' (changeset "%s")')
397 _(
398 b'acl: user "%s" denied on bookmark "%s"'
399 b' (changeset "%s")'
400 )
396 % (user, bookmark, ctx)
401 % (user, bookmark, ctx)
397 )
402 )
398 if allowbookmarks and not allowbookmarks(bookmark):
403 if allowbookmarks and not allowbookmarks(bookmark):
399 raise error.Abort(
404 raise error.Abort(
400 _(
405 _(
401 'acl: user "%s" not allowed on bookmark "%s"'
406 b'acl: user "%s" not allowed on bookmark "%s"'
402 ' (changeset "%s")'
407 b' (changeset "%s")'
403 )
408 )
404 % (user, bookmark, ctx)
409 % (user, bookmark, ctx)
405 )
410 )
406 ui.debug(
411 ui.debug(
407 'acl: bookmark access granted: "%s" on bookmark "%s"\n'
412 b'acl: bookmark access granted: "%s" on bookmark "%s"\n'
408 % (ctx, bookmark)
413 % (ctx, bookmark)
409 )
414 )
410
415
411
416
412 def _txnhook(ui, repo, hooktype, node, source, user, **kwargs):
417 def _txnhook(ui, repo, hooktype, node, source, user, **kwargs):
413 # deprecated config: acl.config
418 # deprecated config: acl.config
414 cfg = ui.config('acl', 'config')
419 cfg = ui.config(b'acl', b'config')
415 if cfg:
420 if cfg:
416 ui.readconfig(
421 ui.readconfig(
417 cfg,
422 cfg,
418 sections=[
423 sections=[
419 'acl.groups',
424 b'acl.groups',
420 'acl.allow.branches',
425 b'acl.allow.branches',
421 'acl.deny.branches',
426 b'acl.deny.branches',
422 'acl.allow',
427 b'acl.allow',
423 'acl.deny',
428 b'acl.deny',
424 ],
429 ],
425 )
430 )
426
431
427 allowbranches = buildmatch(ui, None, user, 'acl.allow.branches')
432 allowbranches = buildmatch(ui, None, user, b'acl.allow.branches')
428 denybranches = buildmatch(ui, None, user, 'acl.deny.branches')
433 denybranches = buildmatch(ui, None, user, b'acl.deny.branches')
429 allow = buildmatch(ui, repo, user, 'acl.allow')
434 allow = buildmatch(ui, repo, user, b'acl.allow')
430 deny = buildmatch(ui, repo, user, 'acl.deny')
435 deny = buildmatch(ui, repo, user, b'acl.deny')
431
436
432 for rev in pycompat.xrange(repo[node].rev(), len(repo)):
437 for rev in pycompat.xrange(repo[node].rev(), len(repo)):
433 ctx = repo[rev]
438 ctx = repo[rev]
434 branch = ctx.branch()
439 branch = ctx.branch()
435 if denybranches and denybranches(branch):
440 if denybranches and denybranches(branch):
436 raise error.Abort(
441 raise error.Abort(
437 _('acl: user "%s" denied on branch "%s"' ' (changeset "%s")')
442 _(b'acl: user "%s" denied on branch "%s"' b' (changeset "%s")')
438 % (user, branch, ctx)
443 % (user, branch, ctx)
439 )
444 )
440 if allowbranches and not allowbranches(branch):
445 if allowbranches and not allowbranches(branch):
441 raise error.Abort(
446 raise error.Abort(
442 _(
447 _(
443 'acl: user "%s" not allowed on branch "%s"'
448 b'acl: user "%s" not allowed on branch "%s"'
444 ' (changeset "%s")'
449 b' (changeset "%s")'
445 )
450 )
446 % (user, branch, ctx)
451 % (user, branch, ctx)
447 )
452 )
448 ui.debug(
453 ui.debug(
449 'acl: branch access granted: "%s" on branch "%s"\n' % (ctx, branch)
454 b'acl: branch access granted: "%s" on branch "%s"\n' % (ctx, branch)
450 )
455 )
451
456
452 for f in ctx.files():
457 for f in ctx.files():
453 if deny and deny(f):
458 if deny and deny(f):
454 raise error.Abort(
459 raise error.Abort(
455 _('acl: user "%s" denied on "%s"' ' (changeset "%s")')
460 _(b'acl: user "%s" denied on "%s"' b' (changeset "%s")')
456 % (user, f, ctx)
461 % (user, f, ctx)
457 )
462 )
458 if allow and not allow(f):
463 if allow and not allow(f):
459 raise error.Abort(
464 raise error.Abort(
460 _('acl: user "%s" not allowed on "%s"' ' (changeset "%s")')
465 _(
466 b'acl: user "%s" not allowed on "%s"'
467 b' (changeset "%s")'
468 )
461 % (user, f, ctx)
469 % (user, f, ctx)
462 )
470 )
463 ui.debug('acl: path access granted: "%s"\n' % ctx)
471 ui.debug(b'acl: path access granted: "%s"\n' % ctx)
@@ -24,23 +24,23 b' from mercurial import ('
24 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
24 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
25 # be specifying the version(s) of Mercurial they are tested with, or
25 # be specifying the version(s) of Mercurial they are tested with, or
26 # leave the attribute unspecified.
26 # leave the attribute unspecified.
27 testedwith = 'ships-with-hg-core'
27 testedwith = b'ships-with-hg-core'
28
28
29 cmdtable = {}
29 cmdtable = {}
30 command = registrar.command(cmdtable)
30 command = registrar.command(cmdtable)
31
31
32
32
33 @command(
33 @command(
34 'amend',
34 b'amend',
35 [
35 [
36 (
36 (
37 'A',
37 b'A',
38 'addremove',
38 b'addremove',
39 None,
39 None,
40 _('mark new/missing files as added/removed before committing'),
40 _(b'mark new/missing files as added/removed before committing'),
41 ),
41 ),
42 ('e', 'edit', None, _('invoke editor on commit messages')),
42 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
43 ('i', 'interactive', None, _('use interactive mode')),
43 (b'i', b'interactive', None, _(b'use interactive mode')),
44 (
44 (
45 b'',
45 b'',
46 b'close-branch',
46 b'close-branch',
@@ -48,13 +48,13 b' command = registrar.command(cmdtable)'
48 _(b'mark a branch as closed, hiding it from the branch list'),
48 _(b'mark a branch as closed, hiding it from the branch list'),
49 ),
49 ),
50 (b's', b'secret', None, _(b'use the secret phase for committing')),
50 (b's', b'secret', None, _(b'use the secret phase for committing')),
51 ('n', 'note', '', _('store a note on the amend')),
51 (b'n', b'note', b'', _(b'store a note on the amend')),
52 ]
52 ]
53 + cmdutil.walkopts
53 + cmdutil.walkopts
54 + cmdutil.commitopts
54 + cmdutil.commitopts
55 + cmdutil.commitopts2
55 + cmdutil.commitopts2
56 + cmdutil.commitopts3,
56 + cmdutil.commitopts3,
57 _('[OPTION]... [FILE]...'),
57 _(b'[OPTION]... [FILE]...'),
58 helpcategory=command.CATEGORY_COMMITTING,
58 helpcategory=command.CATEGORY_COMMITTING,
59 inferrepo=True,
59 inferrepo=True,
60 )
60 )
@@ -70,7 +70,7 b' def amend(ui, repo, *pats, **opts):'
70 cmdutil.checknotesize(ui, opts)
70 cmdutil.checknotesize(ui, opts)
71
71
72 with repo.wlock(), repo.lock():
72 with repo.wlock(), repo.lock():
73 if not opts.get('logfile'):
73 if not opts.get(b'logfile'):
74 opts['message'] = opts.get('message') or repo['.'].description()
74 opts[b'message'] = opts.get(b'message') or repo[b'.'].description()
75 opts['amend'] = True
75 opts[b'amend'] = True
76 return commands._docommit(ui, repo, *pats, **pycompat.strkwargs(opts))
76 return commands._docommit(ui, repo, *pats, **pycompat.strkwargs(opts))
@@ -42,14 +42,14 b' configtable = {}'
42 configitem = registrar.configitem(configtable)
42 configitem = registrar.configitem(configtable)
43
43
44 configitem(
44 configitem(
45 'automv', 'similarity', default=95,
45 b'automv', b'similarity', default=95,
46 )
46 )
47
47
48
48
49 def extsetup(ui):
49 def extsetup(ui):
50 entry = extensions.wrapcommand(commands.table, 'commit', mvcheck)
50 entry = extensions.wrapcommand(commands.table, b'commit', mvcheck)
51 entry[1].append(
51 entry[1].append(
52 ('', 'no-automv', None, _('disable automatic file move detection'))
52 (b'', b'no-automv', None, _(b'disable automatic file move detection'))
53 )
53 )
54
54
55
55
@@ -57,11 +57,11 b' def mvcheck(orig, ui, repo, *pats, **opt'
57 """Hook to check for moves at commit time"""
57 """Hook to check for moves at commit time"""
58 opts = pycompat.byteskwargs(opts)
58 opts = pycompat.byteskwargs(opts)
59 renames = None
59 renames = None
60 disabled = opts.pop('no_automv', False)
60 disabled = opts.pop(b'no_automv', False)
61 if not disabled:
61 if not disabled:
62 threshold = ui.configint('automv', 'similarity')
62 threshold = ui.configint(b'automv', b'similarity')
63 if not 0 <= threshold <= 100:
63 if not 0 <= threshold <= 100:
64 raise error.Abort(_('automv.similarity must be between 0 and 100'))
64 raise error.Abort(_(b'automv.similarity must be between 0 and 100'))
65 if threshold > 0:
65 if threshold > 0:
66 match = scmutil.match(repo[None], pats, opts)
66 match = scmutil.match(repo[None], pats, opts)
67 added, removed = _interestingfiles(repo, match)
67 added, removed = _interestingfiles(repo, match)
@@ -87,7 +87,7 b' def _interestingfiles(repo, matcher):'
87 added = stat.added
87 added = stat.added
88 removed = stat.removed
88 removed = stat.removed
89
89
90 copy = copies.pathcopies(repo['.'], repo[None], matcher)
90 copy = copies.pathcopies(repo[b'.'], repo[None], matcher)
91 # remove the copy files for which we already have copy info
91 # remove the copy files for which we already have copy info
92 added = [f for f in added if f not in copy]
92 added = [f for f in added if f not in copy]
93
93
@@ -108,10 +108,10 b' def _findrenames(repo, uipathfn, added, '
108 ):
108 ):
109 if repo.ui.verbose:
109 if repo.ui.verbose:
110 repo.ui.status(
110 repo.ui.status(
111 _('detected move of %s as %s (%d%% similar)\n')
111 _(b'detected move of %s as %s (%d%% similar)\n')
112 % (uipathfn(src), uipathfn(dst), score * 100)
112 % (uipathfn(src), uipathfn(dst), score * 100)
113 )
113 )
114 renames[dst] = src
114 renames[dst] = src
115 if renames:
115 if renames:
116 repo.ui.status(_('detected move of %d files\n') % len(renames))
116 repo.ui.status(_(b'detected move of %d files\n') % len(renames))
117 return renames
117 return renames
@@ -26,33 +26,33 b' from mercurial import ('
26 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
26 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
27 # be specifying the version(s) of Mercurial they are tested with, or
27 # be specifying the version(s) of Mercurial they are tested with, or
28 # leave the attribute unspecified.
28 # leave the attribute unspecified.
29 testedwith = 'ships-with-hg-core'
29 testedwith = b'ships-with-hg-core'
30
30
31
31
32 def prettyedge(before, edge, after):
32 def prettyedge(before, edge, after):
33 if edge == '~':
33 if edge == b'~':
34 return '\xE2\x95\xA7' # U+2567 ╧
34 return b'\xE2\x95\xA7' # U+2567 ╧
35 if edge == '/':
35 if edge == b'/':
36 return '\xE2\x95\xB1' # U+2571 ╱
36 return b'\xE2\x95\xB1' # U+2571 ╱
37 if edge == '-':
37 if edge == b'-':
38 return '\xE2\x94\x80' # U+2500 ─
38 return b'\xE2\x94\x80' # U+2500 ─
39 if edge == '|':
39 if edge == b'|':
40 return '\xE2\x94\x82' # U+2502 │
40 return b'\xE2\x94\x82' # U+2502 │
41 if edge == ':':
41 if edge == b':':
42 return '\xE2\x94\x86' # U+2506 ┆
42 return b'\xE2\x94\x86' # U+2506 ┆
43 if edge == '\\':
43 if edge == b'\\':
44 return '\xE2\x95\xB2' # U+2572 ╲
44 return b'\xE2\x95\xB2' # U+2572 ╲
45 if edge == '+':
45 if edge == b'+':
46 if before == ' ' and not after == ' ':
46 if before == b' ' and not after == b' ':
47 return '\xE2\x94\x9C' # U+251C ├
47 return b'\xE2\x94\x9C' # U+251C ├
48 if after == ' ' and not before == ' ':
48 if after == b' ' and not before == b' ':
49 return '\xE2\x94\xA4' # U+2524 ┤
49 return b'\xE2\x94\xA4' # U+2524 ┤
50 return '\xE2\x94\xBC' # U+253C ┼
50 return b'\xE2\x94\xBC' # U+253C ┼
51 return edge
51 return edge
52
52
53
53
54 def convertedges(line):
54 def convertedges(line):
55 line = ' %s ' % line
55 line = b' %s ' % line
56 pretty = []
56 pretty = []
57 for idx in pycompat.xrange(len(line) - 2):
57 for idx in pycompat.xrange(len(line) - 2):
58 pretty.append(
58 pretty.append(
@@ -62,21 +62,21 b' def convertedges(line):'
62 line[idx + 2 : idx + 3],
62 line[idx + 2 : idx + 3],
63 )
63 )
64 )
64 )
65 return ''.join(pretty)
65 return b''.join(pretty)
66
66
67
67
68 def getprettygraphnode(orig, *args, **kwargs):
68 def getprettygraphnode(orig, *args, **kwargs):
69 node = orig(*args, **kwargs)
69 node = orig(*args, **kwargs)
70 if node == 'o':
70 if node == b'o':
71 return '\xE2\x97\x8B' # U+25CB ○
71 return b'\xE2\x97\x8B' # U+25CB ○
72 if node == '@':
72 if node == b'@':
73 return '\xE2\x97\x8D' # U+25CD ◍
73 return b'\xE2\x97\x8D' # U+25CD ◍
74 if node == '*':
74 if node == b'*':
75 return '\xE2\x88\x97' # U+2217 ∗
75 return b'\xE2\x88\x97' # U+2217 ∗
76 if node == 'x':
76 if node == b'x':
77 return '\xE2\x97\x8C' # U+25CC ◌
77 return b'\xE2\x97\x8C' # U+25CC ◌
78 if node == '_':
78 if node == b'_':
79 return '\xE2\x95\xA4' # U+2564 ╤
79 return b'\xE2\x95\xA4' # U+2564 ╤
80 return node
80 return node
81
81
82
82
@@ -87,21 +87,21 b' def outputprettygraph(orig, ui, graph, *'
87
87
88
88
89 def extsetup(ui):
89 def extsetup(ui):
90 if ui.plain('graph'):
90 if ui.plain(b'graph'):
91 return
91 return
92
92
93 if encoding.encoding != 'UTF-8':
93 if encoding.encoding != b'UTF-8':
94 ui.warn(_('beautifygraph: unsupported encoding, UTF-8 required\n'))
94 ui.warn(_(b'beautifygraph: unsupported encoding, UTF-8 required\n'))
95 return
95 return
96
96
97 if r'A' in encoding._wide:
97 if r'A' in encoding._wide:
98 ui.warn(
98 ui.warn(
99 _(
99 _(
100 'beautifygraph: unsupported terminal settings, '
100 b'beautifygraph: unsupported terminal settings, '
101 'monospace narrow text required\n'
101 b'monospace narrow text required\n'
102 )
102 )
103 )
103 )
104 return
104 return
105
105
106 extensions.wrapfunction(graphmod, 'outputgraph', outputprettygraph)
106 extensions.wrapfunction(graphmod, b'outputgraph', outputprettygraph)
107 extensions.wrapfunction(templatekw, 'getgraphnode', getprettygraphnode)
107 extensions.wrapfunction(templatekw, b'getgraphnode', getprettygraphnode)
@@ -63,7 +63,7 b' from mercurial.utils import ('
63 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
63 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
64 # be specifying the version(s) of Mercurial they are tested with, or
64 # be specifying the version(s) of Mercurial they are tested with, or
65 # leave the attribute unspecified.
65 # leave the attribute unspecified.
66 testedwith = 'ships-with-hg-core'
66 testedwith = b'ships-with-hg-core'
67
67
68 cmdtable = {}
68 cmdtable = {}
69 command = registrar.command(cmdtable)
69 command = registrar.command(cmdtable)
@@ -72,27 +72,27 b' configtable = {}'
72 configitem = registrar.configitem(configtable)
72 configitem = registrar.configitem(configtable)
73
73
74 configitem(
74 configitem(
75 'blackbox', 'dirty', default=False,
75 b'blackbox', b'dirty', default=False,
76 )
76 )
77 configitem(
77 configitem(
78 'blackbox', 'maxsize', default='1 MB',
78 b'blackbox', b'maxsize', default=b'1 MB',
79 )
79 )
80 configitem(
80 configitem(
81 'blackbox', 'logsource', default=False,
81 b'blackbox', b'logsource', default=False,
82 )
82 )
83 configitem(
83 configitem(
84 'blackbox', 'maxfiles', default=7,
84 b'blackbox', b'maxfiles', default=7,
85 )
85 )
86 configitem(
86 configitem(
87 'blackbox', 'track', default=lambda: ['*'],
87 b'blackbox', b'track', default=lambda: [b'*'],
88 )
88 )
89 configitem(
89 configitem(
90 'blackbox',
90 b'blackbox',
91 'ignore',
91 b'ignore',
92 default=lambda: ['chgserver', 'cmdserver', 'extension'],
92 default=lambda: [b'chgserver', b'cmdserver', b'extension'],
93 )
93 )
94 configitem(
94 configitem(
95 'blackbox', 'date-format', default='%Y/%m/%d %H:%M:%S',
95 b'blackbox', b'date-format', default=b'%Y/%m/%d %H:%M:%S',
96 )
96 )
97
97
98 _lastlogger = loggingutil.proxylogger()
98 _lastlogger = loggingutil.proxylogger()
@@ -101,10 +101,10 b' configitem('
101 class blackboxlogger(object):
101 class blackboxlogger(object):
102 def __init__(self, ui, repo):
102 def __init__(self, ui, repo):
103 self._repo = repo
103 self._repo = repo
104 self._trackedevents = set(ui.configlist('blackbox', 'track'))
104 self._trackedevents = set(ui.configlist(b'blackbox', b'track'))
105 self._ignoredevents = set(ui.configlist('blackbox', 'ignore'))
105 self._ignoredevents = set(ui.configlist(b'blackbox', b'ignore'))
106 self._maxfiles = ui.configint('blackbox', 'maxfiles')
106 self._maxfiles = ui.configint(b'blackbox', b'maxfiles')
107 self._maxsize = ui.configbytes('blackbox', 'maxsize')
107 self._maxsize = ui.configbytes(b'blackbox', b'maxsize')
108 self._inlog = False
108 self._inlog = False
109
109
110 def tracked(self, event):
110 def tracked(self, event):
@@ -125,29 +125,29 b' class blackboxlogger(object):'
125 self._inlog = False
125 self._inlog = False
126
126
127 def _log(self, ui, event, msg, opts):
127 def _log(self, ui, event, msg, opts):
128 default = ui.configdate('devel', 'default-date')
128 default = ui.configdate(b'devel', b'default-date')
129 date = dateutil.datestr(default, ui.config('blackbox', 'date-format'))
129 date = dateutil.datestr(default, ui.config(b'blackbox', b'date-format'))
130 user = procutil.getuser()
130 user = procutil.getuser()
131 pid = '%d' % procutil.getpid()
131 pid = b'%d' % procutil.getpid()
132 changed = ''
132 changed = b''
133 ctx = self._repo[None]
133 ctx = self._repo[None]
134 parents = ctx.parents()
134 parents = ctx.parents()
135 rev = '+'.join([hex(p.node()) for p in parents])
135 rev = b'+'.join([hex(p.node()) for p in parents])
136 if ui.configbool('blackbox', 'dirty') and ctx.dirty(
136 if ui.configbool(b'blackbox', b'dirty') and ctx.dirty(
137 missing=True, merge=False, branch=False
137 missing=True, merge=False, branch=False
138 ):
138 ):
139 changed = '+'
139 changed = b'+'
140 if ui.configbool('blackbox', 'logsource'):
140 if ui.configbool(b'blackbox', b'logsource'):
141 src = ' [%s]' % event
141 src = b' [%s]' % event
142 else:
142 else:
143 src = ''
143 src = b''
144 try:
144 try:
145 fmt = '%s %s @%s%s (%s)%s> %s'
145 fmt = b'%s %s @%s%s (%s)%s> %s'
146 args = (date, user, rev, changed, pid, src, msg)
146 args = (date, user, rev, changed, pid, src, msg)
147 with loggingutil.openlogfile(
147 with loggingutil.openlogfile(
148 ui,
148 ui,
149 self._repo.vfs,
149 self._repo.vfs,
150 name='blackbox.log',
150 name=b'blackbox.log',
151 maxfiles=self._maxfiles,
151 maxfiles=self._maxfiles,
152 maxsize=self._maxsize,
152 maxsize=self._maxsize,
153 ) as fp:
153 ) as fp:
@@ -156,7 +156,7 b' class blackboxlogger(object):'
156 # deactivate this to avoid failed logging again
156 # deactivate this to avoid failed logging again
157 self._trackedevents.clear()
157 self._trackedevents.clear()
158 ui.debug(
158 ui.debug(
159 'warning: cannot write to blackbox.log: %s\n'
159 b'warning: cannot write to blackbox.log: %s\n'
160 % encoding.strtolocal(err.strerror)
160 % encoding.strtolocal(err.strerror)
161 )
161 )
162 return
162 return
@@ -184,13 +184,13 b' def reposetup(ui, repo):'
184 if _lastlogger.logger is None:
184 if _lastlogger.logger is None:
185 _lastlogger.logger = logger
185 _lastlogger.logger = logger
186
186
187 repo._wlockfreeprefix.add('blackbox.log')
187 repo._wlockfreeprefix.add(b'blackbox.log')
188
188
189
189
190 @command(
190 @command(
191 'blackbox',
191 b'blackbox',
192 [('l', 'limit', 10, _('the number of events to show')),],
192 [(b'l', b'limit', 10, _(b'the number of events to show')),],
193 _('hg blackbox [OPTION]...'),
193 _(b'hg blackbox [OPTION]...'),
194 helpcategory=command.CATEGORY_MAINTENANCE,
194 helpcategory=command.CATEGORY_MAINTENANCE,
195 helpbasic=True,
195 helpbasic=True,
196 )
196 )
@@ -198,12 +198,12 b' def blackbox(ui, repo, *revs, **opts):'
198 '''view the recent repository events
198 '''view the recent repository events
199 '''
199 '''
200
200
201 if not repo.vfs.exists('blackbox.log'):
201 if not repo.vfs.exists(b'blackbox.log'):
202 return
202 return
203
203
204 limit = opts.get(r'limit')
204 limit = opts.get(r'limit')
205 fp = repo.vfs('blackbox.log', 'r')
205 fp = repo.vfs(b'blackbox.log', b'r')
206 lines = fp.read().split('\n')
206 lines = fp.read().split(b'\n')
207
207
208 count = 0
208 count = 0
209 output = []
209 output = []
@@ -216,4 +216,4 b' def blackbox(ui, repo, *revs, **opts):'
216 count += 1
216 count += 1
217 output.append(line)
217 output.append(line)
218
218
219 ui.status('\n'.join(reversed(output)))
219 ui.status(b'\n'.join(reversed(output)))
@@ -24,14 +24,14 b' from mercurial import ('
24 registrar,
24 registrar,
25 )
25 )
26
26
27 MY_NAME = 'bookflow'
27 MY_NAME = b'bookflow'
28
28
29 configtable = {}
29 configtable = {}
30 configitem = registrar.configitem(configtable)
30 configitem = registrar.configitem(configtable)
31
31
32 configitem(MY_NAME, 'protect', ['@'])
32 configitem(MY_NAME, b'protect', [b'@'])
33 configitem(MY_NAME, 'require-bookmark', True)
33 configitem(MY_NAME, b'require-bookmark', True)
34 configitem(MY_NAME, 'enable-branches', False)
34 configitem(MY_NAME, b'enable-branches', False)
35
35
36 cmdtable = {}
36 cmdtable = {}
37 command = registrar.command(cmdtable)
37 command = registrar.command(cmdtable)
@@ -40,19 +40,19 b' command = registrar.command(cmdtable)'
40 def commit_hook(ui, repo, **kwargs):
40 def commit_hook(ui, repo, **kwargs):
41 active = repo._bookmarks.active
41 active = repo._bookmarks.active
42 if active:
42 if active:
43 if active in ui.configlist(MY_NAME, 'protect'):
43 if active in ui.configlist(MY_NAME, b'protect'):
44 raise error.Abort(
44 raise error.Abort(
45 _('cannot commit, bookmark %s is protected') % active
45 _(b'cannot commit, bookmark %s is protected') % active
46 )
46 )
47 if not cwd_at_bookmark(repo, active):
47 if not cwd_at_bookmark(repo, active):
48 raise error.Abort(
48 raise error.Abort(
49 _(
49 _(
50 'cannot commit, working directory out of sync with active bookmark'
50 b'cannot commit, working directory out of sync with active bookmark'
51 ),
51 ),
52 hint=_("run 'hg up %s'") % active,
52 hint=_(b"run 'hg up %s'") % active,
53 )
53 )
54 elif ui.configbool(MY_NAME, 'require-bookmark', True):
54 elif ui.configbool(MY_NAME, b'require-bookmark', True):
55 raise error.Abort(_('cannot commit without an active bookmark'))
55 raise error.Abort(_(b'cannot commit without an active bookmark'))
56 return 0
56 return 0
57
57
58
58
@@ -74,7 +74,7 b' def bookmarks_addbookmarks('
74 if name in marks:
74 if name in marks:
75 raise error.Abort(
75 raise error.Abort(
76 _(
76 _(
77 "bookmark %s already exists, to move use the --rev option"
77 b"bookmark %s already exists, to move use the --rev option"
78 )
78 )
79 % name
79 % name
80 )
80 )
@@ -92,8 +92,8 b' def commands_pull(orig, ui, repo, *args,'
92 if active and not cwd_at_bookmark(repo, active):
92 if active and not cwd_at_bookmark(repo, active):
93 ui.warn(
93 ui.warn(
94 _(
94 _(
95 "working directory out of sync with active bookmark, run "
95 b"working directory out of sync with active bookmark, run "
96 "'hg up %s'"
96 b"'hg up %s'"
97 )
97 )
98 % active
98 % active
99 )
99 )
@@ -104,23 +104,23 b' def commands_branch(orig, ui, repo, labe'
104 if label and not opts.get(r'clean') and not opts.get(r'rev'):
104 if label and not opts.get(r'clean') and not opts.get(r'rev'):
105 raise error.Abort(
105 raise error.Abort(
106 _(
106 _(
107 "creating named branches is disabled and you should use bookmarks"
107 b"creating named branches is disabled and you should use bookmarks"
108 ),
108 ),
109 hint="see 'hg help bookflow'",
109 hint=b"see 'hg help bookflow'",
110 )
110 )
111 return orig(ui, repo, label, **opts)
111 return orig(ui, repo, label, **opts)
112
112
113
113
114 def cwd_at_bookmark(repo, mark):
114 def cwd_at_bookmark(repo, mark):
115 mark_id = repo._bookmarks[mark]
115 mark_id = repo._bookmarks[mark]
116 cur_id = repo.lookup('.')
116 cur_id = repo.lookup(b'.')
117 return cur_id == mark_id
117 return cur_id == mark_id
118
118
119
119
120 def uisetup(ui):
120 def uisetup(ui):
121 extensions.wrapfunction(bookmarks, 'update', bookmarks_update)
121 extensions.wrapfunction(bookmarks, b'update', bookmarks_update)
122 extensions.wrapfunction(bookmarks, 'addbookmarks', bookmarks_addbookmarks)
122 extensions.wrapfunction(bookmarks, b'addbookmarks', bookmarks_addbookmarks)
123 extensions.wrapcommand(commands.table, 'commit', commands_commit)
123 extensions.wrapcommand(commands.table, b'commit', commands_commit)
124 extensions.wrapcommand(commands.table, 'pull', commands_pull)
124 extensions.wrapcommand(commands.table, b'pull', commands_pull)
125 if not ui.configbool(MY_NAME, 'enable-branches'):
125 if not ui.configbool(MY_NAME, b'enable-branches'):
126 extensions.wrapcommand(commands.table, 'branch', commands_branch)
126 extensions.wrapcommand(commands.table, b'branch', commands_branch)
@@ -319,32 +319,32 b' xmlrpclib = util.xmlrpclib'
319 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
319 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
320 # be specifying the version(s) of Mercurial they are tested with, or
320 # be specifying the version(s) of Mercurial they are tested with, or
321 # leave the attribute unspecified.
321 # leave the attribute unspecified.
322 testedwith = 'ships-with-hg-core'
322 testedwith = b'ships-with-hg-core'
323
323
324 configtable = {}
324 configtable = {}
325 configitem = registrar.configitem(configtable)
325 configitem = registrar.configitem(configtable)
326
326
327 configitem(
327 configitem(
328 'bugzilla', 'apikey', default='',
328 b'bugzilla', b'apikey', default=b'',
329 )
329 )
330 configitem(
330 configitem(
331 'bugzilla', 'bzdir', default='/var/www/html/bugzilla',
331 b'bugzilla', b'bzdir', default=b'/var/www/html/bugzilla',
332 )
332 )
333 configitem(
333 configitem(
334 'bugzilla', 'bzemail', default=None,
334 b'bugzilla', b'bzemail', default=None,
335 )
335 )
336 configitem(
336 configitem(
337 'bugzilla', 'bzurl', default='http://localhost/bugzilla/',
337 b'bugzilla', b'bzurl', default=b'http://localhost/bugzilla/',
338 )
338 )
339 configitem(
339 configitem(
340 'bugzilla', 'bzuser', default=None,
340 b'bugzilla', b'bzuser', default=None,
341 )
341 )
342 configitem(
342 configitem(
343 'bugzilla', 'db', default='bugs',
343 b'bugzilla', b'db', default=b'bugs',
344 )
344 )
345 configitem(
345 configitem(
346 'bugzilla',
346 b'bugzilla',
347 'fixregexp',
347 b'fixregexp',
348 default=(
348 default=(
349 br'fix(?:es)?\s*(?:bugs?\s*)?,?\s*'
349 br'fix(?:es)?\s*(?:bugs?\s*)?,?\s*'
350 br'(?:nos?\.?|num(?:ber)?s?)?\s*'
350 br'(?:nos?\.?|num(?:ber)?s?)?\s*'
@@ -353,23 +353,23 b' configitem('
353 ),
353 ),
354 )
354 )
355 configitem(
355 configitem(
356 'bugzilla', 'fixresolution', default='FIXED',
356 b'bugzilla', b'fixresolution', default=b'FIXED',
357 )
357 )
358 configitem(
358 configitem(
359 'bugzilla', 'fixstatus', default='RESOLVED',
359 b'bugzilla', b'fixstatus', default=b'RESOLVED',
360 )
360 )
361 configitem(
361 configitem(
362 'bugzilla', 'host', default='localhost',
362 b'bugzilla', b'host', default=b'localhost',
363 )
363 )
364 configitem(
364 configitem(
365 'bugzilla', 'notify', default=configitem.dynamicdefault,
365 b'bugzilla', b'notify', default=configitem.dynamicdefault,
366 )
366 )
367 configitem(
367 configitem(
368 'bugzilla', 'password', default=None,
368 b'bugzilla', b'password', default=None,
369 )
369 )
370 configitem(
370 configitem(
371 'bugzilla',
371 b'bugzilla',
372 'regexp',
372 b'regexp',
373 default=(
373 default=(
374 br'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
374 br'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
375 br'(?P<ids>(?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)'
375 br'(?P<ids>(?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)'
@@ -377,25 +377,25 b' configitem('
377 ),
377 ),
378 )
378 )
379 configitem(
379 configitem(
380 'bugzilla', 'strip', default=0,
380 b'bugzilla', b'strip', default=0,
381 )
381 )
382 configitem(
382 configitem(
383 'bugzilla', 'style', default=None,
383 b'bugzilla', b'style', default=None,
384 )
384 )
385 configitem(
385 configitem(
386 'bugzilla', 'template', default=None,
386 b'bugzilla', b'template', default=None,
387 )
387 )
388 configitem(
388 configitem(
389 'bugzilla', 'timeout', default=5,
389 b'bugzilla', b'timeout', default=5,
390 )
390 )
391 configitem(
391 configitem(
392 'bugzilla', 'user', default='bugs',
392 b'bugzilla', b'user', default=b'bugs',
393 )
393 )
394 configitem(
394 configitem(
395 'bugzilla', 'usermap', default=None,
395 b'bugzilla', b'usermap', default=None,
396 )
396 )
397 configitem(
397 configitem(
398 'bugzilla', 'version', default=None,
398 b'bugzilla', b'version', default=None,
399 )
399 )
400
400
401
401
@@ -404,13 +404,13 b' class bzaccess(object):'
404
404
405 def __init__(self, ui):
405 def __init__(self, ui):
406 self.ui = ui
406 self.ui = ui
407 usermap = self.ui.config('bugzilla', 'usermap')
407 usermap = self.ui.config(b'bugzilla', b'usermap')
408 if usermap:
408 if usermap:
409 self.ui.readconfig(usermap, sections=['usermap'])
409 self.ui.readconfig(usermap, sections=[b'usermap'])
410
410
411 def map_committer(self, user):
411 def map_committer(self, user):
412 '''map name of committer to Bugzilla user name.'''
412 '''map name of committer to Bugzilla user name.'''
413 for committer, bzuser in self.ui.configitems('usermap'):
413 for committer, bzuser in self.ui.configitems(b'usermap'):
414 if committer.lower() == user.lower():
414 if committer.lower() == user.lower():
415 return bzuser
415 return bzuser
416 return user
416 return user
@@ -457,7 +457,7 b' class bzmysql(bzaccess):'
457 @staticmethod
457 @staticmethod
458 def sql_buglist(ids):
458 def sql_buglist(ids):
459 '''return SQL-friendly list of bug ids'''
459 '''return SQL-friendly list of bug ids'''
460 return '(' + ','.join(map(str, ids)) + ')'
460 return b'(' + b','.join(map(str, ids)) + b')'
461
461
462 _MySQLdb = None
462 _MySQLdb = None
463
463
@@ -467,18 +467,20 b' class bzmysql(bzaccess):'
467
467
468 bzmysql._MySQLdb = mysql
468 bzmysql._MySQLdb = mysql
469 except ImportError as err:
469 except ImportError as err:
470 raise error.Abort(_('python mysql support not available: %s') % err)
470 raise error.Abort(
471 _(b'python mysql support not available: %s') % err
472 )
471
473
472 bzaccess.__init__(self, ui)
474 bzaccess.__init__(self, ui)
473
475
474 host = self.ui.config('bugzilla', 'host')
476 host = self.ui.config(b'bugzilla', b'host')
475 user = self.ui.config('bugzilla', 'user')
477 user = self.ui.config(b'bugzilla', b'user')
476 passwd = self.ui.config('bugzilla', 'password')
478 passwd = self.ui.config(b'bugzilla', b'password')
477 db = self.ui.config('bugzilla', 'db')
479 db = self.ui.config(b'bugzilla', b'db')
478 timeout = int(self.ui.config('bugzilla', 'timeout'))
480 timeout = int(self.ui.config(b'bugzilla', b'timeout'))
479 self.ui.note(
481 self.ui.note(
480 _('connecting to %s:%s as %s, password %s\n')
482 _(b'connecting to %s:%s as %s, password %s\n')
481 % (host, db, user, '*' * len(passwd))
483 % (host, db, user, b'*' * len(passwd))
482 )
484 )
483 self.conn = bzmysql._MySQLdb.connect(
485 self.conn = bzmysql._MySQLdb.connect(
484 host=host, user=user, passwd=passwd, db=db, connect_timeout=timeout
486 host=host, user=user, passwd=passwd, db=db, connect_timeout=timeout
@@ -486,35 +488,35 b' class bzmysql(bzaccess):'
486 self.cursor = self.conn.cursor()
488 self.cursor = self.conn.cursor()
487 self.longdesc_id = self.get_longdesc_id()
489 self.longdesc_id = self.get_longdesc_id()
488 self.user_ids = {}
490 self.user_ids = {}
489 self.default_notify = "cd %(bzdir)s && ./processmail %(id)s %(user)s"
491 self.default_notify = b"cd %(bzdir)s && ./processmail %(id)s %(user)s"
490
492
491 def run(self, *args, **kwargs):
493 def run(self, *args, **kwargs):
492 '''run a query.'''
494 '''run a query.'''
493 self.ui.note(_('query: %s %s\n') % (args, kwargs))
495 self.ui.note(_(b'query: %s %s\n') % (args, kwargs))
494 try:
496 try:
495 self.cursor.execute(*args, **kwargs)
497 self.cursor.execute(*args, **kwargs)
496 except bzmysql._MySQLdb.MySQLError:
498 except bzmysql._MySQLdb.MySQLError:
497 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
499 self.ui.note(_(b'failed query: %s %s\n') % (args, kwargs))
498 raise
500 raise
499
501
500 def get_longdesc_id(self):
502 def get_longdesc_id(self):
501 '''get identity of longdesc field'''
503 '''get identity of longdesc field'''
502 self.run('select fieldid from fielddefs where name = "longdesc"')
504 self.run(b'select fieldid from fielddefs where name = "longdesc"')
503 ids = self.cursor.fetchall()
505 ids = self.cursor.fetchall()
504 if len(ids) != 1:
506 if len(ids) != 1:
505 raise error.Abort(_('unknown database schema'))
507 raise error.Abort(_(b'unknown database schema'))
506 return ids[0][0]
508 return ids[0][0]
507
509
508 def filter_real_bug_ids(self, bugs):
510 def filter_real_bug_ids(self, bugs):
509 '''filter not-existing bugs from set.'''
511 '''filter not-existing bugs from set.'''
510 self.run(
512 self.run(
511 'select bug_id from bugs where bug_id in %s'
513 b'select bug_id from bugs where bug_id in %s'
512 % bzmysql.sql_buglist(bugs.keys())
514 % bzmysql.sql_buglist(bugs.keys())
513 )
515 )
514 existing = [id for (id,) in self.cursor.fetchall()]
516 existing = [id for (id,) in self.cursor.fetchall()]
515 for id in bugs.keys():
517 for id in bugs.keys():
516 if id not in existing:
518 if id not in existing:
517 self.ui.status(_('bug %d does not exist\n') % id)
519 self.ui.status(_(b'bug %d does not exist\n') % id)
518 del bugs[id]
520 del bugs[id]
519
521
520 def filter_cset_known_bug_ids(self, node, bugs):
522 def filter_cset_known_bug_ids(self, node, bugs):
@@ -526,36 +528,36 b' class bzmysql(bzaccess):'
526 )
528 )
527 for (id,) in self.cursor.fetchall():
529 for (id,) in self.cursor.fetchall():
528 self.ui.status(
530 self.ui.status(
529 _('bug %d already knows about changeset %s\n')
531 _(b'bug %d already knows about changeset %s\n')
530 % (id, short(node))
532 % (id, short(node))
531 )
533 )
532 del bugs[id]
534 del bugs[id]
533
535
534 def notify(self, bugs, committer):
536 def notify(self, bugs, committer):
535 '''tell bugzilla to send mail.'''
537 '''tell bugzilla to send mail.'''
536 self.ui.status(_('telling bugzilla to send mail:\n'))
538 self.ui.status(_(b'telling bugzilla to send mail:\n'))
537 (user, userid) = self.get_bugzilla_user(committer)
539 (user, userid) = self.get_bugzilla_user(committer)
538 for id in bugs.keys():
540 for id in bugs.keys():
539 self.ui.status(_(' bug %s\n') % id)
541 self.ui.status(_(b' bug %s\n') % id)
540 cmdfmt = self.ui.config('bugzilla', 'notify', self.default_notify)
542 cmdfmt = self.ui.config(b'bugzilla', b'notify', self.default_notify)
541 bzdir = self.ui.config('bugzilla', 'bzdir')
543 bzdir = self.ui.config(b'bugzilla', b'bzdir')
542 try:
544 try:
543 # Backwards-compatible with old notify string, which
545 # Backwards-compatible with old notify string, which
544 # took one string. This will throw with a new format
546 # took one string. This will throw with a new format
545 # string.
547 # string.
546 cmd = cmdfmt % id
548 cmd = cmdfmt % id
547 except TypeError:
549 except TypeError:
548 cmd = cmdfmt % {'bzdir': bzdir, 'id': id, 'user': user}
550 cmd = cmdfmt % {b'bzdir': bzdir, b'id': id, b'user': user}
549 self.ui.note(_('running notify command %s\n') % cmd)
551 self.ui.note(_(b'running notify command %s\n') % cmd)
550 fp = procutil.popen('(%s) 2>&1' % cmd, 'rb')
552 fp = procutil.popen(b'(%s) 2>&1' % cmd, b'rb')
551 out = util.fromnativeeol(fp.read())
553 out = util.fromnativeeol(fp.read())
552 ret = fp.close()
554 ret = fp.close()
553 if ret:
555 if ret:
554 self.ui.warn(out)
556 self.ui.warn(out)
555 raise error.Abort(
557 raise error.Abort(
556 _('bugzilla notify command %s') % procutil.explainexit(ret)
558 _(b'bugzilla notify command %s') % procutil.explainexit(ret)
557 )
559 )
558 self.ui.status(_('done\n'))
560 self.ui.status(_(b'done\n'))
559
561
560 def get_user_id(self, user):
562 def get_user_id(self, user):
561 '''look up numeric bugzilla user id.'''
563 '''look up numeric bugzilla user id.'''
@@ -565,7 +567,7 b' class bzmysql(bzaccess):'
565 try:
567 try:
566 userid = int(user)
568 userid = int(user)
567 except ValueError:
569 except ValueError:
568 self.ui.note(_('looking up user %s\n') % user)
570 self.ui.note(_(b'looking up user %s\n') % user)
569 self.run(
571 self.run(
570 '''select userid from profiles
572 '''select userid from profiles
571 where login_name like %s''',
573 where login_name like %s''',
@@ -587,16 +589,16 b' class bzmysql(bzaccess):'
587 userid = self.get_user_id(user)
589 userid = self.get_user_id(user)
588 except KeyError:
590 except KeyError:
589 try:
591 try:
590 defaultuser = self.ui.config('bugzilla', 'bzuser')
592 defaultuser = self.ui.config(b'bugzilla', b'bzuser')
591 if not defaultuser:
593 if not defaultuser:
592 raise error.Abort(
594 raise error.Abort(
593 _('cannot find bugzilla user id for %s') % user
595 _(b'cannot find bugzilla user id for %s') % user
594 )
596 )
595 userid = self.get_user_id(defaultuser)
597 userid = self.get_user_id(defaultuser)
596 user = defaultuser
598 user = defaultuser
597 except KeyError:
599 except KeyError:
598 raise error.Abort(
600 raise error.Abort(
599 _('cannot find bugzilla user id for %s or %s')
601 _(b'cannot find bugzilla user id for %s or %s')
600 % (user, defaultuser)
602 % (user, defaultuser)
601 )
603 )
602 return (user, userid)
604 return (user, userid)
@@ -607,7 +609,7 b' class bzmysql(bzaccess):'
607 Try adding comment as committer of changeset, otherwise as
609 Try adding comment as committer of changeset, otherwise as
608 default bugzilla user.'''
610 default bugzilla user.'''
609 if len(newstate) > 0:
611 if len(newstate) > 0:
610 self.ui.warn(_("Bugzilla/MySQL cannot update bug state\n"))
612 self.ui.warn(_(b"Bugzilla/MySQL cannot update bug state\n"))
611
613
612 (user, userid) = self.get_bugzilla_user(committer)
614 (user, userid) = self.get_bugzilla_user(committer)
613 now = time.strftime(r'%Y-%m-%d %H:%M:%S')
615 now = time.strftime(r'%Y-%m-%d %H:%M:%S')
@@ -631,7 +633,7 b' class bzmysql_2_18(bzmysql):'
631 def __init__(self, ui):
633 def __init__(self, ui):
632 bzmysql.__init__(self, ui)
634 bzmysql.__init__(self, ui)
633 self.default_notify = (
635 self.default_notify = (
634 "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s"
636 b"cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s"
635 )
637 )
636
638
637
639
@@ -643,10 +645,10 b' class bzmysql_3_0(bzmysql_2_18):'
643
645
644 def get_longdesc_id(self):
646 def get_longdesc_id(self):
645 '''get identity of longdesc field'''
647 '''get identity of longdesc field'''
646 self.run('select id from fielddefs where name = "longdesc"')
648 self.run(b'select id from fielddefs where name = "longdesc"')
647 ids = self.cursor.fetchall()
649 ids = self.cursor.fetchall()
648 if len(ids) != 1:
650 if len(ids) != 1:
649 raise error.Abort(_('unknown database schema'))
651 raise error.Abort(_(b'unknown database schema'))
650 return ids[0][0]
652 return ids[0][0]
651
653
652
654
@@ -674,7 +676,7 b' class cookietransportrequest(object):'
674 def send_cookies(self, connection):
676 def send_cookies(self, connection):
675 if self.cookies:
677 if self.cookies:
676 for cookie in self.cookies:
678 for cookie in self.cookies:
677 connection.putheader("Cookie", cookie)
679 connection.putheader(b"Cookie", cookie)
678
680
679 def request(self, host, handler, request_body, verbose=0):
681 def request(self, host, handler, request_body, verbose=0):
680 self.verbose = verbose
682 self.verbose = verbose
@@ -702,9 +704,9 b' class cookietransportrequest(object):'
702 response = h._conn.getresponse()
704 response = h._conn.getresponse()
703
705
704 # Add any cookie definitions to our list.
706 # Add any cookie definitions to our list.
705 for header in response.msg.getallmatchingheaders("Set-Cookie"):
707 for header in response.msg.getallmatchingheaders(b"Set-Cookie"):
706 val = header.split(": ", 1)[1]
708 val = header.split(b": ", 1)[1]
707 cookie = val.split(";", 1)[0]
709 cookie = val.split(b";", 1)[0]
708 self.cookies.append(cookie)
710 self.cookies.append(cookie)
709
711
710 if response.status != 200:
712 if response.status != 200:
@@ -729,13 +731,13 b' class cookietransportrequest(object):'
729 # inheritance with a new-style class.
731 # inheritance with a new-style class.
730 class cookietransport(cookietransportrequest, xmlrpclib.Transport):
732 class cookietransport(cookietransportrequest, xmlrpclib.Transport):
731 def __init__(self, use_datetime=0):
733 def __init__(self, use_datetime=0):
732 if util.safehasattr(xmlrpclib.Transport, "__init__"):
734 if util.safehasattr(xmlrpclib.Transport, b"__init__"):
733 xmlrpclib.Transport.__init__(self, use_datetime)
735 xmlrpclib.Transport.__init__(self, use_datetime)
734
736
735
737
736 class cookiesafetransport(cookietransportrequest, xmlrpclib.SafeTransport):
738 class cookiesafetransport(cookietransportrequest, xmlrpclib.SafeTransport):
737 def __init__(self, use_datetime=0):
739 def __init__(self, use_datetime=0):
738 if util.safehasattr(xmlrpclib.Transport, "__init__"):
740 if util.safehasattr(xmlrpclib.Transport, b"__init__"):
739 xmlrpclib.SafeTransport.__init__(self, use_datetime)
741 xmlrpclib.SafeTransport.__init__(self, use_datetime)
740
742
741
743
@@ -748,26 +750,26 b' class bzxmlrpc(bzaccess):'
748 def __init__(self, ui):
750 def __init__(self, ui):
749 bzaccess.__init__(self, ui)
751 bzaccess.__init__(self, ui)
750
752
751 bzweb = self.ui.config('bugzilla', 'bzurl')
753 bzweb = self.ui.config(b'bugzilla', b'bzurl')
752 bzweb = bzweb.rstrip("/") + "/xmlrpc.cgi"
754 bzweb = bzweb.rstrip(b"/") + b"/xmlrpc.cgi"
753
755
754 user = self.ui.config('bugzilla', 'user')
756 user = self.ui.config(b'bugzilla', b'user')
755 passwd = self.ui.config('bugzilla', 'password')
757 passwd = self.ui.config(b'bugzilla', b'password')
756
758
757 self.fixstatus = self.ui.config('bugzilla', 'fixstatus')
759 self.fixstatus = self.ui.config(b'bugzilla', b'fixstatus')
758 self.fixresolution = self.ui.config('bugzilla', 'fixresolution')
760 self.fixresolution = self.ui.config(b'bugzilla', b'fixresolution')
759
761
760 self.bzproxy = xmlrpclib.ServerProxy(bzweb, self.transport(bzweb))
762 self.bzproxy = xmlrpclib.ServerProxy(bzweb, self.transport(bzweb))
761 ver = self.bzproxy.Bugzilla.version()['version'].split('.')
763 ver = self.bzproxy.Bugzilla.version()[b'version'].split(b'.')
762 self.bzvermajor = int(ver[0])
764 self.bzvermajor = int(ver[0])
763 self.bzverminor = int(ver[1])
765 self.bzverminor = int(ver[1])
764 login = self.bzproxy.User.login(
766 login = self.bzproxy.User.login(
765 {'login': user, 'password': passwd, 'restrict_login': True}
767 {b'login': user, b'password': passwd, b'restrict_login': True}
766 )
768 )
767 self.bztoken = login.get('token', '')
769 self.bztoken = login.get(b'token', b'')
768
770
769 def transport(self, uri):
771 def transport(self, uri):
770 if util.urlreq.urlparse(uri, "http")[0] == "https":
772 if util.urlreq.urlparse(uri, b"http")[0] == b"https":
771 return cookiesafetransport()
773 return cookiesafetransport()
772 else:
774 else:
773 return cookietransport()
775 return cookietransport()
@@ -775,56 +777,58 b' class bzxmlrpc(bzaccess):'
775 def get_bug_comments(self, id):
777 def get_bug_comments(self, id):
776 """Return a string with all comment text for a bug."""
778 """Return a string with all comment text for a bug."""
777 c = self.bzproxy.Bug.comments(
779 c = self.bzproxy.Bug.comments(
778 {'ids': [id], 'include_fields': ['text'], 'token': self.bztoken}
780 {b'ids': [id], b'include_fields': [b'text'], b'token': self.bztoken}
779 )
781 )
780 return ''.join([t['text'] for t in c['bugs']['%d' % id]['comments']])
782 return b''.join(
783 [t[b'text'] for t in c[b'bugs'][b'%d' % id][b'comments']]
784 )
781
785
782 def filter_real_bug_ids(self, bugs):
786 def filter_real_bug_ids(self, bugs):
783 probe = self.bzproxy.Bug.get(
787 probe = self.bzproxy.Bug.get(
784 {
788 {
785 'ids': sorted(bugs.keys()),
789 b'ids': sorted(bugs.keys()),
786 'include_fields': [],
790 b'include_fields': [],
787 'permissive': True,
791 b'permissive': True,
788 'token': self.bztoken,
792 b'token': self.bztoken,
789 }
793 }
790 )
794 )
791 for badbug in probe['faults']:
795 for badbug in probe[b'faults']:
792 id = badbug['id']
796 id = badbug[b'id']
793 self.ui.status(_('bug %d does not exist\n') % id)
797 self.ui.status(_(b'bug %d does not exist\n') % id)
794 del bugs[id]
798 del bugs[id]
795
799
796 def filter_cset_known_bug_ids(self, node, bugs):
800 def filter_cset_known_bug_ids(self, node, bugs):
797 for id in sorted(bugs.keys()):
801 for id in sorted(bugs.keys()):
798 if self.get_bug_comments(id).find(short(node)) != -1:
802 if self.get_bug_comments(id).find(short(node)) != -1:
799 self.ui.status(
803 self.ui.status(
800 _('bug %d already knows about changeset %s\n')
804 _(b'bug %d already knows about changeset %s\n')
801 % (id, short(node))
805 % (id, short(node))
802 )
806 )
803 del bugs[id]
807 del bugs[id]
804
808
805 def updatebug(self, bugid, newstate, text, committer):
809 def updatebug(self, bugid, newstate, text, committer):
806 args = {}
810 args = {}
807 if 'hours' in newstate:
811 if b'hours' in newstate:
808 args['work_time'] = newstate['hours']
812 args[b'work_time'] = newstate[b'hours']
809
813
810 if self.bzvermajor >= 4:
814 if self.bzvermajor >= 4:
811 args['ids'] = [bugid]
815 args[b'ids'] = [bugid]
812 args['comment'] = {'body': text}
816 args[b'comment'] = {b'body': text}
813 if 'fix' in newstate:
817 if b'fix' in newstate:
814 args['status'] = self.fixstatus
818 args[b'status'] = self.fixstatus
815 args['resolution'] = self.fixresolution
819 args[b'resolution'] = self.fixresolution
816 args['token'] = self.bztoken
820 args[b'token'] = self.bztoken
817 self.bzproxy.Bug.update(args)
821 self.bzproxy.Bug.update(args)
818 else:
822 else:
819 if 'fix' in newstate:
823 if b'fix' in newstate:
820 self.ui.warn(
824 self.ui.warn(
821 _(
825 _(
822 "Bugzilla/XMLRPC needs Bugzilla 4.0 or later "
826 b"Bugzilla/XMLRPC needs Bugzilla 4.0 or later "
823 "to mark bugs fixed\n"
827 b"to mark bugs fixed\n"
824 )
828 )
825 )
829 )
826 args['id'] = bugid
830 args[b'id'] = bugid
827 args['comment'] = text
831 args[b'comment'] = text
828 self.bzproxy.Bug.add_comment(args)
832 self.bzproxy.Bug.add_comment(args)
829
833
830
834
@@ -851,18 +855,18 b' class bzxmlrpcemail(bzxmlrpc):'
851 def __init__(self, ui):
855 def __init__(self, ui):
852 bzxmlrpc.__init__(self, ui)
856 bzxmlrpc.__init__(self, ui)
853
857
854 self.bzemail = self.ui.config('bugzilla', 'bzemail')
858 self.bzemail = self.ui.config(b'bugzilla', b'bzemail')
855 if not self.bzemail:
859 if not self.bzemail:
856 raise error.Abort(_("configuration 'bzemail' missing"))
860 raise error.Abort(_(b"configuration 'bzemail' missing"))
857 mail.validateconfig(self.ui)
861 mail.validateconfig(self.ui)
858
862
859 def makecommandline(self, fieldname, value):
863 def makecommandline(self, fieldname, value):
860 if self.bzvermajor >= 4:
864 if self.bzvermajor >= 4:
861 return "@%s %s" % (fieldname, pycompat.bytestr(value))
865 return b"@%s %s" % (fieldname, pycompat.bytestr(value))
862 else:
866 else:
863 if fieldname == "id":
867 if fieldname == b"id":
864 fieldname = "bug_id"
868 fieldname = b"bug_id"
865 return "@%s = %s" % (fieldname, pycompat.bytestr(value))
869 return b"@%s = %s" % (fieldname, pycompat.bytestr(value))
866
870
867 def send_bug_modify_email(self, bugid, commands, comment, committer):
871 def send_bug_modify_email(self, bugid, commands, comment, committer):
868 '''send modification message to Bugzilla bug via email.
872 '''send modification message to Bugzilla bug via email.
@@ -877,39 +881,41 b' class bzxmlrpcemail(bzxmlrpc):'
877 '''
881 '''
878 user = self.map_committer(committer)
882 user = self.map_committer(committer)
879 matches = self.bzproxy.User.get(
883 matches = self.bzproxy.User.get(
880 {'match': [user], 'token': self.bztoken}
884 {b'match': [user], b'token': self.bztoken}
881 )
885 )
882 if not matches['users']:
886 if not matches[b'users']:
883 user = self.ui.config('bugzilla', 'user')
887 user = self.ui.config(b'bugzilla', b'user')
884 matches = self.bzproxy.User.get(
888 matches = self.bzproxy.User.get(
885 {'match': [user], 'token': self.bztoken}
889 {b'match': [user], b'token': self.bztoken}
886 )
890 )
887 if not matches['users']:
891 if not matches[b'users']:
888 raise error.Abort(
892 raise error.Abort(
889 _("default bugzilla user %s email not found") % user
893 _(b"default bugzilla user %s email not found") % user
890 )
894 )
891 user = matches['users'][0]['email']
895 user = matches[b'users'][0][b'email']
892 commands.append(self.makecommandline("id", bugid))
896 commands.append(self.makecommandline(b"id", bugid))
893
897
894 text = "\n".join(commands) + "\n\n" + comment
898 text = b"\n".join(commands) + b"\n\n" + comment
895
899
896 _charsets = mail._charsets(self.ui)
900 _charsets = mail._charsets(self.ui)
897 user = mail.addressencode(self.ui, user, _charsets)
901 user = mail.addressencode(self.ui, user, _charsets)
898 bzemail = mail.addressencode(self.ui, self.bzemail, _charsets)
902 bzemail = mail.addressencode(self.ui, self.bzemail, _charsets)
899 msg = mail.mimeencode(self.ui, text, _charsets)
903 msg = mail.mimeencode(self.ui, text, _charsets)
900 msg['From'] = user
904 msg[b'From'] = user
901 msg['To'] = bzemail
905 msg[b'To'] = bzemail
902 msg['Subject'] = mail.headencode(self.ui, "Bug modification", _charsets)
906 msg[b'Subject'] = mail.headencode(
907 self.ui, b"Bug modification", _charsets
908 )
903 sendmail = mail.connect(self.ui)
909 sendmail = mail.connect(self.ui)
904 sendmail(user, bzemail, msg.as_string())
910 sendmail(user, bzemail, msg.as_string())
905
911
906 def updatebug(self, bugid, newstate, text, committer):
912 def updatebug(self, bugid, newstate, text, committer):
907 cmds = []
913 cmds = []
908 if 'hours' in newstate:
914 if b'hours' in newstate:
909 cmds.append(self.makecommandline("work_time", newstate['hours']))
915 cmds.append(self.makecommandline(b"work_time", newstate[b'hours']))
910 if 'fix' in newstate:
916 if b'fix' in newstate:
911 cmds.append(self.makecommandline("bug_status", self.fixstatus))
917 cmds.append(self.makecommandline(b"bug_status", self.fixstatus))
912 cmds.append(self.makecommandline("resolution", self.fixresolution))
918 cmds.append(self.makecommandline(b"resolution", self.fixresolution))
913 self.send_bug_modify_email(bugid, cmds, text, committer)
919 self.send_bug_modify_email(bugid, cmds, text, committer)
914
920
915
921
@@ -924,26 +930,26 b' class bzrestapi(bzaccess):'
924
930
925 def __init__(self, ui):
931 def __init__(self, ui):
926 bzaccess.__init__(self, ui)
932 bzaccess.__init__(self, ui)
927 bz = self.ui.config('bugzilla', 'bzurl')
933 bz = self.ui.config(b'bugzilla', b'bzurl')
928 self.bzroot = '/'.join([bz, 'rest'])
934 self.bzroot = b'/'.join([bz, b'rest'])
929 self.apikey = self.ui.config('bugzilla', 'apikey')
935 self.apikey = self.ui.config(b'bugzilla', b'apikey')
930 self.user = self.ui.config('bugzilla', 'user')
936 self.user = self.ui.config(b'bugzilla', b'user')
931 self.passwd = self.ui.config('bugzilla', 'password')
937 self.passwd = self.ui.config(b'bugzilla', b'password')
932 self.fixstatus = self.ui.config('bugzilla', 'fixstatus')
938 self.fixstatus = self.ui.config(b'bugzilla', b'fixstatus')
933 self.fixresolution = self.ui.config('bugzilla', 'fixresolution')
939 self.fixresolution = self.ui.config(b'bugzilla', b'fixresolution')
934
940
935 def apiurl(self, targets, include_fields=None):
941 def apiurl(self, targets, include_fields=None):
936 url = '/'.join([self.bzroot] + [pycompat.bytestr(t) for t in targets])
942 url = b'/'.join([self.bzroot] + [pycompat.bytestr(t) for t in targets])
937 qv = {}
943 qv = {}
938 if self.apikey:
944 if self.apikey:
939 qv['api_key'] = self.apikey
945 qv[b'api_key'] = self.apikey
940 elif self.user and self.passwd:
946 elif self.user and self.passwd:
941 qv['login'] = self.user
947 qv[b'login'] = self.user
942 qv['password'] = self.passwd
948 qv[b'password'] = self.passwd
943 if include_fields:
949 if include_fields:
944 qv['include_fields'] = include_fields
950 qv[b'include_fields'] = include_fields
945 if qv:
951 if qv:
946 url = '%s?%s' % (url, util.urlreq.urlencode(qv))
952 url = b'%s?%s' % (url, util.urlreq.urlencode(qv))
947 return url
953 return url
948
954
949 def _fetch(self, burl):
955 def _fetch(self, burl):
@@ -952,30 +958,30 b' class bzrestapi(bzaccess):'
952 return json.loads(resp.read())
958 return json.loads(resp.read())
953 except util.urlerr.httperror as inst:
959 except util.urlerr.httperror as inst:
954 if inst.code == 401:
960 if inst.code == 401:
955 raise error.Abort(_('authorization failed'))
961 raise error.Abort(_(b'authorization failed'))
956 if inst.code == 404:
962 if inst.code == 404:
957 raise NotFound()
963 raise NotFound()
958 else:
964 else:
959 raise
965 raise
960
966
961 def _submit(self, burl, data, method='POST'):
967 def _submit(self, burl, data, method=b'POST'):
962 data = json.dumps(data)
968 data = json.dumps(data)
963 if method == 'PUT':
969 if method == b'PUT':
964
970
965 class putrequest(util.urlreq.request):
971 class putrequest(util.urlreq.request):
966 def get_method(self):
972 def get_method(self):
967 return 'PUT'
973 return b'PUT'
968
974
969 request_type = putrequest
975 request_type = putrequest
970 else:
976 else:
971 request_type = util.urlreq.request
977 request_type = util.urlreq.request
972 req = request_type(burl, data, {'Content-Type': 'application/json'})
978 req = request_type(burl, data, {b'Content-Type': b'application/json'})
973 try:
979 try:
974 resp = url.opener(self.ui).open(req)
980 resp = url.opener(self.ui).open(req)
975 return json.loads(resp.read())
981 return json.loads(resp.read())
976 except util.urlerr.httperror as inst:
982 except util.urlerr.httperror as inst:
977 if inst.code == 401:
983 if inst.code == 401:
978 raise error.Abort(_('authorization failed'))
984 raise error.Abort(_(b'authorization failed'))
979 if inst.code == 404:
985 if inst.code == 404:
980 raise NotFound()
986 raise NotFound()
981 else:
987 else:
@@ -985,7 +991,7 b' class bzrestapi(bzaccess):'
985 '''remove bug IDs that do not exist in Bugzilla from bugs.'''
991 '''remove bug IDs that do not exist in Bugzilla from bugs.'''
986 badbugs = set()
992 badbugs = set()
987 for bugid in bugs:
993 for bugid in bugs:
988 burl = self.apiurl(('bug', bugid), include_fields='status')
994 burl = self.apiurl((b'bug', bugid), include_fields=b'status')
989 try:
995 try:
990 self._fetch(burl)
996 self._fetch(burl)
991 except NotFound:
997 except NotFound:
@@ -997,12 +1003,15 b' class bzrestapi(bzaccess):'
997 '''remove bug IDs where node occurs in comment text from bugs.'''
1003 '''remove bug IDs where node occurs in comment text from bugs.'''
998 sn = short(node)
1004 sn = short(node)
999 for bugid in bugs.keys():
1005 for bugid in bugs.keys():
1000 burl = self.apiurl(('bug', bugid, 'comment'), include_fields='text')
1006 burl = self.apiurl(
1007 (b'bug', bugid, b'comment'), include_fields=b'text'
1008 )
1001 result = self._fetch(burl)
1009 result = self._fetch(burl)
1002 comments = result['bugs'][pycompat.bytestr(bugid)]['comments']
1010 comments = result[b'bugs'][pycompat.bytestr(bugid)][b'comments']
1003 if any(sn in c['text'] for c in comments):
1011 if any(sn in c[b'text'] for c in comments):
1004 self.ui.status(
1012 self.ui.status(
1005 _('bug %d already knows about changeset %s\n') % (bugid, sn)
1013 _(b'bug %d already knows about changeset %s\n')
1014 % (bugid, sn)
1006 )
1015 )
1007 del bugs[bugid]
1016 del bugs[bugid]
1008
1017
@@ -1013,28 +1022,32 b' class bzrestapi(bzaccess):'
1013 the changeset. Otherwise use the default Bugzilla user.
1022 the changeset. Otherwise use the default Bugzilla user.
1014 '''
1023 '''
1015 bugmod = {}
1024 bugmod = {}
1016 if 'hours' in newstate:
1025 if b'hours' in newstate:
1017 bugmod['work_time'] = newstate['hours']
1026 bugmod[b'work_time'] = newstate[b'hours']
1018 if 'fix' in newstate:
1027 if b'fix' in newstate:
1019 bugmod['status'] = self.fixstatus
1028 bugmod[b'status'] = self.fixstatus
1020 bugmod['resolution'] = self.fixresolution
1029 bugmod[b'resolution'] = self.fixresolution
1021 if bugmod:
1030 if bugmod:
1022 # if we have to change the bugs state do it here
1031 # if we have to change the bugs state do it here
1023 bugmod['comment'] = {
1032 bugmod[b'comment'] = {
1024 'comment': text,
1033 b'comment': text,
1025 'is_private': False,
1034 b'is_private': False,
1026 'is_markdown': False,
1035 b'is_markdown': False,
1027 }
1036 }
1028 burl = self.apiurl(('bug', bugid))
1037 burl = self.apiurl((b'bug', bugid))
1029 self._submit(burl, bugmod, method='PUT')
1038 self._submit(burl, bugmod, method=b'PUT')
1030 self.ui.debug('updated bug %s\n' % bugid)
1039 self.ui.debug(b'updated bug %s\n' % bugid)
1031 else:
1040 else:
1032 burl = self.apiurl(('bug', bugid, 'comment'))
1041 burl = self.apiurl((b'bug', bugid, b'comment'))
1033 self._submit(
1042 self._submit(
1034 burl,
1043 burl,
1035 {'comment': text, 'is_private': False, 'is_markdown': False,},
1044 {
1045 b'comment': text,
1046 b'is_private': False,
1047 b'is_markdown': False,
1048 },
1036 )
1049 )
1037 self.ui.debug('added comment to bug %s\n' % bugid)
1050 self.ui.debug(b'added comment to bug %s\n' % bugid)
1038
1051
1039 def notify(self, bugs, committer):
1052 def notify(self, bugs, committer):
1040 '''Force sending of Bugzilla notification emails.
1053 '''Force sending of Bugzilla notification emails.
@@ -1049,32 +1062,32 b' class bugzilla(object):'
1049 # supported versions of bugzilla. different versions have
1062 # supported versions of bugzilla. different versions have
1050 # different schemas.
1063 # different schemas.
1051 _versions = {
1064 _versions = {
1052 '2.16': bzmysql,
1065 b'2.16': bzmysql,
1053 '2.18': bzmysql_2_18,
1066 b'2.18': bzmysql_2_18,
1054 '3.0': bzmysql_3_0,
1067 b'3.0': bzmysql_3_0,
1055 'xmlrpc': bzxmlrpc,
1068 b'xmlrpc': bzxmlrpc,
1056 'xmlrpc+email': bzxmlrpcemail,
1069 b'xmlrpc+email': bzxmlrpcemail,
1057 'restapi': bzrestapi,
1070 b'restapi': bzrestapi,
1058 }
1071 }
1059
1072
1060 def __init__(self, ui, repo):
1073 def __init__(self, ui, repo):
1061 self.ui = ui
1074 self.ui = ui
1062 self.repo = repo
1075 self.repo = repo
1063
1076
1064 bzversion = self.ui.config('bugzilla', 'version')
1077 bzversion = self.ui.config(b'bugzilla', b'version')
1065 try:
1078 try:
1066 bzclass = bugzilla._versions[bzversion]
1079 bzclass = bugzilla._versions[bzversion]
1067 except KeyError:
1080 except KeyError:
1068 raise error.Abort(
1081 raise error.Abort(
1069 _('bugzilla version %s not supported') % bzversion
1082 _(b'bugzilla version %s not supported') % bzversion
1070 )
1083 )
1071 self.bzdriver = bzclass(self.ui)
1084 self.bzdriver = bzclass(self.ui)
1072
1085
1073 self.bug_re = re.compile(
1086 self.bug_re = re.compile(
1074 self.ui.config('bugzilla', 'regexp'), re.IGNORECASE
1087 self.ui.config(b'bugzilla', b'regexp'), re.IGNORECASE
1075 )
1088 )
1076 self.fix_re = re.compile(
1089 self.fix_re = re.compile(
1077 self.ui.config('bugzilla', 'fixregexp'), re.IGNORECASE
1090 self.ui.config(b'bugzilla', b'fixregexp'), re.IGNORECASE
1078 )
1091 )
1079 self.split_re = re.compile(br'\D+')
1092 self.split_re = re.compile(br'\D+')
1080
1093
@@ -1106,25 +1119,25 b' class bugzilla(object):'
1106 start = m.end()
1119 start = m.end()
1107 if m is bugmatch:
1120 if m is bugmatch:
1108 bugmatch = self.bug_re.search(ctx.description(), start)
1121 bugmatch = self.bug_re.search(ctx.description(), start)
1109 if 'fix' in bugattribs:
1122 if b'fix' in bugattribs:
1110 del bugattribs['fix']
1123 del bugattribs[b'fix']
1111 else:
1124 else:
1112 fixmatch = self.fix_re.search(ctx.description(), start)
1125 fixmatch = self.fix_re.search(ctx.description(), start)
1113 bugattribs['fix'] = None
1126 bugattribs[b'fix'] = None
1114
1127
1115 try:
1128 try:
1116 ids = m.group('ids')
1129 ids = m.group(b'ids')
1117 except IndexError:
1130 except IndexError:
1118 ids = m.group(1)
1131 ids = m.group(1)
1119 try:
1132 try:
1120 hours = float(m.group('hours'))
1133 hours = float(m.group(b'hours'))
1121 bugattribs['hours'] = hours
1134 bugattribs[b'hours'] = hours
1122 except IndexError:
1135 except IndexError:
1123 pass
1136 pass
1124 except TypeError:
1137 except TypeError:
1125 pass
1138 pass
1126 except ValueError:
1139 except ValueError:
1127 self.ui.status(_("%s: invalid hours\n") % m.group('hours'))
1140 self.ui.status(_(b"%s: invalid hours\n") % m.group(b'hours'))
1128
1141
1129 for id in self.split_re.split(ids):
1142 for id in self.split_re.split(ids):
1130 if not id:
1143 if not id:
@@ -1142,10 +1155,10 b' class bugzilla(object):'
1142 def webroot(root):
1155 def webroot(root):
1143 '''strip leading prefix of repo root and turn into
1156 '''strip leading prefix of repo root and turn into
1144 url-safe path.'''
1157 url-safe path.'''
1145 count = int(self.ui.config('bugzilla', 'strip'))
1158 count = int(self.ui.config(b'bugzilla', b'strip'))
1146 root = util.pconvert(root)
1159 root = util.pconvert(root)
1147 while count > 0:
1160 while count > 0:
1148 c = root.find('/')
1161 c = root.find(b'/')
1149 if c == -1:
1162 if c == -1:
1150 break
1163 break
1151 root = root[c + 1 :]
1164 root = root[c + 1 :]
@@ -1153,13 +1166,13 b' class bugzilla(object):'
1153 return root
1166 return root
1154
1167
1155 mapfile = None
1168 mapfile = None
1156 tmpl = self.ui.config('bugzilla', 'template')
1169 tmpl = self.ui.config(b'bugzilla', b'template')
1157 if not tmpl:
1170 if not tmpl:
1158 mapfile = self.ui.config('bugzilla', 'style')
1171 mapfile = self.ui.config(b'bugzilla', b'style')
1159 if not mapfile and not tmpl:
1172 if not mapfile and not tmpl:
1160 tmpl = _(
1173 tmpl = _(
1161 'changeset {node|short} in repo {root} refers '
1174 b'changeset {node|short} in repo {root} refers '
1162 'to bug {bug}.\ndetails:\n\t{desc|tabindent}'
1175 b'to bug {bug}.\ndetails:\n\t{desc|tabindent}'
1163 )
1176 )
1164 spec = logcmdutil.templatespec(tmpl, mapfile)
1177 spec = logcmdutil.templatespec(tmpl, mapfile)
1165 t = logcmdutil.changesettemplater(self.ui, self.repo, spec)
1178 t = logcmdutil.changesettemplater(self.ui, self.repo, spec)
@@ -1168,7 +1181,7 b' class bugzilla(object):'
1168 ctx,
1181 ctx,
1169 changes=ctx.changeset(),
1182 changes=ctx.changeset(),
1170 bug=pycompat.bytestr(bugid),
1183 bug=pycompat.bytestr(bugid),
1171 hgweb=self.ui.config('web', 'baseurl'),
1184 hgweb=self.ui.config(b'web', b'baseurl'),
1172 root=self.repo.root,
1185 root=self.repo.root,
1173 webroot=webroot(self.repo.root),
1186 webroot=webroot(self.repo.root),
1174 )
1187 )
@@ -1188,7 +1201,7 b' def hook(ui, repo, hooktype, node=None, '
1188 seen multiple times does not fill bug with duplicate data.'''
1201 seen multiple times does not fill bug with duplicate data.'''
1189 if node is None:
1202 if node is None:
1190 raise error.Abort(
1203 raise error.Abort(
1191 _('hook type %s does not pass a changeset id') % hooktype
1204 _(b'hook type %s does not pass a changeset id') % hooktype
1192 )
1205 )
1193 try:
1206 try:
1194 bz = bugzilla(ui, repo)
1207 bz = bugzilla(ui, repo)
@@ -1199,4 +1212,4 b' def hook(ui, repo, hooktype, node=None, '
1199 bz.update(bug, bugs[bug], ctx)
1212 bz.update(bug, bugs[bug], ctx)
1200 bz.notify(bugs, stringutil.email(ctx.user()))
1213 bz.notify(bugs, stringutil.email(ctx.user()))
1201 except Exception as e:
1214 except Exception as e:
1202 raise error.Abort(_('Bugzilla error: %s') % e)
1215 raise error.Abort(_(b'Bugzilla error: %s') % e)
@@ -42,49 +42,55 b' command = registrar.command(cmdtable)'
42 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
42 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
43 # be specifying the version(s) of Mercurial they are tested with, or
43 # be specifying the version(s) of Mercurial they are tested with, or
44 # leave the attribute unspecified.
44 # leave the attribute unspecified.
45 testedwith = 'ships-with-hg-core'
45 testedwith = b'ships-with-hg-core'
46
46
47
47
48 @command(
48 @command(
49 'censor',
49 b'censor',
50 [
50 [
51 ('r', 'rev', '', _('censor file from specified revision'), _('REV')),
51 (
52 ('t', 'tombstone', '', _('replacement tombstone data'), _('TEXT')),
52 b'r',
53 b'rev',
54 b'',
55 _(b'censor file from specified revision'),
56 _(b'REV'),
57 ),
58 (b't', b'tombstone', b'', _(b'replacement tombstone data'), _(b'TEXT')),
53 ],
59 ],
54 _('-r REV [-t TEXT] [FILE]'),
60 _(b'-r REV [-t TEXT] [FILE]'),
55 helpcategory=command.CATEGORY_MAINTENANCE,
61 helpcategory=command.CATEGORY_MAINTENANCE,
56 )
62 )
57 def censor(ui, repo, path, rev='', tombstone='', **opts):
63 def censor(ui, repo, path, rev=b'', tombstone=b'', **opts):
58 with repo.wlock(), repo.lock():
64 with repo.wlock(), repo.lock():
59 return _docensor(ui, repo, path, rev, tombstone, **opts)
65 return _docensor(ui, repo, path, rev, tombstone, **opts)
60
66
61
67
62 def _docensor(ui, repo, path, rev='', tombstone='', **opts):
68 def _docensor(ui, repo, path, rev=b'', tombstone=b'', **opts):
63 if not path:
69 if not path:
64 raise error.Abort(_('must specify file path to censor'))
70 raise error.Abort(_(b'must specify file path to censor'))
65 if not rev:
71 if not rev:
66 raise error.Abort(_('must specify revision to censor'))
72 raise error.Abort(_(b'must specify revision to censor'))
67
73
68 wctx = repo[None]
74 wctx = repo[None]
69
75
70 m = scmutil.match(wctx, (path,))
76 m = scmutil.match(wctx, (path,))
71 if m.anypats() or len(m.files()) != 1:
77 if m.anypats() or len(m.files()) != 1:
72 raise error.Abort(_('can only specify an explicit filename'))
78 raise error.Abort(_(b'can only specify an explicit filename'))
73 path = m.files()[0]
79 path = m.files()[0]
74 flog = repo.file(path)
80 flog = repo.file(path)
75 if not len(flog):
81 if not len(flog):
76 raise error.Abort(_('cannot censor file with no history'))
82 raise error.Abort(_(b'cannot censor file with no history'))
77
83
78 rev = scmutil.revsingle(repo, rev, rev).rev()
84 rev = scmutil.revsingle(repo, rev, rev).rev()
79 try:
85 try:
80 ctx = repo[rev]
86 ctx = repo[rev]
81 except KeyError:
87 except KeyError:
82 raise error.Abort(_('invalid revision identifier %s') % rev)
88 raise error.Abort(_(b'invalid revision identifier %s') % rev)
83
89
84 try:
90 try:
85 fctx = ctx.filectx(path)
91 fctx = ctx.filectx(path)
86 except error.LookupError:
92 except error.LookupError:
87 raise error.Abort(_('file does not exist at revision %s') % rev)
93 raise error.Abort(_(b'file does not exist at revision %s') % rev)
88
94
89 fnode = fctx.filenode()
95 fnode = fctx.filenode()
90 heads = []
96 heads = []
@@ -93,17 +99,17 b" def _docensor(ui, repo, path, rev='', to"
93 if path in hc and hc.filenode(path) == fnode:
99 if path in hc and hc.filenode(path) == fnode:
94 heads.append(hc)
100 heads.append(hc)
95 if heads:
101 if heads:
96 headlist = ', '.join([short(c.node()) for c in heads])
102 headlist = b', '.join([short(c.node()) for c in heads])
97 raise error.Abort(
103 raise error.Abort(
98 _('cannot censor file in heads (%s)') % headlist,
104 _(b'cannot censor file in heads (%s)') % headlist,
99 hint=_('clean/delete and commit first'),
105 hint=_(b'clean/delete and commit first'),
100 )
106 )
101
107
102 wp = wctx.parents()
108 wp = wctx.parents()
103 if ctx.node() in [p.node() for p in wp]:
109 if ctx.node() in [p.node() for p in wp]:
104 raise error.Abort(
110 raise error.Abort(
105 _('cannot censor working directory'),
111 _(b'cannot censor working directory'),
106 hint=_('clean/delete/update first'),
112 hint=_(b'clean/delete/update first'),
107 )
113 )
108
114
109 with repo.transaction(b'censor') as tr:
115 with repo.transaction(b'censor') as tr:
@@ -33,14 +33,22 b' command = registrar.command(cmdtable)'
33 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
33 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
34 # be specifying the version(s) of Mercurial they are tested with, or
34 # be specifying the version(s) of Mercurial they are tested with, or
35 # leave the attribute unspecified.
35 # leave the attribute unspecified.
36 testedwith = 'ships-with-hg-core'
36 testedwith = b'ships-with-hg-core'
37
37
38
38
39 @command(
39 @command(
40 'children',
40 b'children',
41 [('r', 'rev', '.', _('show children of the specified revision'), _('REV')),]
41 [
42 (
43 b'r',
44 b'rev',
45 b'.',
46 _(b'show children of the specified revision'),
47 _(b'REV'),
48 ),
49 ]
42 + templateopts,
50 + templateopts,
43 _('hg children [-r REV] [FILE]'),
51 _(b'hg children [-r REV] [FILE]'),
44 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
52 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
45 inferrepo=True,
53 inferrepo=True,
46 )
54 )
@@ -62,7 +70,7 b' def children(ui, repo, file_=None, **opt'
62
70
63 """
71 """
64 opts = pycompat.byteskwargs(opts)
72 opts = pycompat.byteskwargs(opts)
65 rev = opts.get('rev')
73 rev = opts.get(b'rev')
66 ctx = scmutil.revsingle(repo, rev)
74 ctx = scmutil.revsingle(repo, rev)
67 if file_:
75 if file_:
68 fctx = repo.filectx(file_, changeid=ctx.rev())
76 fctx = repo.filectx(file_, changeid=ctx.rev())
@@ -32,17 +32,17 b' command = registrar.command(cmdtable)'
32 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
32 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
33 # be specifying the version(s) of Mercurial they are tested with, or
33 # be specifying the version(s) of Mercurial they are tested with, or
34 # leave the attribute unspecified.
34 # leave the attribute unspecified.
35 testedwith = 'ships-with-hg-core'
35 testedwith = b'ships-with-hg-core'
36
36
37
37
38 def changedlines(ui, repo, ctx1, ctx2, fns):
38 def changedlines(ui, repo, ctx1, ctx2, fns):
39 added, removed = 0, 0
39 added, removed = 0, 0
40 fmatch = scmutil.matchfiles(repo, fns)
40 fmatch = scmutil.matchfiles(repo, fns)
41 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
41 diff = b''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
42 for l in diff.split('\n'):
42 for l in diff.split(b'\n'):
43 if l.startswith("+") and not l.startswith("+++ "):
43 if l.startswith(b"+") and not l.startswith(b"+++ "):
44 added += 1
44 added += 1
45 elif l.startswith("-") and not l.startswith("--- "):
45 elif l.startswith(b"-") and not l.startswith(b"--- "):
46 removed += 1
46 removed += 1
47 return (added, removed)
47 return (added, removed)
48
48
@@ -50,17 +50,17 b' def changedlines(ui, repo, ctx1, ctx2, f'
50 def countrate(ui, repo, amap, *pats, **opts):
50 def countrate(ui, repo, amap, *pats, **opts):
51 """Calculate stats"""
51 """Calculate stats"""
52 opts = pycompat.byteskwargs(opts)
52 opts = pycompat.byteskwargs(opts)
53 if opts.get('dateformat'):
53 if opts.get(b'dateformat'):
54
54
55 def getkey(ctx):
55 def getkey(ctx):
56 t, tz = ctx.date()
56 t, tz = ctx.date()
57 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
57 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
58 return encoding.strtolocal(
58 return encoding.strtolocal(
59 date.strftime(encoding.strfromlocal(opts['dateformat']))
59 date.strftime(encoding.strfromlocal(opts[b'dateformat']))
60 )
60 )
61
61
62 else:
62 else:
63 tmpl = opts.get('oldtemplate') or opts.get('template')
63 tmpl = opts.get(b'oldtemplate') or opts.get(b'template')
64 tmpl = logcmdutil.maketemplater(ui, repo, tmpl)
64 tmpl = logcmdutil.maketemplater(ui, repo, tmpl)
65
65
66 def getkey(ctx):
66 def getkey(ctx):
@@ -69,12 +69,12 b' def countrate(ui, repo, amap, *pats, **o'
69 return ui.popbuffer()
69 return ui.popbuffer()
70
70
71 progress = ui.makeprogress(
71 progress = ui.makeprogress(
72 _('analyzing'), unit=_('revisions'), total=len(repo)
72 _(b'analyzing'), unit=_(b'revisions'), total=len(repo)
73 )
73 )
74 rate = {}
74 rate = {}
75 df = False
75 df = False
76 if opts.get('date'):
76 if opts.get(b'date'):
77 df = dateutil.matchdate(opts['date'])
77 df = dateutil.matchdate(opts[b'date'])
78
78
79 m = scmutil.match(repo[None], pats, opts)
79 m = scmutil.match(repo[None], pats, opts)
80
80
@@ -85,12 +85,12 b' def countrate(ui, repo, amap, *pats, **o'
85
85
86 key = getkey(ctx).strip()
86 key = getkey(ctx).strip()
87 key = amap.get(key, key) # alias remap
87 key = amap.get(key, key) # alias remap
88 if opts.get('changesets'):
88 if opts.get(b'changesets'):
89 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
89 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
90 else:
90 else:
91 parents = ctx.parents()
91 parents = ctx.parents()
92 if len(parents) > 1:
92 if len(parents) > 1:
93 ui.note(_('revision %d is a merge, ignoring...\n') % (rev,))
93 ui.note(_(b'revision %d is a merge, ignoring...\n') % (rev,))
94 return
94 return
95
95
96 ctx1 = parents[0]
96 ctx1 = parents[0]
@@ -108,50 +108,50 b' def countrate(ui, repo, amap, *pats, **o'
108
108
109
109
110 @command(
110 @command(
111 'churn',
111 b'churn',
112 [
112 [
113 (
113 (
114 'r',
114 b'r',
115 'rev',
115 b'rev',
116 [],
116 [],
117 _('count rate for the specified revision or revset'),
117 _(b'count rate for the specified revision or revset'),
118 _('REV'),
118 _(b'REV'),
119 ),
119 ),
120 (
120 (
121 'd',
121 b'd',
122 'date',
122 b'date',
123 '',
123 b'',
124 _('count rate for revisions matching date spec'),
124 _(b'count rate for revisions matching date spec'),
125 _('DATE'),
125 _(b'DATE'),
126 ),
126 ),
127 (
127 (
128 't',
128 b't',
129 'oldtemplate',
129 b'oldtemplate',
130 '',
130 b'',
131 _('template to group changesets (DEPRECATED)'),
131 _(b'template to group changesets (DEPRECATED)'),
132 _('TEMPLATE'),
132 _(b'TEMPLATE'),
133 ),
133 ),
134 (
134 (
135 'T',
135 b'T',
136 'template',
136 b'template',
137 '{author|email}',
137 b'{author|email}',
138 _('template to group changesets'),
138 _(b'template to group changesets'),
139 _('TEMPLATE'),
139 _(b'TEMPLATE'),
140 ),
140 ),
141 (
141 (
142 'f',
142 b'f',
143 'dateformat',
143 b'dateformat',
144 '',
144 b'',
145 _('strftime-compatible format for grouping by date'),
145 _(b'strftime-compatible format for grouping by date'),
146 _('FORMAT'),
146 _(b'FORMAT'),
147 ),
147 ),
148 ('c', 'changesets', False, _('count rate by number of changesets')),
148 (b'c', b'changesets', False, _(b'count rate by number of changesets')),
149 ('s', 'sort', False, _('sort by key (default: sort by count)')),
149 (b's', b'sort', False, _(b'sort by key (default: sort by count)')),
150 ('', 'diffstat', False, _('display added/removed lines separately')),
150 (b'', b'diffstat', False, _(b'display added/removed lines separately')),
151 ('', 'aliases', '', _('file with email aliases'), _('FILE')),
151 (b'', b'aliases', b'', _(b'file with email aliases'), _(b'FILE')),
152 ]
152 ]
153 + cmdutil.walkopts,
153 + cmdutil.walkopts,
154 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]"),
154 _(b"hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]"),
155 helpcategory=command.CATEGORY_MAINTENANCE,
155 helpcategory=command.CATEGORY_MAINTENANCE,
156 inferrepo=True,
156 inferrepo=True,
157 )
157 )
@@ -193,21 +193,21 b' def churn(ui, repo, *pats, **opts):'
193 '''
193 '''
194
194
195 def pad(s, l):
195 def pad(s, l):
196 return s + " " * (l - encoding.colwidth(s))
196 return s + b" " * (l - encoding.colwidth(s))
197
197
198 amap = {}
198 amap = {}
199 aliases = opts.get(r'aliases')
199 aliases = opts.get(r'aliases')
200 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
200 if not aliases and os.path.exists(repo.wjoin(b'.hgchurn')):
201 aliases = repo.wjoin('.hgchurn')
201 aliases = repo.wjoin(b'.hgchurn')
202 if aliases:
202 if aliases:
203 for l in open(aliases, "rb"):
203 for l in open(aliases, b"rb"):
204 try:
204 try:
205 alias, actual = l.rsplit('=' in l and '=' or None, 1)
205 alias, actual = l.rsplit(b'=' in l and b'=' or None, 1)
206 amap[alias.strip()] = actual.strip()
206 amap[alias.strip()] = actual.strip()
207 except ValueError:
207 except ValueError:
208 l = l.strip()
208 l = l.strip()
209 if l:
209 if l:
210 ui.warn(_("skipping malformed alias: %s\n") % l)
210 ui.warn(_(b"skipping malformed alias: %s\n") % l)
211 continue
211 continue
212
212
213 rate = list(countrate(ui, repo, amap, *pats, **opts).items())
213 rate = list(countrate(ui, repo, amap, *pats, **opts).items())
@@ -224,7 +224,7 b' def churn(ui, repo, *pats, **opts):'
224 maxname = max(len(k) for k, v in rate)
224 maxname = max(len(k) for k, v in rate)
225
225
226 ttywidth = ui.termwidth()
226 ttywidth = ui.termwidth()
227 ui.debug("assuming %i character terminal\n" % ttywidth)
227 ui.debug(b"assuming %i character terminal\n" % ttywidth)
228 width = ttywidth - maxname - 2 - 2 - 2
228 width = ttywidth - maxname - 2 - 2 - 2
229
229
230 if opts.get(r'diffstat'):
230 if opts.get(r'diffstat'):
@@ -232,21 +232,21 b' def churn(ui, repo, *pats, **opts):'
232
232
233 def format(name, diffstat):
233 def format(name, diffstat):
234 added, removed = diffstat
234 added, removed = diffstat
235 return "%s %15s %s%s\n" % (
235 return b"%s %15s %s%s\n" % (
236 pad(name, maxname),
236 pad(name, maxname),
237 '+%d/-%d' % (added, removed),
237 b'+%d/-%d' % (added, removed),
238 ui.label('+' * charnum(added), 'diffstat.inserted'),
238 ui.label(b'+' * charnum(added), b'diffstat.inserted'),
239 ui.label('-' * charnum(removed), 'diffstat.deleted'),
239 ui.label(b'-' * charnum(removed), b'diffstat.deleted'),
240 )
240 )
241
241
242 else:
242 else:
243 width -= 6
243 width -= 6
244
244
245 def format(name, count):
245 def format(name, count):
246 return "%s %6d %s\n" % (
246 return b"%s %6d %s\n" % (
247 pad(name, maxname),
247 pad(name, maxname),
248 sum(count),
248 sum(count),
249 '*' * charnum(sum(count)),
249 b'*' * charnum(sum(count)),
250 )
250 )
251
251
252 def charnum(count):
252 def charnum(count):
@@ -201,7 +201,7 b' from mercurial import ('
201 wireprotov1server,
201 wireprotov1server,
202 )
202 )
203
203
204 testedwith = 'ships-with-hg-core'
204 testedwith = b'ships-with-hg-core'
205
205
206
206
207 def capabilities(orig, repo, proto):
207 def capabilities(orig, repo, proto):
@@ -210,11 +210,11 b' def capabilities(orig, repo, proto):'
210 # Only advertise if a manifest exists. This does add some I/O to requests.
210 # Only advertise if a manifest exists. This does add some I/O to requests.
211 # But this should be cheaper than a wasted network round trip due to
211 # But this should be cheaper than a wasted network round trip due to
212 # missing file.
212 # missing file.
213 if repo.vfs.exists('clonebundles.manifest'):
213 if repo.vfs.exists(b'clonebundles.manifest'):
214 caps.append('clonebundles')
214 caps.append(b'clonebundles')
215
215
216 return caps
216 return caps
217
217
218
218
219 def extsetup(ui):
219 def extsetup(ui):
220 extensions.wrapfunction(wireprotov1server, '_capabilities', capabilities)
220 extensions.wrapfunction(wireprotov1server, b'_capabilities', capabilities)
@@ -24,17 +24,17 b' command = registrar.command(cmdtable)'
24 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
24 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
25 # be specifying the version(s) of Mercurial they are tested with, or
25 # be specifying the version(s) of Mercurial they are tested with, or
26 # leave the attribute unspecified.
26 # leave the attribute unspecified.
27 testedwith = 'ships-with-hg-core'
27 testedwith = b'ships-with-hg-core'
28
28
29 commitopts = cmdutil.commitopts
29 commitopts = cmdutil.commitopts
30 commitopts2 = cmdutil.commitopts2
30 commitopts2 = cmdutil.commitopts2
31 commitopts3 = [('r', 'rev', [], _('revision to check'), _('REV'))]
31 commitopts3 = [(b'r', b'rev', [], _(b'revision to check'), _(b'REV'))]
32
32
33
33
34 @command(
34 @command(
35 'close-head|close-heads',
35 b'close-head|close-heads',
36 commitopts + commitopts2 + commitopts3,
36 commitopts + commitopts2 + commitopts3,
37 _('[OPTION]... [REV]...'),
37 _(b'[OPTION]... [REV]...'),
38 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
38 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
39 inferrepo=True,
39 inferrepo=True,
40 )
40 )
@@ -55,11 +55,11 b' def close_branch(ui, repo, *revs, **opts'
55 text=message,
55 text=message,
56 files=[],
56 files=[],
57 filectxfn=None,
57 filectxfn=None,
58 user=opts.get('user'),
58 user=opts.get(b'user'),
59 date=opts.get('date'),
59 date=opts.get(b'date'),
60 extra=extra,
60 extra=extra,
61 )
61 )
62 tr = repo.transaction('commit')
62 tr = repo.transaction(b'commit')
63 ret = repo.commitctx(cctx, True)
63 ret = repo.commitctx(cctx, True)
64 bookmarks.update(repo, [rev, None], ret)
64 bookmarks.update(repo, [rev, None], ret)
65 cctx.markcommitted(ret)
65 cctx.markcommitted(ret)
@@ -67,11 +67,11 b' def close_branch(ui, repo, *revs, **opts'
67
67
68 opts = pycompat.byteskwargs(opts)
68 opts = pycompat.byteskwargs(opts)
69
69
70 revs += tuple(opts.get('rev', []))
70 revs += tuple(opts.get(b'rev', []))
71 revs = scmutil.revrange(repo, revs)
71 revs = scmutil.revrange(repo, revs)
72
72
73 if not revs:
73 if not revs:
74 raise error.Abort(_('no revisions specified'))
74 raise error.Abort(_(b'no revisions specified'))
75
75
76 heads = []
76 heads = []
77 for branch in repo.branchmap():
77 for branch in repo.branchmap():
@@ -79,17 +79,17 b' def close_branch(ui, repo, *revs, **opts'
79 heads = set(repo[h].rev() for h in heads)
79 heads = set(repo[h].rev() for h in heads)
80 for rev in revs:
80 for rev in revs:
81 if rev not in heads:
81 if rev not in heads:
82 raise error.Abort(_('revision is not an open head: %d') % rev)
82 raise error.Abort(_(b'revision is not an open head: %d') % rev)
83
83
84 message = cmdutil.logmessage(ui, opts)
84 message = cmdutil.logmessage(ui, opts)
85 if not message:
85 if not message:
86 raise error.Abort(_("no commit message specified with -l or -m"))
86 raise error.Abort(_(b"no commit message specified with -l or -m"))
87 extra = {'close': '1'}
87 extra = {b'close': b'1'}
88
88
89 with repo.wlock(), repo.lock():
89 with repo.wlock(), repo.lock():
90 for rev in revs:
90 for rev in revs:
91 r = repo[rev]
91 r = repo[rev]
92 branch = r.branch()
92 branch = r.branch()
93 extra['branch'] = branch
93 extra[b'branch'] = branch
94 docommit(r)
94 docommit(r)
95 return 0
95 return 0
@@ -22,57 +22,64 b' from mercurial import ('
22
22
23 cmdtable = {}
23 cmdtable = {}
24 command = registrar.command(cmdtable)
24 command = registrar.command(cmdtable)
25 testedwith = 'ships-with-hg-core'
25 testedwith = b'ships-with-hg-core'
26
26
27 usedinternally = {
27 usedinternally = {
28 'amend_source',
28 b'amend_source',
29 'branch',
29 b'branch',
30 'close',
30 b'close',
31 'histedit_source',
31 b'histedit_source',
32 'topic',
32 b'topic',
33 'rebase_source',
33 b'rebase_source',
34 'intermediate-source',
34 b'intermediate-source',
35 '__touch-noise__',
35 b'__touch-noise__',
36 'source',
36 b'source',
37 'transplant_source',
37 b'transplant_source',
38 }
38 }
39
39
40
40
41 def extsetup(ui):
41 def extsetup(ui):
42 entry = extensions.wrapcommand(commands.table, 'commit', _commit)
42 entry = extensions.wrapcommand(commands.table, b'commit', _commit)
43 options = entry[1]
43 options = entry[1]
44 options.append(
44 options.append(
45 ('', 'extra', [], _('set a changeset\'s extra values'), _("KEY=VALUE"))
45 (
46 b'',
47 b'extra',
48 [],
49 _(b'set a changeset\'s extra values'),
50 _(b"KEY=VALUE"),
51 )
46 )
52 )
47
53
48
54
49 def _commit(orig, ui, repo, *pats, **opts):
55 def _commit(orig, ui, repo, *pats, **opts):
50 if util.safehasattr(repo, 'unfiltered'):
56 if util.safehasattr(repo, b'unfiltered'):
51 repo = repo.unfiltered()
57 repo = repo.unfiltered()
52
58
53 class repoextra(repo.__class__):
59 class repoextra(repo.__class__):
54 def commit(self, *innerpats, **inneropts):
60 def commit(self, *innerpats, **inneropts):
55 extras = opts.get(r'extra')
61 extras = opts.get(r'extra')
56 for raw in extras:
62 for raw in extras:
57 if '=' not in raw:
63 if b'=' not in raw:
58 msg = _(
64 msg = _(
59 "unable to parse '%s', should follow "
65 b"unable to parse '%s', should follow "
60 "KEY=VALUE format"
66 b"KEY=VALUE format"
61 )
67 )
62 raise error.Abort(msg % raw)
68 raise error.Abort(msg % raw)
63 k, v = raw.split('=', 1)
69 k, v = raw.split(b'=', 1)
64 if not k:
70 if not k:
65 msg = _("unable to parse '%s', keys can't be empty")
71 msg = _(b"unable to parse '%s', keys can't be empty")
66 raise error.Abort(msg % raw)
72 raise error.Abort(msg % raw)
67 if re.search(br'[^\w-]', k):
73 if re.search(br'[^\w-]', k):
68 msg = _(
74 msg = _(
69 "keys can only contain ascii letters, digits,"
75 b"keys can only contain ascii letters, digits,"
70 " '_' and '-'"
76 b" '_' and '-'"
71 )
77 )
72 raise error.Abort(msg)
78 raise error.Abort(msg)
73 if k in usedinternally:
79 if k in usedinternally:
74 msg = _(
80 msg = _(
75 "key '%s' is used internally, can't be set " "manually"
81 b"key '%s' is used internally, can't be set "
82 b"manually"
76 )
83 )
77 raise error.Abort(msg % k)
84 raise error.Abort(msg % k)
78 inneropts[r'extra'][k] = v
85 inneropts[r'extra'][k] = v
@@ -24,60 +24,72 b' command = registrar.command(cmdtable)'
24 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
24 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
25 # be specifying the version(s) of Mercurial they are tested with, or
25 # be specifying the version(s) of Mercurial they are tested with, or
26 # leave the attribute unspecified.
26 # leave the attribute unspecified.
27 testedwith = 'ships-with-hg-core'
27 testedwith = b'ships-with-hg-core'
28
28
29 # Commands definition was moved elsewhere to ease demandload job.
29 # Commands definition was moved elsewhere to ease demandload job.
30
30
31
31
32 @command(
32 @command(
33 'convert',
33 b'convert',
34 [
34 [
35 (
35 (
36 '',
36 b'',
37 'authors',
37 b'authors',
38 '',
38 b'',
39 _(
39 _(
40 'username mapping filename (DEPRECATED) (use --authormap instead)'
40 b'username mapping filename (DEPRECATED) (use --authormap instead)'
41 ),
41 ),
42 _('FILE'),
42 _(b'FILE'),
43 ),
43 ),
44 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
44 (b's', b'source-type', b'', _(b'source repository type'), _(b'TYPE')),
45 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
46 ('r', 'rev', [], _('import up to source revision REV'), _('REV')),
47 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
48 (
45 (
49 '',
46 b'd',
50 'filemap',
47 b'dest-type',
51 '',
48 b'',
52 _('remap file names using contents of file'),
49 _(b'destination repository type'),
53 _('FILE'),
50 _(b'TYPE'),
51 ),
52 (b'r', b'rev', [], _(b'import up to source revision REV'), _(b'REV')),
53 (
54 b'A',
55 b'authormap',
56 b'',
57 _(b'remap usernames using this file'),
58 _(b'FILE'),
54 ),
59 ),
55 (
60 (
56 '',
61 b'',
57 'full',
62 b'filemap',
58 None,
63 b'',
59 _('apply filemap changes by converting all files again'),
64 _(b'remap file names using contents of file'),
65 _(b'FILE'),
60 ),
66 ),
61 (
67 (
62 '',
68 b'',
63 'splicemap',
69 b'full',
64 '',
70 None,
65 _('splice synthesized history into place'),
71 _(b'apply filemap changes by converting all files again'),
66 _('FILE'),
67 ),
72 ),
68 (
73 (
69 '',
74 b'',
70 'branchmap',
75 b'splicemap',
71 '',
76 b'',
72 _('change branch names while converting'),
77 _(b'splice synthesized history into place'),
73 _('FILE'),
78 _(b'FILE'),
74 ),
79 ),
75 ('', 'branchsort', None, _('try to sort changesets by branches')),
80 (
76 ('', 'datesort', None, _('try to sort changesets by date')),
81 b'',
77 ('', 'sourcesort', None, _('preserve source changesets order')),
82 b'branchmap',
78 ('', 'closesort', None, _('try to reorder closed revisions')),
83 b'',
84 _(b'change branch names while converting'),
85 _(b'FILE'),
86 ),
87 (b'', b'branchsort', None, _(b'try to sort changesets by branches')),
88 (b'', b'datesort', None, _(b'try to sort changesets by date')),
89 (b'', b'sourcesort', None, _(b'preserve source changesets order')),
90 (b'', b'closesort', None, _(b'try to reorder closed revisions')),
79 ],
91 ],
80 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
92 _(b'hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
81 norepo=True,
93 norepo=True,
82 )
94 )
83 def convert(ui, src, dest=None, revmapfile=None, **opts):
95 def convert(ui, src, dest=None, revmapfile=None, **opts):
@@ -483,34 +495,44 b' def convert(ui, src, dest=None, revmapfi'
483 return convcmd.convert(ui, src, dest, revmapfile, **opts)
495 return convcmd.convert(ui, src, dest, revmapfile, **opts)
484
496
485
497
486 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
498 @command(b'debugsvnlog', [], b'hg debugsvnlog', norepo=True)
487 def debugsvnlog(ui, **opts):
499 def debugsvnlog(ui, **opts):
488 return subversion.debugsvnlog(ui, **opts)
500 return subversion.debugsvnlog(ui, **opts)
489
501
490
502
491 @command(
503 @command(
492 'debugcvsps',
504 b'debugcvsps',
493 [
505 [
494 # Main options shared with cvsps-2.1
506 # Main options shared with cvsps-2.1
495 ('b', 'branches', [], _('only return changes on specified branches')),
496 ('p', 'prefix', '', _('prefix to remove from file names')),
497 (
507 (
498 'r',
508 b'b',
499 'revisions',
509 b'branches',
500 [],
510 [],
501 _('only return changes after or between specified tags'),
511 _(b'only return changes on specified branches'),
512 ),
513 (b'p', b'prefix', b'', _(b'prefix to remove from file names')),
514 (
515 b'r',
516 b'revisions',
517 [],
518 _(b'only return changes after or between specified tags'),
502 ),
519 ),
503 ('u', 'update-cache', None, _("update cvs log cache")),
520 (b'u', b'update-cache', None, _(b"update cvs log cache")),
504 ('x', 'new-cache', None, _("create new cvs log cache")),
521 (b'x', b'new-cache', None, _(b"create new cvs log cache")),
505 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
522 (b'z', b'fuzz', 60, _(b'set commit time fuzz in seconds')),
506 ('', 'root', '', _('specify cvsroot')),
523 (b'', b'root', b'', _(b'specify cvsroot')),
507 # Options specific to builtin cvsps
524 # Options specific to builtin cvsps
508 ('', 'parents', '', _('show parent changesets')),
525 (b'', b'parents', b'', _(b'show parent changesets')),
509 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
526 (
527 b'',
528 b'ancestors',
529 b'',
530 _(b'show current changeset in ancestor branches'),
531 ),
510 # Options that are ignored for compatibility with cvsps-2.1
532 # Options that are ignored for compatibility with cvsps-2.1
511 ('A', 'cvs-direct', None, _('ignored for compatibility')),
533 (b'A', b'cvs-direct', None, _(b'ignored for compatibility')),
512 ],
534 ],
513 _('hg debugcvsps [OPTION]... [PATH]...'),
535 _(b'hg debugcvsps [OPTION]... [PATH]...'),
514 norepo=True,
536 norepo=True,
515 )
537 )
516 def debugcvsps(ui, *args, **opts):
538 def debugcvsps(ui, *args, **opts):
@@ -528,14 +550,14 b' def debugcvsps(ui, *args, **opts):'
528
550
529
551
530 def kwconverted(context, mapping, name):
552 def kwconverted(context, mapping, name):
531 ctx = context.resource(mapping, 'ctx')
553 ctx = context.resource(mapping, b'ctx')
532 rev = ctx.extra().get('convert_revision', '')
554 rev = ctx.extra().get(b'convert_revision', b'')
533 if rev.startswith('svn:'):
555 if rev.startswith(b'svn:'):
534 if name == 'svnrev':
556 if name == b'svnrev':
535 return b"%d" % subversion.revsplit(rev)[2]
557 return b"%d" % subversion.revsplit(rev)[2]
536 elif name == 'svnpath':
558 elif name == b'svnpath':
537 return subversion.revsplit(rev)[1]
559 return subversion.revsplit(rev)[1]
538 elif name == 'svnuuid':
560 elif name == b'svnuuid':
539 return subversion.revsplit(rev)[0]
561 return subversion.revsplit(rev)[0]
540 return rev
562 return rev
541
563
@@ -543,22 +565,22 b' def kwconverted(context, mapping, name):'
543 templatekeyword = registrar.templatekeyword()
565 templatekeyword = registrar.templatekeyword()
544
566
545
567
546 @templatekeyword('svnrev', requires={'ctx'})
568 @templatekeyword(b'svnrev', requires={b'ctx'})
547 def kwsvnrev(context, mapping):
569 def kwsvnrev(context, mapping):
548 """String. Converted subversion revision number."""
570 """String. Converted subversion revision number."""
549 return kwconverted(context, mapping, 'svnrev')
571 return kwconverted(context, mapping, b'svnrev')
550
572
551
573
552 @templatekeyword('svnpath', requires={'ctx'})
574 @templatekeyword(b'svnpath', requires={b'ctx'})
553 def kwsvnpath(context, mapping):
575 def kwsvnpath(context, mapping):
554 """String. Converted subversion revision project path."""
576 """String. Converted subversion revision project path."""
555 return kwconverted(context, mapping, 'svnpath')
577 return kwconverted(context, mapping, b'svnpath')
556
578
557
579
558 @templatekeyword('svnuuid', requires={'ctx'})
580 @templatekeyword(b'svnuuid', requires={b'ctx'})
559 def kwsvnuuid(context, mapping):
581 def kwsvnuuid(context, mapping):
560 """String. Converted subversion revision repository identifier."""
582 """String. Converted subversion revision repository identifier."""
561 return kwconverted(context, mapping, 'svnuuid')
583 return kwconverted(context, mapping, b'svnuuid')
562
584
563
585
564 # tell hggettext to extract docstrings from these functions:
586 # tell hggettext to extract docstrings from these functions:
@@ -17,7 +17,7 b' from . import common'
17
17
18 # these do not work with demandimport, blacklist
18 # these do not work with demandimport, blacklist
19 demandimport.IGNORES.update(
19 demandimport.IGNORES.update(
20 ['bzrlib.transactions', 'bzrlib.urlutils', 'ElementPath',]
20 [b'bzrlib.transactions', b'bzrlib.urlutils', b'ElementPath',]
21 )
21 )
22
22
23 try:
23 try:
@@ -35,7 +35,7 b' try:'
35 except ImportError:
35 except ImportError:
36 pass
36 pass
37
37
38 supportedkinds = ('file', 'symlink')
38 supportedkinds = (b'file', b'symlink')
39
39
40
40
41 class bzr_source(common.converter_source):
41 class bzr_source(common.converter_source):
@@ -44,16 +44,16 b' class bzr_source(common.converter_source'
44 def __init__(self, ui, repotype, path, revs=None):
44 def __init__(self, ui, repotype, path, revs=None):
45 super(bzr_source, self).__init__(ui, repotype, path, revs=revs)
45 super(bzr_source, self).__init__(ui, repotype, path, revs=revs)
46
46
47 if not os.path.exists(os.path.join(path, '.bzr')):
47 if not os.path.exists(os.path.join(path, b'.bzr')):
48 raise common.NoRepo(
48 raise common.NoRepo(
49 _('%s does not look like a Bazaar repository') % path
49 _(b'%s does not look like a Bazaar repository') % path
50 )
50 )
51
51
52 try:
52 try:
53 # access bzrlib stuff
53 # access bzrlib stuff
54 bzrdir
54 bzrdir
55 except NameError:
55 except NameError:
56 raise common.NoRepo(_('Bazaar modules could not be loaded'))
56 raise common.NoRepo(_(b'Bazaar modules could not be loaded'))
57
57
58 path = os.path.abspath(path)
58 path = os.path.abspath(path)
59 self._checkrepotype(path)
59 self._checkrepotype(path)
@@ -61,10 +61,10 b' class bzr_source(common.converter_source'
61 self.sourcerepo = bzrdir.BzrDir.open(path).open_repository()
61 self.sourcerepo = bzrdir.BzrDir.open(path).open_repository()
62 except errors.NoRepositoryPresent:
62 except errors.NoRepositoryPresent:
63 raise common.NoRepo(
63 raise common.NoRepo(
64 _('%s does not look like a Bazaar repository') % path
64 _(b'%s does not look like a Bazaar repository') % path
65 )
65 )
66 self._parentids = {}
66 self._parentids = {}
67 self._saverev = ui.configbool('convert', 'bzr.saverev')
67 self._saverev = ui.configbool(b'convert', b'bzr.saverev')
68
68
69 def _checkrepotype(self, path):
69 def _checkrepotype(self, path):
70 # Lightweight checkouts detection is informational but probably
70 # Lightweight checkouts detection is informational but probably
@@ -84,13 +84,13 b' class bzr_source(common.converter_source'
84 ):
84 ):
85 self.ui.warn(
85 self.ui.warn(
86 _(
86 _(
87 'warning: lightweight checkouts may cause '
87 b'warning: lightweight checkouts may cause '
88 'conversion failures, try with a regular '
88 b'conversion failures, try with a regular '
89 'branch instead.\n'
89 b'branch instead.\n'
90 )
90 )
91 )
91 )
92 except Exception:
92 except Exception:
93 self.ui.note(_('bzr source type could not be determined\n'))
93 self.ui.note(_(b'bzr source type could not be determined\n'))
94
94
95 def before(self):
95 def before(self):
96 """Before the conversion begins, acquire a read lock
96 """Before the conversion begins, acquire a read lock
@@ -126,16 +126,16 b' class bzr_source(common.converter_source'
126 revid = info.rev_id
126 revid = info.rev_id
127 if revid is None:
127 if revid is None:
128 raise error.Abort(
128 raise error.Abort(
129 _('%s is not a valid revision') % self.revs[0]
129 _(b'%s is not a valid revision') % self.revs[0]
130 )
130 )
131 heads = [revid]
131 heads = [revid]
132 # Empty repositories return 'null:', which cannot be retrieved
132 # Empty repositories return 'null:', which cannot be retrieved
133 heads = [h for h in heads if h != 'null:']
133 heads = [h for h in heads if h != b'null:']
134 return heads
134 return heads
135
135
136 def getfile(self, name, rev):
136 def getfile(self, name, rev):
137 revtree = self.sourcerepo.revision_tree(rev)
137 revtree = self.sourcerepo.revision_tree(rev)
138 fileid = revtree.path2id(name.decode(self.encoding or 'utf-8'))
138 fileid = revtree.path2id(name.decode(self.encoding or b'utf-8'))
139 kind = None
139 kind = None
140 if fileid is not None:
140 if fileid is not None:
141 kind = revtree.kind(fileid)
141 kind = revtree.kind(fileid)
@@ -143,11 +143,11 b' class bzr_source(common.converter_source'
143 # the file is not available anymore - was deleted
143 # the file is not available anymore - was deleted
144 return None, None
144 return None, None
145 mode = self._modecache[(name, rev)]
145 mode = self._modecache[(name, rev)]
146 if kind == 'symlink':
146 if kind == b'symlink':
147 target = revtree.get_symlink_target(fileid)
147 target = revtree.get_symlink_target(fileid)
148 if target is None:
148 if target is None:
149 raise error.Abort(
149 raise error.Abort(
150 _('%s.%s symlink has no target') % (name, rev)
150 _(b'%s.%s symlink has no target') % (name, rev)
151 )
151 )
152 return target, mode
152 return target, mode
153 else:
153 else:
@@ -156,7 +156,7 b' class bzr_source(common.converter_source'
156
156
157 def getchanges(self, version, full):
157 def getchanges(self, version, full):
158 if full:
158 if full:
159 raise error.Abort(_("convert from cvs does not support --full"))
159 raise error.Abort(_(b"convert from cvs does not support --full"))
160 self._modecache = {}
160 self._modecache = {}
161 self._revtree = self.sourcerepo.revision_tree(version)
161 self._revtree = self.sourcerepo.revision_tree(version)
162 # get the parentids from the cache
162 # get the parentids from the cache
@@ -176,12 +176,12 b' class bzr_source(common.converter_source'
176 parents = self._filterghosts(rev.parent_ids)
176 parents = self._filterghosts(rev.parent_ids)
177 self._parentids[version] = parents
177 self._parentids[version] = parents
178
178
179 branch = self.recode(rev.properties.get('branch-nick', u'default'))
179 branch = self.recode(rev.properties.get(b'branch-nick', u'default'))
180 if branch == 'trunk':
180 if branch == b'trunk':
181 branch = 'default'
181 branch = b'default'
182 return common.commit(
182 return common.commit(
183 parents=parents,
183 parents=parents,
184 date='%d %d' % (rev.timestamp, -rev.timezone),
184 date=b'%d %d' % (rev.timestamp, -rev.timezone),
185 author=self.recode(rev.committer),
185 author=self.recode(rev.committer),
186 desc=self.recode(rev.message),
186 desc=self.recode(rev.message),
187 branch=branch,
187 branch=branch,
@@ -248,13 +248,13 b' class bzr_source(common.converter_source'
248
248
249 # bazaar tracks directories, mercurial does not, so
249 # bazaar tracks directories, mercurial does not, so
250 # we have to rename the directory contents
250 # we have to rename the directory contents
251 if kind[1] == 'directory':
251 if kind[1] == b'directory':
252 if kind[0] not in (None, 'directory'):
252 if kind[0] not in (None, b'directory'):
253 # Replacing 'something' with a directory, record it
253 # Replacing 'something' with a directory, record it
254 # so it can be removed.
254 # so it can be removed.
255 changes.append((self.recode(paths[0]), revid))
255 changes.append((self.recode(paths[0]), revid))
256
256
257 if kind[0] == 'directory' and None not in paths:
257 if kind[0] == b'directory' and None not in paths:
258 renaming = paths[0] != paths[1]
258 renaming = paths[0] != paths[1]
259 # neither an add nor an delete - a move
259 # neither an add nor an delete - a move
260 # rename all directory contents manually
260 # rename all directory contents manually
@@ -262,9 +262,9 b' class bzr_source(common.converter_source'
262 # get all child-entries of the directory
262 # get all child-entries of the directory
263 for name, entry in inventory.iter_entries(subdir):
263 for name, entry in inventory.iter_entries(subdir):
264 # hg does not track directory renames
264 # hg does not track directory renames
265 if entry.kind == 'directory':
265 if entry.kind == b'directory':
266 continue
266 continue
267 frompath = self.recode(paths[0] + '/' + name)
267 frompath = self.recode(paths[0] + b'/' + name)
268 if frompath in seen:
268 if frompath in seen:
269 # Already handled by a more specific change entry
269 # Already handled by a more specific change entry
270 # This is important when you have:
270 # This is important when you have:
@@ -275,15 +275,15 b' class bzr_source(common.converter_source'
275 seen.add(frompath)
275 seen.add(frompath)
276 if not renaming:
276 if not renaming:
277 continue
277 continue
278 topath = self.recode(paths[1] + '/' + name)
278 topath = self.recode(paths[1] + b'/' + name)
279 # register the files as changed
279 # register the files as changed
280 changes.append((frompath, revid))
280 changes.append((frompath, revid))
281 changes.append((topath, revid))
281 changes.append((topath, revid))
282 # add to mode cache
282 # add to mode cache
283 mode = (
283 mode = (
284 (entry.executable and 'x')
284 (entry.executable and b'x')
285 or (entry.kind == 'symlink' and 's')
285 or (entry.kind == b'symlink' and b's')
286 or ''
286 or b''
287 )
287 )
288 self._modecache[(topath, revid)] = mode
288 self._modecache[(topath, revid)] = mode
289 # register the change as move
289 # register the change as move
@@ -312,7 +312,7 b' class bzr_source(common.converter_source'
312
312
313 # populate the mode cache
313 # populate the mode cache
314 kind, executable = [e[1] for e in (kind, executable)]
314 kind, executable = [e[1] for e in (kind, executable)]
315 mode = (executable and 'x') or (kind == 'symlink' and 'l') or ''
315 mode = (executable and b'x') or (kind == b'symlink' and b'l') or b''
316 self._modecache[(topath, revid)] = mode
316 self._modecache[(topath, revid)] = mode
317 changes.append((topath, revid))
317 changes.append((topath, revid))
318
318
@@ -46,7 +46,7 b' class _shlexpy3proxy(object):'
46
46
47 @property
47 @property
48 def infile(self):
48 def infile(self):
49 return self._l.infile or '<unknown>'
49 return self._l.infile or b'<unknown>'
50
50
51 @property
51 @property
52 def lineno(self):
52 def lineno(self):
@@ -56,13 +56,13 b' class _shlexpy3proxy(object):'
56 def shlexer(data=None, filepath=None, wordchars=None, whitespace=None):
56 def shlexer(data=None, filepath=None, wordchars=None, whitespace=None):
57 if data is None:
57 if data is None:
58 if pycompat.ispy3:
58 if pycompat.ispy3:
59 data = open(filepath, 'r', encoding=r'latin1')
59 data = open(filepath, b'r', encoding=r'latin1')
60 else:
60 else:
61 data = open(filepath, 'r')
61 data = open(filepath, b'r')
62 else:
62 else:
63 if filepath is not None:
63 if filepath is not None:
64 raise error.ProgrammingError(
64 raise error.ProgrammingError(
65 'shlexer only accepts data or filepath, not both'
65 b'shlexer only accepts data or filepath, not both'
66 )
66 )
67 if pycompat.ispy3:
67 if pycompat.ispy3:
68 data = data.decode('latin1')
68 data = data.decode('latin1')
@@ -87,7 +87,7 b' def encodeargs(args):'
87 def encodearg(s):
87 def encodearg(s):
88 lines = base64.encodestring(s)
88 lines = base64.encodestring(s)
89 lines = [l.splitlines()[0] for l in lines]
89 lines = [l.splitlines()[0] for l in lines]
90 return ''.join(lines)
90 return b''.join(lines)
91
91
92 s = pickle.dumps(args)
92 s = pickle.dumps(args)
93 return encodearg(s)
93 return encodearg(s)
@@ -109,14 +109,14 b' def checktool(exe, name=None, abort=True'
109 exc = error.Abort
109 exc = error.Abort
110 else:
110 else:
111 exc = MissingTool
111 exc = MissingTool
112 raise exc(_('cannot find required "%s" tool') % name)
112 raise exc(_(b'cannot find required "%s" tool') % name)
113
113
114
114
115 class NoRepo(Exception):
115 class NoRepo(Exception):
116 pass
116 pass
117
117
118
118
119 SKIPREV = 'SKIP'
119 SKIPREV = b'SKIP'
120
120
121
121
122 class commit(object):
122 class commit(object):
@@ -135,8 +135,8 b' class commit(object):'
135 optparents=None,
135 optparents=None,
136 ctx=None,
136 ctx=None,
137 ):
137 ):
138 self.author = author or 'unknown'
138 self.author = author or b'unknown'
139 self.date = date or '0 0'
139 self.date = date or b'0 0'
140 self.desc = desc
140 self.desc = desc
141 self.parents = parents # will be converted and used as parents
141 self.parents = parents # will be converted and used as parents
142 self.optparents = optparents or [] # will be used if already converted
142 self.optparents = optparents or [] # will be used if already converted
@@ -160,15 +160,15 b' class converter_source(object):'
160 self.revs = revs
160 self.revs = revs
161 self.repotype = repotype
161 self.repotype = repotype
162
162
163 self.encoding = 'utf-8'
163 self.encoding = b'utf-8'
164
164
165 def checkhexformat(self, revstr, mapname='splicemap'):
165 def checkhexformat(self, revstr, mapname=b'splicemap'):
166 """ fails if revstr is not a 40 byte hex. mercurial and git both uses
166 """ fails if revstr is not a 40 byte hex. mercurial and git both uses
167 such format for their revision numbering
167 such format for their revision numbering
168 """
168 """
169 if not re.match(br'[0-9a-fA-F]{40,40}$', revstr):
169 if not re.match(br'[0-9a-fA-F]{40,40}$', revstr):
170 raise error.Abort(
170 raise error.Abort(
171 _('%s entry %s is not a valid revision' ' identifier')
171 _(b'%s entry %s is not a valid revision' b' identifier')
172 % (mapname, revstr)
172 % (mapname, revstr)
173 )
173 )
174
174
@@ -236,7 +236,7 b' class converter_source(object):'
236
236
237 def recode(self, s, encoding=None):
237 def recode(self, s, encoding=None):
238 if not encoding:
238 if not encoding:
239 encoding = self.encoding or 'utf-8'
239 encoding = self.encoding or b'utf-8'
240
240
241 if isinstance(s, pycompat.unicode):
241 if isinstance(s, pycompat.unicode):
242 return s.encode("utf-8")
242 return s.encode("utf-8")
@@ -292,7 +292,7 b' class converter_source(object):'
292 """
292 """
293 return {}
293 return {}
294
294
295 def checkrevformat(self, revstr, mapname='splicemap'):
295 def checkrevformat(self, revstr, mapname=b'splicemap'):
296 """revstr is a string that describes a revision in the given
296 """revstr is a string that describes a revision in the given
297 source control system. Return true if revstr has correct
297 source control system. Return true if revstr has correct
298 format.
298 format.
@@ -412,20 +412,20 b' class commandline(object):'
412 cmdline = [self.command, cmd] + list(args)
412 cmdline = [self.command, cmd] + list(args)
413 for k, v in kwargs.iteritems():
413 for k, v in kwargs.iteritems():
414 if len(k) == 1:
414 if len(k) == 1:
415 cmdline.append('-' + k)
415 cmdline.append(b'-' + k)
416 else:
416 else:
417 cmdline.append('--' + k.replace('_', '-'))
417 cmdline.append(b'--' + k.replace(b'_', b'-'))
418 try:
418 try:
419 if len(k) == 1:
419 if len(k) == 1:
420 cmdline.append('' + v)
420 cmdline.append(b'' + v)
421 else:
421 else:
422 cmdline[-1] += '=' + v
422 cmdline[-1] += b'=' + v
423 except TypeError:
423 except TypeError:
424 pass
424 pass
425 cmdline = [procutil.shellquote(arg) for arg in cmdline]
425 cmdline = [procutil.shellquote(arg) for arg in cmdline]
426 if not self.ui.debugflag:
426 if not self.ui.debugflag:
427 cmdline += ['2>', pycompat.bytestr(os.devnull)]
427 cmdline += [b'2>', pycompat.bytestr(os.devnull)]
428 cmdline = ' '.join(cmdline)
428 cmdline = b' '.join(cmdline)
429 return cmdline
429 return cmdline
430
430
431 def _run(self, cmd, *args, **kwargs):
431 def _run(self, cmd, *args, **kwargs):
@@ -449,7 +449,7 b' class commandline(object):'
449
449
450 def _dorun(self, openfunc, cmd, *args, **kwargs):
450 def _dorun(self, openfunc, cmd, *args, **kwargs):
451 cmdline = self._cmdline(cmd, *args, **kwargs)
451 cmdline = self._cmdline(cmd, *args, **kwargs)
452 self.ui.debug('running: %s\n' % (cmdline,))
452 self.ui.debug(b'running: %s\n' % (cmdline,))
453 self.prerun()
453 self.prerun()
454 try:
454 try:
455 return openfunc(cmdline)
455 return openfunc(cmdline)
@@ -466,16 +466,16 b' class commandline(object):'
466 p = self._run(cmd, *args, **kwargs)
466 p = self._run(cmd, *args, **kwargs)
467 output = p.stdout.readlines()
467 output = p.stdout.readlines()
468 p.wait()
468 p.wait()
469 self.ui.debug(''.join(output))
469 self.ui.debug(b''.join(output))
470 return output, p.returncode
470 return output, p.returncode
471
471
472 def checkexit(self, status, output=''):
472 def checkexit(self, status, output=b''):
473 if status:
473 if status:
474 if output:
474 if output:
475 self.ui.warn(_('%s error:\n') % self.command)
475 self.ui.warn(_(b'%s error:\n') % self.command)
476 self.ui.warn(output)
476 self.ui.warn(output)
477 msg = procutil.explainexit(status)
477 msg = procutil.explainexit(status)
478 raise error.Abort('%s %s' % (self.command, msg))
478 raise error.Abort(b'%s %s' % (self.command, msg))
479
479
480 def run0(self, cmd, *args, **kwargs):
480 def run0(self, cmd, *args, **kwargs):
481 output, status = self.run(cmd, *args, **kwargs)
481 output, status = self.run(cmd, *args, **kwargs)
@@ -484,7 +484,7 b' class commandline(object):'
484
484
485 def runlines0(self, cmd, *args, **kwargs):
485 def runlines0(self, cmd, *args, **kwargs):
486 output, status = self.runlines(cmd, *args, **kwargs)
486 output, status = self.runlines(cmd, *args, **kwargs)
487 self.checkexit(status, ''.join(output))
487 self.checkexit(status, b''.join(output))
488 return output
488 return output
489
489
490 @propertycache
490 @propertycache
@@ -540,7 +540,7 b' class mapfile(dict):'
540 if not self.path:
540 if not self.path:
541 return
541 return
542 try:
542 try:
543 fp = open(self.path, 'rb')
543 fp = open(self.path, b'rb')
544 except IOError as err:
544 except IOError as err:
545 if err.errno != errno.ENOENT:
545 if err.errno != errno.ENOENT:
546 raise
546 raise
@@ -551,10 +551,10 b' class mapfile(dict):'
551 # Ignore blank lines
551 # Ignore blank lines
552 continue
552 continue
553 try:
553 try:
554 key, value = line.rsplit(' ', 1)
554 key, value = line.rsplit(b' ', 1)
555 except ValueError:
555 except ValueError:
556 raise error.Abort(
556 raise error.Abort(
557 _('syntax error in %s(%d): key/value pair expected')
557 _(b'syntax error in %s(%d): key/value pair expected')
558 % (self.path, i + 1)
558 % (self.path, i + 1)
559 )
559 )
560 if key not in self:
560 if key not in self:
@@ -565,13 +565,13 b' class mapfile(dict):'
565 def __setitem__(self, key, value):
565 def __setitem__(self, key, value):
566 if self.fp is None:
566 if self.fp is None:
567 try:
567 try:
568 self.fp = open(self.path, 'ab')
568 self.fp = open(self.path, b'ab')
569 except IOError as err:
569 except IOError as err:
570 raise error.Abort(
570 raise error.Abort(
571 _('could not open map file %r: %s')
571 _(b'could not open map file %r: %s')
572 % (self.path, encoding.strtolocal(err.strerror))
572 % (self.path, encoding.strtolocal(err.strerror))
573 )
573 )
574 self.fp.write(util.tonativeeol('%s %s\n' % (key, value)))
574 self.fp.write(util.tonativeeol(b'%s %s\n' % (key, value)))
575 self.fp.flush()
575 self.fp.flush()
576 super(mapfile, self).__setitem__(key, value)
576 super(mapfile, self).__setitem__(key, value)
577
577
@@ -52,7 +52,7 b' p4_source = p4.p4_source'
52 svn_sink = subversion.svn_sink
52 svn_sink = subversion.svn_sink
53 svn_source = subversion.svn_source
53 svn_source = subversion.svn_source
54
54
55 orig_encoding = 'ascii'
55 orig_encoding = b'ascii'
56
56
57
57
58 def recode(s):
58 def recode(s):
@@ -90,36 +90,36 b' def mapbranch(branch, branchmap):'
90 # destination repository. For such commits, using a literal "default"
90 # destination repository. For such commits, using a literal "default"
91 # in branchmap below allows the user to map "default" to an alternate
91 # in branchmap below allows the user to map "default" to an alternate
92 # default branch in the destination repository.
92 # default branch in the destination repository.
93 branch = branchmap.get(branch or 'default', branch)
93 branch = branchmap.get(branch or b'default', branch)
94 # At some point we used "None" literal to denote the default branch,
94 # At some point we used "None" literal to denote the default branch,
95 # attempt to use that for backward compatibility.
95 # attempt to use that for backward compatibility.
96 if not branch:
96 if not branch:
97 branch = branchmap.get('None', branch)
97 branch = branchmap.get(b'None', branch)
98 return branch
98 return branch
99
99
100
100
101 source_converters = [
101 source_converters = [
102 ('cvs', convert_cvs, 'branchsort'),
102 (b'cvs', convert_cvs, b'branchsort'),
103 ('git', convert_git, 'branchsort'),
103 (b'git', convert_git, b'branchsort'),
104 ('svn', svn_source, 'branchsort'),
104 (b'svn', svn_source, b'branchsort'),
105 ('hg', mercurial_source, 'sourcesort'),
105 (b'hg', mercurial_source, b'sourcesort'),
106 ('darcs', darcs_source, 'branchsort'),
106 (b'darcs', darcs_source, b'branchsort'),
107 ('mtn', monotone_source, 'branchsort'),
107 (b'mtn', monotone_source, b'branchsort'),
108 ('gnuarch', gnuarch_source, 'branchsort'),
108 (b'gnuarch', gnuarch_source, b'branchsort'),
109 ('bzr', bzr_source, 'branchsort'),
109 (b'bzr', bzr_source, b'branchsort'),
110 ('p4', p4_source, 'branchsort'),
110 (b'p4', p4_source, b'branchsort'),
111 ]
111 ]
112
112
113 sink_converters = [
113 sink_converters = [
114 ('hg', mercurial_sink),
114 (b'hg', mercurial_sink),
115 ('svn', svn_sink),
115 (b'svn', svn_sink),
116 ]
116 ]
117
117
118
118
119 def convertsource(ui, path, type, revs):
119 def convertsource(ui, path, type, revs):
120 exceptions = []
120 exceptions = []
121 if type and type not in [s[0] for s in source_converters]:
121 if type and type not in [s[0] for s in source_converters]:
122 raise error.Abort(_('%s: invalid source repository type') % type)
122 raise error.Abort(_(b'%s: invalid source repository type') % type)
123 for name, source, sortmode in source_converters:
123 for name, source, sortmode in source_converters:
124 try:
124 try:
125 if not type or name == type:
125 if not type or name == type:
@@ -128,22 +128,22 b' def convertsource(ui, path, type, revs):'
128 exceptions.append(inst)
128 exceptions.append(inst)
129 if not ui.quiet:
129 if not ui.quiet:
130 for inst in exceptions:
130 for inst in exceptions:
131 ui.write("%s\n" % pycompat.bytestr(inst.args[0]))
131 ui.write(b"%s\n" % pycompat.bytestr(inst.args[0]))
132 raise error.Abort(_('%s: missing or unsupported repository') % path)
132 raise error.Abort(_(b'%s: missing or unsupported repository') % path)
133
133
134
134
135 def convertsink(ui, path, type):
135 def convertsink(ui, path, type):
136 if type and type not in [s[0] for s in sink_converters]:
136 if type and type not in [s[0] for s in sink_converters]:
137 raise error.Abort(_('%s: invalid destination repository type') % type)
137 raise error.Abort(_(b'%s: invalid destination repository type') % type)
138 for name, sink in sink_converters:
138 for name, sink in sink_converters:
139 try:
139 try:
140 if not type or name == type:
140 if not type or name == type:
141 return sink(ui, name, path)
141 return sink(ui, name, path)
142 except NoRepo as inst:
142 except NoRepo as inst:
143 ui.note(_("convert: %s\n") % inst)
143 ui.note(_(b"convert: %s\n") % inst)
144 except MissingTool as inst:
144 except MissingTool as inst:
145 raise error.Abort('%s\n' % inst)
145 raise error.Abort(b'%s\n' % inst)
146 raise error.Abort(_('%s: unknown repository type') % path)
146 raise error.Abort(_(b'%s: unknown repository type') % path)
147
147
148
148
149 class progresssource(object):
149 class progresssource(object):
@@ -151,7 +151,7 b' class progresssource(object):'
151 self.ui = ui
151 self.ui = ui
152 self.source = source
152 self.source = source
153 self.progress = ui.makeprogress(
153 self.progress = ui.makeprogress(
154 _('getting files'), unit=_('files'), total=filecount
154 _(b'getting files'), unit=_(b'files'), total=filecount
155 )
155 )
156
156
157 def getfile(self, file, rev):
157 def getfile(self, file, rev):
@@ -189,12 +189,12 b' class converter(object):'
189 if authorfile and os.path.exists(authorfile):
189 if authorfile and os.path.exists(authorfile):
190 self.readauthormap(authorfile)
190 self.readauthormap(authorfile)
191 # Extend/Override with new author map if necessary
191 # Extend/Override with new author map if necessary
192 if opts.get('authormap'):
192 if opts.get(b'authormap'):
193 self.readauthormap(opts.get('authormap'))
193 self.readauthormap(opts.get(b'authormap'))
194 self.authorfile = self.dest.authorfile()
194 self.authorfile = self.dest.authorfile()
195
195
196 self.splicemap = self.parsesplicemap(opts.get('splicemap'))
196 self.splicemap = self.parsesplicemap(opts.get(b'splicemap'))
197 self.branchmap = mapfile(ui, opts.get('branchmap'))
197 self.branchmap = mapfile(ui, opts.get(b'branchmap'))
198
198
199 def parsesplicemap(self, path):
199 def parsesplicemap(self, path):
200 """ check and validate the splicemap format and
200 """ check and validate the splicemap format and
@@ -211,21 +211,21 b' class converter(object):'
211 return {}
211 return {}
212 m = {}
212 m = {}
213 try:
213 try:
214 fp = open(path, 'rb')
214 fp = open(path, b'rb')
215 for i, line in enumerate(util.iterfile(fp)):
215 for i, line in enumerate(util.iterfile(fp)):
216 line = line.splitlines()[0].rstrip()
216 line = line.splitlines()[0].rstrip()
217 if not line:
217 if not line:
218 # Ignore blank lines
218 # Ignore blank lines
219 continue
219 continue
220 # split line
220 # split line
221 lex = common.shlexer(data=line, whitespace=',')
221 lex = common.shlexer(data=line, whitespace=b',')
222 line = list(lex)
222 line = list(lex)
223 # check number of parents
223 # check number of parents
224 if not (2 <= len(line) <= 3):
224 if not (2 <= len(line) <= 3):
225 raise error.Abort(
225 raise error.Abort(
226 _(
226 _(
227 'syntax error in %s(%d): child parent1'
227 b'syntax error in %s(%d): child parent1'
228 '[,parent2] expected'
228 b'[,parent2] expected'
229 )
229 )
230 % (path, i + 1)
230 % (path, i + 1)
231 )
231 )
@@ -239,7 +239,7 b' class converter(object):'
239 # if file does not exist or error reading, exit
239 # if file does not exist or error reading, exit
240 except IOError:
240 except IOError:
241 raise error.Abort(
241 raise error.Abort(
242 _('splicemap file not found or error reading %s:') % path
242 _(b'splicemap file not found or error reading %s:') % path
243 )
243 )
244 return m
244 return m
245
245
@@ -251,7 +251,7 b' class converter(object):'
251 parents = {}
251 parents = {}
252 numcommits = self.source.numcommits()
252 numcommits = self.source.numcommits()
253 progress = self.ui.makeprogress(
253 progress = self.ui.makeprogress(
254 _('scanning'), unit=_('revisions'), total=numcommits
254 _(b'scanning'), unit=_(b'revisions'), total=numcommits
255 )
255 )
256 while visit:
256 while visit:
257 n = visit.pop(0)
257 n = visit.pop(0)
@@ -283,8 +283,8 b' class converter(object):'
283 # Could be in source but not converted during this run
283 # Could be in source but not converted during this run
284 self.ui.warn(
284 self.ui.warn(
285 _(
285 _(
286 'splice map revision %s is not being '
286 b'splice map revision %s is not being '
287 'converted, ignoring\n'
287 b'converted, ignoring\n'
288 )
288 )
289 % c
289 % c
290 )
290 )
@@ -296,7 +296,7 b' class converter(object):'
296 continue
296 continue
297 # Parent is not in dest and not being converted, not good
297 # Parent is not in dest and not being converted, not good
298 if p not in parents:
298 if p not in parents:
299 raise error.Abort(_('unknown splice map parent: %s') % p)
299 raise error.Abort(_(b'unknown splice map parent: %s') % p)
300 pc.append(p)
300 pc.append(p)
301 parents[c] = pc
301 parents[c] = pc
302
302
@@ -369,7 +369,7 b' class converter(object):'
369 def makeclosesorter():
369 def makeclosesorter():
370 """Close order sort."""
370 """Close order sort."""
371 keyfn = lambda n: (
371 keyfn = lambda n: (
372 'close' not in self.commitcache[n].extra,
372 b'close' not in self.commitcache[n].extra,
373 self.commitcache[n].sortkey,
373 self.commitcache[n].sortkey,
374 )
374 )
375
375
@@ -392,16 +392,16 b' class converter(object):'
392
392
393 return picknext
393 return picknext
394
394
395 if sortmode == 'branchsort':
395 if sortmode == b'branchsort':
396 picknext = makebranchsorter()
396 picknext = makebranchsorter()
397 elif sortmode == 'datesort':
397 elif sortmode == b'datesort':
398 picknext = makedatesorter()
398 picknext = makedatesorter()
399 elif sortmode == 'sourcesort':
399 elif sortmode == b'sourcesort':
400 picknext = makesourcesorter()
400 picknext = makesourcesorter()
401 elif sortmode == 'closesort':
401 elif sortmode == b'closesort':
402 picknext = makeclosesorter()
402 picknext = makeclosesorter()
403 else:
403 else:
404 raise error.Abort(_('unknown sort mode: %s') % sortmode)
404 raise error.Abort(_(b'unknown sort mode: %s') % sortmode)
405
405
406 children, actives = mapchildren(parents)
406 children, actives = mapchildren(parents)
407
407
@@ -420,7 +420,7 b' class converter(object):'
420 pendings[c].remove(n)
420 pendings[c].remove(n)
421 except ValueError:
421 except ValueError:
422 raise error.Abort(
422 raise error.Abort(
423 _('cycle detected between %s and %s')
423 _(b'cycle detected between %s and %s')
424 % (recode(c), recode(n))
424 % (recode(c), recode(n))
425 )
425 )
426 if not pendings[c]:
426 if not pendings[c]:
@@ -429,45 +429,47 b' class converter(object):'
429 pendings[c] = None
429 pendings[c] = None
430
430
431 if len(s) != len(parents):
431 if len(s) != len(parents):
432 raise error.Abort(_("not all revisions were sorted"))
432 raise error.Abort(_(b"not all revisions were sorted"))
433
433
434 return s
434 return s
435
435
436 def writeauthormap(self):
436 def writeauthormap(self):
437 authorfile = self.authorfile
437 authorfile = self.authorfile
438 if authorfile:
438 if authorfile:
439 self.ui.status(_('writing author map file %s\n') % authorfile)
439 self.ui.status(_(b'writing author map file %s\n') % authorfile)
440 ofile = open(authorfile, 'wb+')
440 ofile = open(authorfile, b'wb+')
441 for author in self.authors:
441 for author in self.authors:
442 ofile.write(
442 ofile.write(
443 util.tonativeeol("%s=%s\n" % (author, self.authors[author]))
443 util.tonativeeol(
444 b"%s=%s\n" % (author, self.authors[author])
445 )
444 )
446 )
445 ofile.close()
447 ofile.close()
446
448
447 def readauthormap(self, authorfile):
449 def readauthormap(self, authorfile):
448 afile = open(authorfile, 'rb')
450 afile = open(authorfile, b'rb')
449 for line in afile:
451 for line in afile:
450
452
451 line = line.strip()
453 line = line.strip()
452 if not line or line.startswith('#'):
454 if not line or line.startswith(b'#'):
453 continue
455 continue
454
456
455 try:
457 try:
456 srcauthor, dstauthor = line.split('=', 1)
458 srcauthor, dstauthor = line.split(b'=', 1)
457 except ValueError:
459 except ValueError:
458 msg = _('ignoring bad line in author map file %s: %s\n')
460 msg = _(b'ignoring bad line in author map file %s: %s\n')
459 self.ui.warn(msg % (authorfile, line.rstrip()))
461 self.ui.warn(msg % (authorfile, line.rstrip()))
460 continue
462 continue
461
463
462 srcauthor = srcauthor.strip()
464 srcauthor = srcauthor.strip()
463 dstauthor = dstauthor.strip()
465 dstauthor = dstauthor.strip()
464 if self.authors.get(srcauthor) in (None, dstauthor):
466 if self.authors.get(srcauthor) in (None, dstauthor):
465 msg = _('mapping author %s to %s\n')
467 msg = _(b'mapping author %s to %s\n')
466 self.ui.debug(msg % (srcauthor, dstauthor))
468 self.ui.debug(msg % (srcauthor, dstauthor))
467 self.authors[srcauthor] = dstauthor
469 self.authors[srcauthor] = dstauthor
468 continue
470 continue
469
471
470 m = _('overriding mapping for author %s, was %s, will be %s\n')
472 m = _(b'overriding mapping for author %s, was %s, will be %s\n')
471 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
473 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
472
474
473 afile.close()
475 afile.close()
@@ -481,7 +483,7 b' class converter(object):'
481
483
482 def copy(self, rev):
484 def copy(self, rev):
483 commit = self.commitcache[rev]
485 commit = self.commitcache[rev]
484 full = self.opts.get('full')
486 full = self.opts.get(b'full')
485 changes = self.source.getchanges(rev, full)
487 changes = self.source.getchanges(rev, full)
486 if isinstance(changes, bytes):
488 if isinstance(changes, bytes):
487 if changes == SKIPREV:
489 if changes == SKIPREV:
@@ -503,8 +505,8 b' class converter(object):'
503 try:
505 try:
504 parents = self.splicemap[rev]
506 parents = self.splicemap[rev]
505 self.ui.status(
507 self.ui.status(
506 _('spliced in %s as parents of %s\n')
508 _(b'spliced in %s as parents of %s\n')
507 % (_(' and ').join(parents), rev)
509 % (_(b' and ').join(parents), rev)
508 )
510 )
509 parents = [self.map.get(p, p) for p in parents]
511 parents = [self.map.get(p, p) for p in parents]
510 except KeyError:
512 except KeyError:
@@ -536,34 +538,34 b' class converter(object):'
536 self.source.before()
538 self.source.before()
537 self.dest.before()
539 self.dest.before()
538 self.source.setrevmap(self.map)
540 self.source.setrevmap(self.map)
539 self.ui.status(_("scanning source...\n"))
541 self.ui.status(_(b"scanning source...\n"))
540 heads = self.source.getheads()
542 heads = self.source.getheads()
541 parents = self.walktree(heads)
543 parents = self.walktree(heads)
542 self.mergesplicemap(parents, self.splicemap)
544 self.mergesplicemap(parents, self.splicemap)
543 self.ui.status(_("sorting...\n"))
545 self.ui.status(_(b"sorting...\n"))
544 t = self.toposort(parents, sortmode)
546 t = self.toposort(parents, sortmode)
545 num = len(t)
547 num = len(t)
546 c = None
548 c = None
547
549
548 self.ui.status(_("converting...\n"))
550 self.ui.status(_(b"converting...\n"))
549 progress = self.ui.makeprogress(
551 progress = self.ui.makeprogress(
550 _('converting'), unit=_('revisions'), total=len(t)
552 _(b'converting'), unit=_(b'revisions'), total=len(t)
551 )
553 )
552 for i, c in enumerate(t):
554 for i, c in enumerate(t):
553 num -= 1
555 num -= 1
554 desc = self.commitcache[c].desc
556 desc = self.commitcache[c].desc
555 if "\n" in desc:
557 if b"\n" in desc:
556 desc = desc.splitlines()[0]
558 desc = desc.splitlines()[0]
557 # convert log message to local encoding without using
559 # convert log message to local encoding without using
558 # tolocal() because the encoding.encoding convert()
560 # tolocal() because the encoding.encoding convert()
559 # uses is 'utf-8'
561 # uses is 'utf-8'
560 self.ui.status("%d %s\n" % (num, recode(desc)))
562 self.ui.status(b"%d %s\n" % (num, recode(desc)))
561 self.ui.note(_("source: %s\n") % recode(c))
563 self.ui.note(_(b"source: %s\n") % recode(c))
562 progress.update(i)
564 progress.update(i)
563 self.copy(c)
565 self.copy(c)
564 progress.complete()
566 progress.complete()
565
567
566 if not self.ui.configbool('convert', 'skiptags'):
568 if not self.ui.configbool(b'convert', b'skiptags'):
567 tags = self.source.gettags()
569 tags = self.source.gettags()
568 ctags = {}
570 ctags = {}
569 for k in tags:
571 for k in tags:
@@ -610,45 +612,47 b' def convert(ui, src, dest=None, revmapfi'
610 opts = pycompat.byteskwargs(opts)
612 opts = pycompat.byteskwargs(opts)
611 global orig_encoding
613 global orig_encoding
612 orig_encoding = encoding.encoding
614 orig_encoding = encoding.encoding
613 encoding.encoding = 'UTF-8'
615 encoding.encoding = b'UTF-8'
614
616
615 # support --authors as an alias for --authormap
617 # support --authors as an alias for --authormap
616 if not opts.get('authormap'):
618 if not opts.get(b'authormap'):
617 opts['authormap'] = opts.get('authors')
619 opts[b'authormap'] = opts.get(b'authors')
618
620
619 if not dest:
621 if not dest:
620 dest = hg.defaultdest(src) + "-hg"
622 dest = hg.defaultdest(src) + b"-hg"
621 ui.status(_("assuming destination %s\n") % dest)
623 ui.status(_(b"assuming destination %s\n") % dest)
622
624
623 destc = convertsink(ui, dest, opts.get('dest_type'))
625 destc = convertsink(ui, dest, opts.get(b'dest_type'))
624 destc = scmutil.wrapconvertsink(destc)
626 destc = scmutil.wrapconvertsink(destc)
625
627
626 try:
628 try:
627 srcc, defaultsort = convertsource(
629 srcc, defaultsort = convertsource(
628 ui, src, opts.get('source_type'), opts.get('rev')
630 ui, src, opts.get(b'source_type'), opts.get(b'rev')
629 )
631 )
630 except Exception:
632 except Exception:
631 for path in destc.created:
633 for path in destc.created:
632 shutil.rmtree(path, True)
634 shutil.rmtree(path, True)
633 raise
635 raise
634
636
635 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
637 sortmodes = (b'branchsort', b'datesort', b'sourcesort', b'closesort')
636 sortmode = [m for m in sortmodes if opts.get(m)]
638 sortmode = [m for m in sortmodes if opts.get(m)]
637 if len(sortmode) > 1:
639 if len(sortmode) > 1:
638 raise error.Abort(_('more than one sort mode specified'))
640 raise error.Abort(_(b'more than one sort mode specified'))
639 if sortmode:
641 if sortmode:
640 sortmode = sortmode[0]
642 sortmode = sortmode[0]
641 else:
643 else:
642 sortmode = defaultsort
644 sortmode = defaultsort
643
645
644 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
646 if sortmode == b'sourcesort' and not srcc.hasnativeorder():
645 raise error.Abort(
647 raise error.Abort(
646 _('--sourcesort is not supported by this data source')
648 _(b'--sourcesort is not supported by this data source')
647 )
649 )
648 if sortmode == 'closesort' and not srcc.hasnativeclose():
650 if sortmode == b'closesort' and not srcc.hasnativeclose():
649 raise error.Abort(_('--closesort is not supported by this data source'))
651 raise error.Abort(
652 _(b'--closesort is not supported by this data source')
653 )
650
654
651 fmap = opts.get('filemap')
655 fmap = opts.get(b'filemap')
652 if fmap:
656 if fmap:
653 srcc = filemap.filemap_source(ui, srcc, fmap)
657 srcc = filemap.filemap_source(ui, srcc, fmap)
654 destc.setfilemapmode(True)
658 destc.setfilemapmode(True)
@@ -39,19 +39,19 b' class convert_cvs(converter_source):'
39 def __init__(self, ui, repotype, path, revs=None):
39 def __init__(self, ui, repotype, path, revs=None):
40 super(convert_cvs, self).__init__(ui, repotype, path, revs=revs)
40 super(convert_cvs, self).__init__(ui, repotype, path, revs=revs)
41
41
42 cvs = os.path.join(path, "CVS")
42 cvs = os.path.join(path, b"CVS")
43 if not os.path.exists(cvs):
43 if not os.path.exists(cvs):
44 raise NoRepo(_("%s does not look like a CVS checkout") % path)
44 raise NoRepo(_(b"%s does not look like a CVS checkout") % path)
45
45
46 checktool('cvs')
46 checktool(b'cvs')
47
47
48 self.changeset = None
48 self.changeset = None
49 self.files = {}
49 self.files = {}
50 self.tags = {}
50 self.tags = {}
51 self.lastbranch = {}
51 self.lastbranch = {}
52 self.socket = None
52 self.socket = None
53 self.cvsroot = open(os.path.join(cvs, "Root"), 'rb').read()[:-1]
53 self.cvsroot = open(os.path.join(cvs, b"Root"), b'rb').read()[:-1]
54 self.cvsrepo = open(os.path.join(cvs, "Repository"), 'rb').read()[:-1]
54 self.cvsrepo = open(os.path.join(cvs, b"Repository"), b'rb').read()[:-1]
55 self.encoding = encoding.encoding
55 self.encoding = encoding.encoding
56
56
57 self._connect()
57 self._connect()
@@ -65,7 +65,10 b' class convert_cvs(converter_source):'
65 if self.revs:
65 if self.revs:
66 if len(self.revs) > 1:
66 if len(self.revs) > 1:
67 raise error.Abort(
67 raise error.Abort(
68 _('cvs source does not support specifying ' 'multiple revs')
68 _(
69 b'cvs source does not support specifying '
70 b'multiple revs'
71 )
69 )
72 )
70 # TODO: handle tags
73 # TODO: handle tags
71 try:
74 try:
@@ -73,23 +76,23 b' class convert_cvs(converter_source):'
73 maxrev = int(self.revs[0])
76 maxrev = int(self.revs[0])
74 except ValueError:
77 except ValueError:
75 raise error.Abort(
78 raise error.Abort(
76 _('revision %s is not a patchset number') % self.revs[0]
79 _(b'revision %s is not a patchset number') % self.revs[0]
77 )
80 )
78
81
79 d = encoding.getcwd()
82 d = encoding.getcwd()
80 try:
83 try:
81 os.chdir(self.path)
84 os.chdir(self.path)
82
85
83 cache = 'update'
86 cache = b'update'
84 if not self.ui.configbool('convert', 'cvsps.cache'):
87 if not self.ui.configbool(b'convert', b'cvsps.cache'):
85 cache = None
88 cache = None
86 db = cvsps.createlog(self.ui, cache=cache)
89 db = cvsps.createlog(self.ui, cache=cache)
87 db = cvsps.createchangeset(
90 db = cvsps.createchangeset(
88 self.ui,
91 self.ui,
89 db,
92 db,
90 fuzz=int(self.ui.config('convert', 'cvsps.fuzz')),
93 fuzz=int(self.ui.config(b'convert', b'cvsps.fuzz')),
91 mergeto=self.ui.config('convert', 'cvsps.mergeto'),
94 mergeto=self.ui.config(b'convert', b'cvsps.mergeto'),
92 mergefrom=self.ui.config('convert', 'cvsps.mergefrom'),
95 mergefrom=self.ui.config(b'convert', b'cvsps.mergefrom'),
93 )
96 )
94
97
95 for cs in db:
98 for cs in db:
@@ -99,16 +102,16 b' class convert_cvs(converter_source):'
99 cs.author = self.recode(cs.author)
102 cs.author = self.recode(cs.author)
100 self.lastbranch[cs.branch] = id
103 self.lastbranch[cs.branch] = id
101 cs.comment = self.recode(cs.comment)
104 cs.comment = self.recode(cs.comment)
102 if self.ui.configbool('convert', 'localtimezone'):
105 if self.ui.configbool(b'convert', b'localtimezone'):
103 cs.date = makedatetimestamp(cs.date[0])
106 cs.date = makedatetimestamp(cs.date[0])
104 date = dateutil.datestr(cs.date, '%Y-%m-%d %H:%M:%S %1%2')
107 date = dateutil.datestr(cs.date, b'%Y-%m-%d %H:%M:%S %1%2')
105 self.tags.update(dict.fromkeys(cs.tags, id))
108 self.tags.update(dict.fromkeys(cs.tags, id))
106
109
107 files = {}
110 files = {}
108 for f in cs.entries:
111 for f in cs.entries:
109 files[f.file] = "%s%s" % (
112 files[f.file] = b"%s%s" % (
110 '.'.join([(b"%d" % x) for x in f.revision]),
113 b'.'.join([(b"%d" % x) for x in f.revision]),
111 ['', '(DEAD)'][f.dead],
114 [b'', b'(DEAD)'][f.dead],
112 )
115 )
113
116
114 # add current commit to set
117 # add current commit to set
@@ -117,7 +120,7 b' class convert_cvs(converter_source):'
117 date=date,
120 date=date,
118 parents=[(b"%d" % p.id) for p in cs.parents],
121 parents=[(b"%d" % p.id) for p in cs.parents],
119 desc=cs.comment,
122 desc=cs.comment,
120 branch=cs.branch or '',
123 branch=cs.branch or b'',
121 )
124 )
122 self.changeset[id] = c
125 self.changeset[id] = c
123 self.files[id] = files
126 self.files[id] = files
@@ -130,38 +133,38 b' class convert_cvs(converter_source):'
130 root = self.cvsroot
133 root = self.cvsroot
131 conntype = None
134 conntype = None
132 user, host = None, None
135 user, host = None, None
133 cmd = ['cvs', 'server']
136 cmd = [b'cvs', b'server']
134
137
135 self.ui.status(_("connecting to %s\n") % root)
138 self.ui.status(_(b"connecting to %s\n") % root)
136
139
137 if root.startswith(":pserver:"):
140 if root.startswith(b":pserver:"):
138 root = root[9:]
141 root = root[9:]
139 m = re.match(
142 m = re.match(
140 r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)', root
143 r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)', root
141 )
144 )
142 if m:
145 if m:
143 conntype = "pserver"
146 conntype = b"pserver"
144 user, passw, serv, port, root = m.groups()
147 user, passw, serv, port, root = m.groups()
145 if not user:
148 if not user:
146 user = "anonymous"
149 user = b"anonymous"
147 if not port:
150 if not port:
148 port = 2401
151 port = 2401
149 else:
152 else:
150 port = int(port)
153 port = int(port)
151 format0 = ":pserver:%s@%s:%s" % (user, serv, root)
154 format0 = b":pserver:%s@%s:%s" % (user, serv, root)
152 format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root)
155 format1 = b":pserver:%s@%s:%d%s" % (user, serv, port, root)
153
156
154 if not passw:
157 if not passw:
155 passw = "A"
158 passw = b"A"
156 cvspass = os.path.expanduser("~/.cvspass")
159 cvspass = os.path.expanduser(b"~/.cvspass")
157 try:
160 try:
158 pf = open(cvspass, 'rb')
161 pf = open(cvspass, b'rb')
159 for line in pf.read().splitlines():
162 for line in pf.read().splitlines():
160 part1, part2 = line.split(' ', 1)
163 part1, part2 = line.split(b' ', 1)
161 # /1 :pserver:user@example.com:2401/cvsroot/foo
164 # /1 :pserver:user@example.com:2401/cvsroot/foo
162 # Ah<Z
165 # Ah<Z
163 if part1 == '/1':
166 if part1 == b'/1':
164 part1, part2 = part2.split(' ', 1)
167 part1, part2 = part2.split(b' ', 1)
165 format = format1
168 format = format1
166 # :pserver:user@example.com:/cvsroot/foo Ah<Z
169 # :pserver:user@example.com:/cvsroot/foo Ah<Z
167 else:
170 else:
@@ -179,72 +182,72 b' class convert_cvs(converter_source):'
179 sck = socket.socket()
182 sck = socket.socket()
180 sck.connect((serv, port))
183 sck.connect((serv, port))
181 sck.send(
184 sck.send(
182 "\n".join(
185 b"\n".join(
183 [
186 [
184 "BEGIN AUTH REQUEST",
187 b"BEGIN AUTH REQUEST",
185 root,
188 root,
186 user,
189 user,
187 passw,
190 passw,
188 "END AUTH REQUEST",
191 b"END AUTH REQUEST",
189 "",
192 b"",
190 ]
193 ]
191 )
194 )
192 )
195 )
193 if sck.recv(128) != "I LOVE YOU\n":
196 if sck.recv(128) != b"I LOVE YOU\n":
194 raise error.Abort(_("CVS pserver authentication failed"))
197 raise error.Abort(_(b"CVS pserver authentication failed"))
195
198
196 self.writep = self.readp = sck.makefile('r+')
199 self.writep = self.readp = sck.makefile(b'r+')
197
200
198 if not conntype and root.startswith(":local:"):
201 if not conntype and root.startswith(b":local:"):
199 conntype = "local"
202 conntype = b"local"
200 root = root[7:]
203 root = root[7:]
201
204
202 if not conntype:
205 if not conntype:
203 # :ext:user@host/home/user/path/to/cvsroot
206 # :ext:user@host/home/user/path/to/cvsroot
204 if root.startswith(":ext:"):
207 if root.startswith(b":ext:"):
205 root = root[5:]
208 root = root[5:]
206 m = re.match(br'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
209 m = re.match(br'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
207 # Do not take Windows path "c:\foo\bar" for a connection strings
210 # Do not take Windows path "c:\foo\bar" for a connection strings
208 if os.path.isdir(root) or not m:
211 if os.path.isdir(root) or not m:
209 conntype = "local"
212 conntype = b"local"
210 else:
213 else:
211 conntype = "rsh"
214 conntype = b"rsh"
212 user, host, root = m.group(1), m.group(2), m.group(3)
215 user, host, root = m.group(1), m.group(2), m.group(3)
213
216
214 if conntype != "pserver":
217 if conntype != b"pserver":
215 if conntype == "rsh":
218 if conntype == b"rsh":
216 rsh = encoding.environ.get("CVS_RSH") or "ssh"
219 rsh = encoding.environ.get(b"CVS_RSH") or b"ssh"
217 if user:
220 if user:
218 cmd = [rsh, '-l', user, host] + cmd
221 cmd = [rsh, b'-l', user, host] + cmd
219 else:
222 else:
220 cmd = [rsh, host] + cmd
223 cmd = [rsh, host] + cmd
221
224
222 # popen2 does not support argument lists under Windows
225 # popen2 does not support argument lists under Windows
223 cmd = [procutil.shellquote(arg) for arg in cmd]
226 cmd = [procutil.shellquote(arg) for arg in cmd]
224 cmd = procutil.quotecommand(' '.join(cmd))
227 cmd = procutil.quotecommand(b' '.join(cmd))
225 self.writep, self.readp = procutil.popen2(cmd)
228 self.writep, self.readp = procutil.popen2(cmd)
226
229
227 self.realroot = root
230 self.realroot = root
228
231
229 self.writep.write("Root %s\n" % root)
232 self.writep.write(b"Root %s\n" % root)
230 self.writep.write(
233 self.writep.write(
231 "Valid-responses ok error Valid-requests Mode"
234 b"Valid-responses ok error Valid-requests Mode"
232 " M Mbinary E Checked-in Created Updated"
235 b" M Mbinary E Checked-in Created Updated"
233 " Merged Removed\n"
236 b" Merged Removed\n"
234 )
237 )
235 self.writep.write("valid-requests\n")
238 self.writep.write(b"valid-requests\n")
236 self.writep.flush()
239 self.writep.flush()
237 r = self.readp.readline()
240 r = self.readp.readline()
238 if not r.startswith("Valid-requests"):
241 if not r.startswith(b"Valid-requests"):
239 raise error.Abort(
242 raise error.Abort(
240 _(
243 _(
241 'unexpected response from CVS server '
244 b'unexpected response from CVS server '
242 '(expected "Valid-requests", but got %r)'
245 b'(expected "Valid-requests", but got %r)'
243 )
246 )
244 % r
247 % r
245 )
248 )
246 if "UseUnchanged" in r:
249 if b"UseUnchanged" in r:
247 self.writep.write("UseUnchanged\n")
250 self.writep.write(b"UseUnchanged\n")
248 self.writep.flush()
251 self.writep.flush()
249 self.readp.readline()
252 self.readp.readline()
250
253
@@ -262,55 +265,55 b' class convert_cvs(converter_source):'
262 data = fp.read(min(count, chunksize))
265 data = fp.read(min(count, chunksize))
263 if not data:
266 if not data:
264 raise error.Abort(
267 raise error.Abort(
265 _("%d bytes missing from remote file") % count
268 _(b"%d bytes missing from remote file") % count
266 )
269 )
267 count -= len(data)
270 count -= len(data)
268 output.write(data)
271 output.write(data)
269 return output.getvalue()
272 return output.getvalue()
270
273
271 self._parse()
274 self._parse()
272 if rev.endswith("(DEAD)"):
275 if rev.endswith(b"(DEAD)"):
273 return None, None
276 return None, None
274
277
275 args = ("-N -P -kk -r %s --" % rev).split()
278 args = (b"-N -P -kk -r %s --" % rev).split()
276 args.append(self.cvsrepo + '/' + name)
279 args.append(self.cvsrepo + b'/' + name)
277 for x in args:
280 for x in args:
278 self.writep.write("Argument %s\n" % x)
281 self.writep.write(b"Argument %s\n" % x)
279 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
282 self.writep.write(b"Directory .\n%s\nco\n" % self.realroot)
280 self.writep.flush()
283 self.writep.flush()
281
284
282 data = ""
285 data = b""
283 mode = None
286 mode = None
284 while True:
287 while True:
285 line = self.readp.readline()
288 line = self.readp.readline()
286 if line.startswith("Created ") or line.startswith("Updated "):
289 if line.startswith(b"Created ") or line.startswith(b"Updated "):
287 self.readp.readline() # path
290 self.readp.readline() # path
288 self.readp.readline() # entries
291 self.readp.readline() # entries
289 mode = self.readp.readline()[:-1]
292 mode = self.readp.readline()[:-1]
290 count = int(self.readp.readline()[:-1])
293 count = int(self.readp.readline()[:-1])
291 data = chunkedread(self.readp, count)
294 data = chunkedread(self.readp, count)
292 elif line.startswith(" "):
295 elif line.startswith(b" "):
293 data += line[1:]
296 data += line[1:]
294 elif line.startswith("M "):
297 elif line.startswith(b"M "):
295 pass
298 pass
296 elif line.startswith("Mbinary "):
299 elif line.startswith(b"Mbinary "):
297 count = int(self.readp.readline()[:-1])
300 count = int(self.readp.readline()[:-1])
298 data = chunkedread(self.readp, count)
301 data = chunkedread(self.readp, count)
299 else:
302 else:
300 if line == "ok\n":
303 if line == b"ok\n":
301 if mode is None:
304 if mode is None:
302 raise error.Abort(_('malformed response from CVS'))
305 raise error.Abort(_(b'malformed response from CVS'))
303 return (data, "x" in mode and "x" or "")
306 return (data, b"x" in mode and b"x" or b"")
304 elif line.startswith("E "):
307 elif line.startswith(b"E "):
305 self.ui.warn(_("cvs server: %s\n") % line[2:])
308 self.ui.warn(_(b"cvs server: %s\n") % line[2:])
306 elif line.startswith("Remove"):
309 elif line.startswith(b"Remove"):
307 self.readp.readline()
310 self.readp.readline()
308 else:
311 else:
309 raise error.Abort(_("unknown CVS response: %s") % line)
312 raise error.Abort(_(b"unknown CVS response: %s") % line)
310
313
311 def getchanges(self, rev, full):
314 def getchanges(self, rev, full):
312 if full:
315 if full:
313 raise error.Abort(_("convert from cvs does not support --full"))
316 raise error.Abort(_(b"convert from cvs does not support --full"))
314 self._parse()
317 self._parse()
315 return sorted(self.files[rev].iteritems()), {}, set()
318 return sorted(self.files[rev].iteritems()), {}, set()
316
319
@@ -92,18 +92,18 b' def getrepopath(cvspath):'
92 # of the '/' char after the '@' is located. The solution is the rest of the
92 # of the '/' char after the '@' is located. The solution is the rest of the
93 # string after that '/' sign including it
93 # string after that '/' sign including it
94
94
95 parts = cvspath.split(':')
95 parts = cvspath.split(b':')
96 atposition = parts[-1].find('@')
96 atposition = parts[-1].find(b'@')
97 start = 0
97 start = 0
98
98
99 if atposition != -1:
99 if atposition != -1:
100 start = atposition
100 start = atposition
101
101
102 repopath = parts[-1][parts[-1].find('/', start) :]
102 repopath = parts[-1][parts[-1].find(b'/', start) :]
103 return repopath
103 return repopath
104
104
105
105
106 def createlog(ui, directory=None, root="", rlog=True, cache=None):
106 def createlog(ui, directory=None, root=b"", rlog=True, cache=None):
107 '''Collect the CVS rlog'''
107 '''Collect the CVS rlog'''
108
108
109 # Because we store many duplicate commit log messages, reusing strings
109 # Because we store many duplicate commit log messages, reusing strings
@@ -111,10 +111,10 b' def createlog(ui, directory=None, root="'
111 _scache = {}
111 _scache = {}
112
112
113 def scache(s):
113 def scache(s):
114 "return a shared version of a string"
114 b"return a shared version of a string"
115 return _scache.setdefault(s, s)
115 return _scache.setdefault(s, s)
116
116
117 ui.status(_('collecting CVS rlog\n'))
117 ui.status(_(b'collecting CVS rlog\n'))
118
118
119 log = [] # list of logentry objects containing the CVS state
119 log = [] # list of logentry objects containing the CVS state
120
120
@@ -144,39 +144,39 b' def createlog(ui, directory=None, root="'
144
144
145 file_added_re = re.compile(br'file [^/]+ was (initially )?added on branch')
145 file_added_re = re.compile(br'file [^/]+ was (initially )?added on branch')
146
146
147 prefix = '' # leading path to strip of what we get from CVS
147 prefix = b'' # leading path to strip of what we get from CVS
148
148
149 if directory is None:
149 if directory is None:
150 # Current working directory
150 # Current working directory
151
151
152 # Get the real directory in the repository
152 # Get the real directory in the repository
153 try:
153 try:
154 with open(os.path.join(b'CVS', b'Repository'), 'rb') as f:
154 with open(os.path.join(b'CVS', b'Repository'), b'rb') as f:
155 prefix = f.read().strip()
155 prefix = f.read().strip()
156 directory = prefix
156 directory = prefix
157 if prefix == ".":
157 if prefix == b".":
158 prefix = ""
158 prefix = b""
159 except IOError:
159 except IOError:
160 raise logerror(_('not a CVS sandbox'))
160 raise logerror(_(b'not a CVS sandbox'))
161
161
162 if prefix and not prefix.endswith(pycompat.ossep):
162 if prefix and not prefix.endswith(pycompat.ossep):
163 prefix += pycompat.ossep
163 prefix += pycompat.ossep
164
164
165 # Use the Root file in the sandbox, if it exists
165 # Use the Root file in the sandbox, if it exists
166 try:
166 try:
167 root = open(os.path.join('CVS', 'Root'), 'rb').read().strip()
167 root = open(os.path.join(b'CVS', b'Root'), b'rb').read().strip()
168 except IOError:
168 except IOError:
169 pass
169 pass
170
170
171 if not root:
171 if not root:
172 root = encoding.environ.get('CVSROOT', '')
172 root = encoding.environ.get(b'CVSROOT', b'')
173
173
174 # read log cache if one exists
174 # read log cache if one exists
175 oldlog = []
175 oldlog = []
176 date = None
176 date = None
177
177
178 if cache:
178 if cache:
179 cachedir = os.path.expanduser('~/.hg.cvsps')
179 cachedir = os.path.expanduser(b'~/.hg.cvsps')
180 if not os.path.exists(cachedir):
180 if not os.path.exists(cachedir):
181 os.mkdir(cachedir)
181 os.mkdir(cachedir)
182
182
@@ -189,50 +189,50 b' def createlog(ui, directory=None, root="'
189 # and
189 # and
190 # /pserver/user/server/path
190 # /pserver/user/server/path
191 # are mapped to different cache file names.
191 # are mapped to different cache file names.
192 cachefile = root.split(":") + [directory, "cache"]
192 cachefile = root.split(b":") + [directory, b"cache"]
193 cachefile = ['-'.join(re.findall(br'\w+', s)) for s in cachefile if s]
193 cachefile = [b'-'.join(re.findall(br'\w+', s)) for s in cachefile if s]
194 cachefile = os.path.join(
194 cachefile = os.path.join(
195 cachedir, '.'.join([s for s in cachefile if s])
195 cachedir, b'.'.join([s for s in cachefile if s])
196 )
196 )
197
197
198 if cache == 'update':
198 if cache == b'update':
199 try:
199 try:
200 ui.note(_('reading cvs log cache %s\n') % cachefile)
200 ui.note(_(b'reading cvs log cache %s\n') % cachefile)
201 oldlog = pickle.load(open(cachefile, 'rb'))
201 oldlog = pickle.load(open(cachefile, b'rb'))
202 for e in oldlog:
202 for e in oldlog:
203 if not (
203 if not (
204 util.safehasattr(e, 'branchpoints')
204 util.safehasattr(e, b'branchpoints')
205 and util.safehasattr(e, 'commitid')
205 and util.safehasattr(e, b'commitid')
206 and util.safehasattr(e, 'mergepoint')
206 and util.safehasattr(e, b'mergepoint')
207 ):
207 ):
208 ui.status(_('ignoring old cache\n'))
208 ui.status(_(b'ignoring old cache\n'))
209 oldlog = []
209 oldlog = []
210 break
210 break
211
211
212 ui.note(_('cache has %d log entries\n') % len(oldlog))
212 ui.note(_(b'cache has %d log entries\n') % len(oldlog))
213 except Exception as e:
213 except Exception as e:
214 ui.note(_('error reading cache: %r\n') % e)
214 ui.note(_(b'error reading cache: %r\n') % e)
215
215
216 if oldlog:
216 if oldlog:
217 date = oldlog[-1].date # last commit date as a (time,tz) tuple
217 date = oldlog[-1].date # last commit date as a (time,tz) tuple
218 date = dateutil.datestr(date, '%Y/%m/%d %H:%M:%S %1%2')
218 date = dateutil.datestr(date, b'%Y/%m/%d %H:%M:%S %1%2')
219
219
220 # build the CVS commandline
220 # build the CVS commandline
221 cmd = ['cvs', '-q']
221 cmd = [b'cvs', b'-q']
222 if root:
222 if root:
223 cmd.append('-d%s' % root)
223 cmd.append(b'-d%s' % root)
224 p = util.normpath(getrepopath(root))
224 p = util.normpath(getrepopath(root))
225 if not p.endswith('/'):
225 if not p.endswith(b'/'):
226 p += '/'
226 p += b'/'
227 if prefix:
227 if prefix:
228 # looks like normpath replaces "" by "."
228 # looks like normpath replaces "" by "."
229 prefix = p + util.normpath(prefix)
229 prefix = p + util.normpath(prefix)
230 else:
230 else:
231 prefix = p
231 prefix = p
232 cmd.append(['log', 'rlog'][rlog])
232 cmd.append([b'log', b'rlog'][rlog])
233 if date:
233 if date:
234 # no space between option and date string
234 # no space between option and date string
235 cmd.append('-d>%s' % date)
235 cmd.append(b'-d>%s' % date)
236 cmd.append(directory)
236 cmd.append(directory)
237
237
238 # state machine begins here
238 # state machine begins here
@@ -243,17 +243,17 b' def createlog(ui, directory=None, root="'
243 store = False # set when a new record can be appended
243 store = False # set when a new record can be appended
244
244
245 cmd = [procutil.shellquote(arg) for arg in cmd]
245 cmd = [procutil.shellquote(arg) for arg in cmd]
246 ui.note(_("running %s\n") % (' '.join(cmd)))
246 ui.note(_(b"running %s\n") % (b' '.join(cmd)))
247 ui.debug("prefix=%r directory=%r root=%r\n" % (prefix, directory, root))
247 ui.debug(b"prefix=%r directory=%r root=%r\n" % (prefix, directory, root))
248
248
249 pfp = procutil.popen(' '.join(cmd), 'rb')
249 pfp = procutil.popen(b' '.join(cmd), b'rb')
250 peek = util.fromnativeeol(pfp.readline())
250 peek = util.fromnativeeol(pfp.readline())
251 while True:
251 while True:
252 line = peek
252 line = peek
253 if line == '':
253 if line == b'':
254 break
254 break
255 peek = util.fromnativeeol(pfp.readline())
255 peek = util.fromnativeeol(pfp.readline())
256 if line.endswith('\n'):
256 if line.endswith(b'\n'):
257 line = line[:-1]
257 line = line[:-1]
258 # ui.debug('state=%d line=%r\n' % (state, line))
258 # ui.debug('state=%d line=%r\n' % (state, line))
259
259
@@ -267,12 +267,12 b' def createlog(ui, directory=None, root="'
267 filename = util.normpath(rcs[:-2])
267 filename = util.normpath(rcs[:-2])
268 if filename.startswith(prefix):
268 if filename.startswith(prefix):
269 filename = filename[len(prefix) :]
269 filename = filename[len(prefix) :]
270 if filename.startswith('/'):
270 if filename.startswith(b'/'):
271 filename = filename[1:]
271 filename = filename[1:]
272 if filename.startswith('Attic/'):
272 if filename.startswith(b'Attic/'):
273 filename = filename[6:]
273 filename = filename[6:]
274 else:
274 else:
275 filename = filename.replace('/Attic/', '/')
275 filename = filename.replace(b'/Attic/', b'/')
276 state = 2
276 state = 2
277 continue
277 continue
278 state = 1
278 state = 1
@@ -289,7 +289,7 b' def createlog(ui, directory=None, root="'
289 elif state == 1:
289 elif state == 1:
290 # expect 'Working file' (only when using log instead of rlog)
290 # expect 'Working file' (only when using log instead of rlog)
291 match = re_10.match(line)
291 match = re_10.match(line)
292 assert match, _('RCS file must be followed by working file')
292 assert match, _(b'RCS file must be followed by working file')
293 filename = util.normpath(match.group(1))
293 filename = util.normpath(match.group(1))
294 state = 2
294 state = 2
295
295
@@ -303,7 +303,7 b' def createlog(ui, directory=None, root="'
303 # read the symbolic names and store as tags
303 # read the symbolic names and store as tags
304 match = re_30.match(line)
304 match = re_30.match(line)
305 if match:
305 if match:
306 rev = [int(x) for x in match.group(2).split('.')]
306 rev = [int(x) for x in match.group(2).split(b'.')]
307
307
308 # Convert magic branch number to an odd-numbered one
308 # Convert magic branch number to an odd-numbered one
309 revn = len(rev)
309 revn = len(rev)
@@ -327,7 +327,7 b' def createlog(ui, directory=None, root="'
327 state = 5
327 state = 5
328 else:
328 else:
329 assert not re_32.match(line), _(
329 assert not re_32.match(line), _(
330 'must have at least ' 'some revisions'
330 b'must have at least ' b'some revisions'
331 )
331 )
332
332
333 elif state == 5:
333 elif state == 5:
@@ -335,11 +335,11 b' def createlog(ui, directory=None, root="'
335 # we create the logentry here from values stored in states 0 to 4,
335 # we create the logentry here from values stored in states 0 to 4,
336 # as this state is re-entered for subsequent revisions of a file.
336 # as this state is re-entered for subsequent revisions of a file.
337 match = re_50.match(line)
337 match = re_50.match(line)
338 assert match, _('expected revision number')
338 assert match, _(b'expected revision number')
339 e = logentry(
339 e = logentry(
340 rcs=scache(rcs),
340 rcs=scache(rcs),
341 file=scache(filename),
341 file=scache(filename),
342 revision=tuple([int(x) for x in match.group(1).split('.')]),
342 revision=tuple([int(x) for x in match.group(1).split(b'.')]),
343 branches=[],
343 branches=[],
344 parent=None,
344 parent=None,
345 commitid=None,
345 commitid=None,
@@ -352,21 +352,25 b' def createlog(ui, directory=None, root="'
352 elif state == 6:
352 elif state == 6:
353 # expecting date, author, state, lines changed
353 # expecting date, author, state, lines changed
354 match = re_60.match(line)
354 match = re_60.match(line)
355 assert match, _('revision must be followed by date line')
355 assert match, _(b'revision must be followed by date line')
356 d = match.group(1)
356 d = match.group(1)
357 if d[2] == '/':
357 if d[2] == b'/':
358 # Y2K
358 # Y2K
359 d = '19' + d
359 d = b'19' + d
360
360
361 if len(d.split()) != 3:
361 if len(d.split()) != 3:
362 # cvs log dates always in GMT
362 # cvs log dates always in GMT
363 d = d + ' UTC'
363 d = d + b' UTC'
364 e.date = dateutil.parsedate(
364 e.date = dateutil.parsedate(
365 d,
365 d,
366 ['%y/%m/%d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y-%m-%d %H:%M:%S'],
366 [
367 b'%y/%m/%d %H:%M:%S',
368 b'%Y/%m/%d %H:%M:%S',
369 b'%Y-%m-%d %H:%M:%S',
370 ],
367 )
371 )
368 e.author = scache(match.group(2))
372 e.author = scache(match.group(2))
369 e.dead = match.group(3).lower() == 'dead'
373 e.dead = match.group(3).lower() == b'dead'
370
374
371 if match.group(5):
375 if match.group(5):
372 if match.group(6):
376 if match.group(6):
@@ -382,14 +386,14 b' def createlog(ui, directory=None, root="'
382 e.commitid = match.group(8)
386 e.commitid = match.group(8)
383
387
384 if match.group(9): # cvsnt mergepoint
388 if match.group(9): # cvsnt mergepoint
385 myrev = match.group(10).split('.')
389 myrev = match.group(10).split(b'.')
386 if len(myrev) == 2: # head
390 if len(myrev) == 2: # head
387 e.mergepoint = 'HEAD'
391 e.mergepoint = b'HEAD'
388 else:
392 else:
389 myrev = '.'.join(myrev[:-2] + ['0', myrev[-2]])
393 myrev = b'.'.join(myrev[:-2] + [b'0', myrev[-2]])
390 branches = [b for b in branchmap if branchmap[b] == myrev]
394 branches = [b for b in branchmap if branchmap[b] == myrev]
391 assert len(branches) == 1, (
395 assert len(branches) == 1, (
392 'unknown branch: %s' % e.mergepoint
396 b'unknown branch: %s' % e.mergepoint
393 )
397 )
394 e.mergepoint = branches[0]
398 e.mergepoint = branches[0]
395
399
@@ -402,8 +406,8 b' def createlog(ui, directory=None, root="'
402 m = re_70.match(line)
406 m = re_70.match(line)
403 if m:
407 if m:
404 e.branches = [
408 e.branches = [
405 tuple([int(y) for y in x.strip().split('.')])
409 tuple([int(y) for y in x.strip().split(b'.')])
406 for x in m.group(1).split(';')
410 for x in m.group(1).split(b';')
407 ]
411 ]
408 state = 8
412 state = 8
409 elif re_31.match(line) and re_50.match(peek):
413 elif re_31.match(line) and re_50.match(peek):
@@ -419,7 +423,7 b' def createlog(ui, directory=None, root="'
419 # store commit log message
423 # store commit log message
420 if re_31.match(line):
424 if re_31.match(line):
421 cpeek = peek
425 cpeek = peek
422 if cpeek.endswith('\n'):
426 if cpeek.endswith(b'\n'):
423 cpeek = cpeek[:-1]
427 cpeek = cpeek[:-1]
424 if re_50.match(cpeek):
428 if re_50.match(cpeek):
425 state = 5
429 state = 5
@@ -447,7 +451,7 b' def createlog(ui, directory=None, root="'
447 and file_added_re.match(e.comment[0])
451 and file_added_re.match(e.comment[0])
448 ):
452 ):
449 ui.debug(
453 ui.debug(
450 'found synthetic revision in %s: %r\n' % (e.rcs, e.comment[0])
454 b'found synthetic revision in %s: %r\n' % (e.rcs, e.comment[0])
451 )
455 )
452 e.synthetic = True
456 e.synthetic = True
453
457
@@ -455,7 +459,7 b' def createlog(ui, directory=None, root="'
455 # clean up the results and save in the log.
459 # clean up the results and save in the log.
456 store = False
460 store = False
457 e.tags = sorted([scache(x) for x in tags.get(e.revision, [])])
461 e.tags = sorted([scache(x) for x in tags.get(e.revision, [])])
458 e.comment = scache('\n'.join(e.comment))
462 e.comment = scache(b'\n'.join(e.comment))
459
463
460 revn = len(e.revision)
464 revn = len(e.revision)
461 if revn > 3 and (revn % 2) == 0:
465 if revn > 3 and (revn % 2) == 0:
@@ -466,7 +470,7 b' def createlog(ui, directory=None, root="'
466 # find the branches starting from this revision
470 # find the branches starting from this revision
467 branchpoints = set()
471 branchpoints = set()
468 for branch, revision in branchmap.iteritems():
472 for branch, revision in branchmap.iteritems():
469 revparts = tuple([int(i) for i in revision.split('.')])
473 revparts = tuple([int(i) for i in revision.split(b'.')])
470 if len(revparts) < 2: # bad tags
474 if len(revparts) < 2: # bad tags
471 continue
475 continue
472 if revparts[-2] == 0 and revparts[-1] % 2 == 0:
476 if revparts[-2] == 0 and revparts[-1] % 2 == 0:
@@ -480,11 +484,12 b' def createlog(ui, directory=None, root="'
480
484
481 log.append(e)
485 log.append(e)
482
486
483 rcsmap[e.rcs.replace('/Attic/', '/')] = e.rcs
487 rcsmap[e.rcs.replace(b'/Attic/', b'/')] = e.rcs
484
488
485 if len(log) % 100 == 0:
489 if len(log) % 100 == 0:
486 ui.status(
490 ui.status(
487 stringutil.ellipsis('%d %s' % (len(log), e.file), 80) + '\n'
491 stringutil.ellipsis(b'%d %s' % (len(log), e.file), 80)
492 + b'\n'
488 )
493 )
489
494
490 log.sort(key=lambda x: (x.rcs, x.revision))
495 log.sort(key=lambda x: (x.rcs, x.revision))
@@ -492,7 +497,7 b' def createlog(ui, directory=None, root="'
492 # find parent revisions of individual files
497 # find parent revisions of individual files
493 versions = {}
498 versions = {}
494 for e in sorted(oldlog, key=lambda x: (x.rcs, x.revision)):
499 for e in sorted(oldlog, key=lambda x: (x.rcs, x.revision)):
495 rcs = e.rcs.replace('/Attic/', '/')
500 rcs = e.rcs.replace(b'/Attic/', b'/')
496 if rcs in rcsmap:
501 if rcs in rcsmap:
497 e.rcs = rcsmap[rcs]
502 e.rcs = rcsmap[rcs]
498 branch = e.revision[:-1]
503 branch = e.revision[:-1]
@@ -515,28 +520,28 b' def createlog(ui, directory=None, root="'
515 if oldlog and oldlog[-1].date >= log[0].date:
520 if oldlog and oldlog[-1].date >= log[0].date:
516 raise logerror(
521 raise logerror(
517 _(
522 _(
518 'log cache overlaps with new log entries,'
523 b'log cache overlaps with new log entries,'
519 ' re-run without cache.'
524 b' re-run without cache.'
520 )
525 )
521 )
526 )
522
527
523 log = oldlog + log
528 log = oldlog + log
524
529
525 # write the new cachefile
530 # write the new cachefile
526 ui.note(_('writing cvs log cache %s\n') % cachefile)
531 ui.note(_(b'writing cvs log cache %s\n') % cachefile)
527 pickle.dump(log, open(cachefile, 'wb'))
532 pickle.dump(log, open(cachefile, b'wb'))
528 else:
533 else:
529 log = oldlog
534 log = oldlog
530
535
531 ui.status(_('%d log entries\n') % len(log))
536 ui.status(_(b'%d log entries\n') % len(log))
532
537
533 encodings = ui.configlist('convert', 'cvsps.logencoding')
538 encodings = ui.configlist(b'convert', b'cvsps.logencoding')
534 if encodings:
539 if encodings:
535
540
536 def revstr(r):
541 def revstr(r):
537 # this is needed, because logentry.revision is a tuple of "int"
542 # this is needed, because logentry.revision is a tuple of "int"
538 # (e.g. (1, 2) for "1.2")
543 # (e.g. (1, 2) for "1.2")
539 return '.'.join(pycompat.maplist(pycompat.bytestr, r))
544 return b'.'.join(pycompat.maplist(pycompat.bytestr, r))
540
545
541 for entry in log:
546 for entry in log:
542 comment = entry.comment
547 comment = entry.comment
@@ -547,7 +552,7 b' def createlog(ui, directory=None, root="'
547 )
552 )
548 if ui.debugflag:
553 if ui.debugflag:
549 ui.debug(
554 ui.debug(
550 "transcoding by %s: %s of %s\n"
555 b"transcoding by %s: %s of %s\n"
551 % (e, revstr(entry.revision), entry.file)
556 % (e, revstr(entry.revision), entry.file)
552 )
557 )
553 break
558 break
@@ -557,20 +562,22 b' def createlog(ui, directory=None, root="'
557 raise error.Abort(
562 raise error.Abort(
558 inst,
563 inst,
559 hint=_(
564 hint=_(
560 'check convert.cvsps.logencoding' ' configuration'
565 b'check convert.cvsps.logencoding' b' configuration'
561 ),
566 ),
562 )
567 )
563 else:
568 else:
564 raise error.Abort(
569 raise error.Abort(
565 _(
570 _(
566 "no encoding can transcode"
571 b"no encoding can transcode"
567 " CVS log message for %s of %s"
572 b" CVS log message for %s of %s"
568 )
573 )
569 % (revstr(entry.revision), entry.file),
574 % (revstr(entry.revision), entry.file),
570 hint=_('check convert.cvsps.logencoding' ' configuration'),
575 hint=_(
576 b'check convert.cvsps.logencoding' b' configuration'
577 ),
571 )
578 )
572
579
573 hook.hook(ui, None, "cvslog", True, log=log)
580 hook.hook(ui, None, b"cvslog", True, log=log)
574
581
575 return log
582 return log
576
583
@@ -597,14 +604,16 b' class changeset(object):'
597 self.__dict__.update(entries)
604 self.__dict__.update(entries)
598
605
599 def __repr__(self):
606 def __repr__(self):
600 items = ("%s=%r" % (k, self.__dict__[k]) for k in sorted(self.__dict__))
607 items = (
601 return "%s(%s)" % (type(self).__name__, ", ".join(items))
608 b"%s=%r" % (k, self.__dict__[k]) for k in sorted(self.__dict__)
609 )
610 return b"%s(%s)" % (type(self).__name__, b", ".join(items))
602
611
603
612
604 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
613 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
605 '''Convert log into changesets.'''
614 '''Convert log into changesets.'''
606
615
607 ui.status(_('creating changesets\n'))
616 ui.status(_(b'creating changesets\n'))
608
617
609 # try to order commitids by date
618 # try to order commitids by date
610 mindate = {}
619 mindate = {}
@@ -619,10 +628,10 b' def createchangeset(ui, log, fuzz=60, me'
619 log.sort(
628 log.sort(
620 key=lambda x: (
629 key=lambda x: (
621 mindate.get(x.commitid, (-1, 0)),
630 mindate.get(x.commitid, (-1, 0)),
622 x.commitid or '',
631 x.commitid or b'',
623 x.comment,
632 x.comment,
624 x.author,
633 x.author,
625 x.branch or '',
634 x.branch or b'',
626 x.date,
635 x.date,
627 x.branchpoints,
636 x.branchpoints,
628 )
637 )
@@ -682,8 +691,8 b' def createchangeset(ui, log, fuzz=60, me'
682
691
683 files = set()
692 files = set()
684 if len(changesets) % 100 == 0:
693 if len(changesets) % 100 == 0:
685 t = '%d %s' % (len(changesets), repr(e.comment)[1:-1])
694 t = b'%d %s' % (len(changesets), repr(e.comment)[1:-1])
686 ui.status(stringutil.ellipsis(t, 80) + '\n')
695 ui.status(stringutil.ellipsis(t, 80) + b'\n')
687
696
688 c.entries.append(e)
697 c.entries.append(e)
689 files.add(e.file)
698 files.add(e.file)
@@ -705,9 +714,9 b' def createchangeset(ui, log, fuzz=60, me'
705 # Sort files in each changeset
714 # Sort files in each changeset
706
715
707 def entitycompare(l, r):
716 def entitycompare(l, r):
708 'Mimic cvsps sorting order'
717 b'Mimic cvsps sorting order'
709 l = l.file.split('/')
718 l = l.file.split(b'/')
710 r = r.file.split('/')
719 r = r.file.split(b'/')
711 nl = len(l)
720 nl = len(l)
712 nr = len(r)
721 nr = len(r)
713 n = min(nl, nr)
722 n = min(nl, nr)
@@ -842,7 +851,7 b' def createchangeset(ui, log, fuzz=60, me'
842 # Ensure no changeset has a synthetic changeset as a parent.
851 # Ensure no changeset has a synthetic changeset as a parent.
843 while p.synthetic:
852 while p.synthetic:
844 assert len(p.parents) <= 1, _(
853 assert len(p.parents) <= 1, _(
845 'synthetic changeset cannot have multiple parents'
854 b'synthetic changeset cannot have multiple parents'
846 )
855 )
847 if p.parents:
856 if p.parents:
848 p = p.parents[0]
857 p = p.parents[0]
@@ -854,7 +863,7 b' def createchangeset(ui, log, fuzz=60, me'
854 c.parents.append(p)
863 c.parents.append(p)
855
864
856 if c.mergepoint:
865 if c.mergepoint:
857 if c.mergepoint == 'HEAD':
866 if c.mergepoint == b'HEAD':
858 c.mergepoint = None
867 c.mergepoint = None
859 c.parents.append(changesets[branches[c.mergepoint]])
868 c.parents.append(changesets[branches[c.mergepoint]])
860
869
@@ -862,15 +871,15 b' def createchangeset(ui, log, fuzz=60, me'
862 m = mergefrom.search(c.comment)
871 m = mergefrom.search(c.comment)
863 if m:
872 if m:
864 m = m.group(1)
873 m = m.group(1)
865 if m == 'HEAD':
874 if m == b'HEAD':
866 m = None
875 m = None
867 try:
876 try:
868 candidate = changesets[branches[m]]
877 candidate = changesets[branches[m]]
869 except KeyError:
878 except KeyError:
870 ui.warn(
879 ui.warn(
871 _(
880 _(
872 "warning: CVS commit message references "
881 b"warning: CVS commit message references "
873 "non-existent branch %r:\n%s\n"
882 b"non-existent branch %r:\n%s\n"
874 )
883 )
875 % (pycompat.bytestr(m), c.comment)
884 % (pycompat.bytestr(m), c.comment)
876 )
885 )
@@ -882,7 +891,7 b' def createchangeset(ui, log, fuzz=60, me'
882 if m:
891 if m:
883 if m.groups():
892 if m.groups():
884 m = m.group(1)
893 m = m.group(1)
885 if m == 'HEAD':
894 if m == b'HEAD':
886 m = None
895 m = None
887 else:
896 else:
888 m = None # if no group found then merge to HEAD
897 m = None # if no group found then merge to HEAD
@@ -892,7 +901,7 b' def createchangeset(ui, log, fuzz=60, me'
892 author=c.author,
901 author=c.author,
893 branch=m,
902 branch=m,
894 date=c.date,
903 date=c.date,
895 comment='convert-repo: CVS merge from branch %s'
904 comment=b'convert-repo: CVS merge from branch %s'
896 % c.branch,
905 % c.branch,
897 entries=[],
906 entries=[],
898 tags=[],
907 tags=[],
@@ -927,13 +936,13 b' def createchangeset(ui, log, fuzz=60, me'
927 for l, r in odd:
936 for l, r in odd:
928 if l.id is not None and r.id is not None:
937 if l.id is not None and r.id is not None:
929 ui.warn(
938 ui.warn(
930 _('changeset %d is both before and after %d\n')
939 _(b'changeset %d is both before and after %d\n')
931 % (l.id, r.id)
940 % (l.id, r.id)
932 )
941 )
933
942
934 ui.status(_('%d changeset entries\n') % len(changesets))
943 ui.status(_(b'%d changeset entries\n') % len(changesets))
935
944
936 hook.hook(ui, None, "cvschangesets", True, changesets=changesets)
945 hook.hook(ui, None, b"cvschangesets", True, changesets=changesets)
937
946
938 return changesets
947 return changesets
939
948
@@ -944,27 +953,27 b' def debugcvsps(ui, *args, **opts):'
944 commit log entries and dates.
953 commit log entries and dates.
945 '''
954 '''
946 opts = pycompat.byteskwargs(opts)
955 opts = pycompat.byteskwargs(opts)
947 if opts["new_cache"]:
956 if opts[b"new_cache"]:
948 cache = "write"
957 cache = b"write"
949 elif opts["update_cache"]:
958 elif opts[b"update_cache"]:
950 cache = "update"
959 cache = b"update"
951 else:
960 else:
952 cache = None
961 cache = None
953
962
954 revisions = opts["revisions"]
963 revisions = opts[b"revisions"]
955
964
956 try:
965 try:
957 if args:
966 if args:
958 log = []
967 log = []
959 for d in args:
968 for d in args:
960 log += createlog(ui, d, root=opts["root"], cache=cache)
969 log += createlog(ui, d, root=opts[b"root"], cache=cache)
961 else:
970 else:
962 log = createlog(ui, root=opts["root"], cache=cache)
971 log = createlog(ui, root=opts[b"root"], cache=cache)
963 except logerror as e:
972 except logerror as e:
964 ui.write("%r\n" % e)
973 ui.write(b"%r\n" % e)
965 return
974 return
966
975
967 changesets = createchangeset(ui, log, opts["fuzz"])
976 changesets = createchangeset(ui, log, opts[b"fuzz"])
968 del log
977 del log
969
978
970 # Print changesets (optionally filtered)
979 # Print changesets (optionally filtered)
@@ -974,7 +983,7 b' def debugcvsps(ui, *args, **opts):'
974 ancestors = {} # parent branch
983 ancestors = {} # parent branch
975 for cs in changesets:
984 for cs in changesets:
976
985
977 if opts["ancestors"]:
986 if opts[b"ancestors"]:
978 if cs.branch not in branches and cs.parents and cs.parents[0].id:
987 if cs.branch not in branches and cs.parents and cs.parents[0].id:
979 ancestors[cs.branch] = (
988 ancestors[cs.branch] = (
980 changesets[cs.parents[0].id - 1].branch,
989 changesets[cs.parents[0].id - 1].branch,
@@ -983,72 +992,75 b' def debugcvsps(ui, *args, **opts):'
983 branches[cs.branch] = cs.id
992 branches[cs.branch] = cs.id
984
993
985 # limit by branches
994 # limit by branches
986 if opts["branches"] and (cs.branch or 'HEAD') not in opts["branches"]:
995 if (
996 opts[b"branches"]
997 and (cs.branch or b'HEAD') not in opts[b"branches"]
998 ):
987 continue
999 continue
988
1000
989 if not off:
1001 if not off:
990 # Note: trailing spaces on several lines here are needed to have
1002 # Note: trailing spaces on several lines here are needed to have
991 # bug-for-bug compatibility with cvsps.
1003 # bug-for-bug compatibility with cvsps.
992 ui.write('---------------------\n')
1004 ui.write(b'---------------------\n')
993 ui.write(('PatchSet %d \n' % cs.id))
1005 ui.write((b'PatchSet %d \n' % cs.id))
994 ui.write(
1006 ui.write(
995 (
1007 (
996 'Date: %s\n'
1008 b'Date: %s\n'
997 % dateutil.datestr(cs.date, '%Y/%m/%d %H:%M:%S %1%2')
1009 % dateutil.datestr(cs.date, b'%Y/%m/%d %H:%M:%S %1%2')
998 )
1010 )
999 )
1011 )
1000 ui.write(('Author: %s\n' % cs.author))
1012 ui.write((b'Author: %s\n' % cs.author))
1001 ui.write(('Branch: %s\n' % (cs.branch or 'HEAD')))
1013 ui.write((b'Branch: %s\n' % (cs.branch or b'HEAD')))
1002 ui.write(
1014 ui.write(
1003 (
1015 (
1004 'Tag%s: %s \n'
1016 b'Tag%s: %s \n'
1005 % (
1017 % (
1006 ['', 's'][len(cs.tags) > 1],
1018 [b'', b's'][len(cs.tags) > 1],
1007 ','.join(cs.tags) or '(none)',
1019 b','.join(cs.tags) or b'(none)',
1008 )
1020 )
1009 )
1021 )
1010 )
1022 )
1011 if cs.branchpoints:
1023 if cs.branchpoints:
1012 ui.write(
1024 ui.write(
1013 'Branchpoints: %s \n' % ', '.join(sorted(cs.branchpoints))
1025 b'Branchpoints: %s \n' % b', '.join(sorted(cs.branchpoints))
1014 )
1026 )
1015 if opts["parents"] and cs.parents:
1027 if opts[b"parents"] and cs.parents:
1016 if len(cs.parents) > 1:
1028 if len(cs.parents) > 1:
1017 ui.write(
1029 ui.write(
1018 (
1030 (
1019 'Parents: %s\n'
1031 b'Parents: %s\n'
1020 % (','.join([(b"%d" % p.id) for p in cs.parents]))
1032 % (b','.join([(b"%d" % p.id) for p in cs.parents]))
1021 )
1033 )
1022 )
1034 )
1023 else:
1035 else:
1024 ui.write(('Parent: %d\n' % cs.parents[0].id))
1036 ui.write((b'Parent: %d\n' % cs.parents[0].id))
1025
1037
1026 if opts["ancestors"]:
1038 if opts[b"ancestors"]:
1027 b = cs.branch
1039 b = cs.branch
1028 r = []
1040 r = []
1029 while b:
1041 while b:
1030 b, c = ancestors[b]
1042 b, c = ancestors[b]
1031 r.append('%s:%d:%d' % (b or "HEAD", c, branches[b]))
1043 r.append(b'%s:%d:%d' % (b or b"HEAD", c, branches[b]))
1032 if r:
1044 if r:
1033 ui.write(('Ancestors: %s\n' % (','.join(r))))
1045 ui.write((b'Ancestors: %s\n' % (b','.join(r))))
1034
1046
1035 ui.write('Log:\n')
1047 ui.write(b'Log:\n')
1036 ui.write('%s\n\n' % cs.comment)
1048 ui.write(b'%s\n\n' % cs.comment)
1037 ui.write('Members: \n')
1049 ui.write(b'Members: \n')
1038 for f in cs.entries:
1050 for f in cs.entries:
1039 fn = f.file
1051 fn = f.file
1040 if fn.startswith(opts["prefix"]):
1052 if fn.startswith(opts[b"prefix"]):
1041 fn = fn[len(opts["prefix"]) :]
1053 fn = fn[len(opts[b"prefix"]) :]
1042 ui.write(
1054 ui.write(
1043 '\t%s:%s->%s%s \n'
1055 b'\t%s:%s->%s%s \n'
1044 % (
1056 % (
1045 fn,
1057 fn,
1046 '.'.join([b"%d" % x for x in f.parent]) or 'INITIAL',
1058 b'.'.join([b"%d" % x for x in f.parent]) or b'INITIAL',
1047 '.'.join([(b"%d" % x) for x in f.revision]),
1059 b'.'.join([(b"%d" % x) for x in f.revision]),
1048 ['', '(DEAD)'][f.dead],
1060 [b'', b'(DEAD)'][f.dead],
1049 )
1061 )
1050 )
1062 )
1051 ui.write('\n')
1063 ui.write(b'\n')
1052
1064
1053 # have we seen the start tag?
1065 # have we seen the start tag?
1054 if revisions and off:
1066 if revisions and off:
@@ -46,22 +46,22 b' except ImportError:'
46 class darcs_source(common.converter_source, common.commandline):
46 class darcs_source(common.converter_source, common.commandline):
47 def __init__(self, ui, repotype, path, revs=None):
47 def __init__(self, ui, repotype, path, revs=None):
48 common.converter_source.__init__(self, ui, repotype, path, revs=revs)
48 common.converter_source.__init__(self, ui, repotype, path, revs=revs)
49 common.commandline.__init__(self, ui, 'darcs')
49 common.commandline.__init__(self, ui, b'darcs')
50
50
51 # check for _darcs, ElementTree so that we can easily skip
51 # check for _darcs, ElementTree so that we can easily skip
52 # test-convert-darcs if ElementTree is not around
52 # test-convert-darcs if ElementTree is not around
53 if not os.path.exists(os.path.join(path, '_darcs')):
53 if not os.path.exists(os.path.join(path, b'_darcs')):
54 raise NoRepo(_("%s does not look like a darcs repository") % path)
54 raise NoRepo(_(b"%s does not look like a darcs repository") % path)
55
55
56 common.checktool('darcs')
56 common.checktool(b'darcs')
57 version = self.run0('--version').splitlines()[0].strip()
57 version = self.run0(b'--version').splitlines()[0].strip()
58 if version < '2.1':
58 if version < b'2.1':
59 raise error.Abort(
59 raise error.Abort(
60 _('darcs version 2.1 or newer needed (found %r)') % version
60 _(b'darcs version 2.1 or newer needed (found %r)') % version
61 )
61 )
62
62
63 if "ElementTree" not in globals():
63 if b"ElementTree" not in globals():
64 raise error.Abort(_("Python ElementTree module is not available"))
64 raise error.Abort(_(b"Python ElementTree module is not available"))
65
65
66 self.path = os.path.realpath(path)
66 self.path = os.path.realpath(path)
67
67
@@ -73,30 +73,33 b' class darcs_source(common.converter_sour'
73 # Check darcs repository format
73 # Check darcs repository format
74 format = self.format()
74 format = self.format()
75 if format:
75 if format:
76 if format in ('darcs-1.0', 'hashed'):
76 if format in (b'darcs-1.0', b'hashed'):
77 raise NoRepo(
77 raise NoRepo(
78 _("%s repository format is unsupported, " "please upgrade")
78 _(
79 b"%s repository format is unsupported, "
80 b"please upgrade"
81 )
79 % format
82 % format
80 )
83 )
81 else:
84 else:
82 self.ui.warn(_('failed to detect repository format!'))
85 self.ui.warn(_(b'failed to detect repository format!'))
83
86
84 def before(self):
87 def before(self):
85 self.tmppath = pycompat.mkdtemp(
88 self.tmppath = pycompat.mkdtemp(
86 prefix='convert-' + os.path.basename(self.path) + '-'
89 prefix=b'convert-' + os.path.basename(self.path) + b'-'
87 )
90 )
88 output, status = self.run('init', repodir=self.tmppath)
91 output, status = self.run(b'init', repodir=self.tmppath)
89 self.checkexit(status)
92 self.checkexit(status)
90
93
91 tree = self.xml(
94 tree = self.xml(
92 'changes', xml_output=True, summary=True, repodir=self.path
95 b'changes', xml_output=True, summary=True, repodir=self.path
93 )
96 )
94 tagname = None
97 tagname = None
95 child = None
98 child = None
96 for elt in tree.findall('patch'):
99 for elt in tree.findall(b'patch'):
97 node = elt.get('hash')
100 node = elt.get(b'hash')
98 name = elt.findtext('name', '')
101 name = elt.findtext(b'name', b'')
99 if name.startswith('TAG '):
102 if name.startswith(b'TAG '):
100 tagname = name[4:].strip()
103 tagname = name[4:].strip()
101 elif tagname is not None:
104 elif tagname is not None:
102 self.tags[tagname] = node
105 self.tags[tagname] = node
@@ -107,7 +110,7 b' class darcs_source(common.converter_sour'
107 self.parents[child] = []
110 self.parents[child] = []
108
111
109 def after(self):
112 def after(self):
110 self.ui.debug('cleaning up %s\n' % self.tmppath)
113 self.ui.debug(b'cleaning up %s\n' % self.tmppath)
111 shutil.rmtree(self.tmppath, ignore_errors=True)
114 shutil.rmtree(self.tmppath, ignore_errors=True)
112
115
113 def recode(self, s, encoding=None):
116 def recode(self, s, encoding=None):
@@ -125,7 +128,7 b' class darcs_source(common.converter_sour'
125 # While we are decoding the XML as latin-1 to be as liberal as
128 # While we are decoding the XML as latin-1 to be as liberal as
126 # possible, etree will still raise an exception if any
129 # possible, etree will still raise an exception if any
127 # non-printable characters are in the XML changelog.
130 # non-printable characters are in the XML changelog.
128 parser = XMLParser(encoding='latin-1')
131 parser = XMLParser(encoding=b'latin-1')
129 p = self._run(cmd, **kwargs)
132 p = self._run(cmd, **kwargs)
130 etree.parse(p.stdout, parser=parser)
133 etree.parse(p.stdout, parser=parser)
131 p.wait()
134 p.wait()
@@ -133,20 +136,20 b' class darcs_source(common.converter_sour'
133 return etree.getroot()
136 return etree.getroot()
134
137
135 def format(self):
138 def format(self):
136 output, status = self.run('show', 'repo', repodir=self.path)
139 output, status = self.run(b'show', b'repo', repodir=self.path)
137 self.checkexit(status)
140 self.checkexit(status)
138 m = re.search(r'^\s*Format:\s*(.*)$', output, re.MULTILINE)
141 m = re.search(r'^\s*Format:\s*(.*)$', output, re.MULTILINE)
139 if not m:
142 if not m:
140 return None
143 return None
141 return ','.join(sorted(f.strip() for f in m.group(1).split(',')))
144 return b','.join(sorted(f.strip() for f in m.group(1).split(b',')))
142
145
143 def manifest(self):
146 def manifest(self):
144 man = []
147 man = []
145 output, status = self.run(
148 output, status = self.run(
146 'show', 'files', no_directories=True, repodir=self.tmppath
149 b'show', b'files', no_directories=True, repodir=self.tmppath
147 )
150 )
148 self.checkexit(status)
151 self.checkexit(status)
149 for line in output.split('\n'):
152 for line in output.split(b'\n'):
150 path = line[2:]
153 path = line[2:]
151 if path:
154 if path:
152 man.append(path)
155 man.append(path)
@@ -157,14 +160,14 b' class darcs_source(common.converter_sour'
157
160
158 def getcommit(self, rev):
161 def getcommit(self, rev):
159 elt = self.changes[rev]
162 elt = self.changes[rev]
160 dateformat = '%a %b %d %H:%M:%S %Z %Y'
163 dateformat = b'%a %b %d %H:%M:%S %Z %Y'
161 date = dateutil.strdate(elt.get('local_date'), dateformat)
164 date = dateutil.strdate(elt.get(b'local_date'), dateformat)
162 desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
165 desc = elt.findtext(b'name') + b'\n' + elt.findtext(b'comment', b'')
163 # etree can return unicode objects for name, comment, and author,
166 # etree can return unicode objects for name, comment, and author,
164 # so recode() is used to ensure str objects are emitted.
167 # so recode() is used to ensure str objects are emitted.
165 newdateformat = '%Y-%m-%d %H:%M:%S %1%2'
168 newdateformat = b'%Y-%m-%d %H:%M:%S %1%2'
166 return common.commit(
169 return common.commit(
167 author=self.recode(elt.get('author')),
170 author=self.recode(elt.get(b'author')),
168 date=dateutil.datestr(date, newdateformat),
171 date=dateutil.datestr(date, newdateformat),
169 desc=self.recode(desc).strip(),
172 desc=self.recode(desc).strip(),
170 parents=self.parents[rev],
173 parents=self.parents[rev],
@@ -172,34 +175,34 b' class darcs_source(common.converter_sour'
172
175
173 def pull(self, rev):
176 def pull(self, rev):
174 output, status = self.run(
177 output, status = self.run(
175 'pull',
178 b'pull',
176 self.path,
179 self.path,
177 all=True,
180 all=True,
178 match='hash %s' % rev,
181 match=b'hash %s' % rev,
179 no_test=True,
182 no_test=True,
180 no_posthook=True,
183 no_posthook=True,
181 external_merge='/bin/false',
184 external_merge=b'/bin/false',
182 repodir=self.tmppath,
185 repodir=self.tmppath,
183 )
186 )
184 if status:
187 if status:
185 if output.find('We have conflicts in') == -1:
188 if output.find(b'We have conflicts in') == -1:
186 self.checkexit(status, output)
189 self.checkexit(status, output)
187 output, status = self.run('revert', all=True, repodir=self.tmppath)
190 output, status = self.run(b'revert', all=True, repodir=self.tmppath)
188 self.checkexit(status, output)
191 self.checkexit(status, output)
189
192
190 def getchanges(self, rev, full):
193 def getchanges(self, rev, full):
191 if full:
194 if full:
192 raise error.Abort(_("convert from darcs does not support --full"))
195 raise error.Abort(_(b"convert from darcs does not support --full"))
193 copies = {}
196 copies = {}
194 changes = []
197 changes = []
195 man = None
198 man = None
196 for elt in self.changes[rev].find('summary').getchildren():
199 for elt in self.changes[rev].find(b'summary').getchildren():
197 if elt.tag in ('add_directory', 'remove_directory'):
200 if elt.tag in (b'add_directory', b'remove_directory'):
198 continue
201 continue
199 if elt.tag == 'move':
202 if elt.tag == b'move':
200 if man is None:
203 if man is None:
201 man = self.manifest()
204 man = self.manifest()
202 source, dest = elt.get('from'), elt.get('to')
205 source, dest = elt.get(b'from'), elt.get(b'to')
203 if source in man:
206 if source in man:
204 # File move
207 # File move
205 changes.append((source, rev))
208 changes.append((source, rev))
@@ -207,11 +210,11 b' class darcs_source(common.converter_sour'
207 copies[dest] = source
210 copies[dest] = source
208 else:
211 else:
209 # Directory move, deduce file moves from manifest
212 # Directory move, deduce file moves from manifest
210 source = source + '/'
213 source = source + b'/'
211 for f in man:
214 for f in man:
212 if not f.startswith(source):
215 if not f.startswith(source):
213 continue
216 continue
214 fdest = dest + '/' + f[len(source) :]
217 fdest = dest + b'/' + f[len(source) :]
215 changes.append((f, rev))
218 changes.append((f, rev))
216 changes.append((fdest, rev))
219 changes.append((fdest, rev))
217 copies[fdest] = f
220 copies[fdest] = f
@@ -223,7 +226,7 b' class darcs_source(common.converter_sour'
223
226
224 def getfile(self, name, rev):
227 def getfile(self, name, rev):
225 if rev != self.lastrev:
228 if rev != self.lastrev:
226 raise error.Abort(_('internal calling inconsistency'))
229 raise error.Abort(_(b'internal calling inconsistency'))
227 path = os.path.join(self.tmppath, name)
230 path = os.path.join(self.tmppath, name)
228 try:
231 try:
229 data = util.readfile(path)
232 data = util.readfile(path)
@@ -232,7 +235,7 b' class darcs_source(common.converter_sour'
232 if inst.errno == errno.ENOENT:
235 if inst.errno == errno.ENOENT:
233 return None, None
236 return None, None
234 raise
237 raise
235 mode = (mode & 0o111) and 'x' or ''
238 mode = (mode & 0o111) and b'x' or b''
236 return data, mode
239 return data, mode
237
240
238 def gettags(self):
241 def gettags(self):
@@ -30,8 +30,8 b' def rpairs(path):'
30 i = len(path)
30 i = len(path)
31 while i != -1:
31 while i != -1:
32 yield path[:i], path[i + 1 :]
32 yield path[:i], path[i + 1 :]
33 i = path.rfind('/', 0, i)
33 i = path.rfind(b'/', 0, i)
34 yield '.', path
34 yield b'.', path
35
35
36
36
37 def normalize(path):
37 def normalize(path):
@@ -55,7 +55,7 b' class filemapper(object):'
55 self.targetprefixes = None
55 self.targetprefixes = None
56 if path:
56 if path:
57 if self.parse(path):
57 if self.parse(path):
58 raise error.Abort(_('errors in filemap'))
58 raise error.Abort(_(b'errors in filemap'))
59
59
60 def parse(self, path):
60 def parse(self, path):
61 errs = 0
61 errs = 0
@@ -63,48 +63,48 b' class filemapper(object):'
63 def check(name, mapping, listname):
63 def check(name, mapping, listname):
64 if not name:
64 if not name:
65 self.ui.warn(
65 self.ui.warn(
66 _('%s:%d: path to %s is missing\n')
66 _(b'%s:%d: path to %s is missing\n')
67 % (lex.infile, lex.lineno, listname)
67 % (lex.infile, lex.lineno, listname)
68 )
68 )
69 return 1
69 return 1
70 if name in mapping:
70 if name in mapping:
71 self.ui.warn(
71 self.ui.warn(
72 _('%s:%d: %r already in %s list\n')
72 _(b'%s:%d: %r already in %s list\n')
73 % (lex.infile, lex.lineno, name, listname)
73 % (lex.infile, lex.lineno, name, listname)
74 )
74 )
75 return 1
75 return 1
76 if name.startswith('/') or name.endswith('/') or '//' in name:
76 if name.startswith(b'/') or name.endswith(b'/') or b'//' in name:
77 self.ui.warn(
77 self.ui.warn(
78 _('%s:%d: superfluous / in %s %r\n')
78 _(b'%s:%d: superfluous / in %s %r\n')
79 % (lex.infile, lex.lineno, listname, pycompat.bytestr(name))
79 % (lex.infile, lex.lineno, listname, pycompat.bytestr(name))
80 )
80 )
81 return 1
81 return 1
82 return 0
82 return 0
83
83
84 lex = common.shlexer(
84 lex = common.shlexer(
85 filepath=path, wordchars='!@#$%^&*()-=+[]{}|;:,./<>?'
85 filepath=path, wordchars=b'!@#$%^&*()-=+[]{}|;:,./<>?'
86 )
86 )
87 cmd = lex.get_token()
87 cmd = lex.get_token()
88 while cmd:
88 while cmd:
89 if cmd == 'include':
89 if cmd == b'include':
90 name = normalize(lex.get_token())
90 name = normalize(lex.get_token())
91 errs += check(name, self.exclude, 'exclude')
91 errs += check(name, self.exclude, b'exclude')
92 self.include[name] = name
92 self.include[name] = name
93 elif cmd == 'exclude':
93 elif cmd == b'exclude':
94 name = normalize(lex.get_token())
94 name = normalize(lex.get_token())
95 errs += check(name, self.include, 'include')
95 errs += check(name, self.include, b'include')
96 errs += check(name, self.rename, 'rename')
96 errs += check(name, self.rename, b'rename')
97 self.exclude[name] = name
97 self.exclude[name] = name
98 elif cmd == 'rename':
98 elif cmd == b'rename':
99 src = normalize(lex.get_token())
99 src = normalize(lex.get_token())
100 dest = normalize(lex.get_token())
100 dest = normalize(lex.get_token())
101 errs += check(src, self.exclude, 'exclude')
101 errs += check(src, self.exclude, b'exclude')
102 self.rename[src] = dest
102 self.rename[src] = dest
103 elif cmd == 'source':
103 elif cmd == b'source':
104 errs += self.parse(normalize(lex.get_token()))
104 errs += self.parse(normalize(lex.get_token()))
105 else:
105 else:
106 self.ui.warn(
106 self.ui.warn(
107 _('%s:%d: unknown directive %r\n')
107 _(b'%s:%d: unknown directive %r\n')
108 % (lex.infile, lex.lineno, pycompat.bytestr(cmd))
108 % (lex.infile, lex.lineno, pycompat.bytestr(cmd))
109 )
109 )
110 errs += 1
110 errs += 1
@@ -118,7 +118,7 b' class filemapper(object):'
118 return mapping[pre], pre, suf
118 return mapping[pre], pre, suf
119 except KeyError:
119 except KeyError:
120 pass
120 pass
121 return '', name, ''
121 return b'', name, b''
122
122
123 def istargetfile(self, filename):
123 def istargetfile(self, filename):
124 """Return true if the given target filename is covered as a destination
124 """Return true if the given target filename is covered as a destination
@@ -131,7 +131,7 b' class filemapper(object):'
131
131
132 # If "." is a target, then all target files are considered from the
132 # If "." is a target, then all target files are considered from the
133 # source.
133 # source.
134 if not self.targetprefixes or '.' in self.targetprefixes:
134 if not self.targetprefixes or b'.' in self.targetprefixes:
135 return True
135 return True
136
136
137 filename = normalize(filename)
137 filename = normalize(filename)
@@ -152,17 +152,17 b' class filemapper(object):'
152 if self.exclude:
152 if self.exclude:
153 exc = self.lookup(name, self.exclude)[0]
153 exc = self.lookup(name, self.exclude)[0]
154 else:
154 else:
155 exc = ''
155 exc = b''
156 if (not self.include and exc) or (len(inc) <= len(exc)):
156 if (not self.include and exc) or (len(inc) <= len(exc)):
157 return None
157 return None
158 newpre, pre, suf = self.lookup(name, self.rename)
158 newpre, pre, suf = self.lookup(name, self.rename)
159 if newpre:
159 if newpre:
160 if newpre == '.':
160 if newpre == b'.':
161 return suf
161 return suf
162 if suf:
162 if suf:
163 if newpre.endswith('/'):
163 if newpre.endswith(b'/'):
164 return newpre + suf
164 return newpre + suf
165 return newpre + '/' + suf
165 return newpre + b'/' + suf
166 return newpre
166 return newpre
167 return name
167 return name
168
168
@@ -204,7 +204,7 b' class filemap_source(common.converter_so'
204 self.seenchildren = {}
204 self.seenchildren = {}
205 # experimental config: convert.ignoreancestorcheck
205 # experimental config: convert.ignoreancestorcheck
206 self.ignoreancestorcheck = self.ui.configbool(
206 self.ignoreancestorcheck = self.ui.configbool(
207 'convert', 'ignoreancestorcheck'
207 b'convert', b'ignoreancestorcheck'
208 )
208 )
209
209
210 def before(self):
210 def before(self):
@@ -256,7 +256,7 b' class filemap_source(common.converter_so'
256 try:
256 try:
257 self.origparents[rev] = self.getcommit(rev).parents
257 self.origparents[rev] = self.getcommit(rev).parents
258 except error.RepoLookupError:
258 except error.RepoLookupError:
259 self.ui.debug("unknown revmap source: %s\n" % rev)
259 self.ui.debug(b"unknown revmap source: %s\n" % rev)
260 continue
260 continue
261 if arg is not None:
261 if arg is not None:
262 self.children[arg] = self.children.get(arg, 0) + 1
262 self.children[arg] = self.children.get(arg, 0) + 1
@@ -316,7 +316,7 b' class filemap_source(common.converter_so'
316 try:
316 try:
317 files = self.base.getchangedfiles(rev, i)
317 files = self.base.getchangedfiles(rev, i)
318 except NotImplementedError:
318 except NotImplementedError:
319 raise error.Abort(_("source repository doesn't support --filemap"))
319 raise error.Abort(_(b"source repository doesn't support --filemap"))
320 for f in files:
320 for f in files:
321 if self.filemapper(f):
321 if self.filemapper(f):
322 return True
322 return True
@@ -331,7 +331,7 b' class filemap_source(common.converter_so'
331 # close marker is significant (i.e. all of the branch ancestors weren't
331 # close marker is significant (i.e. all of the branch ancestors weren't
332 # eliminated). Therefore if there *is* a close marker, getchanges()
332 # eliminated). Therefore if there *is* a close marker, getchanges()
333 # doesn't consider it significant, and this revision should be dropped.
333 # doesn't consider it significant, and this revision should be dropped.
334 return not files and 'close' not in self.commits[rev].extra
334 return not files and b'close' not in self.commits[rev].extra
335
335
336 def mark_not_wanted(self, rev, p):
336 def mark_not_wanted(self, rev, p):
337 # Mark rev as not interesting and update data structures.
337 # Mark rev as not interesting and update data structures.
@@ -363,7 +363,9 b' class filemap_source(common.converter_so'
363 if p in self.wantedancestors:
363 if p in self.wantedancestors:
364 wrev.update(self.wantedancestors[p])
364 wrev.update(self.wantedancestors[p])
365 else:
365 else:
366 self.ui.warn(_('warning: %s parent %s is missing\n') % (rev, p))
366 self.ui.warn(
367 _(b'warning: %s parent %s is missing\n') % (rev, p)
368 )
367 wrev.add(rev)
369 wrev.add(rev)
368 self.wantedancestors[rev] = wrev
370 self.wantedancestors[rev] = wrev
369
371
@@ -423,7 +425,7 b' class filemap_source(common.converter_so'
423 self.origparents[rev] = parents
425 self.origparents[rev] = parents
424
426
425 closed = False
427 closed = False
426 if 'close' in self.commits[rev].extra:
428 if b'close' in self.commits[rev].extra:
427 # A branch closing revision is only useful if one of its
429 # A branch closing revision is only useful if one of its
428 # parents belong to the branch being closed
430 # parents belong to the branch being closed
429 pbranches = [self._cachedcommit(p).branch for p in mparents]
431 pbranches = [self._cachedcommit(p).branch for p in mparents]
@@ -26,22 +26,22 b' class submodule(object):'
26 self.url = url
26 self.url = url
27
27
28 def hgsub(self):
28 def hgsub(self):
29 return "%s = [git]%s" % (self.path, self.url)
29 return b"%s = [git]%s" % (self.path, self.url)
30
30
31 def hgsubstate(self):
31 def hgsubstate(self):
32 return "%s %s" % (self.node, self.path)
32 return b"%s %s" % (self.node, self.path)
33
33
34
34
35 # Keys in extra fields that should not be copied if the user requests.
35 # Keys in extra fields that should not be copied if the user requests.
36 bannedextrakeys = {
36 bannedextrakeys = {
37 # Git commit object built-ins.
37 # Git commit object built-ins.
38 'tree',
38 b'tree',
39 'parent',
39 b'parent',
40 'author',
40 b'author',
41 'committer',
41 b'committer',
42 # Mercurial built-ins.
42 # Mercurial built-ins.
43 'branch',
43 b'branch',
44 'close',
44 b'close',
45 }
45 }
46
46
47
47
@@ -51,7 +51,7 b' class convert_git(common.converter_sourc'
51 # both issues.
51 # both issues.
52
52
53 def _gitcmd(self, cmd, *args, **kwargs):
53 def _gitcmd(self, cmd, *args, **kwargs):
54 return cmd('--git-dir=%s' % self.path, *args, **kwargs)
54 return cmd(b'--git-dir=%s' % self.path, *args, **kwargs)
55
55
56 def gitrun0(self, *args, **kwargs):
56 def gitrun0(self, *args, **kwargs):
57 return self._gitcmd(self.run0, *args, **kwargs)
57 return self._gitcmd(self.run0, *args, **kwargs)
@@ -70,100 +70,104 b' class convert_git(common.converter_sourc'
70
70
71 def __init__(self, ui, repotype, path, revs=None):
71 def __init__(self, ui, repotype, path, revs=None):
72 super(convert_git, self).__init__(ui, repotype, path, revs=revs)
72 super(convert_git, self).__init__(ui, repotype, path, revs=revs)
73 common.commandline.__init__(self, ui, 'git')
73 common.commandline.__init__(self, ui, b'git')
74
74
75 # Pass an absolute path to git to prevent from ever being interpreted
75 # Pass an absolute path to git to prevent from ever being interpreted
76 # as a URL
76 # as a URL
77 path = os.path.abspath(path)
77 path = os.path.abspath(path)
78
78
79 if os.path.isdir(path + "/.git"):
79 if os.path.isdir(path + b"/.git"):
80 path += "/.git"
80 path += b"/.git"
81 if not os.path.exists(path + "/objects"):
81 if not os.path.exists(path + b"/objects"):
82 raise common.NoRepo(
82 raise common.NoRepo(
83 _("%s does not look like a Git repository") % path
83 _(b"%s does not look like a Git repository") % path
84 )
84 )
85
85
86 # The default value (50) is based on the default for 'git diff'.
86 # The default value (50) is based on the default for 'git diff'.
87 similarity = ui.configint('convert', 'git.similarity')
87 similarity = ui.configint(b'convert', b'git.similarity')
88 if similarity < 0 or similarity > 100:
88 if similarity < 0 or similarity > 100:
89 raise error.Abort(_('similarity must be between 0 and 100'))
89 raise error.Abort(_(b'similarity must be between 0 and 100'))
90 if similarity > 0:
90 if similarity > 0:
91 self.simopt = ['-C%d%%' % similarity]
91 self.simopt = [b'-C%d%%' % similarity]
92 findcopiesharder = ui.configbool('convert', 'git.findcopiesharder')
92 findcopiesharder = ui.configbool(
93 b'convert', b'git.findcopiesharder'
94 )
93 if findcopiesharder:
95 if findcopiesharder:
94 self.simopt.append('--find-copies-harder')
96 self.simopt.append(b'--find-copies-harder')
95
97
96 renamelimit = ui.configint('convert', 'git.renamelimit')
98 renamelimit = ui.configint(b'convert', b'git.renamelimit')
97 self.simopt.append('-l%d' % renamelimit)
99 self.simopt.append(b'-l%d' % renamelimit)
98 else:
100 else:
99 self.simopt = []
101 self.simopt = []
100
102
101 common.checktool('git', 'git')
103 common.checktool(b'git', b'git')
102
104
103 self.path = path
105 self.path = path
104 self.submodules = []
106 self.submodules = []
105
107
106 self.catfilepipe = self.gitpipe('cat-file', '--batch')
108 self.catfilepipe = self.gitpipe(b'cat-file', b'--batch')
107
109
108 self.copyextrakeys = self.ui.configlist('convert', 'git.extrakeys')
110 self.copyextrakeys = self.ui.configlist(b'convert', b'git.extrakeys')
109 banned = set(self.copyextrakeys) & bannedextrakeys
111 banned = set(self.copyextrakeys) & bannedextrakeys
110 if banned:
112 if banned:
111 raise error.Abort(
113 raise error.Abort(
112 _('copying of extra key is forbidden: %s')
114 _(b'copying of extra key is forbidden: %s')
113 % _(', ').join(sorted(banned))
115 % _(b', ').join(sorted(banned))
114 )
116 )
115
117
116 committeractions = self.ui.configlist('convert', 'git.committeractions')
118 committeractions = self.ui.configlist(
119 b'convert', b'git.committeractions'
120 )
117
121
118 messagedifferent = None
122 messagedifferent = None
119 messagealways = None
123 messagealways = None
120 for a in committeractions:
124 for a in committeractions:
121 if a.startswith(('messagedifferent', 'messagealways')):
125 if a.startswith((b'messagedifferent', b'messagealways')):
122 k = a
126 k = a
123 v = None
127 v = None
124 if '=' in a:
128 if b'=' in a:
125 k, v = a.split('=', 1)
129 k, v = a.split(b'=', 1)
126
130
127 if k == 'messagedifferent':
131 if k == b'messagedifferent':
128 messagedifferent = v or 'committer:'
132 messagedifferent = v or b'committer:'
129 elif k == 'messagealways':
133 elif k == b'messagealways':
130 messagealways = v or 'committer:'
134 messagealways = v or b'committer:'
131
135
132 if messagedifferent and messagealways:
136 if messagedifferent and messagealways:
133 raise error.Abort(
137 raise error.Abort(
134 _(
138 _(
135 'committeractions cannot define both '
139 b'committeractions cannot define both '
136 'messagedifferent and messagealways'
140 b'messagedifferent and messagealways'
137 )
141 )
138 )
142 )
139
143
140 dropcommitter = 'dropcommitter' in committeractions
144 dropcommitter = b'dropcommitter' in committeractions
141 replaceauthor = 'replaceauthor' in committeractions
145 replaceauthor = b'replaceauthor' in committeractions
142
146
143 if dropcommitter and replaceauthor:
147 if dropcommitter and replaceauthor:
144 raise error.Abort(
148 raise error.Abort(
145 _(
149 _(
146 'committeractions cannot define both '
150 b'committeractions cannot define both '
147 'dropcommitter and replaceauthor'
151 b'dropcommitter and replaceauthor'
148 )
152 )
149 )
153 )
150
154
151 if dropcommitter and messagealways:
155 if dropcommitter and messagealways:
152 raise error.Abort(
156 raise error.Abort(
153 _(
157 _(
154 'committeractions cannot define both '
158 b'committeractions cannot define both '
155 'dropcommitter and messagealways'
159 b'dropcommitter and messagealways'
156 )
160 )
157 )
161 )
158
162
159 if not messagedifferent and not messagealways:
163 if not messagedifferent and not messagealways:
160 messagedifferent = 'committer:'
164 messagedifferent = b'committer:'
161
165
162 self.committeractions = {
166 self.committeractions = {
163 'dropcommitter': dropcommitter,
167 b'dropcommitter': dropcommitter,
164 'replaceauthor': replaceauthor,
168 b'replaceauthor': replaceauthor,
165 'messagedifferent': messagedifferent,
169 b'messagedifferent': messagedifferent,
166 'messagealways': messagealways,
170 b'messagealways': messagealways,
167 }
171 }
168
172
169 def after(self):
173 def after(self):
@@ -172,35 +176,38 b' class convert_git(common.converter_sourc'
172
176
173 def getheads(self):
177 def getheads(self):
174 if not self.revs:
178 if not self.revs:
175 output, status = self.gitrun('rev-parse', '--branches', '--remotes')
179 output, status = self.gitrun(
180 b'rev-parse', b'--branches', b'--remotes'
181 )
176 heads = output.splitlines()
182 heads = output.splitlines()
177 if status:
183 if status:
178 raise error.Abort(_('cannot retrieve git heads'))
184 raise error.Abort(_(b'cannot retrieve git heads'))
179 else:
185 else:
180 heads = []
186 heads = []
181 for rev in self.revs:
187 for rev in self.revs:
182 rawhead, ret = self.gitrun('rev-parse', '--verify', rev)
188 rawhead, ret = self.gitrun(b'rev-parse', b'--verify', rev)
183 heads.append(rawhead[:-1])
189 heads.append(rawhead[:-1])
184 if ret:
190 if ret:
185 raise error.Abort(_('cannot retrieve git head "%s"') % rev)
191 raise error.Abort(_(b'cannot retrieve git head "%s"') % rev)
186 return heads
192 return heads
187
193
188 def catfile(self, rev, ftype):
194 def catfile(self, rev, ftype):
189 if rev == nodemod.nullhex:
195 if rev == nodemod.nullhex:
190 raise IOError
196 raise IOError
191 self.catfilepipe[0].write(rev + '\n')
197 self.catfilepipe[0].write(rev + b'\n')
192 self.catfilepipe[0].flush()
198 self.catfilepipe[0].flush()
193 info = self.catfilepipe[1].readline().split()
199 info = self.catfilepipe[1].readline().split()
194 if info[1] != ftype:
200 if info[1] != ftype:
195 raise error.Abort(
201 raise error.Abort(
196 _('cannot read %r object at %s')
202 _(b'cannot read %r object at %s')
197 % (pycompat.bytestr(ftype), rev)
203 % (pycompat.bytestr(ftype), rev)
198 )
204 )
199 size = int(info[2])
205 size = int(info[2])
200 data = self.catfilepipe[1].read(size)
206 data = self.catfilepipe[1].read(size)
201 if len(data) < size:
207 if len(data) < size:
202 raise error.Abort(
208 raise error.Abort(
203 _('cannot read %r object at %s: unexpected size') % (ftype, rev)
209 _(b'cannot read %r object at %s: unexpected size')
210 % (ftype, rev)
204 )
211 )
205 # read the trailing newline
212 # read the trailing newline
206 self.catfilepipe[1].read(1)
213 self.catfilepipe[1].read(1)
@@ -209,14 +216,14 b' class convert_git(common.converter_sourc'
209 def getfile(self, name, rev):
216 def getfile(self, name, rev):
210 if rev == nodemod.nullhex:
217 if rev == nodemod.nullhex:
211 return None, None
218 return None, None
212 if name == '.hgsub':
219 if name == b'.hgsub':
213 data = '\n'.join([m.hgsub() for m in self.submoditer()])
220 data = b'\n'.join([m.hgsub() for m in self.submoditer()])
214 mode = ''
221 mode = b''
215 elif name == '.hgsubstate':
222 elif name == b'.hgsubstate':
216 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
223 data = b'\n'.join([m.hgsubstate() for m in self.submoditer()])
217 mode = ''
224 mode = b''
218 else:
225 else:
219 data = self.catfile(rev, "blob")
226 data = self.catfile(rev, b"blob")
220 mode = self.modecache[(name, rev)]
227 mode = self.modecache[(name, rev)]
221 return data, mode
228 return data, mode
222
229
@@ -236,21 +243,23 b' class convert_git(common.converter_sourc'
236 c = config.config()
243 c = config.config()
237 # Each item in .gitmodules starts with whitespace that cant be parsed
244 # Each item in .gitmodules starts with whitespace that cant be parsed
238 c.parse(
245 c.parse(
239 '.gitmodules',
246 b'.gitmodules',
240 '\n'.join(line.strip() for line in content.split('\n')),
247 b'\n'.join(line.strip() for line in content.split(b'\n')),
241 )
248 )
242 for sec in c.sections():
249 for sec in c.sections():
243 s = c[sec]
250 s = c[sec]
244 if 'url' in s and 'path' in s:
251 if b'url' in s and b'path' in s:
245 self.submodules.append(submodule(s['path'], '', s['url']))
252 self.submodules.append(submodule(s[b'path'], b'', s[b'url']))
246
253
247 def retrievegitmodules(self, version):
254 def retrievegitmodules(self, version):
248 modules, ret = self.gitrun('show', '%s:%s' % (version, '.gitmodules'))
255 modules, ret = self.gitrun(
256 b'show', b'%s:%s' % (version, b'.gitmodules')
257 )
249 if ret:
258 if ret:
250 # This can happen if a file is in the repo that has permissions
259 # This can happen if a file is in the repo that has permissions
251 # 160000, but there is no .gitmodules file.
260 # 160000, but there is no .gitmodules file.
252 self.ui.warn(
261 self.ui.warn(
253 _("warning: cannot read submodules config file in " "%s\n")
262 _(b"warning: cannot read submodules config file in " b"%s\n")
254 % version
263 % version
255 )
264 )
256 return
265 return
@@ -259,74 +268,76 b' class convert_git(common.converter_sourc'
259 self.parsegitmodules(modules)
268 self.parsegitmodules(modules)
260 except error.ParseError:
269 except error.ParseError:
261 self.ui.warn(
270 self.ui.warn(
262 _("warning: unable to parse .gitmodules in %s\n") % version
271 _(b"warning: unable to parse .gitmodules in %s\n") % version
263 )
272 )
264 return
273 return
265
274
266 for m in self.submodules:
275 for m in self.submodules:
267 node, ret = self.gitrun('rev-parse', '%s:%s' % (version, m.path))
276 node, ret = self.gitrun(b'rev-parse', b'%s:%s' % (version, m.path))
268 if ret:
277 if ret:
269 continue
278 continue
270 m.node = node.strip()
279 m.node = node.strip()
271
280
272 def getchanges(self, version, full):
281 def getchanges(self, version, full):
273 if full:
282 if full:
274 raise error.Abort(_("convert from git does not support --full"))
283 raise error.Abort(_(b"convert from git does not support --full"))
275 self.modecache = {}
284 self.modecache = {}
276 cmd = (
285 cmd = (
277 ['diff-tree', '-z', '--root', '-m', '-r'] + self.simopt + [version]
286 [b'diff-tree', b'-z', b'--root', b'-m', b'-r']
287 + self.simopt
288 + [version]
278 )
289 )
279 output, status = self.gitrun(*cmd)
290 output, status = self.gitrun(*cmd)
280 if status:
291 if status:
281 raise error.Abort(_('cannot read changes in %s') % version)
292 raise error.Abort(_(b'cannot read changes in %s') % version)
282 changes = []
293 changes = []
283 copies = {}
294 copies = {}
284 seen = set()
295 seen = set()
285 entry = None
296 entry = None
286 subexists = [False]
297 subexists = [False]
287 subdeleted = [False]
298 subdeleted = [False]
288 difftree = output.split('\x00')
299 difftree = output.split(b'\x00')
289 lcount = len(difftree)
300 lcount = len(difftree)
290 i = 0
301 i = 0
291
302
292 skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules')
303 skipsubmodules = self.ui.configbool(b'convert', b'git.skipsubmodules')
293
304
294 def add(entry, f, isdest):
305 def add(entry, f, isdest):
295 seen.add(f)
306 seen.add(f)
296 h = entry[3]
307 h = entry[3]
297 p = entry[1] == "100755"
308 p = entry[1] == b"100755"
298 s = entry[1] == "120000"
309 s = entry[1] == b"120000"
299 renamesource = not isdest and entry[4][0] == 'R'
310 renamesource = not isdest and entry[4][0] == b'R'
300
311
301 if f == '.gitmodules':
312 if f == b'.gitmodules':
302 if skipsubmodules:
313 if skipsubmodules:
303 return
314 return
304
315
305 subexists[0] = True
316 subexists[0] = True
306 if entry[4] == 'D' or renamesource:
317 if entry[4] == b'D' or renamesource:
307 subdeleted[0] = True
318 subdeleted[0] = True
308 changes.append(('.hgsub', nodemod.nullhex))
319 changes.append((b'.hgsub', nodemod.nullhex))
309 else:
320 else:
310 changes.append(('.hgsub', ''))
321 changes.append((b'.hgsub', b''))
311 elif entry[1] == '160000' or entry[0] == ':160000':
322 elif entry[1] == b'160000' or entry[0] == b':160000':
312 if not skipsubmodules:
323 if not skipsubmodules:
313 subexists[0] = True
324 subexists[0] = True
314 else:
325 else:
315 if renamesource:
326 if renamesource:
316 h = nodemod.nullhex
327 h = nodemod.nullhex
317 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
328 self.modecache[(f, h)] = (p and b"x") or (s and b"l") or b""
318 changes.append((f, h))
329 changes.append((f, h))
319
330
320 while i < lcount:
331 while i < lcount:
321 l = difftree[i]
332 l = difftree[i]
322 i += 1
333 i += 1
323 if not entry:
334 if not entry:
324 if not l.startswith(':'):
335 if not l.startswith(b':'):
325 continue
336 continue
326 entry = tuple(pycompat.bytestr(p) for p in l.split())
337 entry = tuple(pycompat.bytestr(p) for p in l.split())
327 continue
338 continue
328 f = l
339 f = l
329 if entry[4][0] == 'C':
340 if entry[4][0] == b'C':
330 copysrc = f
341 copysrc = f
331 copydest = difftree[i]
342 copydest = difftree[i]
332 i += 1
343 i += 1
@@ -336,7 +347,7 b' class convert_git(common.converter_sourc'
336 add(entry, f, False)
347 add(entry, f, False)
337 # A file can be copied multiple times, or modified and copied
348 # A file can be copied multiple times, or modified and copied
338 # simultaneously. So f can be repeated even if fdest isn't.
349 # simultaneously. So f can be repeated even if fdest isn't.
339 if entry[4][0] == 'R':
350 if entry[4][0] == b'R':
340 # rename: next line is the destination
351 # rename: next line is the destination
341 fdest = difftree[i]
352 fdest = difftree[i]
342 i += 1
353 i += 1
@@ -344,21 +355,21 b' class convert_git(common.converter_sourc'
344 add(entry, fdest, True)
355 add(entry, fdest, True)
345 # .gitmodules isn't imported at all, so it being copied to
356 # .gitmodules isn't imported at all, so it being copied to
346 # and fro doesn't really make sense
357 # and fro doesn't really make sense
347 if f != '.gitmodules' and fdest != '.gitmodules':
358 if f != b'.gitmodules' and fdest != b'.gitmodules':
348 copies[fdest] = f
359 copies[fdest] = f
349 entry = None
360 entry = None
350
361
351 if subexists[0]:
362 if subexists[0]:
352 if subdeleted[0]:
363 if subdeleted[0]:
353 changes.append(('.hgsubstate', nodemod.nullhex))
364 changes.append((b'.hgsubstate', nodemod.nullhex))
354 else:
365 else:
355 self.retrievegitmodules(version)
366 self.retrievegitmodules(version)
356 changes.append(('.hgsubstate', ''))
367 changes.append((b'.hgsubstate', b''))
357 return (changes, copies, set())
368 return (changes, copies, set())
358
369
359 def getcommit(self, version):
370 def getcommit(self, version):
360 c = self.catfile(version, "commit") # read the commit hash
371 c = self.catfile(version, b"commit") # read the commit hash
361 end = c.find("\n\n")
372 end = c.find(b"\n\n")
362 message = c[end + 2 :]
373 message = c[end + 2 :]
363 message = self.recode(message)
374 message = self.recode(message)
364 l = c[:end].splitlines()
375 l = c[:end].splitlines()
@@ -366,43 +377,43 b' class convert_git(common.converter_sourc'
366 author = committer = None
377 author = committer = None
367 extra = {}
378 extra = {}
368 for e in l[1:]:
379 for e in l[1:]:
369 n, v = e.split(" ", 1)
380 n, v = e.split(b" ", 1)
370 if n == "author":
381 if n == b"author":
371 p = v.split()
382 p = v.split()
372 tm, tz = p[-2:]
383 tm, tz = p[-2:]
373 author = " ".join(p[:-2])
384 author = b" ".join(p[:-2])
374 if author[0] == "<":
385 if author[0] == b"<":
375 author = author[1:-1]
386 author = author[1:-1]
376 author = self.recode(author)
387 author = self.recode(author)
377 if n == "committer":
388 if n == b"committer":
378 p = v.split()
389 p = v.split()
379 tm, tz = p[-2:]
390 tm, tz = p[-2:]
380 committer = " ".join(p[:-2])
391 committer = b" ".join(p[:-2])
381 if committer[0] == "<":
392 if committer[0] == b"<":
382 committer = committer[1:-1]
393 committer = committer[1:-1]
383 committer = self.recode(committer)
394 committer = self.recode(committer)
384 if n == "parent":
395 if n == b"parent":
385 parents.append(v)
396 parents.append(v)
386 if n in self.copyextrakeys:
397 if n in self.copyextrakeys:
387 extra[n] = v
398 extra[n] = v
388
399
389 if self.committeractions['dropcommitter']:
400 if self.committeractions[b'dropcommitter']:
390 committer = None
401 committer = None
391 elif self.committeractions['replaceauthor']:
402 elif self.committeractions[b'replaceauthor']:
392 author = committer
403 author = committer
393
404
394 if committer:
405 if committer:
395 messagealways = self.committeractions['messagealways']
406 messagealways = self.committeractions[b'messagealways']
396 messagedifferent = self.committeractions['messagedifferent']
407 messagedifferent = self.committeractions[b'messagedifferent']
397 if messagealways:
408 if messagealways:
398 message += '\n%s %s\n' % (messagealways, committer)
409 message += b'\n%s %s\n' % (messagealways, committer)
399 elif messagedifferent and author != committer:
410 elif messagedifferent and author != committer:
400 message += '\n%s %s\n' % (messagedifferent, committer)
411 message += b'\n%s %s\n' % (messagedifferent, committer)
401
412
402 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
413 tzs, tzh, tzm = tz[-5:-4] + b"1", tz[-4:-2], tz[-2:]
403 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
414 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
404 date = tm + " " + (b"%d" % tz)
415 date = tm + b" " + (b"%d" % tz)
405 saverev = self.ui.configbool('convert', 'git.saverev')
416 saverev = self.ui.configbool(b'convert', b'git.saverev')
406
417
407 c = common.commit(
418 c = common.commit(
408 parents=parents,
419 parents=parents,
@@ -416,27 +427,27 b' class convert_git(common.converter_sourc'
416 return c
427 return c
417
428
418 def numcommits(self):
429 def numcommits(self):
419 output, ret = self.gitrunlines('rev-list', '--all')
430 output, ret = self.gitrunlines(b'rev-list', b'--all')
420 if ret:
431 if ret:
421 raise error.Abort(
432 raise error.Abort(
422 _('cannot retrieve number of commits in %s') % self.path
433 _(b'cannot retrieve number of commits in %s') % self.path
423 )
434 )
424 return len(output)
435 return len(output)
425
436
426 def gettags(self):
437 def gettags(self):
427 tags = {}
438 tags = {}
428 alltags = {}
439 alltags = {}
429 output, status = self.gitrunlines('ls-remote', '--tags', self.path)
440 output, status = self.gitrunlines(b'ls-remote', b'--tags', self.path)
430
441
431 if status:
442 if status:
432 raise error.Abort(_('cannot read tags from %s') % self.path)
443 raise error.Abort(_(b'cannot read tags from %s') % self.path)
433 prefix = 'refs/tags/'
444 prefix = b'refs/tags/'
434
445
435 # Build complete list of tags, both annotated and bare ones
446 # Build complete list of tags, both annotated and bare ones
436 for line in output:
447 for line in output:
437 line = line.strip()
448 line = line.strip()
438 if line.startswith("error:") or line.startswith("fatal:"):
449 if line.startswith(b"error:") or line.startswith(b"fatal:"):
439 raise error.Abort(_('cannot read tags from %s') % self.path)
450 raise error.Abort(_(b'cannot read tags from %s') % self.path)
440 node, tag = line.split(None, 1)
451 node, tag = line.split(None, 1)
441 if not tag.startswith(prefix):
452 if not tag.startswith(prefix):
442 continue
453 continue
@@ -444,10 +455,10 b' class convert_git(common.converter_sourc'
444
455
445 # Filter out tag objects for annotated tag refs
456 # Filter out tag objects for annotated tag refs
446 for tag in alltags:
457 for tag in alltags:
447 if tag.endswith('^{}'):
458 if tag.endswith(b'^{}'):
448 tags[tag[:-3]] = alltags[tag]
459 tags[tag[:-3]] = alltags[tag]
449 else:
460 else:
450 if tag + '^{}' in alltags:
461 if tag + b'^{}' in alltags:
451 continue
462 continue
452 else:
463 else:
453 tags[tag] = alltags[tag]
464 tags[tag] = alltags[tag]
@@ -458,28 +469,28 b' class convert_git(common.converter_sourc'
458 changes = []
469 changes = []
459 if i is None:
470 if i is None:
460 output, status = self.gitrunlines(
471 output, status = self.gitrunlines(
461 'diff-tree', '--root', '-m', '-r', version
472 b'diff-tree', b'--root', b'-m', b'-r', version
462 )
473 )
463 if status:
474 if status:
464 raise error.Abort(_('cannot read changes in %s') % version)
475 raise error.Abort(_(b'cannot read changes in %s') % version)
465 for l in output:
476 for l in output:
466 if "\t" not in l:
477 if b"\t" not in l:
467 continue
478 continue
468 m, f = l[:-1].split("\t")
479 m, f = l[:-1].split(b"\t")
469 changes.append(f)
480 changes.append(f)
470 else:
481 else:
471 output, status = self.gitrunlines(
482 output, status = self.gitrunlines(
472 'diff-tree',
483 b'diff-tree',
473 '--name-only',
484 b'--name-only',
474 '--root',
485 b'--root',
475 '-r',
486 b'-r',
476 version,
487 version,
477 '%s^%d' % (version, i + 1),
488 b'%s^%d' % (version, i + 1),
478 '--',
489 b'--',
479 )
490 )
480 if status:
491 if status:
481 raise error.Abort(_('cannot read changes in %s') % version)
492 raise error.Abort(_(b'cannot read changes in %s') % version)
482 changes = [f.rstrip('\n') for f in output]
493 changes = [f.rstrip(b'\n') for f in output]
483
494
484 return changes
495 return changes
485
496
@@ -487,19 +498,19 b' class convert_git(common.converter_sourc'
487 bookmarks = {}
498 bookmarks = {}
488
499
489 # Handle local and remote branches
500 # Handle local and remote branches
490 remoteprefix = self.ui.config('convert', 'git.remoteprefix')
501 remoteprefix = self.ui.config(b'convert', b'git.remoteprefix')
491 reftypes = [
502 reftypes = [
492 # (git prefix, hg prefix)
503 # (git prefix, hg prefix)
493 ('refs/remotes/origin/', remoteprefix + '/'),
504 (b'refs/remotes/origin/', remoteprefix + b'/'),
494 ('refs/heads/', ''),
505 (b'refs/heads/', b''),
495 ]
506 ]
496
507
497 exclude = {
508 exclude = {
498 'refs/remotes/origin/HEAD',
509 b'refs/remotes/origin/HEAD',
499 }
510 }
500
511
501 try:
512 try:
502 output, status = self.gitrunlines('show-ref')
513 output, status = self.gitrunlines(b'show-ref')
503 for line in output:
514 for line in output:
504 line = line.strip()
515 line = line.strip()
505 rev, name = line.split(None, 1)
516 rev, name = line.split(None, 1)
@@ -507,13 +518,13 b' class convert_git(common.converter_sourc'
507 for gitprefix, hgprefix in reftypes:
518 for gitprefix, hgprefix in reftypes:
508 if not name.startswith(gitprefix) or name in exclude:
519 if not name.startswith(gitprefix) or name in exclude:
509 continue
520 continue
510 name = '%s%s' % (hgprefix, name[len(gitprefix) :])
521 name = b'%s%s' % (hgprefix, name[len(gitprefix) :])
511 bookmarks[name] = rev
522 bookmarks[name] = rev
512 except Exception:
523 except Exception:
513 pass
524 pass
514
525
515 return bookmarks
526 return bookmarks
516
527
517 def checkrevformat(self, revstr, mapname='splicemap'):
528 def checkrevformat(self, revstr, mapname=b'splicemap'):
518 """ git revision string is a 40 byte hex """
529 """ git revision string is a 40 byte hex """
519 self.checkhexformat(revstr, mapname)
530 self.checkhexformat(revstr, mapname)
@@ -31,9 +31,9 b' class gnuarch_source(common.converter_so'
31 class gnuarch_rev(object):
31 class gnuarch_rev(object):
32 def __init__(self, rev):
32 def __init__(self, rev):
33 self.rev = rev
33 self.rev = rev
34 self.summary = ''
34 self.summary = b''
35 self.date = None
35 self.date = None
36 self.author = ''
36 self.author = b''
37 self.continuationof = None
37 self.continuationof = None
38 self.add_files = []
38 self.add_files = []
39 self.mod_files = []
39 self.mod_files = []
@@ -44,20 +44,20 b' class gnuarch_source(common.converter_so'
44 def __init__(self, ui, repotype, path, revs=None):
44 def __init__(self, ui, repotype, path, revs=None):
45 super(gnuarch_source, self).__init__(ui, repotype, path, revs=revs)
45 super(gnuarch_source, self).__init__(ui, repotype, path, revs=revs)
46
46
47 if not os.path.exists(os.path.join(path, '{arch}')):
47 if not os.path.exists(os.path.join(path, b'{arch}')):
48 raise common.NoRepo(
48 raise common.NoRepo(
49 _("%s does not look like a GNU Arch repository") % path
49 _(b"%s does not look like a GNU Arch repository") % path
50 )
50 )
51
51
52 # Could use checktool, but we want to check for baz or tla.
52 # Could use checktool, but we want to check for baz or tla.
53 self.execmd = None
53 self.execmd = None
54 if procutil.findexe('baz'):
54 if procutil.findexe(b'baz'):
55 self.execmd = 'baz'
55 self.execmd = b'baz'
56 else:
56 else:
57 if procutil.findexe('tla'):
57 if procutil.findexe(b'tla'):
58 self.execmd = 'tla'
58 self.execmd = b'tla'
59 else:
59 else:
60 raise error.Abort(_('cannot find a GNU Arch tool'))
60 raise error.Abort(_(b'cannot find a GNU Arch tool'))
61
61
62 common.commandline.__init__(self, ui, self.execmd)
62 common.commandline.__init__(self, ui, self.execmd)
63
63
@@ -76,19 +76,19 b' class gnuarch_source(common.converter_so'
76 def before(self):
76 def before(self):
77 # Get registered archives
77 # Get registered archives
78 self.archives = [
78 self.archives = [
79 i.rstrip('\n') for i in self.runlines0('archives', '-n')
79 i.rstrip(b'\n') for i in self.runlines0(b'archives', b'-n')
80 ]
80 ]
81
81
82 if self.execmd == 'tla':
82 if self.execmd == b'tla':
83 output = self.run0('tree-version', self.path)
83 output = self.run0(b'tree-version', self.path)
84 else:
84 else:
85 output = self.run0('tree-version', '-d', self.path)
85 output = self.run0(b'tree-version', b'-d', self.path)
86 self.treeversion = output.strip()
86 self.treeversion = output.strip()
87
87
88 # Get name of temporary directory
88 # Get name of temporary directory
89 version = self.treeversion.split('/')
89 version = self.treeversion.split(b'/')
90 self.tmppath = os.path.join(
90 self.tmppath = os.path.join(
91 pycompat.fsencode(tempfile.gettempdir()), 'hg-%s' % version[1]
91 pycompat.fsencode(tempfile.gettempdir()), b'hg-%s' % version[1]
92 )
92 )
93
93
94 # Generate parents dictionary
94 # Generate parents dictionary
@@ -96,23 +96,25 b' class gnuarch_source(common.converter_so'
96 treeversion = self.treeversion
96 treeversion = self.treeversion
97 child = None
97 child = None
98 while treeversion:
98 while treeversion:
99 self.ui.status(_('analyzing tree version %s...\n') % treeversion)
99 self.ui.status(_(b'analyzing tree version %s...\n') % treeversion)
100
100
101 archive = treeversion.split('/')[0]
101 archive = treeversion.split(b'/')[0]
102 if archive not in self.archives:
102 if archive not in self.archives:
103 self.ui.status(
103 self.ui.status(
104 _(
104 _(
105 'tree analysis stopped because it points to '
105 b'tree analysis stopped because it points to '
106 'an unregistered archive %s...\n'
106 b'an unregistered archive %s...\n'
107 )
107 )
108 % archive
108 % archive
109 )
109 )
110 break
110 break
111
111
112 # Get the complete list of revisions for that tree version
112 # Get the complete list of revisions for that tree version
113 output, status = self.runlines('revisions', '-r', '-f', treeversion)
113 output, status = self.runlines(
114 b'revisions', b'-r', b'-f', treeversion
115 )
114 self.checkexit(
116 self.checkexit(
115 status, 'failed retrieving revisions for %s' % treeversion
117 status, b'failed retrieving revisions for %s' % treeversion
116 )
118 )
117
119
118 # No new iteration unless a revision has a continuation-of header
120 # No new iteration unless a revision has a continuation-of header
@@ -124,9 +126,9 b' class gnuarch_source(common.converter_so'
124 self.parents[rev] = []
126 self.parents[rev] = []
125
127
126 # Read author, date and summary
128 # Read author, date and summary
127 catlog, status = self.run('cat-log', '-d', self.path, rev)
129 catlog, status = self.run(b'cat-log', b'-d', self.path, rev)
128 if status:
130 if status:
129 catlog = self.run0('cat-archive-log', rev)
131 catlog = self.run0(b'cat-archive-log', rev)
130 self._parsecatlog(catlog, rev)
132 self._parsecatlog(catlog, rev)
131
133
132 # Populate the parents map
134 # Populate the parents map
@@ -140,18 +142,18 b' class gnuarch_source(common.converter_so'
140 # or if we have to 'jump' to a different treeversion given
142 # or if we have to 'jump' to a different treeversion given
141 # by the continuation-of header.
143 # by the continuation-of header.
142 if self.changes[rev].continuationof:
144 if self.changes[rev].continuationof:
143 treeversion = '--'.join(
145 treeversion = b'--'.join(
144 self.changes[rev].continuationof.split('--')[:-1]
146 self.changes[rev].continuationof.split(b'--')[:-1]
145 )
147 )
146 break
148 break
147
149
148 # If we reached a base-0 revision w/o any continuation-of
150 # If we reached a base-0 revision w/o any continuation-of
149 # header, it means the tree history ends here.
151 # header, it means the tree history ends here.
150 if rev[-6:] == 'base-0':
152 if rev[-6:] == b'base-0':
151 break
153 break
152
154
153 def after(self):
155 def after(self):
154 self.ui.debug('cleaning up %s\n' % self.tmppath)
156 self.ui.debug(b'cleaning up %s\n' % self.tmppath)
155 shutil.rmtree(self.tmppath, ignore_errors=True)
157 shutil.rmtree(self.tmppath, ignore_errors=True)
156
158
157 def getheads(self):
159 def getheads(self):
@@ -159,7 +161,7 b' class gnuarch_source(common.converter_so'
159
161
160 def getfile(self, name, rev):
162 def getfile(self, name, rev):
161 if rev != self.lastrev:
163 if rev != self.lastrev:
162 raise error.Abort(_('internal calling inconsistency'))
164 raise error.Abort(_(b'internal calling inconsistency'))
163
165
164 if not os.path.lexists(os.path.join(self.tmppath, name)):
166 if not os.path.lexists(os.path.join(self.tmppath, name)):
165 return None, None
167 return None, None
@@ -168,7 +170,7 b' class gnuarch_source(common.converter_so'
168
170
169 def getchanges(self, rev, full):
171 def getchanges(self, rev, full):
170 if full:
172 if full:
171 raise error.Abort(_("convert from arch does not support --full"))
173 raise error.Abort(_(b"convert from arch does not support --full"))
172 self._update(rev)
174 self._update(rev)
173 changes = []
175 changes = []
174 copies = {}
176 copies = {}
@@ -214,14 +216,14 b' class gnuarch_source(common.converter_so'
214 cmdline = [self.execmd, cmd]
216 cmdline = [self.execmd, cmd]
215 cmdline += args
217 cmdline += args
216 cmdline = [procutil.shellquote(arg) for arg in cmdline]
218 cmdline = [procutil.shellquote(arg) for arg in cmdline]
217 cmdline += ['>', os.devnull, '2>', os.devnull]
219 cmdline += [b'>', os.devnull, b'2>', os.devnull]
218 cmdline = procutil.quotecommand(' '.join(cmdline))
220 cmdline = procutil.quotecommand(b' '.join(cmdline))
219 self.ui.debug(cmdline, '\n')
221 self.ui.debug(cmdline, b'\n')
220 return os.system(pycompat.rapply(procutil.tonativestr, cmdline))
222 return os.system(pycompat.rapply(procutil.tonativestr, cmdline))
221
223
222 def _update(self, rev):
224 def _update(self, rev):
223 self.ui.debug('applying revision %s...\n' % rev)
225 self.ui.debug(b'applying revision %s...\n' % rev)
224 changeset, status = self.runlines('replay', '-d', self.tmppath, rev)
226 changeset, status = self.runlines(b'replay', b'-d', self.tmppath, rev)
225 if status:
227 if status:
226 # Something went wrong while merging (baz or tla
228 # Something went wrong while merging (baz or tla
227 # issue?), get latest revision and try from there
229 # issue?), get latest revision and try from there
@@ -230,7 +232,7 b' class gnuarch_source(common.converter_so'
230 else:
232 else:
231 old_rev = self.parents[rev][0]
233 old_rev = self.parents[rev][0]
232 self.ui.debug(
234 self.ui.debug(
233 'computing changeset between %s and %s...\n' % (old_rev, rev)
235 b'computing changeset between %s and %s...\n' % (old_rev, rev)
234 )
236 )
235 self._parsechangeset(changeset, rev)
237 self._parsechangeset(changeset, rev)
236
238
@@ -239,16 +241,16 b' class gnuarch_source(common.converter_so'
239 if stat.S_ISLNK(mode):
241 if stat.S_ISLNK(mode):
240 data = util.readlink(os.path.join(self.tmppath, name))
242 data = util.readlink(os.path.join(self.tmppath, name))
241 if mode:
243 if mode:
242 mode = 'l'
244 mode = b'l'
243 else:
245 else:
244 mode = ''
246 mode = b''
245 else:
247 else:
246 data = util.readfile(os.path.join(self.tmppath, name))
248 data = util.readfile(os.path.join(self.tmppath, name))
247 mode = (mode & 0o111) and 'x' or ''
249 mode = (mode & 0o111) and b'x' or b''
248 return data, mode
250 return data, mode
249
251
250 def _exclude(self, name):
252 def _exclude(self, name):
251 exclude = ['{arch}', '.arch-ids', '.arch-inventory']
253 exclude = [b'{arch}', b'.arch-ids', b'.arch-inventory']
252 for exc in exclude:
254 for exc in exclude:
253 if name.find(exc) != -1:
255 if name.find(exc) != -1:
254 return True
256 return True
@@ -282,15 +284,15 b' class gnuarch_source(common.converter_so'
282 return changes, copies
284 return changes, copies
283
285
284 def _obtainrevision(self, rev):
286 def _obtainrevision(self, rev):
285 self.ui.debug('obtaining revision %s...\n' % rev)
287 self.ui.debug(b'obtaining revision %s...\n' % rev)
286 output = self._execute('get', rev, self.tmppath)
288 output = self._execute(b'get', rev, self.tmppath)
287 self.checkexit(output)
289 self.checkexit(output)
288 self.ui.debug('analyzing revision %s...\n' % rev)
290 self.ui.debug(b'analyzing revision %s...\n' % rev)
289 files = self._readcontents(self.tmppath)
291 files = self._readcontents(self.tmppath)
290 self.changes[rev].add_files += files
292 self.changes[rev].add_files += files
291
293
292 def _stripbasepath(self, path):
294 def _stripbasepath(self, path):
293 if path.startswith('./'):
295 if path.startswith(b'./'):
294 return path[2:]
296 return path[2:]
295 return path
297 return path
296
298
@@ -300,73 +302,73 b' class gnuarch_source(common.converter_so'
300
302
301 # Commit date
303 # Commit date
302 self.changes[rev].date = dateutil.datestr(
304 self.changes[rev].date = dateutil.datestr(
303 dateutil.strdate(catlog['Standard-date'], '%Y-%m-%d %H:%M:%S')
305 dateutil.strdate(catlog[b'Standard-date'], b'%Y-%m-%d %H:%M:%S')
304 )
306 )
305
307
306 # Commit author
308 # Commit author
307 self.changes[rev].author = self.recode(catlog['Creator'])
309 self.changes[rev].author = self.recode(catlog[b'Creator'])
308
310
309 # Commit description
311 # Commit description
310 self.changes[rev].summary = '\n\n'.join(
312 self.changes[rev].summary = b'\n\n'.join(
311 (catlog['Summary'], catlog.get_payload())
313 (catlog[b'Summary'], catlog.get_payload())
312 )
314 )
313 self.changes[rev].summary = self.recode(self.changes[rev].summary)
315 self.changes[rev].summary = self.recode(self.changes[rev].summary)
314
316
315 # Commit revision origin when dealing with a branch or tag
317 # Commit revision origin when dealing with a branch or tag
316 if 'Continuation-of' in catlog:
318 if b'Continuation-of' in catlog:
317 self.changes[rev].continuationof = self.recode(
319 self.changes[rev].continuationof = self.recode(
318 catlog['Continuation-of']
320 catlog[b'Continuation-of']
319 )
321 )
320 except Exception:
322 except Exception:
321 raise error.Abort(_('could not parse cat-log of %s') % rev)
323 raise error.Abort(_(b'could not parse cat-log of %s') % rev)
322
324
323 def _parsechangeset(self, data, rev):
325 def _parsechangeset(self, data, rev):
324 for l in data:
326 for l in data:
325 l = l.strip()
327 l = l.strip()
326 # Added file (ignore added directory)
328 # Added file (ignore added directory)
327 if l.startswith('A') and not l.startswith('A/'):
329 if l.startswith(b'A') and not l.startswith(b'A/'):
328 file = self._stripbasepath(l[1:].strip())
330 file = self._stripbasepath(l[1:].strip())
329 if not self._exclude(file):
331 if not self._exclude(file):
330 self.changes[rev].add_files.append(file)
332 self.changes[rev].add_files.append(file)
331 # Deleted file (ignore deleted directory)
333 # Deleted file (ignore deleted directory)
332 elif l.startswith('D') and not l.startswith('D/'):
334 elif l.startswith(b'D') and not l.startswith(b'D/'):
333 file = self._stripbasepath(l[1:].strip())
335 file = self._stripbasepath(l[1:].strip())
334 if not self._exclude(file):
336 if not self._exclude(file):
335 self.changes[rev].del_files.append(file)
337 self.changes[rev].del_files.append(file)
336 # Modified binary file
338 # Modified binary file
337 elif l.startswith('Mb'):
339 elif l.startswith(b'Mb'):
338 file = self._stripbasepath(l[2:].strip())
340 file = self._stripbasepath(l[2:].strip())
339 if not self._exclude(file):
341 if not self._exclude(file):
340 self.changes[rev].mod_files.append(file)
342 self.changes[rev].mod_files.append(file)
341 # Modified link
343 # Modified link
342 elif l.startswith('M->'):
344 elif l.startswith(b'M->'):
343 file = self._stripbasepath(l[3:].strip())
345 file = self._stripbasepath(l[3:].strip())
344 if not self._exclude(file):
346 if not self._exclude(file):
345 self.changes[rev].mod_files.append(file)
347 self.changes[rev].mod_files.append(file)
346 # Modified file
348 # Modified file
347 elif l.startswith('M'):
349 elif l.startswith(b'M'):
348 file = self._stripbasepath(l[1:].strip())
350 file = self._stripbasepath(l[1:].strip())
349 if not self._exclude(file):
351 if not self._exclude(file):
350 self.changes[rev].mod_files.append(file)
352 self.changes[rev].mod_files.append(file)
351 # Renamed file (or link)
353 # Renamed file (or link)
352 elif l.startswith('=>'):
354 elif l.startswith(b'=>'):
353 files = l[2:].strip().split(' ')
355 files = l[2:].strip().split(b' ')
354 if len(files) == 1:
356 if len(files) == 1:
355 files = l[2:].strip().split('\t')
357 files = l[2:].strip().split(b'\t')
356 src = self._stripbasepath(files[0])
358 src = self._stripbasepath(files[0])
357 dst = self._stripbasepath(files[1])
359 dst = self._stripbasepath(files[1])
358 if not self._exclude(src) and not self._exclude(dst):
360 if not self._exclude(src) and not self._exclude(dst):
359 self.changes[rev].ren_files[src] = dst
361 self.changes[rev].ren_files[src] = dst
360 # Conversion from file to link or from link to file (modified)
362 # Conversion from file to link or from link to file (modified)
361 elif l.startswith('ch'):
363 elif l.startswith(b'ch'):
362 file = self._stripbasepath(l[2:].strip())
364 file = self._stripbasepath(l[2:].strip())
363 if not self._exclude(file):
365 if not self._exclude(file):
364 self.changes[rev].mod_files.append(file)
366 self.changes[rev].mod_files.append(file)
365 # Renamed directory
367 # Renamed directory
366 elif l.startswith('/>'):
368 elif l.startswith(b'/>'):
367 dirs = l[2:].strip().split(' ')
369 dirs = l[2:].strip().split(b' ')
368 if len(dirs) == 1:
370 if len(dirs) == 1:
369 dirs = l[2:].strip().split('\t')
371 dirs = l[2:].strip().split(b'\t')
370 src = self._stripbasepath(dirs[0])
372 src = self._stripbasepath(dirs[0])
371 dst = self._stripbasepath(dirs[1])
373 dst = self._stripbasepath(dirs[1])
372 if not self._exclude(src) and not self._exclude(dst):
374 if not self._exclude(src) and not self._exclude(dst):
@@ -51,33 +51,33 b" sha1re = re.compile(br'\\b[0-9a-f]{12,40}"
51 class mercurial_sink(common.converter_sink):
51 class mercurial_sink(common.converter_sink):
52 def __init__(self, ui, repotype, path):
52 def __init__(self, ui, repotype, path):
53 common.converter_sink.__init__(self, ui, repotype, path)
53 common.converter_sink.__init__(self, ui, repotype, path)
54 self.branchnames = ui.configbool('convert', 'hg.usebranchnames')
54 self.branchnames = ui.configbool(b'convert', b'hg.usebranchnames')
55 self.clonebranches = ui.configbool('convert', 'hg.clonebranches')
55 self.clonebranches = ui.configbool(b'convert', b'hg.clonebranches')
56 self.tagsbranch = ui.config('convert', 'hg.tagsbranch')
56 self.tagsbranch = ui.config(b'convert', b'hg.tagsbranch')
57 self.lastbranch = None
57 self.lastbranch = None
58 if os.path.isdir(path) and len(os.listdir(path)) > 0:
58 if os.path.isdir(path) and len(os.listdir(path)) > 0:
59 try:
59 try:
60 self.repo = hg.repository(self.ui, path)
60 self.repo = hg.repository(self.ui, path)
61 if not self.repo.local():
61 if not self.repo.local():
62 raise NoRepo(
62 raise NoRepo(
63 _('%s is not a local Mercurial repository') % path
63 _(b'%s is not a local Mercurial repository') % path
64 )
64 )
65 except error.RepoError as err:
65 except error.RepoError as err:
66 ui.traceback()
66 ui.traceback()
67 raise NoRepo(err.args[0])
67 raise NoRepo(err.args[0])
68 else:
68 else:
69 try:
69 try:
70 ui.status(_('initializing destination %s repository\n') % path)
70 ui.status(_(b'initializing destination %s repository\n') % path)
71 self.repo = hg.repository(self.ui, path, create=True)
71 self.repo = hg.repository(self.ui, path, create=True)
72 if not self.repo.local():
72 if not self.repo.local():
73 raise NoRepo(
73 raise NoRepo(
74 _('%s is not a local Mercurial repository') % path
74 _(b'%s is not a local Mercurial repository') % path
75 )
75 )
76 self.created.append(path)
76 self.created.append(path)
77 except error.RepoError:
77 except error.RepoError:
78 ui.traceback()
78 ui.traceback()
79 raise NoRepo(
79 raise NoRepo(
80 _("could not create hg repository %s as sink") % path
80 _(b"could not create hg repository %s as sink") % path
81 )
81 )
82 self.lock = None
82 self.lock = None
83 self.wlock = None
83 self.wlock = None
@@ -85,22 +85,22 b' class mercurial_sink(common.converter_si'
85 self.subrevmaps = {}
85 self.subrevmaps = {}
86
86
87 def before(self):
87 def before(self):
88 self.ui.debug('run hg sink pre-conversion action\n')
88 self.ui.debug(b'run hg sink pre-conversion action\n')
89 self.wlock = self.repo.wlock()
89 self.wlock = self.repo.wlock()
90 self.lock = self.repo.lock()
90 self.lock = self.repo.lock()
91
91
92 def after(self):
92 def after(self):
93 self.ui.debug('run hg sink post-conversion action\n')
93 self.ui.debug(b'run hg sink post-conversion action\n')
94 if self.lock:
94 if self.lock:
95 self.lock.release()
95 self.lock.release()
96 if self.wlock:
96 if self.wlock:
97 self.wlock.release()
97 self.wlock.release()
98
98
99 def revmapfile(self):
99 def revmapfile(self):
100 return self.repo.vfs.join("shamap")
100 return self.repo.vfs.join(b"shamap")
101
101
102 def authorfile(self):
102 def authorfile(self):
103 return self.repo.vfs.join("authormap")
103 return self.repo.vfs.join(b"authormap")
104
104
105 def setbranch(self, branch, pbranches):
105 def setbranch(self, branch, pbranches):
106 if not self.clonebranches:
106 if not self.clonebranches:
@@ -109,8 +109,8 b' class mercurial_sink(common.converter_si'
109 setbranch = branch != self.lastbranch
109 setbranch = branch != self.lastbranch
110 self.lastbranch = branch
110 self.lastbranch = branch
111 if not branch:
111 if not branch:
112 branch = 'default'
112 branch = b'default'
113 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
113 pbranches = [(b[0], b[1] and b[1] or b'default') for b in pbranches]
114
114
115 branchpath = os.path.join(self.path, branch)
115 branchpath = os.path.join(self.path, branch)
116 if setbranch:
116 if setbranch:
@@ -135,7 +135,9 b' class mercurial_sink(common.converter_si'
135 for pbranch, heads in sorted(missings.iteritems()):
135 for pbranch, heads in sorted(missings.iteritems()):
136 pbranchpath = os.path.join(self.path, pbranch)
136 pbranchpath = os.path.join(self.path, pbranch)
137 prepo = hg.peer(self.ui, {}, pbranchpath)
137 prepo = hg.peer(self.ui, {}, pbranchpath)
138 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
138 self.ui.note(
139 _(b'pulling from %s into %s\n') % (pbranch, branch)
140 )
139 exchange.pull(
141 exchange.pull(
140 self.repo, prepo, [prepo.lookup(h) for h in heads]
142 self.repo, prepo, [prepo.lookup(h) for h in heads]
141 )
143 )
@@ -144,10 +146,10 b' class mercurial_sink(common.converter_si'
144 def _rewritetags(self, source, revmap, data):
146 def _rewritetags(self, source, revmap, data):
145 fp = stringio()
147 fp = stringio()
146 for line in data.splitlines():
148 for line in data.splitlines():
147 s = line.split(' ', 1)
149 s = line.split(b' ', 1)
148 if len(s) != 2:
150 if len(s) != 2:
149 self.ui.warn(_('invalid tag entry: "%s"\n') % line)
151 self.ui.warn(_(b'invalid tag entry: "%s"\n') % line)
150 fp.write('%s\n' % line) # Bogus, but keep for hash stability
152 fp.write(b'%s\n' % line) # Bogus, but keep for hash stability
151 continue
153 continue
152 revid = revmap.get(source.lookuprev(s[0]))
154 revid = revmap.get(source.lookuprev(s[0]))
153 if not revid:
155 if not revid:
@@ -155,16 +157,16 b' class mercurial_sink(common.converter_si'
155 revid = s[0]
157 revid = s[0]
156 else:
158 else:
157 # missing, but keep for hash stability
159 # missing, but keep for hash stability
158 self.ui.warn(_('missing tag entry: "%s"\n') % line)
160 self.ui.warn(_(b'missing tag entry: "%s"\n') % line)
159 fp.write('%s\n' % line)
161 fp.write(b'%s\n' % line)
160 continue
162 continue
161 fp.write('%s %s\n' % (revid, s[1]))
163 fp.write(b'%s %s\n' % (revid, s[1]))
162 return fp.getvalue()
164 return fp.getvalue()
163
165
164 def _rewritesubstate(self, source, data):
166 def _rewritesubstate(self, source, data):
165 fp = stringio()
167 fp = stringio()
166 for line in data.splitlines():
168 for line in data.splitlines():
167 s = line.split(' ', 1)
169 s = line.split(b' ', 1)
168 if len(s) != 2:
170 if len(s) != 2:
169 continue
171 continue
170
172
@@ -174,7 +176,7 b' class mercurial_sink(common.converter_si'
174 revmap = self.subrevmaps.get(subpath)
176 revmap = self.subrevmaps.get(subpath)
175 if revmap is None:
177 if revmap is None:
176 revmap = mapfile(
178 revmap = mapfile(
177 self.ui, self.repo.wjoin(subpath, '.hg/shamap')
179 self.ui, self.repo.wjoin(subpath, b'.hg/shamap')
178 )
180 )
179 self.subrevmaps[subpath] = revmap
181 self.subrevmaps[subpath] = revmap
180
182
@@ -182,9 +184,9 b' class mercurial_sink(common.converter_si'
182 # need to be converted, in which case they can be cloned
184 # need to be converted, in which case they can be cloned
183 # into place instead of converted. Therefore, only warn
185 # into place instead of converted. Therefore, only warn
184 # once.
186 # once.
185 msg = _('no ".hgsubstate" updates will be made for "%s"\n')
187 msg = _(b'no ".hgsubstate" updates will be made for "%s"\n')
186 if len(revmap) == 0:
188 if len(revmap) == 0:
187 sub = self.repo.wvfs.reljoin(subpath, '.hg')
189 sub = self.repo.wvfs.reljoin(subpath, b'.hg')
188
190
189 if self.repo.wvfs.exists(sub):
191 if self.repo.wvfs.exists(sub):
190 self.ui.warn(msg % subpath)
192 self.ui.warn(msg % subpath)
@@ -193,13 +195,13 b' class mercurial_sink(common.converter_si'
193 if not newid:
195 if not newid:
194 if len(revmap) > 0:
196 if len(revmap) > 0:
195 self.ui.warn(
197 self.ui.warn(
196 _("%s is missing from %s/.hg/shamap\n")
198 _(b"%s is missing from %s/.hg/shamap\n")
197 % (revid, subpath)
199 % (revid, subpath)
198 )
200 )
199 else:
201 else:
200 revid = newid
202 revid = newid
201
203
202 fp.write('%s %s\n' % (revid, subpath))
204 fp.write(b'%s %s\n' % (revid, subpath))
203
205
204 return fp.getvalue()
206 return fp.getvalue()
205
207
@@ -232,16 +234,16 b' class mercurial_sink(common.converter_si'
232
234
233 # If the file requires actual merging, abort. We don't have enough
235 # If the file requires actual merging, abort. We don't have enough
234 # context to resolve merges correctly.
236 # context to resolve merges correctly.
235 if action in ['m', 'dm', 'cd', 'dc']:
237 if action in [b'm', b'dm', b'cd', b'dc']:
236 raise error.Abort(
238 raise error.Abort(
237 _(
239 _(
238 "unable to convert merge commit "
240 b"unable to convert merge commit "
239 "since target parents do not merge cleanly (file "
241 b"since target parents do not merge cleanly (file "
240 "%s, parents %s and %s)"
242 b"%s, parents %s and %s)"
241 )
243 )
242 % (file, p1ctx, p2ctx)
244 % (file, p1ctx, p2ctx)
243 )
245 )
244 elif action == 'k':
246 elif action == b'k':
245 # 'keep' means nothing changed from p1
247 # 'keep' means nothing changed from p1
246 continue
248 continue
247 else:
249 else:
@@ -255,7 +257,7 b' class mercurial_sink(common.converter_si'
255
257
256 def getfilectx(repo, memctx, f):
258 def getfilectx(repo, memctx, f):
257 if p2ctx and f in p2files and f not in copies:
259 if p2ctx and f in p2files and f not in copies:
258 self.ui.debug('reusing %s from p2\n' % f)
260 self.ui.debug(b'reusing %s from p2\n' % f)
259 try:
261 try:
260 return p2ctx[f]
262 return p2ctx[f]
261 except error.ManifestLookupError:
263 except error.ManifestLookupError:
@@ -269,17 +271,17 b' class mercurial_sink(common.converter_si'
269 data, mode = source.getfile(f, v)
271 data, mode = source.getfile(f, v)
270 if data is None:
272 if data is None:
271 return None
273 return None
272 if f == '.hgtags':
274 if f == b'.hgtags':
273 data = self._rewritetags(source, revmap, data)
275 data = self._rewritetags(source, revmap, data)
274 if f == '.hgsubstate':
276 if f == b'.hgsubstate':
275 data = self._rewritesubstate(source, data)
277 data = self._rewritesubstate(source, data)
276 return context.memfilectx(
278 return context.memfilectx(
277 self.repo,
279 self.repo,
278 memctx,
280 memctx,
279 f,
281 f,
280 data,
282 data,
281 'l' in mode,
283 b'l' in mode,
282 'x' in mode,
284 b'x' in mode,
283 copies.get(f),
285 copies.get(f),
284 )
286 )
285
287
@@ -310,15 +312,15 b' class mercurial_sink(common.converter_si'
310
312
311 extra = commit.extra.copy()
313 extra = commit.extra.copy()
312
314
313 sourcename = self.repo.ui.config('convert', 'hg.sourcename')
315 sourcename = self.repo.ui.config(b'convert', b'hg.sourcename')
314 if sourcename:
316 if sourcename:
315 extra['convert_source'] = sourcename
317 extra[b'convert_source'] = sourcename
316
318
317 for label in (
319 for label in (
318 'source',
320 b'source',
319 'transplant_source',
321 b'transplant_source',
320 'rebase_source',
322 b'rebase_source',
321 'intermediate-source',
323 b'intermediate-source',
322 ):
324 ):
323 node = extra.get(label)
325 node = extra.get(label)
324
326
@@ -326,20 +328,20 b' class mercurial_sink(common.converter_si'
326 continue
328 continue
327
329
328 # Only transplant stores its reference in binary
330 # Only transplant stores its reference in binary
329 if label == 'transplant_source':
331 if label == b'transplant_source':
330 node = nodemod.hex(node)
332 node = nodemod.hex(node)
331
333
332 newrev = revmap.get(node)
334 newrev = revmap.get(node)
333 if newrev is not None:
335 if newrev is not None:
334 if label == 'transplant_source':
336 if label == b'transplant_source':
335 newrev = nodemod.bin(newrev)
337 newrev = nodemod.bin(newrev)
336
338
337 extra[label] = newrev
339 extra[label] = newrev
338
340
339 if self.branchnames and commit.branch:
341 if self.branchnames and commit.branch:
340 extra['branch'] = commit.branch
342 extra[b'branch'] = commit.branch
341 if commit.rev and commit.saverev:
343 if commit.rev and commit.saverev:
342 extra['convert_revision'] = commit.rev
344 extra[b'convert_revision'] = commit.rev
343
345
344 while parents:
346 while parents:
345 p1 = p2
347 p1 = p2
@@ -373,14 +375,14 b' class mercurial_sink(common.converter_si'
373 # We won't know if the conversion changes the node until after the
375 # We won't know if the conversion changes the node until after the
374 # commit, so copy the source's phase for now.
376 # commit, so copy the source's phase for now.
375 self.repo.ui.setconfig(
377 self.repo.ui.setconfig(
376 'phases',
378 b'phases',
377 'new-commit',
379 b'new-commit',
378 phases.phasenames[commit.phase],
380 phases.phasenames[commit.phase],
379 'convert',
381 b'convert',
380 )
382 )
381
383
382 with self.repo.transaction("convert") as tr:
384 with self.repo.transaction(b"convert") as tr:
383 if self.repo.ui.config('convert', 'hg.preserve-hash'):
385 if self.repo.ui.config(b'convert', b'hg.preserve-hash'):
384 origctx = commit.ctx
386 origctx = commit.ctx
385 else:
387 else:
386 origctx = None
388 origctx = None
@@ -396,15 +398,15 b' class mercurial_sink(common.converter_si'
396 self.repo, tr, phases.draft, [ctx.node()]
398 self.repo, tr, phases.draft, [ctx.node()]
397 )
399 )
398
400
399 text = "(octopus merge fixup)\n"
401 text = b"(octopus merge fixup)\n"
400 p2 = node
402 p2 = node
401
403
402 if self.filemapmode and nparents == 1:
404 if self.filemapmode and nparents == 1:
403 man = self.repo.manifestlog.getstorage(b'')
405 man = self.repo.manifestlog.getstorage(b'')
404 mnode = self.repo.changelog.read(nodemod.bin(p2))[0]
406 mnode = self.repo.changelog.read(nodemod.bin(p2))[0]
405 closed = 'close' in commit.extra
407 closed = b'close' in commit.extra
406 if not closed and not man.cmp(m1node, man.revision(mnode)):
408 if not closed and not man.cmp(m1node, man.revision(mnode)):
407 self.ui.status(_("filtering out empty revision\n"))
409 self.ui.status(_(b"filtering out empty revision\n"))
408 self.repo.rollback(force=True)
410 self.repo.rollback(force=True)
409 return parent
411 return parent
410 return p2
412 return p2
@@ -416,13 +418,13 b' class mercurial_sink(common.converter_si'
416 oldlines = set()
418 oldlines = set()
417 for branch, heads in self.repo.branchmap().iteritems():
419 for branch, heads in self.repo.branchmap().iteritems():
418 for h in heads:
420 for h in heads:
419 if '.hgtags' in self.repo[h]:
421 if b'.hgtags' in self.repo[h]:
420 oldlines.update(
422 oldlines.update(
421 set(self.repo[h]['.hgtags'].data().splitlines(True))
423 set(self.repo[h][b'.hgtags'].data().splitlines(True))
422 )
424 )
423 oldlines = sorted(list(oldlines))
425 oldlines = sorted(list(oldlines))
424
426
425 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
427 newlines = sorted([(b"%s %s\n" % (tags[tag], tag)) for tag in tags])
426 if newlines == oldlines:
428 if newlines == oldlines:
427 return None, None
429 return None, None
428
430
@@ -430,12 +432,12 b' class mercurial_sink(common.converter_si'
430 oldtags = set()
432 oldtags = set()
431 newtags = set()
433 newtags = set()
432 for line in oldlines:
434 for line in oldlines:
433 s = line.strip().split(' ', 1)
435 s = line.strip().split(b' ', 1)
434 if len(s) != 2:
436 if len(s) != 2:
435 continue
437 continue
436 oldtags.add(s[1])
438 oldtags.add(s[1])
437 for line in newlines:
439 for line in newlines:
438 s = line.strip().split(' ', 1)
440 s = line.strip().split(b' ', 1)
439 if len(s) != 2:
441 if len(s) != 2:
440 continue
442 continue
441 if s[1] not in oldtags:
443 if s[1] not in oldtags:
@@ -444,21 +446,21 b' class mercurial_sink(common.converter_si'
444 if not newtags:
446 if not newtags:
445 return None, None
447 return None, None
446
448
447 data = "".join(newlines)
449 data = b"".join(newlines)
448
450
449 def getfilectx(repo, memctx, f):
451 def getfilectx(repo, memctx, f):
450 return context.memfilectx(repo, memctx, f, data, False, False, None)
452 return context.memfilectx(repo, memctx, f, data, False, False, None)
451
453
452 self.ui.status(_("updating tags\n"))
454 self.ui.status(_(b"updating tags\n"))
453 date = "%d 0" % int(time.mktime(time.gmtime()))
455 date = b"%d 0" % int(time.mktime(time.gmtime()))
454 extra = {'branch': self.tagsbranch}
456 extra = {b'branch': self.tagsbranch}
455 ctx = context.memctx(
457 ctx = context.memctx(
456 self.repo,
458 self.repo,
457 (tagparent, None),
459 (tagparent, None),
458 "update tags",
460 b"update tags",
459 [".hgtags"],
461 [b".hgtags"],
460 getfilectx,
462 getfilectx,
461 "convert-repo",
463 b"convert-repo",
462 date,
464 date,
463 extra,
465 extra,
464 )
466 )
@@ -475,8 +477,8 b' class mercurial_sink(common.converter_si'
475 try:
477 try:
476 wlock = self.repo.wlock()
478 wlock = self.repo.wlock()
477 lock = self.repo.lock()
479 lock = self.repo.lock()
478 tr = self.repo.transaction('bookmark')
480 tr = self.repo.transaction(b'bookmark')
479 self.ui.status(_("updating bookmarks\n"))
481 self.ui.status(_(b"updating bookmarks\n"))
480 destmarks = self.repo._bookmarks
482 destmarks = self.repo._bookmarks
481 changes = [
483 changes = [
482 (bookmark, nodemod.bin(updatedbookmark[bookmark]))
484 (bookmark, nodemod.bin(updatedbookmark[bookmark]))
@@ -495,9 +497,9 b' class mercurial_sink(common.converter_si'
495 if rev not in self.repo and self.clonebranches:
497 if rev not in self.repo and self.clonebranches:
496 raise error.Abort(
498 raise error.Abort(
497 _(
499 _(
498 'revision %s not found in destination '
500 b'revision %s not found in destination '
499 'repository (lookups with clonebranches=true '
501 b'repository (lookups with clonebranches=true '
500 'are not implemented)'
502 b'are not implemented)'
501 )
503 )
502 % rev
504 % rev
503 )
505 )
@@ -507,9 +509,9 b' class mercurial_sink(common.converter_si'
507 class mercurial_source(common.converter_source):
509 class mercurial_source(common.converter_source):
508 def __init__(self, ui, repotype, path, revs=None):
510 def __init__(self, ui, repotype, path, revs=None):
509 common.converter_source.__init__(self, ui, repotype, path, revs)
511 common.converter_source.__init__(self, ui, repotype, path, revs)
510 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors')
512 self.ignoreerrors = ui.configbool(b'convert', b'hg.ignoreerrors')
511 self.ignored = set()
513 self.ignored = set()
512 self.saverev = ui.configbool('convert', 'hg.saverev')
514 self.saverev = ui.configbool(b'convert', b'hg.saverev')
513 try:
515 try:
514 self.repo = hg.repository(self.ui, path)
516 self.repo = hg.repository(self.ui, path)
515 # try to provoke an exception if this isn't really a hg
517 # try to provoke an exception if this isn't really a hg
@@ -518,21 +520,21 b' class mercurial_source(common.converter_'
518 raise error.RepoError
520 raise error.RepoError
519 except error.RepoError:
521 except error.RepoError:
520 ui.traceback()
522 ui.traceback()
521 raise NoRepo(_("%s is not a local Mercurial repository") % path)
523 raise NoRepo(_(b"%s is not a local Mercurial repository") % path)
522 self.lastrev = None
524 self.lastrev = None
523 self.lastctx = None
525 self.lastctx = None
524 self._changescache = None, None
526 self._changescache = None, None
525 self.convertfp = None
527 self.convertfp = None
526 # Restrict converted revisions to startrev descendants
528 # Restrict converted revisions to startrev descendants
527 startnode = ui.config('convert', 'hg.startrev')
529 startnode = ui.config(b'convert', b'hg.startrev')
528 hgrevs = ui.config('convert', 'hg.revs')
530 hgrevs = ui.config(b'convert', b'hg.revs')
529 if hgrevs is None:
531 if hgrevs is None:
530 if startnode is not None:
532 if startnode is not None:
531 try:
533 try:
532 startnode = self.repo.lookup(startnode)
534 startnode = self.repo.lookup(startnode)
533 except error.RepoError:
535 except error.RepoError:
534 raise error.Abort(
536 raise error.Abort(
535 _('%s is not a valid start revision') % startnode
537 _(b'%s is not a valid start revision') % startnode
536 )
538 )
537 startrev = self.repo.changelog.rev(startnode)
539 startrev = self.repo.changelog.rev(startnode)
538 children = {startnode: 1}
540 children = {startnode: 1}
@@ -548,7 +550,10 b' class mercurial_source(common.converter_'
548 else:
550 else:
549 if revs or startnode is not None:
551 if revs or startnode is not None:
550 raise error.Abort(
552 raise error.Abort(
551 _('hg.revs cannot be combined with ' 'hg.startrev or --rev')
553 _(
554 b'hg.revs cannot be combined with '
555 b'hg.startrev or --rev'
556 )
552 )
557 )
553 nodes = set()
558 nodes = set()
554 parents = set()
559 parents = set()
@@ -635,7 +640,7 b' class mercurial_source(common.converter_'
635 if not self.ignoreerrors:
640 if not self.ignoreerrors:
636 raise
641 raise
637 self.ignored.add(name)
642 self.ignored.add(name)
638 self.ui.warn(_('ignoring: %s\n') % e)
643 self.ui.warn(_(b'ignoring: %s\n') % e)
639 return copies
644 return copies
640
645
641 def getcommit(self, rev):
646 def getcommit(self, rev):
@@ -647,7 +652,7 b' class mercurial_source(common.converter_'
647
652
648 return common.commit(
653 return common.commit(
649 author=ctx.user(),
654 author=ctx.user(),
650 date=dateutil.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
655 date=dateutil.datestr(ctx.date(), b'%Y-%m-%d %H:%M:%S %1%2'),
651 desc=ctx.description(),
656 desc=ctx.description(),
652 rev=crev,
657 rev=crev,
653 parents=parents,
658 parents=parents,
@@ -668,7 +673,7 b' class mercurial_source(common.converter_'
668 tags = [
673 tags = [
669 t
674 t
670 for t in self.repo.tagslist()
675 for t in self.repo.tagslist()
671 if self.repo.tagtype(t[0]) == 'global'
676 if self.repo.tagtype(t[0]) == b'global'
672 ]
677 ]
673 return dict(
678 return dict(
674 [
679 [
@@ -696,15 +701,15 b' class mercurial_source(common.converter_'
696
701
697 def converted(self, rev, destrev):
702 def converted(self, rev, destrev):
698 if self.convertfp is None:
703 if self.convertfp is None:
699 self.convertfp = open(self.repo.vfs.join('shamap'), 'ab')
704 self.convertfp = open(self.repo.vfs.join(b'shamap'), b'ab')
700 self.convertfp.write(util.tonativeeol('%s %s\n' % (destrev, rev)))
705 self.convertfp.write(util.tonativeeol(b'%s %s\n' % (destrev, rev)))
701 self.convertfp.flush()
706 self.convertfp.flush()
702
707
703 def before(self):
708 def before(self):
704 self.ui.debug('run hg source pre-conversion action\n')
709 self.ui.debug(b'run hg source pre-conversion action\n')
705
710
706 def after(self):
711 def after(self):
707 self.ui.debug('run hg source post-conversion action\n')
712 self.ui.debug(b'run hg source post-conversion action\n')
708
713
709 def hasnativeorder(self):
714 def hasnativeorder(self):
710 return True
715 return True
@@ -721,6 +726,6 b' class mercurial_source(common.converter_'
721 def getbookmarks(self):
726 def getbookmarks(self):
722 return bookmarks.listbookmarks(self.repo)
727 return bookmarks.listbookmarks(self.repo)
723
728
724 def checkrevformat(self, revstr, mapname='splicemap'):
729 def checkrevformat(self, revstr, mapname=b'splicemap'):
725 """ Mercurial, revision string is a 40 byte hex """
730 """ Mercurial, revision string is a 40 byte hex """
726 self.checkhexformat(revstr, mapname)
731 self.checkhexformat(revstr, mapname)
@@ -26,11 +26,11 b' class monotone_source(common.converter_s'
26 if revs and len(revs) > 1:
26 if revs and len(revs) > 1:
27 raise error.Abort(
27 raise error.Abort(
28 _(
28 _(
29 'monotone source does not support specifying '
29 b'monotone source does not support specifying '
30 'multiple revs'
30 b'multiple revs'
31 )
31 )
32 )
32 )
33 common.commandline.__init__(self, ui, 'mtn')
33 common.commandline.__init__(self, ui, b'mtn')
34
34
35 self.ui = ui
35 self.ui = ui
36 self.path = path
36 self.path = path
@@ -38,17 +38,17 b' class monotone_source(common.converter_s'
38 self.revs = revs
38 self.revs = revs
39
39
40 norepo = common.NoRepo(
40 norepo = common.NoRepo(
41 _("%s does not look like a monotone repository") % path
41 _(b"%s does not look like a monotone repository") % path
42 )
42 )
43 if not os.path.exists(os.path.join(path, '_MTN')):
43 if not os.path.exists(os.path.join(path, b'_MTN')):
44 # Could be a monotone repository (SQLite db file)
44 # Could be a monotone repository (SQLite db file)
45 try:
45 try:
46 f = open(path, 'rb')
46 f = open(path, b'rb')
47 header = f.read(16)
47 header = f.read(16)
48 f.close()
48 f.close()
49 except IOError:
49 except IOError:
50 header = ''
50 header = b''
51 if header != 'SQLite format 3\x00':
51 if header != b'SQLite format 3\x00':
52 raise norepo
52 raise norepo
53
53
54 # regular expressions for parsing monotone output
54 # regular expressions for parsing monotone output
@@ -58,24 +58,26 b' class monotone_source(common.converter_s'
58 revision = br'\s+\[(\w+)\]\s*'
58 revision = br'\s+\[(\w+)\]\s*'
59 lines = br'(?:.|\n)+'
59 lines = br'(?:.|\n)+'
60
60
61 self.dir_re = re.compile(space + "dir" + name)
61 self.dir_re = re.compile(space + b"dir" + name)
62 self.file_re = re.compile(space + "file" + name + "content" + revision)
62 self.file_re = re.compile(
63 space + b"file" + name + b"content" + revision
64 )
63 self.add_file_re = re.compile(
65 self.add_file_re = re.compile(
64 space + "add_file" + name + "content" + revision
66 space + b"add_file" + name + b"content" + revision
65 )
67 )
66 self.patch_re = re.compile(
68 self.patch_re = re.compile(
67 space + "patch" + name + "from" + revision + "to" + revision
69 space + b"patch" + name + b"from" + revision + b"to" + revision
68 )
70 )
69 self.rename_re = re.compile(space + "rename" + name + "to" + name)
71 self.rename_re = re.compile(space + b"rename" + name + b"to" + name)
70 self.delete_re = re.compile(space + "delete" + name)
72 self.delete_re = re.compile(space + b"delete" + name)
71 self.tag_re = re.compile(space + "tag" + name + "revision" + revision)
73 self.tag_re = re.compile(space + b"tag" + name + b"revision" + revision)
72 self.cert_re = re.compile(
74 self.cert_re = re.compile(
73 lines + space + "name" + name + "value" + value
75 lines + space + b"name" + name + b"value" + value
74 )
76 )
75
77
76 attr = space + "file" + lines + space + "attr" + space
78 attr = space + b"file" + lines + space + b"attr" + space
77 self.attr_execute_re = re.compile(
79 self.attr_execute_re = re.compile(
78 attr + '"mtn:execute"' + space + '"true"'
80 attr + b'"mtn:execute"' + space + b'"true"'
79 )
81 )
80
82
81 # cached data
83 # cached data
@@ -84,7 +86,7 b' class monotone_source(common.converter_s'
84 self.files = None
86 self.files = None
85 self.dirs = None
87 self.dirs = None
86
88
87 common.checktool('mtn', abort=False)
89 common.checktool(b'mtn', abort=False)
88
90
89 def mtnrun(self, *args, **kwargs):
91 def mtnrun(self, *args, **kwargs):
90 if self.automatestdio:
92 if self.automatestdio:
@@ -94,27 +96,27 b' class monotone_source(common.converter_s'
94
96
95 def mtnrunsingle(self, *args, **kwargs):
97 def mtnrunsingle(self, *args, **kwargs):
96 kwargs[r'd'] = self.path
98 kwargs[r'd'] = self.path
97 return self.run0('automate', *args, **kwargs)
99 return self.run0(b'automate', *args, **kwargs)
98
100
99 def mtnrunstdio(self, *args, **kwargs):
101 def mtnrunstdio(self, *args, **kwargs):
100 # Prepare the command in automate stdio format
102 # Prepare the command in automate stdio format
101 kwargs = pycompat.byteskwargs(kwargs)
103 kwargs = pycompat.byteskwargs(kwargs)
102 command = []
104 command = []
103 for k, v in kwargs.iteritems():
105 for k, v in kwargs.iteritems():
104 command.append("%d:%s" % (len(k), k))
106 command.append(b"%d:%s" % (len(k), k))
105 if v:
107 if v:
106 command.append("%d:%s" % (len(v), v))
108 command.append(b"%d:%s" % (len(v), v))
107 if command:
109 if command:
108 command.insert(0, 'o')
110 command.insert(0, b'o')
109 command.append('e')
111 command.append(b'e')
110
112
111 command.append('l')
113 command.append(b'l')
112 for arg in args:
114 for arg in args:
113 command.append("%d:%s" % (len(arg), arg))
115 command.append(b"%d:%s" % (len(arg), arg))
114 command.append('e')
116 command.append(b'e')
115 command = ''.join(command)
117 command = b''.join(command)
116
118
117 self.ui.debug("mtn: sending '%s'\n" % command)
119 self.ui.debug(b"mtn: sending '%s'\n" % command)
118 self.mtnwritefp.write(command)
120 self.mtnwritefp.write(command)
119 self.mtnwritefp.flush()
121 self.mtnwritefp.flush()
120
122
@@ -122,42 +124,44 b' class monotone_source(common.converter_s'
122
124
123 def mtnstdioreadpacket(self):
125 def mtnstdioreadpacket(self):
124 read = None
126 read = None
125 commandnbr = ''
127 commandnbr = b''
126 while read != ':':
128 while read != b':':
127 read = self.mtnreadfp.read(1)
129 read = self.mtnreadfp.read(1)
128 if not read:
130 if not read:
129 raise error.Abort(_('bad mtn packet - no end of commandnbr'))
131 raise error.Abort(_(b'bad mtn packet - no end of commandnbr'))
130 commandnbr += read
132 commandnbr += read
131 commandnbr = commandnbr[:-1]
133 commandnbr = commandnbr[:-1]
132
134
133 stream = self.mtnreadfp.read(1)
135 stream = self.mtnreadfp.read(1)
134 if stream not in 'mewptl':
136 if stream not in b'mewptl':
135 raise error.Abort(_('bad mtn packet - bad stream type %s') % stream)
137 raise error.Abort(
138 _(b'bad mtn packet - bad stream type %s') % stream
139 )
136
140
137 read = self.mtnreadfp.read(1)
141 read = self.mtnreadfp.read(1)
138 if read != ':':
142 if read != b':':
139 raise error.Abort(_('bad mtn packet - no divider before size'))
143 raise error.Abort(_(b'bad mtn packet - no divider before size'))
140
144
141 read = None
145 read = None
142 lengthstr = ''
146 lengthstr = b''
143 while read != ':':
147 while read != b':':
144 read = self.mtnreadfp.read(1)
148 read = self.mtnreadfp.read(1)
145 if not read:
149 if not read:
146 raise error.Abort(_('bad mtn packet - no end of packet size'))
150 raise error.Abort(_(b'bad mtn packet - no end of packet size'))
147 lengthstr += read
151 lengthstr += read
148 try:
152 try:
149 length = pycompat.long(lengthstr[:-1])
153 length = pycompat.long(lengthstr[:-1])
150 except TypeError:
154 except TypeError:
151 raise error.Abort(
155 raise error.Abort(
152 _('bad mtn packet - bad packet size %s') % lengthstr
156 _(b'bad mtn packet - bad packet size %s') % lengthstr
153 )
157 )
154
158
155 read = self.mtnreadfp.read(length)
159 read = self.mtnreadfp.read(length)
156 if len(read) != length:
160 if len(read) != length:
157 raise error.Abort(
161 raise error.Abort(
158 _(
162 _(
159 "bad mtn packet - unable to read full packet "
163 b"bad mtn packet - unable to read full packet "
160 "read %s of %s"
164 b"read %s of %s"
161 )
165 )
162 % (len(read), length)
166 % (len(read), length)
163 )
167 )
@@ -169,33 +173,33 b' class monotone_source(common.converter_s'
169 while True:
173 while True:
170 commandnbr, stream, length, output = self.mtnstdioreadpacket()
174 commandnbr, stream, length, output = self.mtnstdioreadpacket()
171 self.ui.debug(
175 self.ui.debug(
172 'mtn: read packet %s:%s:%d\n' % (commandnbr, stream, length)
176 b'mtn: read packet %s:%s:%d\n' % (commandnbr, stream, length)
173 )
177 )
174
178
175 if stream == 'l':
179 if stream == b'l':
176 # End of command
180 # End of command
177 if output != '0':
181 if output != b'0':
178 raise error.Abort(
182 raise error.Abort(
179 _("mtn command '%s' returned %s") % (command, output)
183 _(b"mtn command '%s' returned %s") % (command, output)
180 )
184 )
181 break
185 break
182 elif stream in 'ew':
186 elif stream in b'ew':
183 # Error, warning output
187 # Error, warning output
184 self.ui.warn(_('%s error:\n') % self.command)
188 self.ui.warn(_(b'%s error:\n') % self.command)
185 self.ui.warn(output)
189 self.ui.warn(output)
186 elif stream == 'p':
190 elif stream == b'p':
187 # Progress messages
191 # Progress messages
188 self.ui.debug('mtn: ' + output)
192 self.ui.debug(b'mtn: ' + output)
189 elif stream == 'm':
193 elif stream == b'm':
190 # Main stream - command output
194 # Main stream - command output
191 retval.append(output)
195 retval.append(output)
192
196
193 return ''.join(retval)
197 return b''.join(retval)
194
198
195 def mtnloadmanifest(self, rev):
199 def mtnloadmanifest(self, rev):
196 if self.manifest_rev == rev:
200 if self.manifest_rev == rev:
197 return
201 return
198 self.manifest = self.mtnrun("get_manifest_of", rev).split("\n\n")
202 self.manifest = self.mtnrun(b"get_manifest_of", rev).split(b"\n\n")
199 self.manifest_rev = rev
203 self.manifest_rev = rev
200 self.files = {}
204 self.files = {}
201 self.dirs = {}
205 self.dirs = {}
@@ -203,11 +207,11 b' class monotone_source(common.converter_s'
203 for e in self.manifest:
207 for e in self.manifest:
204 m = self.file_re.match(e)
208 m = self.file_re.match(e)
205 if m:
209 if m:
206 attr = ""
210 attr = b""
207 name = m.group(1)
211 name = m.group(1)
208 node = m.group(2)
212 node = m.group(2)
209 if self.attr_execute_re.match(e):
213 if self.attr_execute_re.match(e):
210 attr += "x"
214 attr += b"x"
211 self.files[name] = (node, attr)
215 self.files[name] = (node, attr)
212 m = self.dir_re.match(e)
216 m = self.dir_re.match(e)
213 if m:
217 if m:
@@ -224,12 +228,12 b' class monotone_source(common.converter_s'
224
228
225 def mtngetcerts(self, rev):
229 def mtngetcerts(self, rev):
226 certs = {
230 certs = {
227 "author": "<missing>",
231 b"author": b"<missing>",
228 "date": "<missing>",
232 b"date": b"<missing>",
229 "changelog": "<missing>",
233 b"changelog": b"<missing>",
230 "branch": "<missing>",
234 b"branch": b"<missing>",
231 }
235 }
232 certlist = self.mtnrun("certs", rev)
236 certlist = self.mtnrun(b"certs", rev)
233 # mtn < 0.45:
237 # mtn < 0.45:
234 # key "test@selenic.com"
238 # key "test@selenic.com"
235 # mtn >= 0.45:
239 # mtn >= 0.45:
@@ -239,28 +243,28 b' class monotone_source(common.converter_s'
239 m = self.cert_re.match(e)
243 m = self.cert_re.match(e)
240 if m:
244 if m:
241 name, value = m.groups()
245 name, value = m.groups()
242 value = value.replace(br'\"', '"')
246 value = value.replace(br'\"', b'"')
243 value = value.replace(br'\\', '\\')
247 value = value.replace(br'\\', b'\\')
244 certs[name] = value
248 certs[name] = value
245 # Monotone may have subsecond dates: 2005-02-05T09:39:12.364306
249 # Monotone may have subsecond dates: 2005-02-05T09:39:12.364306
246 # and all times are stored in UTC
250 # and all times are stored in UTC
247 certs["date"] = certs["date"].split('.')[0] + " UTC"
251 certs[b"date"] = certs[b"date"].split(b'.')[0] + b" UTC"
248 return certs
252 return certs
249
253
250 # implement the converter_source interface:
254 # implement the converter_source interface:
251
255
252 def getheads(self):
256 def getheads(self):
253 if not self.revs:
257 if not self.revs:
254 return self.mtnrun("leaves").splitlines()
258 return self.mtnrun(b"leaves").splitlines()
255 else:
259 else:
256 return self.revs
260 return self.revs
257
261
258 def getchanges(self, rev, full):
262 def getchanges(self, rev, full):
259 if full:
263 if full:
260 raise error.Abort(
264 raise error.Abort(
261 _("convert from monotone does not support " "--full")
265 _(b"convert from monotone does not support " b"--full")
262 )
266 )
263 revision = self.mtnrun("get_revision", rev).split("\n\n")
267 revision = self.mtnrun(b"get_revision", rev).split(b"\n\n")
264 files = {}
268 files = {}
265 ignoremove = {}
269 ignoremove = {}
266 renameddirs = []
270 renameddirs = []
@@ -298,7 +302,7 b' class monotone_source(common.converter_s'
298 for tofile in self.files:
302 for tofile in self.files:
299 if tofile in ignoremove:
303 if tofile in ignoremove:
300 continue
304 continue
301 if tofile.startswith(todir + '/'):
305 if tofile.startswith(todir + b'/'):
302 renamed[tofile] = fromdir + tofile[len(todir) :]
306 renamed[tofile] = fromdir + tofile[len(todir) :]
303 # Avoid chained moves like:
307 # Avoid chained moves like:
304 # d1(/a) => d3/d1(/a)
308 # d1(/a) => d3/d1(/a)
@@ -306,9 +310,9 b' class monotone_source(common.converter_s'
306 ignoremove[tofile] = 1
310 ignoremove[tofile] = 1
307 for tofile, fromfile in renamed.items():
311 for tofile, fromfile in renamed.items():
308 self.ui.debug(
312 self.ui.debug(
309 "copying file in renamed directory from '%s' to '%s'"
313 b"copying file in renamed directory from '%s' to '%s'"
310 % (fromfile, tofile),
314 % (fromfile, tofile),
311 '\n',
315 b'\n',
312 )
316 )
313 files[tofile] = rev
317 files[tofile] = rev
314 copies[tofile] = fromfile
318 copies[tofile] = fromfile
@@ -321,32 +325,32 b' class monotone_source(common.converter_s'
321 if not self.mtnisfile(name, rev):
325 if not self.mtnisfile(name, rev):
322 return None, None
326 return None, None
323 try:
327 try:
324 data = self.mtnrun("get_file_of", name, r=rev)
328 data = self.mtnrun(b"get_file_of", name, r=rev)
325 except Exception:
329 except Exception:
326 return None, None
330 return None, None
327 self.mtnloadmanifest(rev)
331 self.mtnloadmanifest(rev)
328 node, attr = self.files.get(name, (None, ""))
332 node, attr = self.files.get(name, (None, b""))
329 return data, attr
333 return data, attr
330
334
331 def getcommit(self, rev):
335 def getcommit(self, rev):
332 extra = {}
336 extra = {}
333 certs = self.mtngetcerts(rev)
337 certs = self.mtngetcerts(rev)
334 if certs.get('suspend') == certs["branch"]:
338 if certs.get(b'suspend') == certs[b"branch"]:
335 extra['close'] = 1
339 extra[b'close'] = 1
336 dateformat = "%Y-%m-%dT%H:%M:%S"
340 dateformat = b"%Y-%m-%dT%H:%M:%S"
337 return common.commit(
341 return common.commit(
338 author=certs["author"],
342 author=certs[b"author"],
339 date=dateutil.datestr(dateutil.strdate(certs["date"], dateformat)),
343 date=dateutil.datestr(dateutil.strdate(certs[b"date"], dateformat)),
340 desc=certs["changelog"],
344 desc=certs[b"changelog"],
341 rev=rev,
345 rev=rev,
342 parents=self.mtnrun("parents", rev).splitlines(),
346 parents=self.mtnrun(b"parents", rev).splitlines(),
343 branch=certs["branch"],
347 branch=certs[b"branch"],
344 extra=extra,
348 extra=extra,
345 )
349 )
346
350
347 def gettags(self):
351 def gettags(self):
348 tags = {}
352 tags = {}
349 for e in self.mtnrun("tags").split("\n\n"):
353 for e in self.mtnrun(b"tags").split(b"\n\n"):
350 m = self.tag_re.match(e)
354 m = self.tag_re.match(e)
351 if m:
355 if m:
352 tags[m.group(1)] = m.group(2)
356 tags[m.group(1)] = m.group(2)
@@ -360,42 +364,42 b' class monotone_source(common.converter_s'
360 def before(self):
364 def before(self):
361 # Check if we have a new enough version to use automate stdio
365 # Check if we have a new enough version to use automate stdio
362 try:
366 try:
363 versionstr = self.mtnrunsingle("interface_version")
367 versionstr = self.mtnrunsingle(b"interface_version")
364 version = float(versionstr)
368 version = float(versionstr)
365 except Exception:
369 except Exception:
366 raise error.Abort(
370 raise error.Abort(
367 _("unable to determine mtn automate interface " "version")
371 _(b"unable to determine mtn automate interface " b"version")
368 )
372 )
369
373
370 if version >= 12.0:
374 if version >= 12.0:
371 self.automatestdio = True
375 self.automatestdio = True
372 self.ui.debug(
376 self.ui.debug(
373 "mtn automate version %f - using automate stdio\n" % version
377 b"mtn automate version %f - using automate stdio\n" % version
374 )
378 )
375
379
376 # launch the long-running automate stdio process
380 # launch the long-running automate stdio process
377 self.mtnwritefp, self.mtnreadfp = self._run2(
381 self.mtnwritefp, self.mtnreadfp = self._run2(
378 'automate', 'stdio', '-d', self.path
382 b'automate', b'stdio', b'-d', self.path
379 )
383 )
380 # read the headers
384 # read the headers
381 read = self.mtnreadfp.readline()
385 read = self.mtnreadfp.readline()
382 if read != 'format-version: 2\n':
386 if read != b'format-version: 2\n':
383 raise error.Abort(
387 raise error.Abort(
384 _('mtn automate stdio header unexpected: %s') % read
388 _(b'mtn automate stdio header unexpected: %s') % read
385 )
389 )
386 while read != '\n':
390 while read != b'\n':
387 read = self.mtnreadfp.readline()
391 read = self.mtnreadfp.readline()
388 if not read:
392 if not read:
389 raise error.Abort(
393 raise error.Abort(
390 _(
394 _(
391 "failed to reach end of mtn automate "
395 b"failed to reach end of mtn automate "
392 "stdio headers"
396 b"stdio headers"
393 )
397 )
394 )
398 )
395 else:
399 else:
396 self.ui.debug(
400 self.ui.debug(
397 "mtn automate version %s - not using automate stdio "
401 b"mtn automate version %s - not using automate stdio "
398 "(automate >= 12.0 - mtn >= 0.46 is needed)\n" % version
402 b"(automate >= 12.0 - mtn >= 0.46 is needed)\n" % version
399 )
403 )
400
404
401 def after(self):
405 def after(self):
@@ -24,7 +24,7 b' from . import common'
24
24
25
25
26 def loaditer(f):
26 def loaditer(f):
27 "Yield the dictionary objects generated by p4"
27 b"Yield the dictionary objects generated by p4"
28 try:
28 try:
29 while True:
29 while True:
30 d = marshal.load(f)
30 d = marshal.load(f)
@@ -44,7 +44,12 b' def decodefilename(filename):'
44 >>> decodefilename(b'//Depot/Directory/%2525/%2523/%23%40.%2A')
44 >>> decodefilename(b'//Depot/Directory/%2525/%2523/%23%40.%2A')
45 '//Depot/Directory/%25/%23/#@.*'
45 '//Depot/Directory/%25/%23/#@.*'
46 """
46 """
47 replacements = [('%2A', '*'), ('%23', '#'), ('%40', '@'), ('%25', '%')]
47 replacements = [
48 (b'%2A', b'*'),
49 (b'%23', b'#'),
50 (b'%40', b'@'),
51 (b'%25', b'%'),
52 ]
48 for k, v in replacements:
53 for k, v in replacements:
49 filename = filename.replace(k, v)
54 filename = filename.replace(k, v)
50 return filename
55 return filename
@@ -57,16 +62,16 b' class p4_source(common.converter_source)'
57
62
58 super(p4_source, self).__init__(ui, repotype, path, revs=revs)
63 super(p4_source, self).__init__(ui, repotype, path, revs=revs)
59
64
60 if "/" in path and not path.startswith('//'):
65 if b"/" in path and not path.startswith(b'//'):
61 raise common.NoRepo(
66 raise common.NoRepo(
62 _('%s does not look like a P4 repository') % path
67 _(b'%s does not look like a P4 repository') % path
63 )
68 )
64
69
65 common.checktool('p4', abort=False)
70 common.checktool(b'p4', abort=False)
66
71
67 self.revmap = {}
72 self.revmap = {}
68 self.encoding = self.ui.config(
73 self.encoding = self.ui.config(
69 'convert', 'p4.encoding', convcmd.orig_encoding
74 b'convert', b'p4.encoding', convcmd.orig_encoding
70 )
75 )
71 self.re_type = re.compile(
76 self.re_type = re.compile(
72 br"([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
77 br"([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
@@ -80,7 +85,10 b' class p4_source(common.converter_source)'
80
85
81 if revs and len(revs) > 1:
86 if revs and len(revs) > 1:
82 raise error.Abort(
87 raise error.Abort(
83 _("p4 source does not support specifying " "multiple revisions")
88 _(
89 b"p4 source does not support specifying "
90 b"multiple revisions"
91 )
84 )
92 )
85
93
86 def setrevmap(self, revmap):
94 def setrevmap(self, revmap):
@@ -97,18 +105,18 b' class p4_source(common.converter_source)'
97 self.revmap = revmap
105 self.revmap = revmap
98
106
99 def _parse_view(self, path):
107 def _parse_view(self, path):
100 "Read changes affecting the path"
108 b"Read changes affecting the path"
101 cmd = 'p4 -G changes -s submitted %s' % procutil.shellquote(path)
109 cmd = b'p4 -G changes -s submitted %s' % procutil.shellquote(path)
102 stdout = procutil.popen(cmd, mode='rb')
110 stdout = procutil.popen(cmd, mode=b'rb')
103 p4changes = {}
111 p4changes = {}
104 for d in loaditer(stdout):
112 for d in loaditer(stdout):
105 c = d.get("change", None)
113 c = d.get(b"change", None)
106 if c:
114 if c:
107 p4changes[c] = True
115 p4changes[c] = True
108 return p4changes
116 return p4changes
109
117
110 def _parse(self, ui, path):
118 def _parse(self, ui, path):
111 "Prepare list of P4 filenames and revisions to import"
119 b"Prepare list of P4 filenames and revisions to import"
112 p4changes = {}
120 p4changes = {}
113 changeset = {}
121 changeset = {}
114 files_map = {}
122 files_map = {}
@@ -117,29 +125,29 b' class p4_source(common.converter_source)'
117 depotname = {}
125 depotname = {}
118 heads = []
126 heads = []
119
127
120 ui.status(_('reading p4 views\n'))
128 ui.status(_(b'reading p4 views\n'))
121
129
122 # read client spec or view
130 # read client spec or view
123 if "/" in path:
131 if b"/" in path:
124 p4changes.update(self._parse_view(path))
132 p4changes.update(self._parse_view(path))
125 if path.startswith("//") and path.endswith("/..."):
133 if path.startswith(b"//") and path.endswith(b"/..."):
126 views = {path[:-3]: ""}
134 views = {path[:-3]: b""}
127 else:
135 else:
128 views = {"//": ""}
136 views = {b"//": b""}
129 else:
137 else:
130 cmd = 'p4 -G client -o %s' % procutil.shellquote(path)
138 cmd = b'p4 -G client -o %s' % procutil.shellquote(path)
131 clientspec = marshal.load(procutil.popen(cmd, mode='rb'))
139 clientspec = marshal.load(procutil.popen(cmd, mode=b'rb'))
132
140
133 views = {}
141 views = {}
134 for client in clientspec:
142 for client in clientspec:
135 if client.startswith("View"):
143 if client.startswith(b"View"):
136 sview, cview = clientspec[client].split()
144 sview, cview = clientspec[client].split()
137 p4changes.update(self._parse_view(sview))
145 p4changes.update(self._parse_view(sview))
138 if sview.endswith("...") and cview.endswith("..."):
146 if sview.endswith(b"...") and cview.endswith(b"..."):
139 sview = sview[:-3]
147 sview = sview[:-3]
140 cview = cview[:-3]
148 cview = cview[:-3]
141 cview = cview[2:]
149 cview = cview[2:]
142 cview = cview[cview.find("/") + 1 :]
150 cview = cview[cview.find(b"/") + 1 :]
143 views[sview] = cview
151 views[sview] = cview
144
152
145 # list of changes that affect our source files
153 # list of changes that affect our source files
@@ -151,10 +159,10 b' class p4_source(common.converter_source)'
151 vieworder.sort(key=len, reverse=True)
159 vieworder.sort(key=len, reverse=True)
152
160
153 # handle revision limiting
161 # handle revision limiting
154 startrev = self.ui.config('convert', 'p4.startrev')
162 startrev = self.ui.config(b'convert', b'p4.startrev')
155
163
156 # now read the full changelists to get the list of file revisions
164 # now read the full changelists to get the list of file revisions
157 ui.status(_('collecting p4 changelists\n'))
165 ui.status(_(b'collecting p4 changelists\n'))
158 lastid = None
166 lastid = None
159 for change in p4changes:
167 for change in p4changes:
160 if startrev and int(change) < int(startrev):
168 if startrev and int(change) < int(startrev):
@@ -176,28 +184,28 b' class p4_source(common.converter_source)'
176
184
177 descarr = c.desc.splitlines(True)
185 descarr = c.desc.splitlines(True)
178 if len(descarr) > 0:
186 if len(descarr) > 0:
179 shortdesc = descarr[0].rstrip('\r\n')
187 shortdesc = descarr[0].rstrip(b'\r\n')
180 else:
188 else:
181 shortdesc = '**empty changelist description**'
189 shortdesc = b'**empty changelist description**'
182
190
183 t = '%s %s' % (c.rev, repr(shortdesc)[1:-1])
191 t = b'%s %s' % (c.rev, repr(shortdesc)[1:-1])
184 ui.status(stringutil.ellipsis(t, 80) + '\n')
192 ui.status(stringutil.ellipsis(t, 80) + b'\n')
185
193
186 files = []
194 files = []
187 copies = {}
195 copies = {}
188 copiedfiles = []
196 copiedfiles = []
189 i = 0
197 i = 0
190 while ("depotFile%d" % i) in d and ("rev%d" % i) in d:
198 while (b"depotFile%d" % i) in d and (b"rev%d" % i) in d:
191 oldname = d["depotFile%d" % i]
199 oldname = d[b"depotFile%d" % i]
192 filename = None
200 filename = None
193 for v in vieworder:
201 for v in vieworder:
194 if oldname.lower().startswith(v.lower()):
202 if oldname.lower().startswith(v.lower()):
195 filename = decodefilename(views[v] + oldname[len(v) :])
203 filename = decodefilename(views[v] + oldname[len(v) :])
196 break
204 break
197 if filename:
205 if filename:
198 files.append((filename, d["rev%d" % i]))
206 files.append((filename, d[b"rev%d" % i]))
199 depotname[filename] = oldname
207 depotname[filename] = oldname
200 if d.get("action%d" % i) == "move/add":
208 if d.get(b"action%d" % i) == b"move/add":
201 copiedfiles.append(filename)
209 copiedfiles.append(filename)
202 localname[oldname] = filename
210 localname[oldname] = filename
203 i += 1
211 i += 1
@@ -206,23 +214,23 b' class p4_source(common.converter_source)'
206 for filename in copiedfiles:
214 for filename in copiedfiles:
207 oldname = depotname[filename]
215 oldname = depotname[filename]
208
216
209 flcmd = 'p4 -G filelog %s' % procutil.shellquote(oldname)
217 flcmd = b'p4 -G filelog %s' % procutil.shellquote(oldname)
210 flstdout = procutil.popen(flcmd, mode='rb')
218 flstdout = procutil.popen(flcmd, mode=b'rb')
211
219
212 copiedfilename = None
220 copiedfilename = None
213 for d in loaditer(flstdout):
221 for d in loaditer(flstdout):
214 copiedoldname = None
222 copiedoldname = None
215
223
216 i = 0
224 i = 0
217 while ("change%d" % i) in d:
225 while (b"change%d" % i) in d:
218 if (
226 if (
219 d["change%d" % i] == change
227 d[b"change%d" % i] == change
220 and d["action%d" % i] == "move/add"
228 and d[b"action%d" % i] == b"move/add"
221 ):
229 ):
222 j = 0
230 j = 0
223 while ("file%d,%d" % (i, j)) in d:
231 while (b"file%d,%d" % (i, j)) in d:
224 if d["how%d,%d" % (i, j)] == "moved from":
232 if d[b"how%d,%d" % (i, j)] == b"moved from":
225 copiedoldname = d["file%d,%d" % (i, j)]
233 copiedoldname = d[b"file%d,%d" % (i, j)]
226 break
234 break
227 j += 1
235 j += 1
228 i += 1
236 i += 1
@@ -235,7 +243,7 b' class p4_source(common.converter_source)'
235 copies[filename] = copiedfilename
243 copies[filename] = copiedfilename
236 else:
244 else:
237 ui.warn(
245 ui.warn(
238 _("cannot find source for copied file: %s@%s\n")
246 _(b"cannot find source for copied file: %s@%s\n")
239 % (filename, change)
247 % (filename, change)
240 )
248 )
241
249
@@ -248,11 +256,11 b' class p4_source(common.converter_source)'
248 heads = [lastid]
256 heads = [lastid]
249
257
250 return {
258 return {
251 'changeset': changeset,
259 b'changeset': changeset,
252 'files': files_map,
260 b'files': files_map,
253 'copies': copies_map,
261 b'copies': copies_map,
254 'heads': heads,
262 b'heads': heads,
255 'depotname': depotname,
263 b'depotname': depotname,
256 }
264 }
257
265
258 @util.propertycache
266 @util.propertycache
@@ -261,74 +269,74 b' class p4_source(common.converter_source)'
261
269
262 @util.propertycache
270 @util.propertycache
263 def copies(self):
271 def copies(self):
264 return self._parse_once['copies']
272 return self._parse_once[b'copies']
265
273
266 @util.propertycache
274 @util.propertycache
267 def files(self):
275 def files(self):
268 return self._parse_once['files']
276 return self._parse_once[b'files']
269
277
270 @util.propertycache
278 @util.propertycache
271 def changeset(self):
279 def changeset(self):
272 return self._parse_once['changeset']
280 return self._parse_once[b'changeset']
273
281
274 @util.propertycache
282 @util.propertycache
275 def heads(self):
283 def heads(self):
276 return self._parse_once['heads']
284 return self._parse_once[b'heads']
277
285
278 @util.propertycache
286 @util.propertycache
279 def depotname(self):
287 def depotname(self):
280 return self._parse_once['depotname']
288 return self._parse_once[b'depotname']
281
289
282 def getheads(self):
290 def getheads(self):
283 return self.heads
291 return self.heads
284
292
285 def getfile(self, name, rev):
293 def getfile(self, name, rev):
286 cmd = 'p4 -G print %s' % procutil.shellquote(
294 cmd = b'p4 -G print %s' % procutil.shellquote(
287 "%s#%s" % (self.depotname[name], rev)
295 b"%s#%s" % (self.depotname[name], rev)
288 )
296 )
289
297
290 lasterror = None
298 lasterror = None
291 while True:
299 while True:
292 stdout = procutil.popen(cmd, mode='rb')
300 stdout = procutil.popen(cmd, mode=b'rb')
293
301
294 mode = None
302 mode = None
295 contents = []
303 contents = []
296 keywords = None
304 keywords = None
297
305
298 for d in loaditer(stdout):
306 for d in loaditer(stdout):
299 code = d["code"]
307 code = d[b"code"]
300 data = d.get("data")
308 data = d.get(b"data")
301
309
302 if code == "error":
310 if code == b"error":
303 # if this is the first time error happened
311 # if this is the first time error happened
304 # re-attempt getting the file
312 # re-attempt getting the file
305 if not lasterror:
313 if not lasterror:
306 lasterror = IOError(d["generic"], data)
314 lasterror = IOError(d[b"generic"], data)
307 # this will exit inner-most for-loop
315 # this will exit inner-most for-loop
308 break
316 break
309 else:
317 else:
310 raise lasterror
318 raise lasterror
311
319
312 elif code == "stat":
320 elif code == b"stat":
313 action = d.get("action")
321 action = d.get(b"action")
314 if action in ["purge", "delete", "move/delete"]:
322 if action in [b"purge", b"delete", b"move/delete"]:
315 return None, None
323 return None, None
316 p4type = self.re_type.match(d["type"])
324 p4type = self.re_type.match(d[b"type"])
317 if p4type:
325 if p4type:
318 mode = ""
326 mode = b""
319 flags = (p4type.group(1) or "") + (
327 flags = (p4type.group(1) or b"") + (
320 p4type.group(3) or ""
328 p4type.group(3) or b""
321 )
329 )
322 if "x" in flags:
330 if b"x" in flags:
323 mode = "x"
331 mode = b"x"
324 if p4type.group(2) == "symlink":
332 if p4type.group(2) == b"symlink":
325 mode = "l"
333 mode = b"l"
326 if "ko" in flags:
334 if b"ko" in flags:
327 keywords = self.re_keywords_old
335 keywords = self.re_keywords_old
328 elif "k" in flags:
336 elif b"k" in flags:
329 keywords = self.re_keywords
337 keywords = self.re_keywords
330
338
331 elif code == "text" or code == "binary":
339 elif code == b"text" or code == b"binary":
332 contents.append(data)
340 contents.append(data)
333
341
334 lasterror = None
342 lasterror = None
@@ -339,18 +347,18 b' class p4_source(common.converter_source)'
339 if mode is None:
347 if mode is None:
340 return None, None
348 return None, None
341
349
342 contents = ''.join(contents)
350 contents = b''.join(contents)
343
351
344 if keywords:
352 if keywords:
345 contents = keywords.sub("$\\1$", contents)
353 contents = keywords.sub(b"$\\1$", contents)
346 if mode == "l" and contents.endswith("\n"):
354 if mode == b"l" and contents.endswith(b"\n"):
347 contents = contents[:-1]
355 contents = contents[:-1]
348
356
349 return contents, mode
357 return contents, mode
350
358
351 def getchanges(self, rev, full):
359 def getchanges(self, rev, full):
352 if full:
360 if full:
353 raise error.Abort(_("convert from p4 does not support --full"))
361 raise error.Abort(_(b"convert from p4 does not support --full"))
354 return self.files[rev], self.copies[rev], set()
362 return self.files[rev], self.copies[rev], set()
355
363
356 def _construct_commit(self, obj, parents=None):
364 def _construct_commit(self, obj, parents=None):
@@ -358,26 +366,26 b' class p4_source(common.converter_source)'
358 Constructs a common.commit object from an unmarshalled
366 Constructs a common.commit object from an unmarshalled
359 `p4 describe` output
367 `p4 describe` output
360 """
368 """
361 desc = self.recode(obj.get("desc", ""))
369 desc = self.recode(obj.get(b"desc", b""))
362 date = (int(obj["time"]), 0) # timezone not set
370 date = (int(obj[b"time"]), 0) # timezone not set
363 if parents is None:
371 if parents is None:
364 parents = []
372 parents = []
365
373
366 return common.commit(
374 return common.commit(
367 author=self.recode(obj["user"]),
375 author=self.recode(obj[b"user"]),
368 date=dateutil.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'),
376 date=dateutil.datestr(date, b'%Y-%m-%d %H:%M:%S %1%2'),
369 parents=parents,
377 parents=parents,
370 desc=desc,
378 desc=desc,
371 branch=None,
379 branch=None,
372 rev=obj['change'],
380 rev=obj[b'change'],
373 extra={"p4": obj['change'], "convert_revision": obj['change']},
381 extra={b"p4": obj[b'change'], b"convert_revision": obj[b'change']},
374 )
382 )
375
383
376 def _fetch_revision(self, rev):
384 def _fetch_revision(self, rev):
377 """Return an output of `p4 describe` including author, commit date as
385 """Return an output of `p4 describe` including author, commit date as
378 a dictionary."""
386 a dictionary."""
379 cmd = "p4 -G describe -s %s" % rev
387 cmd = b"p4 -G describe -s %s" % rev
380 stdout = procutil.popen(cmd, mode='rb')
388 stdout = procutil.popen(cmd, mode=b'rb')
381 return marshal.load(stdout)
389 return marshal.load(stdout)
382
390
383 def getcommit(self, rev):
391 def getcommit(self, rev):
@@ -387,7 +395,7 b' class p4_source(common.converter_source)'
387 d = self._fetch_revision(rev)
395 d = self._fetch_revision(rev)
388 return self._construct_commit(d, parents=None)
396 return self._construct_commit(d, parents=None)
389 raise error.Abort(
397 raise error.Abort(
390 _("cannot find %s in the revmap or parsed changesets") % rev
398 _(b"cannot find %s in the revmap or parsed changesets") % rev
391 )
399 )
392
400
393 def gettags(self):
401 def gettags(self):
@@ -54,7 +54,7 b' try:'
54 import warnings
54 import warnings
55
55
56 warnings.filterwarnings(
56 warnings.filterwarnings(
57 'ignore', module='svn.core', category=DeprecationWarning
57 b'ignore', module=b'svn.core', category=DeprecationWarning
58 )
58 )
59 svn.core.SubversionException # trigger import to catch error
59 svn.core.SubversionException # trigger import to catch error
60
60
@@ -80,16 +80,16 b' def revsplit(rev):'
80 >>> revsplit(b'bad')
80 >>> revsplit(b'bad')
81 ('', '', 0)
81 ('', '', 0)
82 """
82 """
83 parts = rev.rsplit('@', 1)
83 parts = rev.rsplit(b'@', 1)
84 revnum = 0
84 revnum = 0
85 if len(parts) > 1:
85 if len(parts) > 1:
86 revnum = int(parts[1])
86 revnum = int(parts[1])
87 parts = parts[0].split('/', 1)
87 parts = parts[0].split(b'/', 1)
88 uuid = ''
88 uuid = b''
89 mod = ''
89 mod = b''
90 if len(parts) > 1 and parts[0].startswith('svn:'):
90 if len(parts) > 1 and parts[0].startswith(b'svn:'):
91 uuid = parts[0][4:]
91 uuid = parts[0][4:]
92 mod = '/' + parts[1]
92 mod = b'/' + parts[1]
93 return uuid, mod, revnum
93 return uuid, mod, revnum
94
94
95
95
@@ -101,7 +101,7 b' def quote(s):'
101 # so we can extend it safely with new components. The "safe"
101 # so we can extend it safely with new components. The "safe"
102 # characters were taken from the "svn_uri__char_validity" table in
102 # characters were taken from the "svn_uri__char_validity" table in
103 # libsvn_subr/path.c.
103 # libsvn_subr/path.c.
104 return urlreq.quote(s, "!$&'()*+,-./:=@_~")
104 return urlreq.quote(s, b"!$&'()*+,-./:=@_~")
105
105
106
106
107 def geturl(path):
107 def geturl(path):
@@ -113,11 +113,11 b' def geturl(path):'
113 if os.path.isdir(path):
113 if os.path.isdir(path):
114 path = os.path.normpath(os.path.abspath(path))
114 path = os.path.normpath(os.path.abspath(path))
115 if pycompat.iswindows:
115 if pycompat.iswindows:
116 path = '/' + util.normpath(path)
116 path = b'/' + util.normpath(path)
117 # Module URL is later compared with the repository URL returned
117 # Module URL is later compared with the repository URL returned
118 # by svn API, which is UTF-8.
118 # by svn API, which is UTF-8.
119 path = encoding.tolocal(path)
119 path = encoding.tolocal(path)
120 path = 'file://%s' % quote(path)
120 path = b'file://%s' % quote(path)
121 return svn.core.svn_path_canonicalize(path)
121 return svn.core.svn_path_canonicalize(path)
122
122
123
123
@@ -188,7 +188,7 b' def debugsvnlog(ui, **opts):'
188 """
188 """
189 if svn is None:
189 if svn is None:
190 raise error.Abort(
190 raise error.Abort(
191 _('debugsvnlog could not load Subversion python ' 'bindings')
191 _(b'debugsvnlog could not load Subversion python ' b'bindings')
192 )
192 )
193
193
194 args = decodeargs(ui.fin.read())
194 args = decodeargs(ui.fin.read())
@@ -208,8 +208,8 b' class logstream(object):'
208 except EOFError:
208 except EOFError:
209 raise error.Abort(
209 raise error.Abort(
210 _(
210 _(
211 'Mercurial failed to run itself, check'
211 b'Mercurial failed to run itself, check'
212 ' hg executable is in PATH'
212 b' hg executable is in PATH'
213 )
213 )
214 )
214 )
215 try:
215 try:
@@ -217,7 +217,7 b' class logstream(object):'
217 except (TypeError, ValueError):
217 except (TypeError, ValueError):
218 if entry is None:
218 if entry is None:
219 break
219 break
220 raise error.Abort(_("log stream exception '%s'") % entry)
220 raise error.Abort(_(b"log stream exception '%s'") % entry)
221 yield entry
221 yield entry
222
222
223 def close(self):
223 def close(self):
@@ -270,7 +270,7 b' class directlogstream(list):'
270 # looking for several svn-specific files and directories in the given
270 # looking for several svn-specific files and directories in the given
271 # directory.
271 # directory.
272 def filecheck(ui, path, proto):
272 def filecheck(ui, path, proto):
273 for x in ('locks', 'hooks', 'format', 'db'):
273 for x in (b'locks', b'hooks', b'format', b'db'):
274 if not os.path.exists(os.path.join(path, x)):
274 if not os.path.exists(os.path.join(path, x)):
275 return False
275 return False
276 return True
276 return True
@@ -282,16 +282,16 b' def filecheck(ui, path, proto):'
282 def httpcheck(ui, path, proto):
282 def httpcheck(ui, path, proto):
283 try:
283 try:
284 opener = urlreq.buildopener()
284 opener = urlreq.buildopener()
285 rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path), 'rb')
285 rsp = opener.open(b'%s://%s/!svn/ver/0/.svn' % (proto, path), b'rb')
286 data = rsp.read()
286 data = rsp.read()
287 except urlerr.httperror as inst:
287 except urlerr.httperror as inst:
288 if inst.code != 404:
288 if inst.code != 404:
289 # Except for 404 we cannot know for sure this is not an svn repo
289 # Except for 404 we cannot know for sure this is not an svn repo
290 ui.warn(
290 ui.warn(
291 _(
291 _(
292 'svn: cannot probe remote repository, assume it could '
292 b'svn: cannot probe remote repository, assume it could '
293 'be a subversion repository. Use --source-type if you '
293 b'be a subversion repository. Use --source-type if you '
294 'know better.\n'
294 b'know better.\n'
295 )
295 )
296 )
296 )
297 return True
297 return True
@@ -299,38 +299,38 b' def httpcheck(ui, path, proto):'
299 except Exception:
299 except Exception:
300 # Could be urlerr.urlerror if the URL is invalid or anything else.
300 # Could be urlerr.urlerror if the URL is invalid or anything else.
301 return False
301 return False
302 return '<m:human-readable errcode="160013">' in data
302 return b'<m:human-readable errcode="160013">' in data
303
303
304
304
305 protomap = {
305 protomap = {
306 'http': httpcheck,
306 b'http': httpcheck,
307 'https': httpcheck,
307 b'https': httpcheck,
308 'file': filecheck,
308 b'file': filecheck,
309 }
309 }
310
310
311
311
312 def issvnurl(ui, url):
312 def issvnurl(ui, url):
313 try:
313 try:
314 proto, path = url.split('://', 1)
314 proto, path = url.split(b'://', 1)
315 if proto == 'file':
315 if proto == b'file':
316 if (
316 if (
317 pycompat.iswindows
317 pycompat.iswindows
318 and path[:1] == '/'
318 and path[:1] == b'/'
319 and path[1:2].isalpha()
319 and path[1:2].isalpha()
320 and path[2:6].lower() == '%3a/'
320 and path[2:6].lower() == b'%3a/'
321 ):
321 ):
322 path = path[:2] + ':/' + path[6:]
322 path = path[:2] + b':/' + path[6:]
323 path = urlreq.url2pathname(path)
323 path = urlreq.url2pathname(path)
324 except ValueError:
324 except ValueError:
325 proto = 'file'
325 proto = b'file'
326 path = os.path.abspath(url)
326 path = os.path.abspath(url)
327 if proto == 'file':
327 if proto == b'file':
328 path = util.pconvert(path)
328 path = util.pconvert(path)
329 check = protomap.get(proto, lambda *args: False)
329 check = protomap.get(proto, lambda *args: False)
330 while '/' in path:
330 while b'/' in path:
331 if check(ui, path, proto):
331 if check(ui, path, proto):
332 return True
332 return True
333 path = path.rsplit('/', 1)[0]
333 path = path.rsplit(b'/', 1)[0]
334 return False
334 return False
335
335
336
336
@@ -353,35 +353,35 b' class svn_source(converter_source):'
353 super(svn_source, self).__init__(ui, repotype, url, revs=revs)
353 super(svn_source, self).__init__(ui, repotype, url, revs=revs)
354
354
355 if not (
355 if not (
356 url.startswith('svn://')
356 url.startswith(b'svn://')
357 or url.startswith('svn+ssh://')
357 or url.startswith(b'svn+ssh://')
358 or (
358 or (
359 os.path.exists(url)
359 os.path.exists(url)
360 and os.path.exists(os.path.join(url, '.svn'))
360 and os.path.exists(os.path.join(url, b'.svn'))
361 )
361 )
362 or issvnurl(ui, url)
362 or issvnurl(ui, url)
363 ):
363 ):
364 raise NoRepo(
364 raise NoRepo(
365 _("%s does not look like a Subversion repository") % url
365 _(b"%s does not look like a Subversion repository") % url
366 )
366 )
367 if svn is None:
367 if svn is None:
368 raise MissingTool(_('could not load Subversion python bindings'))
368 raise MissingTool(_(b'could not load Subversion python bindings'))
369
369
370 try:
370 try:
371 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
371 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
372 if version < (1, 4):
372 if version < (1, 4):
373 raise MissingTool(
373 raise MissingTool(
374 _(
374 _(
375 'Subversion python bindings %d.%d found, '
375 b'Subversion python bindings %d.%d found, '
376 '1.4 or later required'
376 b'1.4 or later required'
377 )
377 )
378 % version
378 % version
379 )
379 )
380 except AttributeError:
380 except AttributeError:
381 raise MissingTool(
381 raise MissingTool(
382 _(
382 _(
383 'Subversion python bindings are too old, 1.4 '
383 b'Subversion python bindings are too old, 1.4 '
384 'or later required'
384 b'or later required'
385 )
385 )
386 )
386 )
387
387
@@ -391,14 +391,14 b' class svn_source(converter_source):'
391 try:
391 try:
392 # Support file://path@rev syntax. Useful e.g. to convert
392 # Support file://path@rev syntax. Useful e.g. to convert
393 # deleted branches.
393 # deleted branches.
394 at = url.rfind('@')
394 at = url.rfind(b'@')
395 if at >= 0:
395 if at >= 0:
396 latest = int(url[at + 1 :])
396 latest = int(url[at + 1 :])
397 url = url[:at]
397 url = url[:at]
398 except ValueError:
398 except ValueError:
399 pass
399 pass
400 self.url = geturl(url)
400 self.url = geturl(url)
401 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
401 self.encoding = b'UTF-8' # Subversion is always nominal UTF-8
402 try:
402 try:
403 self.transport = transport.SvnRaTransport(url=self.url)
403 self.transport = transport.SvnRaTransport(url=self.url)
404 self.ra = self.transport.ra
404 self.ra = self.transport.ra
@@ -414,15 +414,15 b' class svn_source(converter_source):'
414 self.uuid = svn.ra.get_uuid(self.ra)
414 self.uuid = svn.ra.get_uuid(self.ra)
415 except svn.core.SubversionException:
415 except svn.core.SubversionException:
416 ui.traceback()
416 ui.traceback()
417 svnversion = '%d.%d.%d' % (
417 svnversion = b'%d.%d.%d' % (
418 svn.core.SVN_VER_MAJOR,
418 svn.core.SVN_VER_MAJOR,
419 svn.core.SVN_VER_MINOR,
419 svn.core.SVN_VER_MINOR,
420 svn.core.SVN_VER_MICRO,
420 svn.core.SVN_VER_MICRO,
421 )
421 )
422 raise NoRepo(
422 raise NoRepo(
423 _(
423 _(
424 "%s does not look like a Subversion repository "
424 b"%s does not look like a Subversion repository "
425 "to libsvn version %s"
425 b"to libsvn version %s"
426 )
426 )
427 % (self.url, svnversion)
427 % (self.url, svnversion)
428 )
428 )
@@ -431,29 +431,29 b' class svn_source(converter_source):'
431 if len(revs) > 1:
431 if len(revs) > 1:
432 raise error.Abort(
432 raise error.Abort(
433 _(
433 _(
434 'subversion source does not support '
434 b'subversion source does not support '
435 'specifying multiple revisions'
435 b'specifying multiple revisions'
436 )
436 )
437 )
437 )
438 try:
438 try:
439 latest = int(revs[0])
439 latest = int(revs[0])
440 except ValueError:
440 except ValueError:
441 raise error.Abort(
441 raise error.Abort(
442 _('svn: revision %s is not an integer') % revs[0]
442 _(b'svn: revision %s is not an integer') % revs[0]
443 )
443 )
444
444
445 trunkcfg = self.ui.config('convert', 'svn.trunk')
445 trunkcfg = self.ui.config(b'convert', b'svn.trunk')
446 if trunkcfg is None:
446 if trunkcfg is None:
447 trunkcfg = 'trunk'
447 trunkcfg = b'trunk'
448 self.trunkname = trunkcfg.strip('/')
448 self.trunkname = trunkcfg.strip(b'/')
449 self.startrev = self.ui.config('convert', 'svn.startrev')
449 self.startrev = self.ui.config(b'convert', b'svn.startrev')
450 try:
450 try:
451 self.startrev = int(self.startrev)
451 self.startrev = int(self.startrev)
452 if self.startrev < 0:
452 if self.startrev < 0:
453 self.startrev = 0
453 self.startrev = 0
454 except ValueError:
454 except ValueError:
455 raise error.Abort(
455 raise error.Abort(
456 _('svn: start revision %s is not an integer') % self.startrev
456 _(b'svn: start revision %s is not an integer') % self.startrev
457 )
457 )
458
458
459 try:
459 try:
@@ -461,12 +461,14 b' class svn_source(converter_source):'
461 except SvnPathNotFound:
461 except SvnPathNotFound:
462 self.head = None
462 self.head = None
463 if not self.head:
463 if not self.head:
464 raise error.Abort(_('no revision found in module %s') % self.module)
464 raise error.Abort(
465 _(b'no revision found in module %s') % self.module
466 )
465 self.last_changed = self.revnum(self.head)
467 self.last_changed = self.revnum(self.head)
466
468
467 self._changescache = (None, None)
469 self._changescache = (None, None)
468
470
469 if os.path.exists(os.path.join(url, '.svn/entries')):
471 if os.path.exists(os.path.join(url, b'.svn/entries')):
470 self.wc = url
472 self.wc = url
471 else:
473 else:
472 self.wc = None
474 self.wc = None
@@ -484,7 +486,7 b' class svn_source(converter_source):'
484 def exists(self, path, optrev):
486 def exists(self, path, optrev):
485 try:
487 try:
486 svn.client.ls(
488 svn.client.ls(
487 self.url.rstrip('/') + '/' + quote(path),
489 self.url.rstrip(b'/') + b'/' + quote(path),
488 optrev,
490 optrev,
489 False,
491 False,
490 self.ctx,
492 self.ctx,
@@ -499,61 +501,62 b' class svn_source(converter_source):'
499 return kind == svn.core.svn_node_dir
501 return kind == svn.core.svn_node_dir
500
502
501 def getcfgpath(name, rev):
503 def getcfgpath(name, rev):
502 cfgpath = self.ui.config('convert', 'svn.' + name)
504 cfgpath = self.ui.config(b'convert', b'svn.' + name)
503 if cfgpath is not None and cfgpath.strip() == '':
505 if cfgpath is not None and cfgpath.strip() == b'':
504 return None
506 return None
505 path = (cfgpath or name).strip('/')
507 path = (cfgpath or name).strip(b'/')
506 if not self.exists(path, rev):
508 if not self.exists(path, rev):
507 if self.module.endswith(path) and name == 'trunk':
509 if self.module.endswith(path) and name == b'trunk':
508 # we are converting from inside this directory
510 # we are converting from inside this directory
509 return None
511 return None
510 if cfgpath:
512 if cfgpath:
511 raise error.Abort(
513 raise error.Abort(
512 _('expected %s to be at %r, but not found')
514 _(b'expected %s to be at %r, but not found')
513 % (name, path)
515 % (name, path)
514 )
516 )
515 return None
517 return None
516 self.ui.note(_('found %s at %r\n') % (name, path))
518 self.ui.note(_(b'found %s at %r\n') % (name, path))
517 return path
519 return path
518
520
519 rev = optrev(self.last_changed)
521 rev = optrev(self.last_changed)
520 oldmodule = ''
522 oldmodule = b''
521 trunk = getcfgpath('trunk', rev)
523 trunk = getcfgpath(b'trunk', rev)
522 self.tags = getcfgpath('tags', rev)
524 self.tags = getcfgpath(b'tags', rev)
523 branches = getcfgpath('branches', rev)
525 branches = getcfgpath(b'branches', rev)
524
526
525 # If the project has a trunk or branches, we will extract heads
527 # If the project has a trunk or branches, we will extract heads
526 # from them. We keep the project root otherwise.
528 # from them. We keep the project root otherwise.
527 if trunk:
529 if trunk:
528 oldmodule = self.module or ''
530 oldmodule = self.module or b''
529 self.module += '/' + trunk
531 self.module += b'/' + trunk
530 self.head = self.latest(self.module, self.last_changed)
532 self.head = self.latest(self.module, self.last_changed)
531 if not self.head:
533 if not self.head:
532 raise error.Abort(
534 raise error.Abort(
533 _('no revision found in module %s') % self.module
535 _(b'no revision found in module %s') % self.module
534 )
536 )
535
537
536 # First head in the list is the module's head
538 # First head in the list is the module's head
537 self.heads = [self.head]
539 self.heads = [self.head]
538 if self.tags is not None:
540 if self.tags is not None:
539 self.tags = '%s/%s' % (oldmodule, (self.tags or 'tags'))
541 self.tags = b'%s/%s' % (oldmodule, (self.tags or b'tags'))
540
542
541 # Check if branches bring a few more heads to the list
543 # Check if branches bring a few more heads to the list
542 if branches:
544 if branches:
543 rpath = self.url.strip('/')
545 rpath = self.url.strip(b'/')
544 branchnames = svn.client.ls(
546 branchnames = svn.client.ls(
545 rpath + '/' + quote(branches), rev, False, self.ctx
547 rpath + b'/' + quote(branches), rev, False, self.ctx
546 )
548 )
547 for branch in sorted(branchnames):
549 for branch in sorted(branchnames):
548 module = '%s/%s/%s' % (oldmodule, branches, branch)
550 module = b'%s/%s/%s' % (oldmodule, branches, branch)
549 if not isdir(module, self.last_changed):
551 if not isdir(module, self.last_changed):
550 continue
552 continue
551 brevid = self.latest(module, self.last_changed)
553 brevid = self.latest(module, self.last_changed)
552 if not brevid:
554 if not brevid:
553 self.ui.note(_('ignoring empty branch %s\n') % branch)
555 self.ui.note(_(b'ignoring empty branch %s\n') % branch)
554 continue
556 continue
555 self.ui.note(
557 self.ui.note(
556 _('found branch %s at %d\n') % (branch, self.revnum(brevid))
558 _(b'found branch %s at %d\n')
559 % (branch, self.revnum(brevid))
557 )
560 )
558 self.heads.append(brevid)
561 self.heads.append(brevid)
559
562
@@ -561,14 +564,14 b' class svn_source(converter_source):'
561 if len(self.heads) > 1:
564 if len(self.heads) > 1:
562 raise error.Abort(
565 raise error.Abort(
563 _(
566 _(
564 'svn: start revision is not supported '
567 b'svn: start revision is not supported '
565 'with more than one branch'
568 b'with more than one branch'
566 )
569 )
567 )
570 )
568 revnum = self.revnum(self.heads[0])
571 revnum = self.revnum(self.heads[0])
569 if revnum < self.startrev:
572 if revnum < self.startrev:
570 raise error.Abort(
573 raise error.Abort(
571 _('svn: no revision found after start revision %d')
574 _(b'svn: no revision found after start revision %d')
572 % self.startrev
575 % self.startrev
573 )
576 )
574
577
@@ -628,13 +631,13 b' class svn_source(converter_source):'
628 stop = revnum + 1
631 stop = revnum + 1
629 self._fetch_revisions(revnum, stop)
632 self._fetch_revisions(revnum, stop)
630 if rev not in self.commits:
633 if rev not in self.commits:
631 raise error.Abort(_('svn: revision %s not found') % revnum)
634 raise error.Abort(_(b'svn: revision %s not found') % revnum)
632 revcommit = self.commits[rev]
635 revcommit = self.commits[rev]
633 # caller caches the result, so free it here to release memory
636 # caller caches the result, so free it here to release memory
634 del self.commits[rev]
637 del self.commits[rev]
635 return revcommit
638 return revcommit
636
639
637 def checkrevformat(self, revstr, mapname='splicemap'):
640 def checkrevformat(self, revstr, mapname=b'splicemap'):
638 """ fails if revision format does not match the correct format"""
641 """ fails if revision format does not match the correct format"""
639 if not re.match(
642 if not re.match(
640 r'svn:[0-9a-f]{8,8}-[0-9a-f]{4,4}-'
643 r'svn:[0-9a-f]{8,8}-[0-9a-f]{4,4}-'
@@ -643,12 +646,12 b' class svn_source(converter_source):'
643 revstr,
646 revstr,
644 ):
647 ):
645 raise error.Abort(
648 raise error.Abort(
646 _('%s entry %s is not a valid revision' ' identifier')
649 _(b'%s entry %s is not a valid revision' b' identifier')
647 % (mapname, revstr)
650 % (mapname, revstr)
648 )
651 )
649
652
650 def numcommits(self):
653 def numcommits(self):
651 return int(self.head.rsplit('@', 1)[1]) - self.startrev
654 return int(self.head.rsplit(b'@', 1)[1]) - self.startrev
652
655
653 def gettags(self):
656 def gettags(self):
654 tags = {}
657 tags = {}
@@ -689,7 +692,7 b' class svn_source(converter_source):'
689 srctagspath = copies.pop()[0]
692 srctagspath = copies.pop()[0]
690
693
691 for source, sourcerev, dest in copies:
694 for source, sourcerev, dest in copies:
692 if not dest.startswith(tagspath + '/'):
695 if not dest.startswith(tagspath + b'/'):
693 continue
696 continue
694 for tag in pendings:
697 for tag in pendings:
695 if tag[0].startswith(dest):
698 if tag[0].startswith(dest):
@@ -709,14 +712,14 b' class svn_source(converter_source):'
709 addeds = dict(
712 addeds = dict(
710 (p, e.copyfrom_path)
713 (p, e.copyfrom_path)
711 for p, e in origpaths.iteritems()
714 for p, e in origpaths.iteritems()
712 if e.action == 'A' and e.copyfrom_path
715 if e.action == b'A' and e.copyfrom_path
713 )
716 )
714 badroots = set()
717 badroots = set()
715 for destroot in addeds:
718 for destroot in addeds:
716 for source, sourcerev, dest in pendings:
719 for source, sourcerev, dest in pendings:
717 if not dest.startswith(
720 if not dest.startswith(
718 destroot + '/'
721 destroot + b'/'
719 ) or source.startswith(addeds[destroot] + '/'):
722 ) or source.startswith(addeds[destroot] + b'/'):
720 continue
723 continue
721 badroots.add(destroot)
724 badroots.add(destroot)
722 break
725 break
@@ -726,13 +729,13 b' class svn_source(converter_source):'
726 p
729 p
727 for p in pendings
730 for p in pendings
728 if p[2] != badroot
731 if p[2] != badroot
729 and not p[2].startswith(badroot + '/')
732 and not p[2].startswith(badroot + b'/')
730 ]
733 ]
731
734
732 # Tell tag renamings from tag creations
735 # Tell tag renamings from tag creations
733 renamings = []
736 renamings = []
734 for source, sourcerev, dest in pendings:
737 for source, sourcerev, dest in pendings:
735 tagname = dest.split('/')[-1]
738 tagname = dest.split(b'/')[-1]
736 if source.startswith(srctagspath):
739 if source.startswith(srctagspath):
737 renamings.append([source, sourcerev, tagname])
740 renamings.append([source, sourcerev, tagname])
738 continue
741 continue
@@ -761,18 +764,18 b' class svn_source(converter_source):'
761 return
764 return
762 if self.convertfp is None:
765 if self.convertfp is None:
763 self.convertfp = open(
766 self.convertfp = open(
764 os.path.join(self.wc, '.svn', 'hg-shamap'), 'ab'
767 os.path.join(self.wc, b'.svn', b'hg-shamap'), b'ab'
765 )
768 )
766 self.convertfp.write(
769 self.convertfp.write(
767 util.tonativeeol('%s %d\n' % (destrev, self.revnum(rev)))
770 util.tonativeeol(b'%s %d\n' % (destrev, self.revnum(rev)))
768 )
771 )
769 self.convertfp.flush()
772 self.convertfp.flush()
770
773
771 def revid(self, revnum, module=None):
774 def revid(self, revnum, module=None):
772 return 'svn:%s%s@%s' % (self.uuid, module or self.module, revnum)
775 return b'svn:%s%s@%s' % (self.uuid, module or self.module, revnum)
773
776
774 def revnum(self, rev):
777 def revnum(self, rev):
775 return int(rev.split('@')[-1])
778 return int(rev.split(b'@')[-1])
776
779
777 def latest(self, path, stop=None):
780 def latest(self, path, stop=None):
778 """Find the latest revid affecting path, up to stop revision
781 """Find the latest revid affecting path, up to stop revision
@@ -800,7 +803,7 b' class svn_source(converter_source):'
800 continue
803 continue
801 newpath = paths[p].copyfrom_path + path[len(p) :]
804 newpath = paths[p].copyfrom_path + path[len(p) :]
802 self.ui.debug(
805 self.ui.debug(
803 "branch renamed from %s to %s at %d\n"
806 b"branch renamed from %s to %s at %d\n"
804 % (path, newpath, revnum)
807 % (path, newpath, revnum)
805 )
808 )
806 path = newpath
809 path = newpath
@@ -813,20 +816,20 b' class svn_source(converter_source):'
813
816
814 if not path.startswith(self.rootmodule):
817 if not path.startswith(self.rootmodule):
815 # Requests on foreign branches may be forbidden at server level
818 # Requests on foreign branches may be forbidden at server level
816 self.ui.debug('ignoring foreign branch %r\n' % path)
819 self.ui.debug(b'ignoring foreign branch %r\n' % path)
817 return None
820 return None
818
821
819 if stop is None:
822 if stop is None:
820 stop = svn.ra.get_latest_revnum(self.ra)
823 stop = svn.ra.get_latest_revnum(self.ra)
821 try:
824 try:
822 prevmodule = self.reparent('')
825 prevmodule = self.reparent(b'')
823 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
826 dirent = svn.ra.stat(self.ra, path.strip(b'/'), stop)
824 self.reparent(prevmodule)
827 self.reparent(prevmodule)
825 except svn.core.SubversionException:
828 except svn.core.SubversionException:
826 dirent = None
829 dirent = None
827 if not dirent:
830 if not dirent:
828 raise SvnPathNotFound(
831 raise SvnPathNotFound(
829 _('%s not found up to revision %d') % (path, stop)
832 _(b'%s not found up to revision %d') % (path, stop)
830 )
833 )
831
834
832 # stat() gives us the previous revision on this line of
835 # stat() gives us the previous revision on this line of
@@ -843,11 +846,11 b' class svn_source(converter_source):'
843 # the whole history.
846 # the whole history.
844 revnum, realpath = findchanges(path, stop)
847 revnum, realpath = findchanges(path, stop)
845 if revnum is None:
848 if revnum is None:
846 self.ui.debug('ignoring empty branch %r\n' % realpath)
849 self.ui.debug(b'ignoring empty branch %r\n' % realpath)
847 return None
850 return None
848
851
849 if not realpath.startswith(self.rootmodule):
852 if not realpath.startswith(self.rootmodule):
850 self.ui.debug('ignoring foreign branch %r\n' % realpath)
853 self.ui.debug(b'ignoring foreign branch %r\n' % realpath)
851 return None
854 return None
852 return self.revid(revnum, realpath)
855 return self.revid(revnum, realpath)
853
856
@@ -858,8 +861,8 b' class svn_source(converter_source):'
858 svnurl = self.baseurl + quote(module)
861 svnurl = self.baseurl + quote(module)
859 prevmodule = self.prevmodule
862 prevmodule = self.prevmodule
860 if prevmodule is None:
863 if prevmodule is None:
861 prevmodule = ''
864 prevmodule = b''
862 self.ui.debug("reparent to %s\n" % svnurl)
865 self.ui.debug(b"reparent to %s\n" % svnurl)
863 svn.ra.reparent(self.ra, svnurl)
866 svn.ra.reparent(self.ra, svnurl)
864 self.prevmodule = module
867 self.prevmodule = module
865 return prevmodule
868 return prevmodule
@@ -874,7 +877,7 b' class svn_source(converter_source):'
874 self.reparent(self.module)
877 self.reparent(self.module)
875
878
876 progress = self.ui.makeprogress(
879 progress = self.ui.makeprogress(
877 _('scanning paths'), unit=_('paths'), total=len(paths)
880 _(b'scanning paths'), unit=_(b'paths'), total=len(paths)
878 )
881 )
879 for i, (path, ent) in enumerate(paths):
882 for i, (path, ent) in enumerate(paths):
880 progress.update(i, item=path)
883 progress.update(i, item=path)
@@ -894,37 +897,37 b' class svn_source(converter_source):'
894 if not copyfrom_path:
897 if not copyfrom_path:
895 continue
898 continue
896 self.ui.debug(
899 self.ui.debug(
897 "copied to %s from %s@%s\n"
900 b"copied to %s from %s@%s\n"
898 % (entrypath, copyfrom_path, ent.copyfrom_rev)
901 % (entrypath, copyfrom_path, ent.copyfrom_rev)
899 )
902 )
900 copies[self.recode(entrypath)] = self.recode(copyfrom_path)
903 copies[self.recode(entrypath)] = self.recode(copyfrom_path)
901 elif kind == 0: # gone, but had better be a deleted *file*
904 elif kind == 0: # gone, but had better be a deleted *file*
902 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
905 self.ui.debug(b"gone from %s\n" % ent.copyfrom_rev)
903 pmodule, prevnum = revsplit(parents[0])[1:]
906 pmodule, prevnum = revsplit(parents[0])[1:]
904 parentpath = pmodule + "/" + entrypath
907 parentpath = pmodule + b"/" + entrypath
905 fromkind = self._checkpath(entrypath, prevnum, pmodule)
908 fromkind = self._checkpath(entrypath, prevnum, pmodule)
906
909
907 if fromkind == svn.core.svn_node_file:
910 if fromkind == svn.core.svn_node_file:
908 removed.add(self.recode(entrypath))
911 removed.add(self.recode(entrypath))
909 elif fromkind == svn.core.svn_node_dir:
912 elif fromkind == svn.core.svn_node_dir:
910 oroot = parentpath.strip('/')
913 oroot = parentpath.strip(b'/')
911 nroot = path.strip('/')
914 nroot = path.strip(b'/')
912 children = self._iterfiles(oroot, prevnum)
915 children = self._iterfiles(oroot, prevnum)
913 for childpath in children:
916 for childpath in children:
914 childpath = childpath.replace(oroot, nroot)
917 childpath = childpath.replace(oroot, nroot)
915 childpath = self.getrelpath("/" + childpath, pmodule)
918 childpath = self.getrelpath(b"/" + childpath, pmodule)
916 if childpath:
919 if childpath:
917 removed.add(self.recode(childpath))
920 removed.add(self.recode(childpath))
918 else:
921 else:
919 self.ui.debug(
922 self.ui.debug(
920 'unknown path in revision %d: %s\n' % (revnum, path)
923 b'unknown path in revision %d: %s\n' % (revnum, path)
921 )
924 )
922 elif kind == svn.core.svn_node_dir:
925 elif kind == svn.core.svn_node_dir:
923 if ent.action == 'M':
926 if ent.action == b'M':
924 # If the directory just had a prop change,
927 # If the directory just had a prop change,
925 # then we shouldn't need to look for its children.
928 # then we shouldn't need to look for its children.
926 continue
929 continue
927 if ent.action == 'R' and parents:
930 if ent.action == b'R' and parents:
928 # If a directory is replacing a file, mark the previous
931 # If a directory is replacing a file, mark the previous
929 # file as deleted
932 # file as deleted
930 pmodule, prevnum = revsplit(parents[0])[1:]
933 pmodule, prevnum = revsplit(parents[0])[1:]
@@ -935,12 +938,12 b' class svn_source(converter_source):'
935 # We do not know what files were kept or removed,
938 # We do not know what files were kept or removed,
936 # mark them all as changed.
939 # mark them all as changed.
937 for childpath in self._iterfiles(pmodule, prevnum):
940 for childpath in self._iterfiles(pmodule, prevnum):
938 childpath = self.getrelpath("/" + childpath)
941 childpath = self.getrelpath(b"/" + childpath)
939 if childpath:
942 if childpath:
940 changed.add(self.recode(childpath))
943 changed.add(self.recode(childpath))
941
944
942 for childpath in self._iterfiles(path, revnum):
945 for childpath in self._iterfiles(path, revnum):
943 childpath = self.getrelpath("/" + childpath)
946 childpath = self.getrelpath(b"/" + childpath)
944 if childpath:
947 if childpath:
945 changed.add(self.recode(childpath))
948 changed.add(self.recode(childpath))
946
949
@@ -956,12 +959,12 b' class svn_source(converter_source):'
956 if not copyfrompath:
959 if not copyfrompath:
957 continue
960 continue
958 self.ui.debug(
961 self.ui.debug(
959 "mark %s came from %s:%d\n"
962 b"mark %s came from %s:%d\n"
960 % (path, copyfrompath, ent.copyfrom_rev)
963 % (path, copyfrompath, ent.copyfrom_rev)
961 )
964 )
962 children = self._iterfiles(ent.copyfrom_path, ent.copyfrom_rev)
965 children = self._iterfiles(ent.copyfrom_path, ent.copyfrom_rev)
963 for childpath in children:
966 for childpath in children:
964 childpath = self.getrelpath("/" + childpath, pmodule)
967 childpath = self.getrelpath(b"/" + childpath, pmodule)
965 if not childpath:
968 if not childpath:
966 continue
969 continue
967 copytopath = path + childpath[len(copyfrompath) :]
970 copytopath = path + childpath[len(copyfrompath) :]
@@ -983,7 +986,8 b' class svn_source(converter_source):'
983 the revision is a branch root.
986 the revision is a branch root.
984 """
987 """
985 self.ui.debug(
988 self.ui.debug(
986 "parsing revision %d (%d changes)\n" % (revnum, len(orig_paths))
989 b"parsing revision %d (%d changes)\n"
990 % (revnum, len(orig_paths))
987 )
991 )
988
992
989 branched = False
993 branched = False
@@ -1012,11 +1016,11 b' class svn_source(converter_source):'
1012 if prevnum >= self.startrev:
1016 if prevnum >= self.startrev:
1013 parents = [previd]
1017 parents = [previd]
1014 self.ui.note(
1018 self.ui.note(
1015 _('found parent of branch %s at %d: %s\n')
1019 _(b'found parent of branch %s at %d: %s\n')
1016 % (self.module, prevnum, prevmodule)
1020 % (self.module, prevnum, prevmodule)
1017 )
1021 )
1018 else:
1022 else:
1019 self.ui.debug("no copyfrom path, don't know what to do.\n")
1023 self.ui.debug(b"no copyfrom path, don't know what to do.\n")
1020
1024
1021 paths = []
1025 paths = []
1022 # filter out unrelated paths
1026 # filter out unrelated paths
@@ -1028,22 +1032,24 b' class svn_source(converter_source):'
1028 # Example SVN datetime. Includes microseconds.
1032 # Example SVN datetime. Includes microseconds.
1029 # ISO-8601 conformant
1033 # ISO-8601 conformant
1030 # '2007-01-04T17:35:00.902377Z'
1034 # '2007-01-04T17:35:00.902377Z'
1031 date = dateutil.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
1035 date = dateutil.parsedate(
1032 if self.ui.configbool('convert', 'localtimezone'):
1036 date[:19] + b" UTC", [b"%Y-%m-%dT%H:%M:%S"]
1037 )
1038 if self.ui.configbool(b'convert', b'localtimezone'):
1033 date = makedatetimestamp(date[0])
1039 date = makedatetimestamp(date[0])
1034
1040
1035 if message:
1041 if message:
1036 log = self.recode(message)
1042 log = self.recode(message)
1037 else:
1043 else:
1038 log = ''
1044 log = b''
1039
1045
1040 if author:
1046 if author:
1041 author = self.recode(author)
1047 author = self.recode(author)
1042 else:
1048 else:
1043 author = ''
1049 author = b''
1044
1050
1045 try:
1051 try:
1046 branch = self.module.split("/")[-1]
1052 branch = self.module.split(b"/")[-1]
1047 if branch == self.trunkname:
1053 if branch == self.trunkname:
1048 branch = None
1054 branch = None
1049 except IndexError:
1055 except IndexError:
@@ -1051,7 +1057,7 b' class svn_source(converter_source):'
1051
1057
1052 cset = commit(
1058 cset = commit(
1053 author=author,
1059 author=author,
1054 date=dateutil.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'),
1060 date=dateutil.datestr(date, b'%Y-%m-%d %H:%M:%S %1%2'),
1055 desc=log,
1061 desc=log,
1056 parents=parents,
1062 parents=parents,
1057 branch=branch,
1063 branch=branch,
@@ -1068,7 +1074,7 b' class svn_source(converter_source):'
1068 return cset, branched
1074 return cset, branched
1069
1075
1070 self.ui.note(
1076 self.ui.note(
1071 _('fetching revision log for "%s" from %d to %d\n')
1077 _(b'fetching revision log for "%s" from %d to %d\n')
1072 % (self.module, from_revnum, to_revnum)
1078 % (self.module, from_revnum, to_revnum)
1073 )
1079 )
1074
1080
@@ -1083,7 +1089,7 b' class svn_source(converter_source):'
1083 lastonbranch = True
1089 lastonbranch = True
1084 break
1090 break
1085 if not paths:
1091 if not paths:
1086 self.ui.debug('revision %d has no entries\n' % revnum)
1092 self.ui.debug(b'revision %d has no entries\n' % revnum)
1087 # If we ever leave the loop on an empty
1093 # If we ever leave the loop on an empty
1088 # revision, do not try to get a parent branch
1094 # revision, do not try to get a parent branch
1089 lastonbranch = lastonbranch or revnum == 0
1095 lastonbranch = lastonbranch or revnum == 0
@@ -1114,7 +1120,7 b' class svn_source(converter_source):'
1114 (inst, num) = xxx_todo_changeme.args
1120 (inst, num) = xxx_todo_changeme.args
1115 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
1121 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
1116 raise error.Abort(
1122 raise error.Abort(
1117 _('svn: branch has no revision %s') % to_revnum
1123 _(b'svn: branch has no revision %s') % to_revnum
1118 )
1124 )
1119 raise
1125 raise
1120
1126
@@ -1135,8 +1141,8 b' class svn_source(converter_source):'
1135 io.close()
1141 io.close()
1136 if isinstance(info, list):
1142 if isinstance(info, list):
1137 info = info[-1]
1143 info = info[-1]
1138 mode = ("svn:executable" in info) and 'x' or ''
1144 mode = (b"svn:executable" in info) and b'x' or b''
1139 mode = ("svn:special" in info) and 'l' or mode
1145 mode = (b"svn:special" in info) and b'l' or mode
1140 except svn.core.SubversionException as e:
1146 except svn.core.SubversionException as e:
1141 notfound = (
1147 notfound = (
1142 svn.core.SVN_ERR_FS_NOT_FOUND,
1148 svn.core.SVN_ERR_FS_NOT_FOUND,
@@ -1145,20 +1151,20 b' class svn_source(converter_source):'
1145 if e.apr_err in notfound: # File not found
1151 if e.apr_err in notfound: # File not found
1146 return None, None
1152 return None, None
1147 raise
1153 raise
1148 if mode == 'l':
1154 if mode == b'l':
1149 link_prefix = "link "
1155 link_prefix = b"link "
1150 if data.startswith(link_prefix):
1156 if data.startswith(link_prefix):
1151 data = data[len(link_prefix) :]
1157 data = data[len(link_prefix) :]
1152 return data, mode
1158 return data, mode
1153
1159
1154 def _iterfiles(self, path, revnum):
1160 def _iterfiles(self, path, revnum):
1155 """Enumerate all files in path at revnum, recursively."""
1161 """Enumerate all files in path at revnum, recursively."""
1156 path = path.strip('/')
1162 path = path.strip(b'/')
1157 pool = svn.core.Pool()
1163 pool = svn.core.Pool()
1158 rpath = '/'.join([self.baseurl, quote(path)]).strip('/')
1164 rpath = b'/'.join([self.baseurl, quote(path)]).strip(b'/')
1159 entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
1165 entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
1160 if path:
1166 if path:
1161 path += '/'
1167 path += b'/'
1162 return (
1168 return (
1163 (path + p)
1169 (path + p)
1164 for p, e in entries.iteritems()
1170 for p, e in entries.iteritems()
@@ -1175,24 +1181,24 b' class svn_source(converter_source):'
1175 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
1181 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
1176 # that is to say "tests/PloneTestCase.py"
1182 # that is to say "tests/PloneTestCase.py"
1177 if path.startswith(module):
1183 if path.startswith(module):
1178 relative = path.rstrip('/')[len(module) :]
1184 relative = path.rstrip(b'/')[len(module) :]
1179 if relative.startswith('/'):
1185 if relative.startswith(b'/'):
1180 return relative[1:]
1186 return relative[1:]
1181 elif relative == '':
1187 elif relative == b'':
1182 return relative
1188 return relative
1183
1189
1184 # The path is outside our tracked tree...
1190 # The path is outside our tracked tree...
1185 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
1191 self.ui.debug(b'%r is not under %r, ignoring\n' % (path, module))
1186 return None
1192 return None
1187
1193
1188 def _checkpath(self, path, revnum, module=None):
1194 def _checkpath(self, path, revnum, module=None):
1189 if module is not None:
1195 if module is not None:
1190 prevmodule = self.reparent('')
1196 prevmodule = self.reparent(b'')
1191 path = module + '/' + path
1197 path = module + b'/' + path
1192 try:
1198 try:
1193 # ra.check_path does not like leading slashes very much, it leads
1199 # ra.check_path does not like leading slashes very much, it leads
1194 # to PROPFIND subversion errors
1200 # to PROPFIND subversion errors
1195 return svn.ra.check_path(self.ra, path.strip('/'), revnum)
1201 return svn.ra.check_path(self.ra, path.strip(b'/'), revnum)
1196 finally:
1202 finally:
1197 if module is not None:
1203 if module is not None:
1198 self.reparent(prevmodule)
1204 self.reparent(prevmodule)
@@ -1210,9 +1216,9 b' class svn_source(converter_source):'
1210 # supplied URL
1216 # supplied URL
1211 relpaths = []
1217 relpaths = []
1212 for p in paths:
1218 for p in paths:
1213 if not p.startswith('/'):
1219 if not p.startswith(b'/'):
1214 p = self.module + '/' + p
1220 p = self.module + b'/' + p
1215 relpaths.append(p.strip('/'))
1221 relpaths.append(p.strip(b'/'))
1216 args = [
1222 args = [
1217 self.baseurl,
1223 self.baseurl,
1218 relpaths,
1224 relpaths,
@@ -1223,11 +1229,11 b' class svn_source(converter_source):'
1223 strict_node_history,
1229 strict_node_history,
1224 ]
1230 ]
1225 # developer config: convert.svn.debugsvnlog
1231 # developer config: convert.svn.debugsvnlog
1226 if not self.ui.configbool('convert', 'svn.debugsvnlog'):
1232 if not self.ui.configbool(b'convert', b'svn.debugsvnlog'):
1227 return directlogstream(*args)
1233 return directlogstream(*args)
1228 arg = encodeargs(args)
1234 arg = encodeargs(args)
1229 hgexe = procutil.hgexecutable()
1235 hgexe = procutil.hgexecutable()
1230 cmd = '%s debugsvnlog' % procutil.shellquote(hgexe)
1236 cmd = b'%s debugsvnlog' % procutil.shellquote(hgexe)
1231 stdin, stdout = procutil.popen2(procutil.quotecommand(cmd))
1237 stdin, stdout = procutil.popen2(procutil.quotecommand(cmd))
1232 stdin.write(arg)
1238 stdin.write(arg)
1233 try:
1239 try:
@@ -1235,8 +1241,8 b' class svn_source(converter_source):'
1235 except IOError:
1241 except IOError:
1236 raise error.Abort(
1242 raise error.Abort(
1237 _(
1243 _(
1238 'Mercurial failed to run itself, check'
1244 b'Mercurial failed to run itself, check'
1239 ' hg executable is in PATH'
1245 b' hg executable is in PATH'
1240 )
1246 )
1241 )
1247 )
1242 return logstream(stdout)
1248 return logstream(stdout)
@@ -1272,18 +1278,18 b' class svn_sink(converter_sink, commandli'
1272 os.chdir(self.cwd)
1278 os.chdir(self.cwd)
1273
1279
1274 def join(self, name):
1280 def join(self, name):
1275 return os.path.join(self.wc, '.svn', name)
1281 return os.path.join(self.wc, b'.svn', name)
1276
1282
1277 def revmapfile(self):
1283 def revmapfile(self):
1278 return self.join('hg-shamap')
1284 return self.join(b'hg-shamap')
1279
1285
1280 def authorfile(self):
1286 def authorfile(self):
1281 return self.join('hg-authormap')
1287 return self.join(b'hg-authormap')
1282
1288
1283 def __init__(self, ui, repotype, path):
1289 def __init__(self, ui, repotype, path):
1284
1290
1285 converter_sink.__init__(self, ui, repotype, path)
1291 converter_sink.__init__(self, ui, repotype, path)
1286 commandline.__init__(self, ui, 'svn')
1292 commandline.__init__(self, ui, b'svn')
1287 self.delete = []
1293 self.delete = []
1288 self.setexec = []
1294 self.setexec = []
1289 self.delexec = []
1295 self.delexec = []
@@ -1292,51 +1298,53 b' class svn_sink(converter_sink, commandli'
1292 self.cwd = encoding.getcwd()
1298 self.cwd = encoding.getcwd()
1293
1299
1294 created = False
1300 created = False
1295 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
1301 if os.path.isfile(os.path.join(path, b'.svn', b'entries')):
1296 self.wc = os.path.realpath(path)
1302 self.wc = os.path.realpath(path)
1297 self.run0('update')
1303 self.run0(b'update')
1298 else:
1304 else:
1299 if not re.search(br'^(file|http|https|svn|svn\+ssh)\://', path):
1305 if not re.search(br'^(file|http|https|svn|svn\+ssh)\://', path):
1300 path = os.path.realpath(path)
1306 path = os.path.realpath(path)
1301 if os.path.isdir(os.path.dirname(path)):
1307 if os.path.isdir(os.path.dirname(path)):
1302 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
1308 if not os.path.exists(
1309 os.path.join(path, b'db', b'fs-type')
1310 ):
1303 ui.status(
1311 ui.status(
1304 _("initializing svn repository '%s'\n")
1312 _(b"initializing svn repository '%s'\n")
1305 % os.path.basename(path)
1313 % os.path.basename(path)
1306 )
1314 )
1307 commandline(ui, 'svnadmin').run0('create', path)
1315 commandline(ui, b'svnadmin').run0(b'create', path)
1308 created = path
1316 created = path
1309 path = util.normpath(path)
1317 path = util.normpath(path)
1310 if not path.startswith('/'):
1318 if not path.startswith(b'/'):
1311 path = '/' + path
1319 path = b'/' + path
1312 path = 'file://' + path
1320 path = b'file://' + path
1313
1321
1314 wcpath = os.path.join(
1322 wcpath = os.path.join(
1315 encoding.getcwd(), os.path.basename(path) + '-wc'
1323 encoding.getcwd(), os.path.basename(path) + b'-wc'
1316 )
1324 )
1317 ui.status(
1325 ui.status(
1318 _("initializing svn working copy '%s'\n")
1326 _(b"initializing svn working copy '%s'\n")
1319 % os.path.basename(wcpath)
1327 % os.path.basename(wcpath)
1320 )
1328 )
1321 self.run0('checkout', path, wcpath)
1329 self.run0(b'checkout', path, wcpath)
1322
1330
1323 self.wc = wcpath
1331 self.wc = wcpath
1324 self.opener = vfsmod.vfs(self.wc)
1332 self.opener = vfsmod.vfs(self.wc)
1325 self.wopener = vfsmod.vfs(self.wc)
1333 self.wopener = vfsmod.vfs(self.wc)
1326 self.childmap = mapfile(ui, self.join('hg-childmap'))
1334 self.childmap = mapfile(ui, self.join(b'hg-childmap'))
1327 if util.checkexec(self.wc):
1335 if util.checkexec(self.wc):
1328 self.is_exec = util.isexec
1336 self.is_exec = util.isexec
1329 else:
1337 else:
1330 self.is_exec = None
1338 self.is_exec = None
1331
1339
1332 if created:
1340 if created:
1333 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
1341 hook = os.path.join(created, b'hooks', b'pre-revprop-change')
1334 fp = open(hook, 'wb')
1342 fp = open(hook, b'wb')
1335 fp.write(pre_revprop_change)
1343 fp.write(pre_revprop_change)
1336 fp.close()
1344 fp.close()
1337 util.setflags(hook, False, True)
1345 util.setflags(hook, False, True)
1338
1346
1339 output = self.run0('info')
1347 output = self.run0(b'info')
1340 self.uuid = self.uuid_re.search(output).group(1).strip()
1348 self.uuid = self.uuid_re.search(output).group(1).strip()
1341
1349
1342 def wjoin(self, *names):
1350 def wjoin(self, *names):
@@ -1348,7 +1356,7 b' class svn_sink(converter_sink, commandli'
1348 # already tracked entries, so we have to track and filter them
1356 # already tracked entries, so we have to track and filter them
1349 # ourselves.
1357 # ourselves.
1350 m = set()
1358 m = set()
1351 output = self.run0('ls', recursive=True, xml=True)
1359 output = self.run0(b'ls', recursive=True, xml=True)
1352 doc = xml.dom.minidom.parseString(output)
1360 doc = xml.dom.minidom.parseString(output)
1353 for e in doc.getElementsByTagName(r'entry'):
1361 for e in doc.getElementsByTagName(r'entry'):
1354 for n in e.childNodes:
1362 for n in e.childNodes:
@@ -1367,7 +1375,7 b' class svn_sink(converter_sink, commandli'
1367 return m
1375 return m
1368
1376
1369 def putfile(self, filename, flags, data):
1377 def putfile(self, filename, flags, data):
1370 if 'l' in flags:
1378 if b'l' in flags:
1371 self.wopener.symlink(data, filename)
1379 self.wopener.symlink(data, filename)
1372 else:
1380 else:
1373 try:
1381 try:
@@ -1387,12 +1395,12 b' class svn_sink(converter_sink, commandli'
1387
1395
1388 if self.is_exec:
1396 if self.is_exec:
1389 if wasexec:
1397 if wasexec:
1390 if 'x' not in flags:
1398 if b'x' not in flags:
1391 self.delexec.append(filename)
1399 self.delexec.append(filename)
1392 else:
1400 else:
1393 if 'x' in flags:
1401 if b'x' in flags:
1394 self.setexec.append(filename)
1402 self.setexec.append(filename)
1395 util.setflags(self.wjoin(filename), False, 'x' in flags)
1403 util.setflags(self.wjoin(filename), False, b'x' in flags)
1396
1404
1397 def _copyfile(self, source, dest):
1405 def _copyfile(self, source, dest):
1398 # SVN's copy command pukes if the destination file exists, but
1406 # SVN's copy command pukes if the destination file exists, but
@@ -1402,13 +1410,13 b' class svn_sink(converter_sink, commandli'
1402 exists = os.path.lexists(wdest)
1410 exists = os.path.lexists(wdest)
1403 if exists:
1411 if exists:
1404 fd, tempname = pycompat.mkstemp(
1412 fd, tempname = pycompat.mkstemp(
1405 prefix='hg-copy-', dir=os.path.dirname(wdest)
1413 prefix=b'hg-copy-', dir=os.path.dirname(wdest)
1406 )
1414 )
1407 os.close(fd)
1415 os.close(fd)
1408 os.unlink(tempname)
1416 os.unlink(tempname)
1409 os.rename(wdest, tempname)
1417 os.rename(wdest, tempname)
1410 try:
1418 try:
1411 self.run0('copy', source, dest)
1419 self.run0(b'copy', source, dest)
1412 finally:
1420 finally:
1413 self.manifest.add(dest)
1421 self.manifest.add(dest)
1414 if exists:
1422 if exists:
@@ -1424,7 +1432,7 b' class svn_sink(converter_sink, commandli'
1424 if os.path.isdir(self.wjoin(f)):
1432 if os.path.isdir(self.wjoin(f)):
1425 dirs.add(f)
1433 dirs.add(f)
1426 i = len(f)
1434 i = len(f)
1427 for i in iter(lambda: f.rfind('/', 0, i), -1):
1435 for i in iter(lambda: f.rfind(b'/', 0, i), -1):
1428 dirs.add(f[:i])
1436 dirs.add(f[:i])
1429 return dirs
1437 return dirs
1430
1438
@@ -1434,21 +1442,21 b' class svn_sink(converter_sink, commandli'
1434 ]
1442 ]
1435 if add_dirs:
1443 if add_dirs:
1436 self.manifest.update(add_dirs)
1444 self.manifest.update(add_dirs)
1437 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1445 self.xargs(add_dirs, b'add', non_recursive=True, quiet=True)
1438 return add_dirs
1446 return add_dirs
1439
1447
1440 def add_files(self, files):
1448 def add_files(self, files):
1441 files = [f for f in files if f not in self.manifest]
1449 files = [f for f in files if f not in self.manifest]
1442 if files:
1450 if files:
1443 self.manifest.update(files)
1451 self.manifest.update(files)
1444 self.xargs(files, 'add', quiet=True)
1452 self.xargs(files, b'add', quiet=True)
1445 return files
1453 return files
1446
1454
1447 def addchild(self, parent, child):
1455 def addchild(self, parent, child):
1448 self.childmap[parent] = child
1456 self.childmap[parent] = child
1449
1457
1450 def revid(self, rev):
1458 def revid(self, rev):
1451 return "svn:%s@%s" % (self.uuid, rev)
1459 return b"svn:%s@%s" % (self.uuid, rev)
1452
1460
1453 def putcommit(
1461 def putcommit(
1454 self, files, copies, parents, commit, source, revmap, full, cleanp2
1462 self, files, copies, parents, commit, source, revmap, full, cleanp2
@@ -1480,49 +1488,49 b' class svn_sink(converter_sink, commandli'
1480 self._copyfile(s, d)
1488 self._copyfile(s, d)
1481 self.copies = []
1489 self.copies = []
1482 if self.delete:
1490 if self.delete:
1483 self.xargs(self.delete, 'delete')
1491 self.xargs(self.delete, b'delete')
1484 for f in self.delete:
1492 for f in self.delete:
1485 self.manifest.remove(f)
1493 self.manifest.remove(f)
1486 self.delete = []
1494 self.delete = []
1487 entries.update(self.add_files(files.difference(entries)))
1495 entries.update(self.add_files(files.difference(entries)))
1488 if self.delexec:
1496 if self.delexec:
1489 self.xargs(self.delexec, 'propdel', 'svn:executable')
1497 self.xargs(self.delexec, b'propdel', b'svn:executable')
1490 self.delexec = []
1498 self.delexec = []
1491 if self.setexec:
1499 if self.setexec:
1492 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1500 self.xargs(self.setexec, b'propset', b'svn:executable', b'*')
1493 self.setexec = []
1501 self.setexec = []
1494
1502
1495 fd, messagefile = pycompat.mkstemp(prefix='hg-convert-')
1503 fd, messagefile = pycompat.mkstemp(prefix=b'hg-convert-')
1496 fp = os.fdopen(fd, r'wb')
1504 fp = os.fdopen(fd, r'wb')
1497 fp.write(util.tonativeeol(commit.desc))
1505 fp.write(util.tonativeeol(commit.desc))
1498 fp.close()
1506 fp.close()
1499 try:
1507 try:
1500 output = self.run0(
1508 output = self.run0(
1501 'commit',
1509 b'commit',
1502 username=stringutil.shortuser(commit.author),
1510 username=stringutil.shortuser(commit.author),
1503 file=messagefile,
1511 file=messagefile,
1504 encoding='utf-8',
1512 encoding=b'utf-8',
1505 )
1513 )
1506 try:
1514 try:
1507 rev = self.commit_re.search(output).group(1)
1515 rev = self.commit_re.search(output).group(1)
1508 except AttributeError:
1516 except AttributeError:
1509 if not files:
1517 if not files:
1510 return parents[0] if parents else 'None'
1518 return parents[0] if parents else b'None'
1511 self.ui.warn(_('unexpected svn output:\n'))
1519 self.ui.warn(_(b'unexpected svn output:\n'))
1512 self.ui.warn(output)
1520 self.ui.warn(output)
1513 raise error.Abort(_('unable to cope with svn output'))
1521 raise error.Abort(_(b'unable to cope with svn output'))
1514 if commit.rev:
1522 if commit.rev:
1515 self.run(
1523 self.run(
1516 'propset',
1524 b'propset',
1517 'hg:convert-rev',
1525 b'hg:convert-rev',
1518 commit.rev,
1526 commit.rev,
1519 revprop=True,
1527 revprop=True,
1520 revision=rev,
1528 revision=rev,
1521 )
1529 )
1522 if commit.branch and commit.branch != 'default':
1530 if commit.branch and commit.branch != b'default':
1523 self.run(
1531 self.run(
1524 'propset',
1532 b'propset',
1525 'hg:convert-branch',
1533 b'hg:convert-branch',
1526 commit.branch,
1534 commit.branch,
1527 revprop=True,
1535 revprop=True,
1528 revision=rev,
1536 revision=rev,
@@ -1534,7 +1542,7 b' class svn_sink(converter_sink, commandli'
1534 os.unlink(messagefile)
1542 os.unlink(messagefile)
1535
1543
1536 def puttags(self, tags):
1544 def puttags(self, tags):
1537 self.ui.warn(_('writing Subversion tags is not yet implemented\n'))
1545 self.ui.warn(_(b'writing Subversion tags is not yet implemented\n'))
1538 return None, None
1546 return None, None
1539
1547
1540 def hascommitfrommap(self, rev):
1548 def hascommitfrommap(self, rev):
@@ -1549,8 +1557,8 b' class svn_sink(converter_sink, commandli'
1549 return True
1557 return True
1550 raise error.Abort(
1558 raise error.Abort(
1551 _(
1559 _(
1552 'splice map revision %s not found in subversion '
1560 b'splice map revision %s not found in subversion '
1553 'child map (revision lookups are not implemented)'
1561 b'child map (revision lookups are not implemented)'
1554 )
1562 )
1555 % rev
1563 % rev
1556 )
1564 )
@@ -54,13 +54,13 b' def _create_auth_baton(pool):'
54 )
54 )
55 if getprovider:
55 if getprovider:
56 # Available in svn >= 1.6
56 # Available in svn >= 1.6
57 for name in ('gnome_keyring', 'keychain', 'kwallet', 'windows'):
57 for name in (b'gnome_keyring', b'keychain', b'kwallet', b'windows'):
58 for type in ('simple', 'ssl_client_cert_pw', 'ssl_server_trust'):
58 for type in (b'simple', b'ssl_client_cert_pw', b'ssl_server_trust'):
59 p = getprovider(name, type, pool)
59 p = getprovider(name, type, pool)
60 if p:
60 if p:
61 providers.append(p)
61 providers.append(p)
62 else:
62 else:
63 if util.safehasattr(svn.client, 'get_windows_simple_provider'):
63 if util.safehasattr(svn.client, b'get_windows_simple_provider'):
64 providers.append(svn.client.get_windows_simple_provider(pool))
64 providers.append(svn.client.get_windows_simple_provider(pool))
65
65
66 return svn.core.svn_auth_open(providers, pool)
66 return svn.core.svn_auth_open(providers, pool)
@@ -75,14 +75,14 b' class SvnRaTransport(object):'
75 Open an ra connection to a Subversion repository.
75 Open an ra connection to a Subversion repository.
76 """
76 """
77
77
78 def __init__(self, url="", ra=None):
78 def __init__(self, url=b"", ra=None):
79 self.pool = Pool()
79 self.pool = Pool()
80 self.svn_url = url
80 self.svn_url = url
81 self.username = ''
81 self.username = b''
82 self.password = ''
82 self.password = b''
83
83
84 # Only Subversion 1.4 has reparent()
84 # Only Subversion 1.4 has reparent()
85 if ra is None or not util.safehasattr(svn.ra, 'reparent'):
85 if ra is None or not util.safehasattr(svn.ra, b'reparent'):
86 self.client = svn.client.create_context(self.pool)
86 self.client = svn.client.create_context(self.pool)
87 ab = _create_auth_baton(self.pool)
87 ab = _create_auth_baton(self.pool)
88 self.client.auth_baton = ab
88 self.client.auth_baton = ab
@@ -112,41 +112,41 b' from mercurial.utils import stringutil'
112 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
112 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
113 # be specifying the version(s) of Mercurial they are tested with, or
113 # be specifying the version(s) of Mercurial they are tested with, or
114 # leave the attribute unspecified.
114 # leave the attribute unspecified.
115 testedwith = 'ships-with-hg-core'
115 testedwith = b'ships-with-hg-core'
116
116
117 configtable = {}
117 configtable = {}
118 configitem = registrar.configitem(configtable)
118 configitem = registrar.configitem(configtable)
119
119
120 configitem(
120 configitem(
121 'eol', 'fix-trailing-newline', default=False,
121 b'eol', b'fix-trailing-newline', default=False,
122 )
122 )
123 configitem(
123 configitem(
124 'eol', 'native', default=pycompat.oslinesep,
124 b'eol', b'native', default=pycompat.oslinesep,
125 )
125 )
126 configitem(
126 configitem(
127 'eol', 'only-consistent', default=True,
127 b'eol', b'only-consistent', default=True,
128 )
128 )
129
129
130 # Matches a lone LF, i.e., one that is not part of CRLF.
130 # Matches a lone LF, i.e., one that is not part of CRLF.
131 singlelf = re.compile('(^|[^\r])\n')
131 singlelf = re.compile(b'(^|[^\r])\n')
132
132
133
133
134 def inconsistenteol(data):
134 def inconsistenteol(data):
135 return '\r\n' in data and singlelf.search(data)
135 return b'\r\n' in data and singlelf.search(data)
136
136
137
137
138 def tolf(s, params, ui, **kwargs):
138 def tolf(s, params, ui, **kwargs):
139 """Filter to convert to LF EOLs."""
139 """Filter to convert to LF EOLs."""
140 if stringutil.binary(s):
140 if stringutil.binary(s):
141 return s
141 return s
142 if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
142 if ui.configbool(b'eol', b'only-consistent') and inconsistenteol(s):
143 return s
143 return s
144 if (
144 if (
145 ui.configbool('eol', 'fix-trailing-newline')
145 ui.configbool(b'eol', b'fix-trailing-newline')
146 and s
146 and s
147 and not s.endswith('\n')
147 and not s.endswith(b'\n')
148 ):
148 ):
149 s = s + '\n'
149 s = s + b'\n'
150 return util.tolf(s)
150 return util.tolf(s)
151
151
152
152
@@ -154,14 +154,14 b' def tocrlf(s, params, ui, **kwargs):'
154 """Filter to convert to CRLF EOLs."""
154 """Filter to convert to CRLF EOLs."""
155 if stringutil.binary(s):
155 if stringutil.binary(s):
156 return s
156 return s
157 if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
157 if ui.configbool(b'eol', b'only-consistent') and inconsistenteol(s):
158 return s
158 return s
159 if (
159 if (
160 ui.configbool('eol', 'fix-trailing-newline')
160 ui.configbool(b'eol', b'fix-trailing-newline')
161 and s
161 and s
162 and not s.endswith('\n')
162 and not s.endswith(b'\n')
163 ):
163 ):
164 s = s + '\n'
164 s = s + b'\n'
165 return util.tocrlf(s)
165 return util.tocrlf(s)
166
166
167
167
@@ -171,60 +171,68 b' def isbinary(s, params):'
171
171
172
172
173 filters = {
173 filters = {
174 'to-lf': tolf,
174 b'to-lf': tolf,
175 'to-crlf': tocrlf,
175 b'to-crlf': tocrlf,
176 'is-binary': isbinary,
176 b'is-binary': isbinary,
177 # The following provide backwards compatibility with win32text
177 # The following provide backwards compatibility with win32text
178 'cleverencode:': tolf,
178 b'cleverencode:': tolf,
179 'cleverdecode:': tocrlf,
179 b'cleverdecode:': tocrlf,
180 }
180 }
181
181
182
182
183 class eolfile(object):
183 class eolfile(object):
184 def __init__(self, ui, root, data):
184 def __init__(self, ui, root, data):
185 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
185 self._decode = {
186 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
186 b'LF': b'to-lf',
187 b'CRLF': b'to-crlf',
188 b'BIN': b'is-binary',
189 }
190 self._encode = {
191 b'LF': b'to-lf',
192 b'CRLF': b'to-crlf',
193 b'BIN': b'is-binary',
194 }
187
195
188 self.cfg = config.config()
196 self.cfg = config.config()
189 # Our files should not be touched. The pattern must be
197 # Our files should not be touched. The pattern must be
190 # inserted first override a '** = native' pattern.
198 # inserted first override a '** = native' pattern.
191 self.cfg.set('patterns', '.hg*', 'BIN', 'eol')
199 self.cfg.set(b'patterns', b'.hg*', b'BIN', b'eol')
192 # We can then parse the user's patterns.
200 # We can then parse the user's patterns.
193 self.cfg.parse('.hgeol', data)
201 self.cfg.parse(b'.hgeol', data)
194
202
195 isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
203 isrepolf = self.cfg.get(b'repository', b'native') != b'CRLF'
196 self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
204 self._encode[b'NATIVE'] = isrepolf and b'to-lf' or b'to-crlf'
197 iswdlf = ui.config('eol', 'native') in ('LF', '\n')
205 iswdlf = ui.config(b'eol', b'native') in (b'LF', b'\n')
198 self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
206 self._decode[b'NATIVE'] = iswdlf and b'to-lf' or b'to-crlf'
199
207
200 include = []
208 include = []
201 exclude = []
209 exclude = []
202 self.patterns = []
210 self.patterns = []
203 for pattern, style in self.cfg.items('patterns'):
211 for pattern, style in self.cfg.items(b'patterns'):
204 key = style.upper()
212 key = style.upper()
205 if key == 'BIN':
213 if key == b'BIN':
206 exclude.append(pattern)
214 exclude.append(pattern)
207 else:
215 else:
208 include.append(pattern)
216 include.append(pattern)
209 m = match.match(root, '', [pattern])
217 m = match.match(root, b'', [pattern])
210 self.patterns.append((pattern, key, m))
218 self.patterns.append((pattern, key, m))
211 # This will match the files for which we need to care
219 # This will match the files for which we need to care
212 # about inconsistent newlines.
220 # about inconsistent newlines.
213 self.match = match.match(root, '', [], include, exclude)
221 self.match = match.match(root, b'', [], include, exclude)
214
222
215 def copytoui(self, ui):
223 def copytoui(self, ui):
216 for pattern, key, m in self.patterns:
224 for pattern, key, m in self.patterns:
217 try:
225 try:
218 ui.setconfig('decode', pattern, self._decode[key], 'eol')
226 ui.setconfig(b'decode', pattern, self._decode[key], b'eol')
219 ui.setconfig('encode', pattern, self._encode[key], 'eol')
227 ui.setconfig(b'encode', pattern, self._encode[key], b'eol')
220 except KeyError:
228 except KeyError:
221 ui.warn(
229 ui.warn(
222 _("ignoring unknown EOL style '%s' from %s\n")
230 _(b"ignoring unknown EOL style '%s' from %s\n")
223 % (key, self.cfg.source('patterns', pattern))
231 % (key, self.cfg.source(b'patterns', pattern))
224 )
232 )
225 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
233 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
226 for k, v in self.cfg.items('eol'):
234 for k, v in self.cfg.items(b'eol'):
227 ui.setconfig('eol', k, v, 'eol')
235 ui.setconfig(b'eol', k, v, b'eol')
228
236
229 def checkrev(self, repo, ctx, files):
237 def checkrev(self, repo, ctx, files):
230 failed = []
238 failed = []
@@ -237,9 +245,9 b' class eolfile(object):'
237 target = self._encode[key]
245 target = self._encode[key]
238 data = ctx[f].data()
246 data = ctx[f].data()
239 if (
247 if (
240 target == "to-lf"
248 target == b"to-lf"
241 and "\r\n" in data
249 and b"\r\n" in data
242 or target == "to-crlf"
250 or target == b"to-crlf"
243 and singlelf.search(data)
251 and singlelf.search(data)
244 ):
252 ):
245 failed.append((f, target, bytes(ctx)))
253 failed.append((f, target, bytes(ctx)))
@@ -254,15 +262,18 b' def parseeol(ui, repo, nodes):'
254 if node is None:
262 if node is None:
255 # Cannot use workingctx.data() since it would load
263 # Cannot use workingctx.data() since it would load
256 # and cache the filters before we configure them.
264 # and cache the filters before we configure them.
257 data = repo.wvfs('.hgeol').read()
265 data = repo.wvfs(b'.hgeol').read()
258 else:
266 else:
259 data = repo[node]['.hgeol'].data()
267 data = repo[node][b'.hgeol'].data()
260 return eolfile(ui, repo.root, data)
268 return eolfile(ui, repo.root, data)
261 except (IOError, LookupError):
269 except (IOError, LookupError):
262 pass
270 pass
263 except errormod.ParseError as inst:
271 except errormod.ParseError as inst:
264 ui.warn(
272 ui.warn(
265 _("warning: ignoring .hgeol file due to parse error " "at %s: %s\n")
273 _(
274 b"warning: ignoring .hgeol file due to parse error "
275 b"at %s: %s\n"
276 )
266 % (inst.args[1], inst.args[0])
277 % (inst.args[1], inst.args[0])
267 )
278 )
268 return None
279 return None
@@ -276,10 +287,10 b' def ensureenabled(ui):'
276 never loaded. This function ensure the extension is enabled when running
287 never loaded. This function ensure the extension is enabled when running
277 hooks.
288 hooks.
278 """
289 """
279 if 'eol' in ui._knownconfig:
290 if b'eol' in ui._knownconfig:
280 return
291 return
281 ui.setconfig('extensions', 'eol', '', source='internal')
292 ui.setconfig(b'extensions', b'eol', b'', source=b'internal')
282 extensions.loadall(ui, ['eol'])
293 extensions.loadall(ui, [b'eol'])
283
294
284
295
285 def _checkhook(ui, repo, node, headsonly):
296 def _checkhook(ui, repo, node, headsonly):
@@ -302,14 +313,16 b' def _checkhook(ui, repo, node, headsonly'
302 failed.extend(eol.checkrev(repo, ctx, files))
313 failed.extend(eol.checkrev(repo, ctx, files))
303
314
304 if failed:
315 if failed:
305 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
316 eols = {b'to-lf': b'CRLF', b'to-crlf': b'LF'}
306 msgs = []
317 msgs = []
307 for f, target, node in sorted(failed):
318 for f, target, node in sorted(failed):
308 msgs.append(
319 msgs.append(
309 _(" %s in %s should not have %s line endings")
320 _(b" %s in %s should not have %s line endings")
310 % (f, node, eols[target])
321 % (f, node, eols[target])
311 )
322 )
312 raise errormod.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
323 raise errormod.Abort(
324 _(b"end-of-line check failed:\n") + b"\n".join(msgs)
325 )
313
326
314
327
315 def checkallhook(ui, repo, node, hooktype, **kwargs):
328 def checkallhook(ui, repo, node, hooktype, **kwargs):
@@ -333,16 +346,16 b' def preupdate(ui, repo, hooktype, parent'
333
346
334
347
335 def uisetup(ui):
348 def uisetup(ui):
336 ui.setconfig('hooks', 'preupdate.eol', preupdate, 'eol')
349 ui.setconfig(b'hooks', b'preupdate.eol', preupdate, b'eol')
337
350
338
351
339 def extsetup(ui):
352 def extsetup(ui):
340 try:
353 try:
341 extensions.find('win32text')
354 extensions.find(b'win32text')
342 ui.warn(
355 ui.warn(
343 _(
356 _(
344 "the eol extension is incompatible with the "
357 b"the eol extension is incompatible with the "
345 "win32text extension\n"
358 b"win32text extension\n"
346 )
359 )
347 )
360 )
348 except KeyError:
361 except KeyError:
@@ -357,7 +370,7 b' def reposetup(ui, repo):'
357 for name, fn in filters.iteritems():
370 for name, fn in filters.iteritems():
358 repo.adddatafilter(name, fn)
371 repo.adddatafilter(name, fn)
359
372
360 ui.setconfig('patch', 'eol', 'auto', 'eol')
373 ui.setconfig(b'patch', b'eol', b'auto', b'eol')
361
374
362 class eolrepo(repo.__class__):
375 class eolrepo(repo.__class__):
363 def loadeol(self, nodes):
376 def loadeol(self, nodes):
@@ -368,37 +381,37 b' def reposetup(ui, repo):'
368 return eol.match
381 return eol.match
369
382
370 def _hgcleardirstate(self):
383 def _hgcleardirstate(self):
371 self._eolmatch = self.loadeol([None, 'tip'])
384 self._eolmatch = self.loadeol([None, b'tip'])
372 if not self._eolmatch:
385 if not self._eolmatch:
373 self._eolmatch = util.never
386 self._eolmatch = util.never
374 return
387 return
375
388
376 oldeol = None
389 oldeol = None
377 try:
390 try:
378 cachemtime = os.path.getmtime(self.vfs.join("eol.cache"))
391 cachemtime = os.path.getmtime(self.vfs.join(b"eol.cache"))
379 except OSError:
392 except OSError:
380 cachemtime = 0
393 cachemtime = 0
381 else:
394 else:
382 olddata = self.vfs.read("eol.cache")
395 olddata = self.vfs.read(b"eol.cache")
383 if olddata:
396 if olddata:
384 oldeol = eolfile(self.ui, self.root, olddata)
397 oldeol = eolfile(self.ui, self.root, olddata)
385
398
386 try:
399 try:
387 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
400 eolmtime = os.path.getmtime(self.wjoin(b".hgeol"))
388 except OSError:
401 except OSError:
389 eolmtime = 0
402 eolmtime = 0
390
403
391 if eolmtime > cachemtime:
404 if eolmtime > cachemtime:
392 self.ui.debug("eol: detected change in .hgeol\n")
405 self.ui.debug(b"eol: detected change in .hgeol\n")
393
406
394 hgeoldata = self.wvfs.read('.hgeol')
407 hgeoldata = self.wvfs.read(b'.hgeol')
395 neweol = eolfile(self.ui, self.root, hgeoldata)
408 neweol = eolfile(self.ui, self.root, hgeoldata)
396
409
397 wlock = None
410 wlock = None
398 try:
411 try:
399 wlock = self.wlock()
412 wlock = self.wlock()
400 for f in self.dirstate:
413 for f in self.dirstate:
401 if self.dirstate[f] != 'n':
414 if self.dirstate[f] != b'n':
402 continue
415 continue
403 if oldeol is not None:
416 if oldeol is not None:
404 if not oldeol.match(f) and not neweol.match(f):
417 if not oldeol.match(f) and not neweol.match(f):
@@ -419,7 +432,7 b' def reposetup(ui, repo):'
419 # the new .hgeol file specify a different filter
432 # the new .hgeol file specify a different filter
420 self.dirstate.normallookup(f)
433 self.dirstate.normallookup(f)
421 # Write the cache to update mtime and cache .hgeol
434 # Write the cache to update mtime and cache .hgeol
422 with self.vfs("eol.cache", "w") as f:
435 with self.vfs(b"eol.cache", b"w") as f:
423 f.write(hgeoldata)
436 f.write(hgeoldata)
424 except errormod.LockUnavailable:
437 except errormod.LockUnavailable:
425 # If we cannot lock the repository and clear the
438 # If we cannot lock the repository and clear the
@@ -447,7 +460,7 b' def reposetup(ui, repo):'
447 continue
460 continue
448 if inconsistenteol(data):
461 if inconsistenteol(data):
449 raise errormod.Abort(
462 raise errormod.Abort(
450 _("inconsistent newline style " "in %s\n") % f
463 _(b"inconsistent newline style " b"in %s\n") % f
451 )
464 )
452 return super(eolrepo, self).commitctx(ctx, error, origctx)
465 return super(eolrepo, self).commitctx(ctx, error, origctx)
453
466
@@ -118,26 +118,26 b' configtable = {}'
118 configitem = registrar.configitem(configtable)
118 configitem = registrar.configitem(configtable)
119
119
120 configitem(
120 configitem(
121 'extdiff', br'opts\..*', default='', generic=True,
121 b'extdiff', br'opts\..*', default=b'', generic=True,
122 )
122 )
123
123
124 configitem(
124 configitem(
125 'extdiff', br'gui\..*', generic=True,
125 b'extdiff', br'gui\..*', generic=True,
126 )
126 )
127
127
128 configitem(
128 configitem(
129 'diff-tools', br'.*\.diffargs$', default=None, generic=True,
129 b'diff-tools', br'.*\.diffargs$', default=None, generic=True,
130 )
130 )
131
131
132 configitem(
132 configitem(
133 'diff-tools', br'.*\.gui$', generic=True,
133 b'diff-tools', br'.*\.gui$', generic=True,
134 )
134 )
135
135
136 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
136 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
137 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
137 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
138 # be specifying the version(s) of Mercurial they are tested with, or
138 # be specifying the version(s) of Mercurial they are tested with, or
139 # leave the attribute unspecified.
139 # leave the attribute unspecified.
140 testedwith = 'ships-with-hg-core'
140 testedwith = b'ships-with-hg-core'
141
141
142
142
143 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
143 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
@@ -145,40 +145,40 b' def snapshot(ui, repo, files, node, tmpr'
145 if not using snapshot, -I/-X does not work and recursive diff
145 if not using snapshot, -I/-X does not work and recursive diff
146 in tools like kdiff3 and meld displays too many files.'''
146 in tools like kdiff3 and meld displays too many files.'''
147 dirname = os.path.basename(repo.root)
147 dirname = os.path.basename(repo.root)
148 if dirname == "":
148 if dirname == b"":
149 dirname = "root"
149 dirname = b"root"
150 if node is not None:
150 if node is not None:
151 dirname = '%s.%s' % (dirname, short(node))
151 dirname = b'%s.%s' % (dirname, short(node))
152 base = os.path.join(tmproot, dirname)
152 base = os.path.join(tmproot, dirname)
153 os.mkdir(base)
153 os.mkdir(base)
154 fnsandstat = []
154 fnsandstat = []
155
155
156 if node is not None:
156 if node is not None:
157 ui.note(
157 ui.note(
158 _('making snapshot of %d files from rev %s\n')
158 _(b'making snapshot of %d files from rev %s\n')
159 % (len(files), short(node))
159 % (len(files), short(node))
160 )
160 )
161 else:
161 else:
162 ui.note(
162 ui.note(
163 _('making snapshot of %d files from working directory\n')
163 _(b'making snapshot of %d files from working directory\n')
164 % (len(files))
164 % (len(files))
165 )
165 )
166
166
167 if files:
167 if files:
168 repo.ui.setconfig("ui", "archivemeta", False)
168 repo.ui.setconfig(b"ui", b"archivemeta", False)
169
169
170 archival.archive(
170 archival.archive(
171 repo,
171 repo,
172 base,
172 base,
173 node,
173 node,
174 'files',
174 b'files',
175 match=scmutil.matchfiles(repo, files),
175 match=scmutil.matchfiles(repo, files),
176 subrepos=listsubrepos,
176 subrepos=listsubrepos,
177 )
177 )
178
178
179 for fn in sorted(files):
179 for fn in sorted(files):
180 wfn = util.pconvert(fn)
180 wfn = util.pconvert(fn)
181 ui.note(' %s\n' % wfn)
181 ui.note(b' %s\n' % wfn)
182
182
183 if node is None:
183 if node is None:
184 dest = os.path.join(base, wfn)
184 dest = os.path.join(base, wfn)
@@ -202,20 +202,20 b' def formatcmdline('
202 # When not operating in 3-way mode, an empty string is
202 # When not operating in 3-way mode, an empty string is
203 # returned for parent2
203 # returned for parent2
204 replace = {
204 replace = {
205 'parent': parent1,
205 b'parent': parent1,
206 'parent1': parent1,
206 b'parent1': parent1,
207 'parent2': parent2,
207 b'parent2': parent2,
208 'plabel1': plabel1,
208 b'plabel1': plabel1,
209 'plabel2': plabel2,
209 b'plabel2': plabel2,
210 'child': child,
210 b'child': child,
211 'clabel': clabel,
211 b'clabel': clabel,
212 'root': repo_root,
212 b'root': repo_root,
213 }
213 }
214
214
215 def quote(match):
215 def quote(match):
216 pre = match.group(2)
216 pre = match.group(2)
217 key = match.group(3)
217 key = match.group(3)
218 if not do3way and key == 'parent2':
218 if not do3way and key == b'parent2':
219 return pre
219 return pre
220 return pre + procutil.shellquote(replace[key])
220 return pre + procutil.shellquote(replace[key])
221
221
@@ -225,7 +225,7 b' def formatcmdline('
225 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1'
225 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1'
226 )
226 )
227 if not do3way and not re.search(regex, cmdline):
227 if not do3way and not re.search(regex, cmdline):
228 cmdline += ' $parent1 $child'
228 cmdline += b' $parent1 $child'
229 return re.sub(regex, quote, cmdline)
229 return re.sub(regex, quote, cmdline)
230
230
231
231
@@ -273,8 +273,8 b' def _runperfilediff('
273 if not os.path.isfile(path1a):
273 if not os.path.isfile(path1a):
274 path1a = os.devnull
274 path1a = os.devnull
275
275
276 path1b = ''
276 path1b = b''
277 label1b = ''
277 label1b = b''
278 if do3way:
278 if do3way:
279 path1b = os.path.join(tmproot, dir1b, commonfile)
279 path1b = os.path.join(tmproot, dir1b, commonfile)
280 label1b = commonfile + rev1b
280 label1b = commonfile + rev1b
@@ -286,24 +286,24 b' def _runperfilediff('
286
286
287 if confirm:
287 if confirm:
288 # Prompt before showing this diff
288 # Prompt before showing this diff
289 difffiles = _('diff %s (%d of %d)') % (
289 difffiles = _(b'diff %s (%d of %d)') % (
290 commonfile,
290 commonfile,
291 idx + 1,
291 idx + 1,
292 totalfiles,
292 totalfiles,
293 )
293 )
294 responses = _(
294 responses = _(
295 '[Yns?]'
295 b'[Yns?]'
296 '$$ &Yes, show diff'
296 b'$$ &Yes, show diff'
297 '$$ &No, skip this diff'
297 b'$$ &No, skip this diff'
298 '$$ &Skip remaining diffs'
298 b'$$ &Skip remaining diffs'
299 '$$ &? (display help)'
299 b'$$ &? (display help)'
300 )
300 )
301 r = ui.promptchoice('%s %s' % (difffiles, responses))
301 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
302 if r == 3: # ?
302 if r == 3: # ?
303 while r == 3:
303 while r == 3:
304 for c, t in ui.extractchoices(responses)[1]:
304 for c, t in ui.extractchoices(responses)[1]:
305 ui.write('%s - %s\n' % (c, encoding.lower(t)))
305 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
306 r = ui.promptchoice('%s %s' % (difffiles, responses))
306 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
307 if r == 0: # yes
307 if r == 0: # yes
308 pass
308 pass
309 elif r == 1: # no
309 elif r == 1: # no
@@ -331,22 +331,22 b' def _runperfilediff('
331 # as we know, the tool doesn't have a GUI, in which case
331 # as we know, the tool doesn't have a GUI, in which case
332 # we can't run multiple CLI programs at the same time.
332 # we can't run multiple CLI programs at the same time.
333 ui.debug(
333 ui.debug(
334 'running %r in %s\n' % (pycompat.bytestr(curcmdline), tmproot)
334 b'running %r in %s\n' % (pycompat.bytestr(curcmdline), tmproot)
335 )
335 )
336 ui.system(curcmdline, cwd=tmproot, blockedtag='extdiff')
336 ui.system(curcmdline, cwd=tmproot, blockedtag=b'extdiff')
337 else:
337 else:
338 # Run the comparison program but don't wait, as we're
338 # Run the comparison program but don't wait, as we're
339 # going to rapid-fire each file diff and then wait on
339 # going to rapid-fire each file diff and then wait on
340 # the whole group.
340 # the whole group.
341 ui.debug(
341 ui.debug(
342 'running %r in %s (backgrounded)\n'
342 b'running %r in %s (backgrounded)\n'
343 % (pycompat.bytestr(curcmdline), tmproot)
343 % (pycompat.bytestr(curcmdline), tmproot)
344 )
344 )
345 proc = _systembackground(curcmdline, cwd=tmproot)
345 proc = _systembackground(curcmdline, cwd=tmproot)
346 waitprocs.append(proc)
346 waitprocs.append(proc)
347
347
348 if waitprocs:
348 if waitprocs:
349 with ui.timeblockedsection('extdiff'):
349 with ui.timeblockedsection(b'extdiff'):
350 for proc in waitprocs:
350 for proc in waitprocs:
351 proc.wait()
351 proc.wait()
352
352
@@ -360,12 +360,12 b' def dodiff(ui, repo, cmdline, pats, opts'
360 - just invoke the diff for a single file in the working dir
360 - just invoke the diff for a single file in the working dir
361 '''
361 '''
362
362
363 revs = opts.get('rev')
363 revs = opts.get(b'rev')
364 change = opts.get('change')
364 change = opts.get(b'change')
365 do3way = '$parent2' in cmdline
365 do3way = b'$parent2' in cmdline
366
366
367 if revs and change:
367 if revs and change:
368 msg = _('cannot specify --rev and --change at the same time')
368 msg = _(b'cannot specify --rev and --change at the same time')
369 raise error.Abort(msg)
369 raise error.Abort(msg)
370 elif change:
370 elif change:
371 ctx2 = scmutil.revsingle(repo, change, None)
371 ctx2 = scmutil.revsingle(repo, change, None)
@@ -377,8 +377,8 b' def dodiff(ui, repo, cmdline, pats, opts'
377 else:
377 else:
378 ctx1b = repo[nullid]
378 ctx1b = repo[nullid]
379
379
380 perfile = opts.get('per_file')
380 perfile = opts.get(b'per_file')
381 confirm = opts.get('confirm')
381 confirm = opts.get(b'confirm')
382
382
383 node1a = ctx1a.node()
383 node1a = ctx1a.node()
384 node1b = ctx1b.node()
384 node1b = ctx1b.node()
@@ -389,17 +389,17 b' def dodiff(ui, repo, cmdline, pats, opts'
389 if node1b == nullid:
389 if node1b == nullid:
390 do3way = False
390 do3way = False
391
391
392 subrepos = opts.get('subrepos')
392 subrepos = opts.get(b'subrepos')
393
393
394 matcher = scmutil.match(repo[node2], pats, opts)
394 matcher = scmutil.match(repo[node2], pats, opts)
395
395
396 if opts.get('patch'):
396 if opts.get(b'patch'):
397 if subrepos:
397 if subrepos:
398 raise error.Abort(_('--patch cannot be used with --subrepos'))
398 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
399 if perfile:
399 if perfile:
400 raise error.Abort(_('--patch cannot be used with --per-file'))
400 raise error.Abort(_(b'--patch cannot be used with --per-file'))
401 if node2 is None:
401 if node2 is None:
402 raise error.Abort(_('--patch requires two revisions'))
402 raise error.Abort(_(b'--patch requires two revisions'))
403 else:
403 else:
404 mod_a, add_a, rem_a = map(
404 mod_a, add_a, rem_a = map(
405 set, repo.status(node1a, node2, matcher, listsubrepos=subrepos)[:3]
405 set, repo.status(node1a, node2, matcher, listsubrepos=subrepos)[:3]
@@ -416,33 +416,33 b' def dodiff(ui, repo, cmdline, pats, opts'
416 if not common:
416 if not common:
417 return 0
417 return 0
418
418
419 tmproot = pycompat.mkdtemp(prefix='extdiff.')
419 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
420 try:
420 try:
421 if not opts.get('patch'):
421 if not opts.get(b'patch'):
422 # Always make a copy of node1a (and node1b, if applicable)
422 # Always make a copy of node1a (and node1b, if applicable)
423 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
423 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
424 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[
424 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[
425 0
425 0
426 ]
426 ]
427 rev1a = '@%d' % repo[node1a].rev()
427 rev1a = b'@%d' % repo[node1a].rev()
428 if do3way:
428 if do3way:
429 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
429 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
430 dir1b = snapshot(
430 dir1b = snapshot(
431 ui, repo, dir1b_files, node1b, tmproot, subrepos
431 ui, repo, dir1b_files, node1b, tmproot, subrepos
432 )[0]
432 )[0]
433 rev1b = '@%d' % repo[node1b].rev()
433 rev1b = b'@%d' % repo[node1b].rev()
434 else:
434 else:
435 dir1b = None
435 dir1b = None
436 rev1b = ''
436 rev1b = b''
437
437
438 fnsandstat = []
438 fnsandstat = []
439
439
440 # If node2 in not the wc or there is >1 change, copy it
440 # If node2 in not the wc or there is >1 change, copy it
441 dir2root = ''
441 dir2root = b''
442 rev2 = ''
442 rev2 = b''
443 if node2:
443 if node2:
444 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
444 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
445 rev2 = '@%d' % repo[node2].rev()
445 rev2 = b'@%d' % repo[node2].rev()
446 elif len(common) > 1:
446 elif len(common) > 1:
447 # we only actually need to get the files to copy back to
447 # we only actually need to get the files to copy back to
448 # the working dir in this case (because the other cases
448 # the working dir in this case (because the other cases
@@ -453,7 +453,7 b' def dodiff(ui, repo, cmdline, pats, opts'
453 )
453 )
454 else:
454 else:
455 # This lets the diff tool open the changed file directly
455 # This lets the diff tool open the changed file directly
456 dir2 = ''
456 dir2 = b''
457 dir2root = repo.root
457 dir2root = repo.root
458
458
459 label1a = rev1a
459 label1a = rev1a
@@ -476,8 +476,8 b' def dodiff(ui, repo, cmdline, pats, opts'
476 dir2 = os.path.join(dir2root, dir2, common_file)
476 dir2 = os.path.join(dir2root, dir2, common_file)
477 label2 = common_file + rev2
477 label2 = common_file + rev2
478 else:
478 else:
479 template = 'hg-%h.patch'
479 template = b'hg-%h.patch'
480 with formatter.nullformatter(ui, 'extdiff', {}) as fm:
480 with formatter.nullformatter(ui, b'extdiff', {}) as fm:
481 cmdutil.export(
481 cmdutil.export(
482 repo,
482 repo,
483 [repo[node1a].rev(), repo[node2].rev()],
483 [repo[node1a].rev(), repo[node2].rev()],
@@ -507,9 +507,9 b' def dodiff(ui, repo, cmdline, pats, opts'
507 clabel=label2,
507 clabel=label2,
508 )
508 )
509 ui.debug(
509 ui.debug(
510 'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot)
510 b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot)
511 )
511 )
512 ui.system(cmdline, cwd=tmproot, blockedtag='extdiff')
512 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
513 else:
513 else:
514 # Run the external tool once for each pair of files
514 # Run the external tool once for each pair of files
515 _runperfilediff(
515 _runperfilediff(
@@ -545,35 +545,41 b' def dodiff(ui, repo, cmdline, pats, opts'
545 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
545 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
546 ):
546 ):
547 ui.debug(
547 ui.debug(
548 'file changed while diffing. '
548 b'file changed while diffing. '
549 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
549 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
550 )
550 )
551 util.copyfile(copy_fn, working_fn)
551 util.copyfile(copy_fn, working_fn)
552
552
553 return 1
553 return 1
554 finally:
554 finally:
555 ui.note(_('cleaning up temp directory\n'))
555 ui.note(_(b'cleaning up temp directory\n'))
556 shutil.rmtree(tmproot)
556 shutil.rmtree(tmproot)
557
557
558
558
559 extdiffopts = (
559 extdiffopts = (
560 [
560 [
561 ('o', 'option', [], _('pass option to comparison program'), _('OPT')),
562 ('r', 'rev', [], _('revision'), _('REV')),
563 ('c', 'change', '', _('change made by revision'), _('REV')),
564 (
561 (
565 '',
562 b'o',
566 'per-file',
563 b'option',
564 [],
565 _(b'pass option to comparison program'),
566 _(b'OPT'),
567 ),
568 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
569 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
570 (
571 b'',
572 b'per-file',
567 False,
573 False,
568 _('compare each file instead of revision snapshots'),
574 _(b'compare each file instead of revision snapshots'),
569 ),
575 ),
570 (
576 (
571 '',
577 b'',
572 'confirm',
578 b'confirm',
573 False,
579 False,
574 _('prompt user before each external program invocation'),
580 _(b'prompt user before each external program invocation'),
575 ),
581 ),
576 ('', 'patch', None, _('compare patches for two revisions')),
582 (b'', b'patch', None, _(b'compare patches for two revisions')),
577 ]
583 ]
578 + cmdutil.walkopts
584 + cmdutil.walkopts
579 + cmdutil.subrepoopts
585 + cmdutil.subrepoopts
@@ -581,10 +587,10 b' extdiffopts = ('
581
587
582
588
583 @command(
589 @command(
584 'extdiff',
590 b'extdiff',
585 [('p', 'program', '', _('comparison program to run'), _('CMD')),]
591 [(b'p', b'program', b'', _(b'comparison program to run'), _(b'CMD')),]
586 + extdiffopts,
592 + extdiffopts,
587 _('hg extdiff [OPT]... [FILE]...'),
593 _(b'hg extdiff [OPT]... [FILE]...'),
588 helpcategory=command.CATEGORY_FILE_CONTENTS,
594 helpcategory=command.CATEGORY_FILE_CONTENTS,
589 inferrepo=True,
595 inferrepo=True,
590 )
596 )
@@ -620,12 +626,12 b' def extdiff(ui, repo, *pats, **opts):'
620 the external program. It is ignored if --per-file isn't specified.
626 the external program. It is ignored if --per-file isn't specified.
621 '''
627 '''
622 opts = pycompat.byteskwargs(opts)
628 opts = pycompat.byteskwargs(opts)
623 program = opts.get('program')
629 program = opts.get(b'program')
624 option = opts.get('option')
630 option = opts.get(b'option')
625 if not program:
631 if not program:
626 program = 'diff'
632 program = b'diff'
627 option = option or ['-Npru']
633 option = option or [b'-Npru']
628 cmdline = ' '.join(map(procutil.shellquote, [program] + option))
634 cmdline = b' '.join(map(procutil.shellquote, [program] + option))
629 return dodiff(ui, repo, cmdline, pats, opts)
635 return dodiff(ui, repo, cmdline, pats, opts)
630
636
631
637
@@ -655,29 +661,29 b' class savedcmd(object):'
655
661
656 def __call__(self, ui, repo, *pats, **opts):
662 def __call__(self, ui, repo, *pats, **opts):
657 opts = pycompat.byteskwargs(opts)
663 opts = pycompat.byteskwargs(opts)
658 options = ' '.join(map(procutil.shellquote, opts['option']))
664 options = b' '.join(map(procutil.shellquote, opts[b'option']))
659 if options:
665 if options:
660 options = ' ' + options
666 options = b' ' + options
661 return dodiff(
667 return dodiff(
662 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
668 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
663 )
669 )
664
670
665
671
666 def uisetup(ui):
672 def uisetup(ui):
667 for cmd, path in ui.configitems('extdiff'):
673 for cmd, path in ui.configitems(b'extdiff'):
668 path = util.expandpath(path)
674 path = util.expandpath(path)
669 if cmd.startswith('cmd.'):
675 if cmd.startswith(b'cmd.'):
670 cmd = cmd[4:]
676 cmd = cmd[4:]
671 if not path:
677 if not path:
672 path = procutil.findexe(cmd)
678 path = procutil.findexe(cmd)
673 if path is None:
679 if path is None:
674 path = filemerge.findexternaltool(ui, cmd) or cmd
680 path = filemerge.findexternaltool(ui, cmd) or cmd
675 diffopts = ui.config('extdiff', 'opts.' + cmd)
681 diffopts = ui.config(b'extdiff', b'opts.' + cmd)
676 cmdline = procutil.shellquote(path)
682 cmdline = procutil.shellquote(path)
677 if diffopts:
683 if diffopts:
678 cmdline += ' ' + diffopts
684 cmdline += b' ' + diffopts
679 isgui = ui.configbool('extdiff', 'gui.' + cmd)
685 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
680 elif cmd.startswith('opts.') or cmd.startswith('gui.'):
686 elif cmd.startswith(b'opts.') or cmd.startswith(b'gui.'):
681 continue
687 continue
682 else:
688 else:
683 if path:
689 if path:
@@ -691,21 +697,21 b' def uisetup(ui):'
691 path = filemerge.findexternaltool(ui, cmd) or cmd
697 path = filemerge.findexternaltool(ui, cmd) or cmd
692 cmdline = procutil.shellquote(path)
698 cmdline = procutil.shellquote(path)
693 diffopts = False
699 diffopts = False
694 isgui = ui.configbool('extdiff', 'gui.' + cmd)
700 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
695 # look for diff arguments in [diff-tools] then [merge-tools]
701 # look for diff arguments in [diff-tools] then [merge-tools]
696 if not diffopts:
702 if not diffopts:
697 key = cmd + '.diffargs'
703 key = cmd + b'.diffargs'
698 for section in ('diff-tools', 'merge-tools'):
704 for section in (b'diff-tools', b'merge-tools'):
699 args = ui.config(section, key)
705 args = ui.config(section, key)
700 if args:
706 if args:
701 cmdline += ' ' + args
707 cmdline += b' ' + args
702 if isgui is None:
708 if isgui is None:
703 isgui = ui.configbool(section, cmd + '.gui') or False
709 isgui = ui.configbool(section, cmd + b'.gui') or False
704 break
710 break
705 command(
711 command(
706 cmd,
712 cmd,
707 extdiffopts[:],
713 extdiffopts[:],
708 _('hg %s [OPTION]... [FILE]...') % cmd,
714 _(b'hg %s [OPTION]... [FILE]...') % cmd,
709 helpcategory=command.CATEGORY_FILE_CONTENTS,
715 helpcategory=command.CATEGORY_FILE_CONTENTS,
710 inferrepo=True,
716 inferrepo=True,
711 )(savedcmd(path, cmdline, isgui))
717 )(savedcmd(path, cmdline, isgui))
@@ -69,44 +69,44 b' configtable = {}'
69 configitem = registrar.configitem(configtable)
69 configitem = registrar.configitem(configtable)
70
70
71 configitem(
71 configitem(
72 'factotum', 'executable', default='/bin/auth/factotum',
72 b'factotum', b'executable', default=b'/bin/auth/factotum',
73 )
73 )
74 configitem(
74 configitem(
75 'factotum', 'mountpoint', default='/mnt/factotum',
75 b'factotum', b'mountpoint', default=b'/mnt/factotum',
76 )
76 )
77 configitem(
77 configitem(
78 'factotum', 'service', default='hg',
78 b'factotum', b'service', default=b'hg',
79 )
79 )
80
80
81
81
82 def auth_getkey(self, params):
82 def auth_getkey(self, params):
83 if not self.ui.interactive():
83 if not self.ui.interactive():
84 raise error.Abort(_('factotum not interactive'))
84 raise error.Abort(_(b'factotum not interactive'))
85 if 'user=' not in params:
85 if b'user=' not in params:
86 params = '%s user?' % params
86 params = b'%s user?' % params
87 params = '%s !password?' % params
87 params = b'%s !password?' % params
88 os.system(procutil.tonativestr("%s -g '%s'" % (_executable, params)))
88 os.system(procutil.tonativestr(b"%s -g '%s'" % (_executable, params)))
89
89
90
90
91 def auth_getuserpasswd(self, getkey, params):
91 def auth_getuserpasswd(self, getkey, params):
92 params = 'proto=pass %s' % params
92 params = b'proto=pass %s' % params
93 while True:
93 while True:
94 fd = os.open('%s/rpc' % _mountpoint, os.O_RDWR)
94 fd = os.open(b'%s/rpc' % _mountpoint, os.O_RDWR)
95 try:
95 try:
96 os.write(fd, 'start %s' % params)
96 os.write(fd, b'start %s' % params)
97 l = os.read(fd, ERRMAX).split()
97 l = os.read(fd, ERRMAX).split()
98 if l[0] == 'ok':
98 if l[0] == b'ok':
99 os.write(fd, 'read')
99 os.write(fd, b'read')
100 status, user, passwd = os.read(fd, ERRMAX).split(None, 2)
100 status, user, passwd = os.read(fd, ERRMAX).split(None, 2)
101 if status == 'ok':
101 if status == b'ok':
102 if passwd.startswith("'"):
102 if passwd.startswith(b"'"):
103 if passwd.endswith("'"):
103 if passwd.endswith(b"'"):
104 passwd = passwd[1:-1].replace("''", "'")
104 passwd = passwd[1:-1].replace(b"''", b"'")
105 else:
105 else:
106 raise error.Abort(_('malformed password string'))
106 raise error.Abort(_(b'malformed password string'))
107 return (user, passwd)
107 return (user, passwd)
108 except (OSError, IOError):
108 except (OSError, IOError):
109 raise error.Abort(_('factotum not responding'))
109 raise error.Abort(_(b'factotum not responding'))
110 finally:
110 finally:
111 os.close(fd)
111 os.close(fd)
112 getkey(self, params)
112 getkey(self, params)
@@ -127,18 +127,18 b' def find_user_password(self, realm, auth'
127 self._writedebug(user, passwd)
127 self._writedebug(user, passwd)
128 return (user, passwd)
128 return (user, passwd)
129
129
130 prefix = ''
130 prefix = b''
131 res = httpconnection.readauthforuri(self.ui, authuri, user)
131 res = httpconnection.readauthforuri(self.ui, authuri, user)
132 if res:
132 if res:
133 _, auth = res
133 _, auth = res
134 prefix = auth.get('prefix')
134 prefix = auth.get(b'prefix')
135 user, passwd = auth.get('username'), auth.get('password')
135 user, passwd = auth.get(b'username'), auth.get(b'password')
136 if not user or not passwd:
136 if not user or not passwd:
137 if not prefix:
137 if not prefix:
138 prefix = realm.split(' ')[0].lower()
138 prefix = realm.split(b' ')[0].lower()
139 params = 'service=%s prefix=%s' % (_service, prefix)
139 params = b'service=%s prefix=%s' % (_service, prefix)
140 if user:
140 if user:
141 params = '%s user=%s' % (params, user)
141 params = b'%s user=%s' % (params, user)
142 user, passwd = auth_getuserpasswd(self, auth_getkey, params)
142 user, passwd = auth_getuserpasswd(self, auth_getkey, params)
143
143
144 self.add_password(realm, authuri, user, passwd)
144 self.add_password(realm, authuri, user, passwd)
@@ -148,8 +148,8 b' def find_user_password(self, realm, auth'
148
148
149 def uisetup(ui):
149 def uisetup(ui):
150 global _executable
150 global _executable
151 _executable = ui.config('factotum', 'executable')
151 _executable = ui.config(b'factotum', b'executable')
152 global _mountpoint
152 global _mountpoint
153 _mountpoint = ui.config('factotum', 'mountpoint')
153 _mountpoint = ui.config(b'factotum', b'mountpoint')
154 global _service
154 global _service
155 _service = ui.config('factotum', 'service')
155 _service = ui.config(b'factotum', b'service')
@@ -119,56 +119,56 b' from . import ('
119 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
119 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
120 # be specifying the version(s) of Mercurial they are tested with, or
120 # be specifying the version(s) of Mercurial they are tested with, or
121 # leave the attribute unspecified.
121 # leave the attribute unspecified.
122 testedwith = 'ships-with-hg-core'
122 testedwith = b'ships-with-hg-core'
123
123
124 cmdtable = commands.cmdtable
124 cmdtable = commands.cmdtable
125
125
126 configtable = {}
126 configtable = {}
127 configitem = registrar.configitem(configtable)
127 configitem = registrar.configitem(configtable)
128
128
129 configitem('fastannotate', 'modes', default=['fastannotate'])
129 configitem(b'fastannotate', b'modes', default=[b'fastannotate'])
130 configitem('fastannotate', 'server', default=False)
130 configitem(b'fastannotate', b'server', default=False)
131 configitem('fastannotate', 'client', default=False)
131 configitem(b'fastannotate', b'client', default=False)
132 configitem('fastannotate', 'unfilteredrepo', default=True)
132 configitem(b'fastannotate', b'unfilteredrepo', default=True)
133 configitem('fastannotate', 'defaultformat', default=['number'])
133 configitem(b'fastannotate', b'defaultformat', default=[b'number'])
134 configitem('fastannotate', 'perfhack', default=False)
134 configitem(b'fastannotate', b'perfhack', default=False)
135 configitem('fastannotate', 'mainbranch')
135 configitem(b'fastannotate', b'mainbranch')
136 configitem('fastannotate', 'forcetext', default=True)
136 configitem(b'fastannotate', b'forcetext', default=True)
137 configitem('fastannotate', 'forcefollow', default=True)
137 configitem(b'fastannotate', b'forcefollow', default=True)
138 configitem('fastannotate', 'clientfetchthreshold', default=10)
138 configitem(b'fastannotate', b'clientfetchthreshold', default=10)
139 configitem('fastannotate', 'serverbuildondemand', default=True)
139 configitem(b'fastannotate', b'serverbuildondemand', default=True)
140 configitem('fastannotate', 'remotepath', default='default')
140 configitem(b'fastannotate', b'remotepath', default=b'default')
141
141
142
142
143 def uisetup(ui):
143 def uisetup(ui):
144 modes = set(ui.configlist('fastannotate', 'modes'))
144 modes = set(ui.configlist(b'fastannotate', b'modes'))
145 if 'fctx' in modes:
145 if b'fctx' in modes:
146 modes.discard('hgweb')
146 modes.discard(b'hgweb')
147 for name in modes:
147 for name in modes:
148 if name == 'fastannotate':
148 if name == b'fastannotate':
149 commands.registercommand()
149 commands.registercommand()
150 elif name == 'hgweb':
150 elif name == b'hgweb':
151 from . import support
151 from . import support
152
152
153 support.replacehgwebannotate()
153 support.replacehgwebannotate()
154 elif name == 'fctx':
154 elif name == b'fctx':
155 from . import support
155 from . import support
156
156
157 support.replacefctxannotate()
157 support.replacefctxannotate()
158 commands.wrapdefault()
158 commands.wrapdefault()
159 else:
159 else:
160 raise hgerror.Abort(_('fastannotate: invalid mode: %s') % name)
160 raise hgerror.Abort(_(b'fastannotate: invalid mode: %s') % name)
161
161
162 if ui.configbool('fastannotate', 'server'):
162 if ui.configbool(b'fastannotate', b'server'):
163 protocol.serveruisetup(ui)
163 protocol.serveruisetup(ui)
164
164
165
165
166 def extsetup(ui):
166 def extsetup(ui):
167 # fastannotate has its own locking, without depending on repo lock
167 # fastannotate has its own locking, without depending on repo lock
168 # TODO: avoid mutating this unless the specific repo has it enabled
168 # TODO: avoid mutating this unless the specific repo has it enabled
169 localrepo.localrepository._wlockfreeprefix.add('fastannotate/')
169 localrepo.localrepository._wlockfreeprefix.add(b'fastannotate/')
170
170
171
171
172 def reposetup(ui, repo):
172 def reposetup(ui, repo):
173 if ui.configbool('fastannotate', 'client'):
173 if ui.configbool(b'fastannotate', b'client'):
174 protocol.clientreposetup(ui, repo)
174 protocol.clientreposetup(ui, repo)
@@ -34,7 +34,7 b' command = registrar.command(cmdtable)'
34
34
35 def _matchpaths(repo, rev, pats, opts, aopts=facontext.defaultopts):
35 def _matchpaths(repo, rev, pats, opts, aopts=facontext.defaultopts):
36 """generate paths matching given patterns"""
36 """generate paths matching given patterns"""
37 perfhack = repo.ui.configbool('fastannotate', 'perfhack')
37 perfhack = repo.ui.configbool(b'fastannotate', b'perfhack')
38
38
39 # disable perfhack if:
39 # disable perfhack if:
40 # a) any walkopt is used
40 # a) any walkopt is used
@@ -44,8 +44,8 b' def _matchpaths(repo, rev, pats, opts, a'
44 # cwd related to reporoot
44 # cwd related to reporoot
45 reporoot = os.path.dirname(repo.path)
45 reporoot = os.path.dirname(repo.path)
46 reldir = os.path.relpath(encoding.getcwd(), reporoot)
46 reldir = os.path.relpath(encoding.getcwd(), reporoot)
47 if reldir == '.':
47 if reldir == b'.':
48 reldir = ''
48 reldir = b''
49 if any(opts.get(o[1]) for o in commands.walkopts): # a)
49 if any(opts.get(o[1]) for o in commands.walkopts): # a)
50 perfhack = False
50 perfhack = False
51 else: # b)
51 else: # b)
@@ -56,7 +56,7 b' def _matchpaths(repo, rev, pats, opts, a'
56 # disable perfhack on '..' since it allows escaping from the repo
56 # disable perfhack on '..' since it allows escaping from the repo
57 if any(
57 if any(
58 (
58 (
59 '..' in f
59 b'..' in f
60 or not os.path.isfile(
60 or not os.path.isfile(
61 facontext.pathhelper(repo, f, aopts).linelogpath
61 facontext.pathhelper(repo, f, aopts).linelogpath
62 )
62 )
@@ -73,7 +73,7 b' def _matchpaths(repo, rev, pats, opts, a'
73 else:
73 else:
74
74
75 def bad(x, y):
75 def bad(x, y):
76 raise error.Abort("%s: %s" % (x, y))
76 raise error.Abort(b"%s: %s" % (x, y))
77
77
78 ctx = scmutil.revsingle(repo, rev)
78 ctx = scmutil.revsingle(repo, rev)
79 m = scmutil.match(ctx, pats, opts, badfn=bad)
79 m = scmutil.match(ctx, pats, opts, badfn=bad)
@@ -83,42 +83,57 b' def _matchpaths(repo, rev, pats, opts, a'
83
83
84 fastannotatecommandargs = {
84 fastannotatecommandargs = {
85 r'options': [
85 r'options': [
86 ('r', 'rev', '.', _('annotate the specified revision'), _('REV')),
86 (b'r', b'rev', b'.', _(b'annotate the specified revision'), _(b'REV')),
87 ('u', 'user', None, _('list the author (long with -v)')),
87 (b'u', b'user', None, _(b'list the author (long with -v)')),
88 ('f', 'file', None, _('list the filename')),
88 (b'f', b'file', None, _(b'list the filename')),
89 ('d', 'date', None, _('list the date (short with -q)')),
89 (b'd', b'date', None, _(b'list the date (short with -q)')),
90 ('n', 'number', None, _('list the revision number (default)')),
90 (b'n', b'number', None, _(b'list the revision number (default)')),
91 ('c', 'changeset', None, _('list the changeset')),
91 (b'c', b'changeset', None, _(b'list the changeset')),
92 (
93 b'l',
94 b'line-number',
95 None,
96 _(b'show line number at the first ' b'appearance'),
97 ),
92 (
98 (
93 'l',
99 b'e',
94 'line-number',
100 b'deleted',
95 None,
101 None,
96 _('show line number at the first ' 'appearance'),
102 _(b'show deleted lines (slow) (EXPERIMENTAL)'),
97 ),
103 ),
98 ('e', 'deleted', None, _('show deleted lines (slow) (EXPERIMENTAL)')),
99 ('', 'no-content', None, _('do not show file content (EXPERIMENTAL)')),
100 ('', 'no-follow', None, _("don't follow copies and renames")),
101 (
104 (
102 '',
105 b'',
103 'linear',
106 b'no-content',
107 None,
108 _(b'do not show file content (EXPERIMENTAL)'),
109 ),
110 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
111 (
112 b'',
113 b'linear',
104 None,
114 None,
105 _(
115 _(
106 'enforce linear history, ignore second parent '
116 b'enforce linear history, ignore second parent '
107 'of merges (EXPERIMENTAL)'
117 b'of merges (EXPERIMENTAL)'
108 ),
118 ),
109 ),
119 ),
110 ('', 'long-hash', None, _('show long changeset hash (EXPERIMENTAL)')),
111 (
120 (
112 '',
121 b'',
113 'rebuild',
122 b'long-hash',
114 None,
123 None,
115 _('rebuild cache even if it exists ' '(EXPERIMENTAL)'),
124 _(b'show long changeset hash (EXPERIMENTAL)'),
125 ),
126 (
127 b'',
128 b'rebuild',
129 None,
130 _(b'rebuild cache even if it exists ' b'(EXPERIMENTAL)'),
116 ),
131 ),
117 ]
132 ]
118 + commands.diffwsopts
133 + commands.diffwsopts
119 + commands.walkopts
134 + commands.walkopts
120 + commands.formatteropts,
135 + commands.formatteropts,
121 r'synopsis': _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
136 r'synopsis': _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
122 r'inferrepo': True,
137 r'inferrepo': True,
123 }
138 }
124
139
@@ -155,52 +170,55 b' def fastannotate(ui, repo, *pats, **opts'
155 affecting results are used.
170 affecting results are used.
156 """
171 """
157 if not pats:
172 if not pats:
158 raise error.Abort(_('at least one filename or pattern is required'))
173 raise error.Abort(_(b'at least one filename or pattern is required'))
159
174
160 # performance hack: filtered repo can be slow. unfilter by default.
175 # performance hack: filtered repo can be slow. unfilter by default.
161 if ui.configbool('fastannotate', 'unfilteredrepo'):
176 if ui.configbool(b'fastannotate', b'unfilteredrepo'):
162 repo = repo.unfiltered()
177 repo = repo.unfiltered()
163
178
164 opts = pycompat.byteskwargs(opts)
179 opts = pycompat.byteskwargs(opts)
165
180
166 rev = opts.get('rev', '.')
181 rev = opts.get(b'rev', b'.')
167 rebuild = opts.get('rebuild', False)
182 rebuild = opts.get(b'rebuild', False)
168
183
169 diffopts = patch.difffeatureopts(
184 diffopts = patch.difffeatureopts(
170 ui, opts, section='annotate', whitespace=True
185 ui, opts, section=b'annotate', whitespace=True
171 )
186 )
172 aopts = facontext.annotateopts(
187 aopts = facontext.annotateopts(
173 diffopts=diffopts,
188 diffopts=diffopts,
174 followmerge=not opts.get('linear', False),
189 followmerge=not opts.get(b'linear', False),
175 followrename=not opts.get('no_follow', False),
190 followrename=not opts.get(b'no_follow', False),
176 )
191 )
177
192
178 if not any(
193 if not any(
179 opts.get(s) for s in ['user', 'date', 'file', 'number', 'changeset']
194 opts.get(s)
195 for s in [b'user', b'date', b'file', b'number', b'changeset']
180 ):
196 ):
181 # default 'number' for compatibility. but fastannotate is more
197 # default 'number' for compatibility. but fastannotate is more
182 # efficient with "changeset", "line-number" and "no-content".
198 # efficient with "changeset", "line-number" and "no-content".
183 for name in ui.configlist('fastannotate', 'defaultformat', ['number']):
199 for name in ui.configlist(
200 b'fastannotate', b'defaultformat', [b'number']
201 ):
184 opts[name] = True
202 opts[name] = True
185
203
186 ui.pager('fastannotate')
204 ui.pager(b'fastannotate')
187 template = opts.get('template')
205 template = opts.get(b'template')
188 if template == 'json':
206 if template == b'json':
189 formatter = faformatter.jsonformatter(ui, repo, opts)
207 formatter = faformatter.jsonformatter(ui, repo, opts)
190 else:
208 else:
191 formatter = faformatter.defaultformatter(ui, repo, opts)
209 formatter = faformatter.defaultformatter(ui, repo, opts)
192 showdeleted = opts.get('deleted', False)
210 showdeleted = opts.get(b'deleted', False)
193 showlines = not bool(opts.get('no_content'))
211 showlines = not bool(opts.get(b'no_content'))
194 showpath = opts.get('file', False)
212 showpath = opts.get(b'file', False)
195
213
196 # find the head of the main (master) branch
214 # find the head of the main (master) branch
197 master = ui.config('fastannotate', 'mainbranch') or rev
215 master = ui.config(b'fastannotate', b'mainbranch') or rev
198
216
199 # paths will be used for prefetching and the real annotating
217 # paths will be used for prefetching and the real annotating
200 paths = list(_matchpaths(repo, rev, pats, opts, aopts))
218 paths = list(_matchpaths(repo, rev, pats, opts, aopts))
201
219
202 # for client, prefetch from the server
220 # for client, prefetch from the server
203 if util.safehasattr(repo, 'prefetchfastannotate'):
221 if util.safehasattr(repo, b'prefetchfastannotate'):
204 repo.prefetchfastannotate(paths)
222 repo.prefetchfastannotate(paths)
205
223
206 for path in paths:
224 for path in paths:
@@ -238,7 +256,7 b' def fastannotate(ui, repo, *pats, **opts'
238
256
239 _newopts = set()
257 _newopts = set()
240 _knownopts = {
258 _knownopts = {
241 opt[1].replace('-', '_')
259 opt[1].replace(b'-', b'_')
242 for opt in (fastannotatecommandargs[r'options'] + commands.globalopts)
260 for opt in (fastannotatecommandargs[r'options'] + commands.globalopts)
243 }
261 }
244
262
@@ -246,16 +264,16 b' def fastannotate(ui, repo, *pats, **opts'
246 def _annotatewrapper(orig, ui, repo, *pats, **opts):
264 def _annotatewrapper(orig, ui, repo, *pats, **opts):
247 """used by wrapdefault"""
265 """used by wrapdefault"""
248 # we need this hack until the obsstore has 0.0 seconds perf impact
266 # we need this hack until the obsstore has 0.0 seconds perf impact
249 if ui.configbool('fastannotate', 'unfilteredrepo'):
267 if ui.configbool(b'fastannotate', b'unfilteredrepo'):
250 repo = repo.unfiltered()
268 repo = repo.unfiltered()
251
269
252 # treat the file as text (skip the isbinary check)
270 # treat the file as text (skip the isbinary check)
253 if ui.configbool('fastannotate', 'forcetext'):
271 if ui.configbool(b'fastannotate', b'forcetext'):
254 opts[r'text'] = True
272 opts[r'text'] = True
255
273
256 # check if we need to do prefetch (client-side)
274 # check if we need to do prefetch (client-side)
257 rev = opts.get(r'rev')
275 rev = opts.get(r'rev')
258 if util.safehasattr(repo, 'prefetchfastannotate') and rev is not None:
276 if util.safehasattr(repo, b'prefetchfastannotate') and rev is not None:
259 paths = list(_matchpaths(repo, rev, pats, pycompat.byteskwargs(opts)))
277 paths = list(_matchpaths(repo, rev, pats, pycompat.byteskwargs(opts)))
260 repo.prefetchfastannotate(paths)
278 repo.prefetchfastannotate(paths)
261
279
@@ -264,20 +282,20 b' def _annotatewrapper(orig, ui, repo, *pa'
264
282
265 def registercommand():
283 def registercommand():
266 """register the fastannotate command"""
284 """register the fastannotate command"""
267 name = 'fastannotate|fastblame|fa'
285 name = b'fastannotate|fastblame|fa'
268 command(name, helpbasic=True, **fastannotatecommandargs)(fastannotate)
286 command(name, helpbasic=True, **fastannotatecommandargs)(fastannotate)
269
287
270
288
271 def wrapdefault():
289 def wrapdefault():
272 """wrap the default annotate command, to be aware of the protocol"""
290 """wrap the default annotate command, to be aware of the protocol"""
273 extensions.wrapcommand(commands.table, 'annotate', _annotatewrapper)
291 extensions.wrapcommand(commands.table, b'annotate', _annotatewrapper)
274
292
275
293
276 @command(
294 @command(
277 'debugbuildannotatecache',
295 b'debugbuildannotatecache',
278 [('r', 'rev', '', _('build up to the specific revision'), _('REV'))]
296 [(b'r', b'rev', b'', _(b'build up to the specific revision'), _(b'REV'))]
279 + commands.walkopts,
297 + commands.walkopts,
280 _('[-r REV] FILE...'),
298 _(b'[-r REV] FILE...'),
281 )
299 )
282 def debugbuildannotatecache(ui, repo, *pats, **opts):
300 def debugbuildannotatecache(ui, repo, *pats, **opts):
283 """incrementally build fastannotate cache up to REV for specified files
301 """incrementally build fastannotate cache up to REV for specified files
@@ -291,25 +309,25 b' def debugbuildannotatecache(ui, repo, *p'
291 options and lives in '.hg/fastannotate/default'.
309 options and lives in '.hg/fastannotate/default'.
292 """
310 """
293 opts = pycompat.byteskwargs(opts)
311 opts = pycompat.byteskwargs(opts)
294 rev = opts.get('REV') or ui.config('fastannotate', 'mainbranch')
312 rev = opts.get(b'REV') or ui.config(b'fastannotate', b'mainbranch')
295 if not rev:
313 if not rev:
296 raise error.Abort(
314 raise error.Abort(
297 _('you need to provide a revision'),
315 _(b'you need to provide a revision'),
298 hint=_('set fastannotate.mainbranch or use --rev'),
316 hint=_(b'set fastannotate.mainbranch or use --rev'),
299 )
317 )
300 if ui.configbool('fastannotate', 'unfilteredrepo'):
318 if ui.configbool(b'fastannotate', b'unfilteredrepo'):
301 repo = repo.unfiltered()
319 repo = repo.unfiltered()
302 ctx = scmutil.revsingle(repo, rev)
320 ctx = scmutil.revsingle(repo, rev)
303 m = scmutil.match(ctx, pats, opts)
321 m = scmutil.match(ctx, pats, opts)
304 paths = list(ctx.walk(m))
322 paths = list(ctx.walk(m))
305 if util.safehasattr(repo, 'prefetchfastannotate'):
323 if util.safehasattr(repo, b'prefetchfastannotate'):
306 # client
324 # client
307 if opts.get('REV'):
325 if opts.get(b'REV'):
308 raise error.Abort(_('--rev cannot be used for client'))
326 raise error.Abort(_(b'--rev cannot be used for client'))
309 repo.prefetchfastannotate(paths)
327 repo.prefetchfastannotate(paths)
310 else:
328 else:
311 # server, or full repo
329 # server, or full repo
312 progress = ui.makeprogress(_('building'), total=len(paths))
330 progress = ui.makeprogress(_(b'building'), total=len(paths))
313 for i, path in enumerate(paths):
331 for i, path in enumerate(paths):
314 progress.update(i)
332 progress.update(i)
315 with facontext.annotatecontext(repo, path) as actx:
333 with facontext.annotatecontext(repo, path) as actx:
@@ -321,7 +339,7 b' def debugbuildannotatecache(ui, repo, *p'
321 # the cache is broken (could happen with renaming so the
339 # the cache is broken (could happen with renaming so the
322 # file history gets invalidated). rebuild and try again.
340 # file history gets invalidated). rebuild and try again.
323 ui.debug(
341 ui.debug(
324 'fastannotate: %s: rebuilding broken cache\n' % path
342 b'fastannotate: %s: rebuilding broken cache\n' % path
325 )
343 )
326 actx.rebuild()
344 actx.rebuild()
327 try:
345 try:
@@ -331,8 +349,8 b' def debugbuildannotatecache(ui, repo, *p'
331 # cache for other files.
349 # cache for other files.
332 ui.warn(
350 ui.warn(
333 _(
351 _(
334 'fastannotate: %s: failed to '
352 b'fastannotate: %s: failed to '
335 'build cache: %r\n'
353 b'build cache: %r\n'
336 )
354 )
337 % (path, ex)
355 % (path, ex)
338 )
356 )
@@ -52,7 +52,7 b' def _parents(f, follow=True):'
52 # renamed filectx won't have a filelog yet, so set it
52 # renamed filectx won't have a filelog yet, so set it
53 # from the cache to save time
53 # from the cache to save time
54 for p in pl:
54 for p in pl:
55 if not '_filelog' in p.__dict__:
55 if not b'_filelog' in p.__dict__:
56 p._filelog = _getflog(f._repo, p.path())
56 p._filelog = _getflog(f._repo, p.path())
57
57
58 return pl
58 return pl
@@ -62,8 +62,8 b' def _parents(f, follow=True):'
62 # so it takes a fctx instead of a pair of text and fctx.
62 # so it takes a fctx instead of a pair of text and fctx.
63 def _decorate(fctx):
63 def _decorate(fctx):
64 text = fctx.data()
64 text = fctx.data()
65 linecount = text.count('\n')
65 linecount = text.count(b'\n')
66 if text and not text.endswith('\n'):
66 if text and not text.endswith(b'\n'):
67 linecount += 1
67 linecount += 1
68 return ([(fctx, i) for i in pycompat.xrange(linecount)], text)
68 return ([(fctx, i) for i in pycompat.xrange(linecount)], text)
69
69
@@ -75,7 +75,7 b' def _pair(parent, child, blocks):'
75 for (a1, a2, b1, b2), t in blocks:
75 for (a1, a2, b1, b2), t in blocks:
76 # Changed blocks ('!') or blocks made only of blank lines ('~')
76 # Changed blocks ('!') or blocks made only of blank lines ('~')
77 # belong to the child.
77 # belong to the child.
78 if t == '=':
78 if t == b'=':
79 child[0][b1:b2] = parent[0][a1:a2]
79 child[0][b1:b2] = parent[0][a1:a2]
80 return child
80 return child
81
81
@@ -119,7 +119,7 b' def resolvefctx(repo, rev, path, resolve'
119 fctx = repo.filectx(path, changeid=ctx.rev())
119 fctx = repo.filectx(path, changeid=ctx.rev())
120 else:
120 else:
121 fctx = ctx[path]
121 fctx = ctx[path]
122 if adjustctx == 'linkrev':
122 if adjustctx == b'linkrev':
123 introrev = fctx.linkrev()
123 introrev = fctx.linkrev()
124 else:
124 else:
125 introrev = fctx.introrev()
125 introrev = fctx.introrev()
@@ -132,10 +132,10 b' def resolvefctx(repo, rev, path, resolve'
132 # like mercurial.store.encodedir, but use linelog suffixes: .m, .l, .lock
132 # like mercurial.store.encodedir, but use linelog suffixes: .m, .l, .lock
133 def encodedir(path):
133 def encodedir(path):
134 return (
134 return (
135 path.replace('.hg/', '.hg.hg/')
135 path.replace(b'.hg/', b'.hg.hg/')
136 .replace('.l/', '.l.hg/')
136 .replace(b'.l/', b'.l.hg/')
137 .replace('.m/', '.m.hg/')
137 .replace(b'.m/', b'.m.hg/')
138 .replace('.lock/', '.lock.hg/')
138 .replace(b'.lock/', b'.lock.hg/')
139 )
139 )
140
140
141
141
@@ -157,9 +157,9 b' class annotateopts(object):'
157 """
157 """
158
158
159 defaults = {
159 defaults = {
160 'diffopts': None,
160 b'diffopts': None,
161 'followrename': True,
161 b'followrename': True,
162 'followmerge': True,
162 b'followmerge': True,
163 }
163 }
164
164
165 def __init__(self, **opts):
165 def __init__(self, **opts):
@@ -170,17 +170,17 b' class annotateopts(object):'
170 @util.propertycache
170 @util.propertycache
171 def shortstr(self):
171 def shortstr(self):
172 """represent opts in a short string, suitable for a directory name"""
172 """represent opts in a short string, suitable for a directory name"""
173 result = ''
173 result = b''
174 if not self.followrename:
174 if not self.followrename:
175 result += 'r0'
175 result += b'r0'
176 if not self.followmerge:
176 if not self.followmerge:
177 result += 'm0'
177 result += b'm0'
178 if self.diffopts is not None:
178 if self.diffopts is not None:
179 assert isinstance(self.diffopts, mdiff.diffopts)
179 assert isinstance(self.diffopts, mdiff.diffopts)
180 diffopthash = hashdiffopts(self.diffopts)
180 diffopthash = hashdiffopts(self.diffopts)
181 if diffopthash != _defaultdiffopthash:
181 if diffopthash != _defaultdiffopthash:
182 result += 'i' + diffopthash
182 result += b'i' + diffopthash
183 return result or 'default'
183 return result or b'default'
184
184
185
185
186 defaultopts = annotateopts()
186 defaultopts = annotateopts()
@@ -206,7 +206,7 b' class _annotatecontext(object):'
206 def linelog(self):
206 def linelog(self):
207 if self._linelog is None:
207 if self._linelog is None:
208 if os.path.exists(self.linelogpath):
208 if os.path.exists(self.linelogpath):
209 with open(self.linelogpath, 'rb') as f:
209 with open(self.linelogpath, b'rb') as f:
210 try:
210 try:
211 self._linelog = linelogmod.linelog.fromdata(f.read())
211 self._linelog = linelogmod.linelog.fromdata(f.read())
212 except linelogmod.LineLogError:
212 except linelogmod.LineLogError:
@@ -226,7 +226,7 b' class _annotatecontext(object):'
226 self._revmap.flush()
226 self._revmap.flush()
227 self._revmap = None
227 self._revmap = None
228 if self._linelog is not None:
228 if self._linelog is not None:
229 with open(self.linelogpath, 'wb') as f:
229 with open(self.linelogpath, b'wb') as f:
230 f.write(self._linelog.encode())
230 f.write(self._linelog.encode())
231 self._linelog = None
231 self._linelog = None
232
232
@@ -308,11 +308,11 b' class _annotatecontext(object):'
308 if directly:
308 if directly:
309 if self.ui.debugflag:
309 if self.ui.debugflag:
310 self.ui.debug(
310 self.ui.debug(
311 'fastannotate: %s: using fast path '
311 b'fastannotate: %s: using fast path '
312 '(resolved fctx: %s)\n'
312 b'(resolved fctx: %s)\n'
313 % (
313 % (
314 self.path,
314 self.path,
315 stringutil.pprint(util.safehasattr(revfctx, 'node')),
315 stringutil.pprint(util.safehasattr(revfctx, b'node')),
316 )
316 )
317 )
317 )
318 return self.annotatedirectly(revfctx, showpath, showlines)
318 return self.annotatedirectly(revfctx, showpath, showlines)
@@ -356,8 +356,8 b' class _annotatecontext(object):'
356 if masterfctx:
356 if masterfctx:
357 if masterfctx.rev() is None:
357 if masterfctx.rev() is None:
358 raise error.Abort(
358 raise error.Abort(
359 _('cannot update linelog to wdir()'),
359 _(b'cannot update linelog to wdir()'),
360 hint=_('set fastannotate.mainbranch'),
360 hint=_(b'set fastannotate.mainbranch'),
361 )
361 )
362 initvisit.append(masterfctx)
362 initvisit.append(masterfctx)
363 visit = initvisit[:]
363 visit = initvisit[:]
@@ -403,13 +403,13 b' class _annotatecontext(object):'
403 if self.ui.debugflag:
403 if self.ui.debugflag:
404 if newmainbranch:
404 if newmainbranch:
405 self.ui.debug(
405 self.ui.debug(
406 'fastannotate: %s: %d new changesets in the main'
406 b'fastannotate: %s: %d new changesets in the main'
407 ' branch\n' % (self.path, len(newmainbranch))
407 b' branch\n' % (self.path, len(newmainbranch))
408 )
408 )
409 elif not hist: # no joints, no updates
409 elif not hist: # no joints, no updates
410 self.ui.debug(
410 self.ui.debug(
411 'fastannotate: %s: linelog cannot help in '
411 b'fastannotate: %s: linelog cannot help in '
412 'annotating this revision\n' % self.path
412 b'annotating this revision\n' % self.path
413 )
413 )
414
414
415 # prepare annotateresult so we can update linelog incrementally
415 # prepare annotateresult so we can update linelog incrementally
@@ -418,7 +418,7 b' class _annotatecontext(object):'
418 # 3rd DFS does the actual annotate
418 # 3rd DFS does the actual annotate
419 visit = initvisit[:]
419 visit = initvisit[:]
420 progress = self.ui.makeprogress(
420 progress = self.ui.makeprogress(
421 'building cache', total=len(newmainbranch)
421 b'building cache', total=len(newmainbranch)
422 )
422 )
423 while visit:
423 while visit:
424 f = visit[-1]
424 f = visit[-1]
@@ -463,7 +463,7 b' class _annotatecontext(object):'
463 if len(pl) == 2 and self.opts.followmerge: # merge
463 if len(pl) == 2 and self.opts.followmerge: # merge
464 bannotated = curr[0]
464 bannotated = curr[0]
465 if blocks is None: # no parents, add an empty one
465 if blocks is None: # no parents, add an empty one
466 blocks = list(self._diffblocks('', curr[1]))
466 blocks = list(self._diffblocks(b'', curr[1]))
467 self._appendrev(f, blocks, bannotated)
467 self._appendrev(f, blocks, bannotated)
468 elif showpath: # not append linelog, but we need to record path
468 elif showpath: # not append linelog, but we need to record path
469 self._node2path[f.node()] = f.path()
469 self._node2path[f.node()] = f.path()
@@ -490,7 +490,7 b' class _annotatecontext(object):'
490 if hsh is not None and (hsh, self.path) in self.revmap:
490 if hsh is not None and (hsh, self.path) in self.revmap:
491 f = hsh
491 f = hsh
492 if f is None:
492 if f is None:
493 adjustctx = 'linkrev' if self._perfhack else True
493 adjustctx = b'linkrev' if self._perfhack else True
494 f = self._resolvefctx(rev, adjustctx=adjustctx, resolverev=True)
494 f = self._resolvefctx(rev, adjustctx=adjustctx, resolverev=True)
495 result = f in self.revmap
495 result = f in self.revmap
496 if not result and self._perfhack:
496 if not result and self._perfhack:
@@ -511,7 +511,7 b' class _annotatecontext(object):'
511 # find a chain from rev to anything in the mainbranch
511 # find a chain from rev to anything in the mainbranch
512 if revfctx not in self.revmap:
512 if revfctx not in self.revmap:
513 chain = [revfctx]
513 chain = [revfctx]
514 a = ''
514 a = b''
515 while True:
515 while True:
516 f = chain[-1]
516 f = chain[-1]
517 pl = self._parentfunc(f)
517 pl = self._parentfunc(f)
@@ -589,8 +589,8 b' class _annotatecontext(object):'
589 hsh = annotateresult[idxs[0]][0]
589 hsh = annotateresult[idxs[0]][0]
590 if self.ui.debugflag:
590 if self.ui.debugflag:
591 self.ui.debug(
591 self.ui.debug(
592 'fastannotate: reading %s line #%d '
592 b'fastannotate: reading %s line #%d '
593 'to resolve lines %r\n'
593 b'to resolve lines %r\n'
594 % (node.short(hsh), linenum, idxs)
594 % (node.short(hsh), linenum, idxs)
595 )
595 )
596 fctx = self._resolvefctx(hsh, revmap.rev2path(rev))
596 fctx = self._resolvefctx(hsh, revmap.rev2path(rev))
@@ -603,14 +603,15 b' class _annotatecontext(object):'
603
603
604 # run the annotate and the lines should match to the file content
604 # run the annotate and the lines should match to the file content
605 self.ui.debug(
605 self.ui.debug(
606 'fastannotate: annotate %s to resolve lines\n' % node.short(hsh)
606 b'fastannotate: annotate %s to resolve lines\n'
607 % node.short(hsh)
607 )
608 )
608 linelog.annotate(rev)
609 linelog.annotate(rev)
609 fctx = self._resolvefctx(hsh, revmap.rev2path(rev))
610 fctx = self._resolvefctx(hsh, revmap.rev2path(rev))
610 annotated = linelog.annotateresult
611 annotated = linelog.annotateresult
611 lines = mdiff.splitnewlines(fctx.data())
612 lines = mdiff.splitnewlines(fctx.data())
612 if len(lines) != len(annotated):
613 if len(lines) != len(annotated):
613 raise faerror.CorruptedFileError('unexpected annotated lines')
614 raise faerror.CorruptedFileError(b'unexpected annotated lines')
614 # resolve lines from the annotate result
615 # resolve lines from the annotate result
615 for i, line in enumerate(lines):
616 for i, line in enumerate(lines):
616 k = annotated[i]
617 k = annotated[i]
@@ -633,11 +634,11 b' class _annotatecontext(object):'
633 llrev = self.revmap.hsh2rev(hsh)
634 llrev = self.revmap.hsh2rev(hsh)
634 if not llrev:
635 if not llrev:
635 raise faerror.CorruptedFileError(
636 raise faerror.CorruptedFileError(
636 '%s is not in revmap' % node.hex(hsh)
637 b'%s is not in revmap' % node.hex(hsh)
637 )
638 )
638 if (self.revmap.rev2flag(llrev) & revmapmod.sidebranchflag) != 0:
639 if (self.revmap.rev2flag(llrev) & revmapmod.sidebranchflag) != 0:
639 raise faerror.CorruptedFileError(
640 raise faerror.CorruptedFileError(
640 '%s is not in revmap mainbranch' % node.hex(hsh)
641 b'%s is not in revmap mainbranch' % node.hex(hsh)
641 )
642 )
642 self.linelog.annotate(llrev)
643 self.linelog.annotate(llrev)
643 result = [
644 result = [
@@ -677,7 +678,7 b' class _annotatecontext(object):'
677 """(fctx) -> int"""
678 """(fctx) -> int"""
678 # f should not be a linelog revision
679 # f should not be a linelog revision
679 if isinstance(f, int):
680 if isinstance(f, int):
680 raise error.ProgrammingError('f should not be an int')
681 raise error.ProgrammingError(b'f should not be an int')
681 # f is a fctx, allocate linelog rev on demand
682 # f is a fctx, allocate linelog rev on demand
682 hsh = f.node()
683 hsh = f.node()
683 rev = revmap.hsh2rev(hsh)
684 rev = revmap.hsh2rev(hsh)
@@ -690,7 +691,7 b' class _annotatecontext(object):'
690 siderevmap = {} # node: int
691 siderevmap = {} # node: int
691 if bannotated is not None:
692 if bannotated is not None:
692 for (a1, a2, b1, b2), op in blocks:
693 for (a1, a2, b1, b2), op in blocks:
693 if op != '=':
694 if op != b'=':
694 # f could be either linelong rev, or fctx.
695 # f could be either linelong rev, or fctx.
695 siderevs += [
696 siderevs += [
696 f
697 f
@@ -708,7 +709,7 b' class _annotatecontext(object):'
708 siderevmap[fctx] = llrev
709 siderevmap[fctx] = llrev
709
710
710 for (a1, a2, b1, b2), op in reversed(blocks):
711 for (a1, a2, b1, b2), op in reversed(blocks):
711 if op == '=':
712 if op == b'=':
712 continue
713 continue
713 if bannotated is None:
714 if bannotated is None:
714 linelog.replacelines(llrev, a1, a2, b1, b2)
715 linelog.replacelines(llrev, a1, a2, b1, b2)
@@ -760,7 +761,7 b' class _annotatecontext(object):'
760
761
761 @util.propertycache
762 @util.propertycache
762 def _perfhack(self):
763 def _perfhack(self):
763 return self.ui.configbool('fastannotate', 'perfhack')
764 return self.ui.configbool(b'fastannotate', b'perfhack')
764
765
765 def _resolvefctx(self, rev, path=None, **kwds):
766 def _resolvefctx(self, rev, path=None, **kwds):
766 return resolvefctx(self.repo, rev, (path or self.path), **kwds)
767 return resolvefctx(self.repo, rev, (path or self.path), **kwds)
@@ -781,7 +782,7 b' class pathhelper(object):'
781 def __init__(self, repo, path, opts=defaultopts):
782 def __init__(self, repo, path, opts=defaultopts):
782 # different options use different directories
783 # different options use different directories
783 self._vfspath = os.path.join(
784 self._vfspath = os.path.join(
784 'fastannotate', opts.shortstr, encodedir(path)
785 b'fastannotate', opts.shortstr, encodedir(path)
785 )
786 )
786 self._repo = repo
787 self._repo = repo
787
788
@@ -791,14 +792,14 b' class pathhelper(object):'
791
792
792 @property
793 @property
793 def linelogpath(self):
794 def linelogpath(self):
794 return self._repo.vfs.join(self._vfspath + '.l')
795 return self._repo.vfs.join(self._vfspath + b'.l')
795
796
796 def lock(self):
797 def lock(self):
797 return lockmod.lock(self._repo.vfs, self._vfspath + '.lock')
798 return lockmod.lock(self._repo.vfs, self._vfspath + b'.lock')
798
799
799 @property
800 @property
800 def revmappath(self):
801 def revmappath(self):
801 return self._repo.vfs.join(self._vfspath + '.m')
802 return self._repo.vfs.join(self._vfspath + b'.m')
802
803
803
804
804 @contextlib.contextmanager
805 @contextlib.contextmanager
@@ -831,7 +832,7 b' def annotatecontext(repo, path, opts=def'
831 except Exception:
832 except Exception:
832 if actx is not None:
833 if actx is not None:
833 actx.rebuild()
834 actx.rebuild()
834 repo.ui.debug('fastannotate: %s: cache broken and deleted\n' % path)
835 repo.ui.debug(b'fastannotate: %s: cache broken and deleted\n' % path)
835 raise
836 raise
836 finally:
837 finally:
837 if actx is not None:
838 if actx is not None:
@@ -844,7 +845,7 b' def fctxannotatecontext(fctx, follow=Tru'
844 """
845 """
845 repo = fctx._repo
846 repo = fctx._repo
846 path = fctx._path
847 path = fctx._path
847 if repo.ui.configbool('fastannotate', 'forcefollow', True):
848 if repo.ui.configbool(b'fastannotate', b'forcefollow', True):
848 follow = True
849 follow = True
849 aopts = annotateopts(diffopts=diffopts, followrename=follow)
850 aopts = annotateopts(diffopts=diffopts, followrename=follow)
850 return annotatecontext(repo, path, aopts, rebuild)
851 return annotatecontext(repo, path, aopts, rebuild)
@@ -33,35 +33,35 b' class defaultformatter(object):'
33 hexfunc = self._hexfunc
33 hexfunc = self._hexfunc
34
34
35 # special handling working copy "changeset" and "rev" functions
35 # special handling working copy "changeset" and "rev" functions
36 if self.opts.get('rev') == 'wdir()':
36 if self.opts.get(b'rev') == b'wdir()':
37 orig = hexfunc
37 orig = hexfunc
38 hexfunc = lambda x: None if x is None else orig(x)
38 hexfunc = lambda x: None if x is None else orig(x)
39 wnode = hexfunc(repo['.'].node()) + '+'
39 wnode = hexfunc(repo[b'.'].node()) + b'+'
40 wrev = '%d' % repo['.'].rev()
40 wrev = b'%d' % repo[b'.'].rev()
41 wrevpad = ''
41 wrevpad = b''
42 if not opts.get('changeset'): # only show + if changeset is hidden
42 if not opts.get(b'changeset'): # only show + if changeset is hidden
43 wrev += '+'
43 wrev += b'+'
44 wrevpad = ' '
44 wrevpad = b' '
45 revenc = lambda x: wrev if x is None else ('%d' % x) + wrevpad
45 revenc = lambda x: wrev if x is None else (b'%d' % x) + wrevpad
46
46
47 def csetenc(x):
47 def csetenc(x):
48 if x is None:
48 if x is None:
49 return wnode
49 return wnode
50 return pycompat.bytestr(x) + ' '
50 return pycompat.bytestr(x) + b' '
51
51
52 else:
52 else:
53 revenc = csetenc = pycompat.bytestr
53 revenc = csetenc = pycompat.bytestr
54
54
55 # opt name, separator, raw value (for json/plain), encoder (for plain)
55 # opt name, separator, raw value (for json/plain), encoder (for plain)
56 opmap = [
56 opmap = [
57 ('user', ' ', lambda x: getctx(x).user(), ui.shortuser),
57 (b'user', b' ', lambda x: getctx(x).user(), ui.shortuser),
58 ('number', ' ', lambda x: getctx(x).rev(), revenc),
58 (b'number', b' ', lambda x: getctx(x).rev(), revenc),
59 ('changeset', ' ', lambda x: hexfunc(x[0]), csetenc),
59 (b'changeset', b' ', lambda x: hexfunc(x[0]), csetenc),
60 ('date', ' ', lambda x: getctx(x).date(), datefunc),
60 (b'date', b' ', lambda x: getctx(x).date(), datefunc),
61 ('file', ' ', lambda x: x[2], pycompat.bytestr),
61 (b'file', b' ', lambda x: x[2], pycompat.bytestr),
62 ('line_number', ':', lambda x: x[1] + 1, pycompat.bytestr),
62 (b'line_number', b':', lambda x: x[1] + 1, pycompat.bytestr),
63 ]
63 ]
64 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
64 fieldnamemap = {b'number': b'rev', b'changeset': b'node'}
65 funcmap = [
65 funcmap = [
66 (get, sep, fieldnamemap.get(op, op), enc)
66 (get, sep, fieldnamemap.get(op, op), enc)
67 for op, sep, get, enc in opmap
67 for op, sep, get, enc in opmap
@@ -69,7 +69,7 b' class defaultformatter(object):'
69 ]
69 ]
70 # no separator for first column
70 # no separator for first column
71 funcmap[0] = list(funcmap[0])
71 funcmap[0] = list(funcmap[0])
72 funcmap[0][1] = ''
72 funcmap[0][1] = b''
73 self.funcmap = funcmap
73 self.funcmap = funcmap
74
74
75 def write(self, annotatedresult, lines=None, existinglines=None):
75 def write(self, annotatedresult, lines=None, existinglines=None):
@@ -83,39 +83,39 b' class defaultformatter(object):'
83 for f, sep, name, enc in self.funcmap:
83 for f, sep, name, enc in self.funcmap:
84 l = [enc(f(x)) for x in annotatedresult]
84 l = [enc(f(x)) for x in annotatedresult]
85 pieces.append(l)
85 pieces.append(l)
86 if name in ['node', 'date']: # node and date has fixed size
86 if name in [b'node', b'date']: # node and date has fixed size
87 l = l[:1]
87 l = l[:1]
88 widths = pycompat.maplist(encoding.colwidth, set(l))
88 widths = pycompat.maplist(encoding.colwidth, set(l))
89 maxwidth = max(widths) if widths else 0
89 maxwidth = max(widths) if widths else 0
90 maxwidths.append(maxwidth)
90 maxwidths.append(maxwidth)
91
91
92 # buffered output
92 # buffered output
93 result = ''
93 result = b''
94 for i in pycompat.xrange(len(annotatedresult)):
94 for i in pycompat.xrange(len(annotatedresult)):
95 for j, p in enumerate(pieces):
95 for j, p in enumerate(pieces):
96 sep = self.funcmap[j][1]
96 sep = self.funcmap[j][1]
97 padding = ' ' * (maxwidths[j] - len(p[i]))
97 padding = b' ' * (maxwidths[j] - len(p[i]))
98 result += sep + padding + p[i]
98 result += sep + padding + p[i]
99 if lines:
99 if lines:
100 if existinglines is None:
100 if existinglines is None:
101 result += ': ' + lines[i]
101 result += b': ' + lines[i]
102 else: # extra formatting showing whether a line exists
102 else: # extra formatting showing whether a line exists
103 key = (annotatedresult[i][0], annotatedresult[i][1])
103 key = (annotatedresult[i][0], annotatedresult[i][1])
104 if key in existinglines:
104 if key in existinglines:
105 result += ': ' + lines[i]
105 result += b': ' + lines[i]
106 else:
106 else:
107 result += ': ' + self.ui.label(
107 result += b': ' + self.ui.label(
108 '-' + lines[i], 'diff.deleted'
108 b'-' + lines[i], b'diff.deleted'
109 )
109 )
110
110
111 if result[-1:] != '\n':
111 if result[-1:] != b'\n':
112 result += '\n'
112 result += b'\n'
113
113
114 self.ui.write(result)
114 self.ui.write(result)
115
115
116 @util.propertycache
116 @util.propertycache
117 def _hexfunc(self):
117 def _hexfunc(self):
118 if self.ui.debugflag or self.opts.get('long_hash'):
118 if self.ui.debugflag or self.opts.get(b'long_hash'):
119 return node.hex
119 return node.hex
120 else:
120 else:
121 return node.short
121 return node.short
@@ -127,7 +127,7 b' class defaultformatter(object):'
127 class jsonformatter(defaultformatter):
127 class jsonformatter(defaultformatter):
128 def __init__(self, ui, repo, opts):
128 def __init__(self, ui, repo, opts):
129 super(jsonformatter, self).__init__(ui, repo, opts)
129 super(jsonformatter, self).__init__(ui, repo, opts)
130 self.ui.write('[')
130 self.ui.write(b'[')
131 self.needcomma = False
131 self.needcomma = False
132
132
133 def write(self, annotatedresult, lines=None, existinglines=None):
133 def write(self, annotatedresult, lines=None, existinglines=None):
@@ -139,23 +139,23 b' class jsonformatter(defaultformatter):'
139 for f, sep, name, enc in self.funcmap
139 for f, sep, name, enc in self.funcmap
140 ]
140 ]
141 if lines is not None:
141 if lines is not None:
142 pieces.append(('line', lines))
142 pieces.append((b'line', lines))
143 pieces.sort()
143 pieces.sort()
144
144
145 seps = [','] * len(pieces[:-1]) + ['']
145 seps = [b','] * len(pieces[:-1]) + [b'']
146
146
147 result = ''
147 result = b''
148 lasti = len(annotatedresult) - 1
148 lasti = len(annotatedresult) - 1
149 for i in pycompat.xrange(len(annotatedresult)):
149 for i in pycompat.xrange(len(annotatedresult)):
150 result += '\n {\n'
150 result += b'\n {\n'
151 for j, p in enumerate(pieces):
151 for j, p in enumerate(pieces):
152 k, vs = p
152 k, vs = p
153 result += ' "%s": %s%s\n' % (
153 result += b' "%s": %s%s\n' % (
154 k,
154 k,
155 templatefilters.json(vs[i], paranoid=False),
155 templatefilters.json(vs[i], paranoid=False),
156 seps[j],
156 seps[j],
157 )
157 )
158 result += ' }%s' % ('' if i == lasti else ',')
158 result += b' }%s' % (b'' if i == lasti else b',')
159 if lasti >= 0:
159 if lasti >= 0:
160 self.needcomma = True
160 self.needcomma = True
161
161
@@ -163,7 +163,7 b' class jsonformatter(defaultformatter):'
163
163
164 def _writecomma(self):
164 def _writecomma(self):
165 if self.needcomma:
165 if self.needcomma:
166 self.ui.write(',')
166 self.ui.write(b',')
167 self.needcomma = False
167 self.needcomma = False
168
168
169 @util.propertycache
169 @util.propertycache
@@ -171,4 +171,4 b' class jsonformatter(defaultformatter):'
171 return node.hex
171 return node.hex
172
172
173 def end(self):
173 def end(self):
174 self.ui.write('\n]\n')
174 self.ui.write(b'\n]\n')
@@ -25,12 +25,12 b' from . import context'
25
25
26 def _getmaster(ui):
26 def _getmaster(ui):
27 """get the mainbranch, and enforce it is set"""
27 """get the mainbranch, and enforce it is set"""
28 master = ui.config('fastannotate', 'mainbranch')
28 master = ui.config(b'fastannotate', b'mainbranch')
29 if not master:
29 if not master:
30 raise error.Abort(
30 raise error.Abort(
31 _(
31 _(
32 'fastannotate.mainbranch is required '
32 b'fastannotate.mainbranch is required '
33 'for both the client and the server'
33 b'for both the client and the server'
34 )
34 )
35 )
35 )
36 return master
36 return master
@@ -41,7 +41,7 b' def _getmaster(ui):'
41
41
42 def _capabilities(orig, repo, proto):
42 def _capabilities(orig, repo, proto):
43 result = orig(repo, proto)
43 result = orig(repo, proto)
44 result.append('getannotate')
44 result.append(b'getannotate')
45 return result
45 return result
46
46
47
47
@@ -49,9 +49,9 b' def _getannotate(repo, proto, path, last'
49 # output:
49 # output:
50 # FILE := vfspath + '\0' + str(size) + '\0' + content
50 # FILE := vfspath + '\0' + str(size) + '\0' + content
51 # OUTPUT := '' | FILE + OUTPUT
51 # OUTPUT := '' | FILE + OUTPUT
52 result = ''
52 result = b''
53 buildondemand = repo.ui.configbool(
53 buildondemand = repo.ui.configbool(
54 'fastannotate', 'serverbuildondemand', True
54 b'fastannotate', b'serverbuildondemand', True
55 )
55 )
56 with context.annotatecontext(repo, path) as actx:
56 with context.annotatecontext(repo, path) as actx:
57 if buildondemand:
57 if buildondemand:
@@ -80,25 +80,25 b' def _getannotate(repo, proto, path, last'
80 for p in [actx.revmappath, actx.linelogpath]:
80 for p in [actx.revmappath, actx.linelogpath]:
81 if not os.path.exists(p):
81 if not os.path.exists(p):
82 continue
82 continue
83 with open(p, 'rb') as f:
83 with open(p, b'rb') as f:
84 content = f.read()
84 content = f.read()
85 vfsbaselen = len(repo.vfs.base + '/')
85 vfsbaselen = len(repo.vfs.base + b'/')
86 relpath = p[vfsbaselen:]
86 relpath = p[vfsbaselen:]
87 result += '%s\0%d\0%s' % (relpath, len(content), content)
87 result += b'%s\0%d\0%s' % (relpath, len(content), content)
88 return result
88 return result
89
89
90
90
91 def _registerwireprotocommand():
91 def _registerwireprotocommand():
92 if 'getannotate' in wireprotov1server.commands:
92 if b'getannotate' in wireprotov1server.commands:
93 return
93 return
94 wireprotov1server.wireprotocommand('getannotate', 'path lastnode')(
94 wireprotov1server.wireprotocommand(b'getannotate', b'path lastnode')(
95 _getannotate
95 _getannotate
96 )
96 )
97
97
98
98
99 def serveruisetup(ui):
99 def serveruisetup(ui):
100 _registerwireprotocommand()
100 _registerwireprotocommand()
101 extensions.wrapfunction(wireprotov1server, '_capabilities', _capabilities)
101 extensions.wrapfunction(wireprotov1server, b'_capabilities', _capabilities)
102
102
103
103
104 # client-side
104 # client-side
@@ -109,15 +109,15 b' def _parseresponse(payload):'
109 i = 0
109 i = 0
110 l = len(payload) - 1
110 l = len(payload) - 1
111 state = 0 # 0: vfspath, 1: size
111 state = 0 # 0: vfspath, 1: size
112 vfspath = size = ''
112 vfspath = size = b''
113 while i < l:
113 while i < l:
114 ch = payload[i : i + 1]
114 ch = payload[i : i + 1]
115 if ch == '\0':
115 if ch == b'\0':
116 if state == 1:
116 if state == 1:
117 result[vfspath] = payload[i + 1 : i + 1 + int(size)]
117 result[vfspath] = payload[i + 1 : i + 1 + int(size)]
118 i += int(size)
118 i += int(size)
119 state = 0
119 state = 0
120 vfspath = size = ''
120 vfspath = size = b''
121 elif state == 0:
121 elif state == 0:
122 state = 1
122 state = 1
123 else:
123 else:
@@ -133,11 +133,11 b' def peersetup(ui, peer):'
133 class fastannotatepeer(peer.__class__):
133 class fastannotatepeer(peer.__class__):
134 @wireprotov1peer.batchable
134 @wireprotov1peer.batchable
135 def getannotate(self, path, lastnode=None):
135 def getannotate(self, path, lastnode=None):
136 if not self.capable('getannotate'):
136 if not self.capable(b'getannotate'):
137 ui.warn(_('remote peer cannot provide annotate cache\n'))
137 ui.warn(_(b'remote peer cannot provide annotate cache\n'))
138 yield None, None
138 yield None, None
139 else:
139 else:
140 args = {'path': path, 'lastnode': lastnode or ''}
140 args = {b'path': path, b'lastnode': lastnode or b''}
141 f = wireprotov1peer.future()
141 f = wireprotov1peer.future()
142 yield args, f
142 yield args, f
143 yield _parseresponse(f.value)
143 yield _parseresponse(f.value)
@@ -150,7 +150,7 b' def annotatepeer(repo):'
150 ui = repo.ui
150 ui = repo.ui
151
151
152 remotepath = ui.expandpath(
152 remotepath = ui.expandpath(
153 ui.config('fastannotate', 'remotepath', 'default')
153 ui.config(b'fastannotate', b'remotepath', b'default')
154 )
154 )
155 peer = hg.peer(ui, {}, remotepath)
155 peer = hg.peer(ui, {}, remotepath)
156
156
@@ -175,11 +175,12 b' def clientfetch(repo, paths, lastnodemap'
175 ui = repo.ui
175 ui = repo.ui
176 results = []
176 results = []
177 with peer.commandexecutor() as batcher:
177 with peer.commandexecutor() as batcher:
178 ui.debug('fastannotate: requesting %d files\n' % len(paths))
178 ui.debug(b'fastannotate: requesting %d files\n' % len(paths))
179 for p in paths:
179 for p in paths:
180 results.append(
180 results.append(
181 batcher.callcommand(
181 batcher.callcommand(
182 'getannotate', {'path': p, 'lastnode': lastnodemap.get(p)}
182 b'getannotate',
183 {b'path': p, b'lastnode': lastnodemap.get(p)},
183 )
184 )
184 )
185 )
185
186
@@ -189,19 +190,21 b' def clientfetch(repo, paths, lastnodemap'
189 r = {util.pconvert(p): v for p, v in r.iteritems()}
190 r = {util.pconvert(p): v for p, v in r.iteritems()}
190 for path in sorted(r):
191 for path in sorted(r):
191 # ignore malicious paths
192 # ignore malicious paths
192 if not path.startswith('fastannotate/') or '/../' in (
193 if not path.startswith(b'fastannotate/') or b'/../' in (
193 path + '/'
194 path + b'/'
194 ):
195 ):
195 ui.debug('fastannotate: ignored malicious path %s\n' % path)
196 ui.debug(
197 b'fastannotate: ignored malicious path %s\n' % path
198 )
196 continue
199 continue
197 content = r[path]
200 content = r[path]
198 if ui.debugflag:
201 if ui.debugflag:
199 ui.debug(
202 ui.debug(
200 'fastannotate: writing %d bytes to %s\n'
203 b'fastannotate: writing %d bytes to %s\n'
201 % (len(content), path)
204 % (len(content), path)
202 )
205 )
203 repo.vfs.makedirs(os.path.dirname(path))
206 repo.vfs.makedirs(os.path.dirname(path))
204 with repo.vfs(path, 'wb') as f:
207 with repo.vfs(path, b'wb') as f:
205 f.write(content)
208 f.write(content)
206
209
207
210
@@ -209,7 +212,7 b' def _filterfetchpaths(repo, paths):'
209 """return a subset of paths whose history is long and need to fetch linelog
212 """return a subset of paths whose history is long and need to fetch linelog
210 from the server. works with remotefilelog and non-remotefilelog repos.
213 from the server. works with remotefilelog and non-remotefilelog repos.
211 """
214 """
212 threshold = repo.ui.configint('fastannotate', 'clientfetchthreshold', 10)
215 threshold = repo.ui.configint(b'fastannotate', b'clientfetchthreshold', 10)
213 if threshold <= 0:
216 if threshold <= 0:
214 return paths
217 return paths
215
218
@@ -240,7 +243,7 b' def localreposetup(ui, repo):'
240 clientfetch(self, needupdatepaths, lastnodemap, peer)
243 clientfetch(self, needupdatepaths, lastnodemap, peer)
241 except Exception as ex:
244 except Exception as ex:
242 # could be directory not writable or so, not fatal
245 # could be directory not writable or so, not fatal
243 self.ui.debug('fastannotate: prefetch failed: %r\n' % ex)
246 self.ui.debug(b'fastannotate: prefetch failed: %r\n' % ex)
244
247
245 repo.__class__ = fastannotaterepo
248 repo.__class__ = fastannotaterepo
246
249
@@ -70,7 +70,7 b' class revmap(object):'
70 # since rename does not happen frequently, do not store path for every
70 # since rename does not happen frequently, do not store path for every
71 # revision. self._renamerevs can be used for bisecting.
71 # revision. self._renamerevs can be used for bisecting.
72 self._renamerevs = [0]
72 self._renamerevs = [0]
73 self._renamepaths = ['']
73 self._renamepaths = [b'']
74 self._lastmaxrev = -1
74 self._lastmaxrev = -1
75 if path:
75 if path:
76 if os.path.exists(path):
76 if os.path.exists(path):
@@ -98,9 +98,13 b' class revmap(object):'
98 if flush is True, incrementally update the file.
98 if flush is True, incrementally update the file.
99 """
99 """
100 if hsh in self._hsh2rev:
100 if hsh in self._hsh2rev:
101 raise error.CorruptedFileError('%r is in revmap already' % hex(hsh))
101 raise error.CorruptedFileError(
102 b'%r is in revmap already' % hex(hsh)
103 )
102 if len(hsh) != _hshlen:
104 if len(hsh) != _hshlen:
103 raise hgerror.ProgrammingError('hsh must be %d-char long' % _hshlen)
105 raise hgerror.ProgrammingError(
106 b'hsh must be %d-char long' % _hshlen
107 )
104 idx = len(self._rev2hsh)
108 idx = len(self._rev2hsh)
105 flag = 0
109 flag = 0
106 if sidebranch:
110 if sidebranch:
@@ -149,7 +153,7 b' class revmap(object):'
149 self._rev2hsh = [None]
153 self._rev2hsh = [None]
150 self._rev2flag = [None]
154 self._rev2flag = [None]
151 self._hsh2rev = {}
155 self._hsh2rev = {}
152 self._rev2path = ['']
156 self._rev2path = [b'']
153 self._lastmaxrev = -1
157 self._lastmaxrev = -1
154 if flush:
158 if flush:
155 self.flush()
159 self.flush()
@@ -159,12 +163,12 b' class revmap(object):'
159 if not self.path:
163 if not self.path:
160 return
164 return
161 if self._lastmaxrev == -1: # write the entire file
165 if self._lastmaxrev == -1: # write the entire file
162 with open(self.path, 'wb') as f:
166 with open(self.path, b'wb') as f:
163 f.write(self.HEADER)
167 f.write(self.HEADER)
164 for i in pycompat.xrange(1, len(self._rev2hsh)):
168 for i in pycompat.xrange(1, len(self._rev2hsh)):
165 self._writerev(i, f)
169 self._writerev(i, f)
166 else: # append incrementally
170 else: # append incrementally
167 with open(self.path, 'ab') as f:
171 with open(self.path, b'ab') as f:
168 for i in pycompat.xrange(
172 for i in pycompat.xrange(
169 self._lastmaxrev + 1, len(self._rev2hsh)
173 self._lastmaxrev + 1, len(self._rev2hsh)
170 ):
174 ):
@@ -179,7 +183,7 b' class revmap(object):'
179 # which is faster than both LOAD_CONST and LOAD_GLOBAL.
183 # which is faster than both LOAD_CONST and LOAD_GLOBAL.
180 flaglen = 1
184 flaglen = 1
181 hshlen = _hshlen
185 hshlen = _hshlen
182 with open(self.path, 'rb') as f:
186 with open(self.path, b'rb') as f:
183 if f.read(len(self.HEADER)) != self.HEADER:
187 if f.read(len(self.HEADER)) != self.HEADER:
184 raise error.CorruptedFileError()
188 raise error.CorruptedFileError()
185 self.clear(flush=False)
189 self.clear(flush=False)
@@ -205,23 +209,23 b' class revmap(object):'
205 """append a revision data to file"""
209 """append a revision data to file"""
206 flag = self._rev2flag[rev]
210 flag = self._rev2flag[rev]
207 hsh = self._rev2hsh[rev]
211 hsh = self._rev2hsh[rev]
208 f.write(struct.pack('B', flag))
212 f.write(struct.pack(b'B', flag))
209 if flag & renameflag:
213 if flag & renameflag:
210 path = self.rev2path(rev)
214 path = self.rev2path(rev)
211 if path is None:
215 if path is None:
212 raise error.CorruptedFileError('cannot find path for %s' % rev)
216 raise error.CorruptedFileError(b'cannot find path for %s' % rev)
213 f.write(path + b'\0')
217 f.write(path + b'\0')
214 f.write(hsh)
218 f.write(hsh)
215
219
216 @staticmethod
220 @staticmethod
217 def _readcstr(f):
221 def _readcstr(f):
218 """read a C-language-like '\0'-terminated string"""
222 """read a C-language-like '\0'-terminated string"""
219 buf = ''
223 buf = b''
220 while True:
224 while True:
221 ch = f.read(1)
225 ch = f.read(1)
222 if not ch: # unexpected eof
226 if not ch: # unexpected eof
223 raise error.CorruptedFileError()
227 raise error.CorruptedFileError()
224 if ch == '\0':
228 if ch == b'\0':
225 break
229 break
226 buf += ch
230 buf += ch
227 return buf
231 return buf
@@ -249,7 +253,7 b' def getlastnode(path):'
249 """
253 """
250 hsh = None
254 hsh = None
251 try:
255 try:
252 with open(path, 'rb') as f:
256 with open(path, b'rb') as f:
253 f.seek(-_hshlen, io.SEEK_END)
257 f.seek(-_hshlen, io.SEEK_END)
254 if f.tell() > len(revmap.HEADER):
258 if f.tell() > len(revmap.HEADER):
255 hsh = f.read(_hshlen)
259 hsh = f.read(_hshlen)
@@ -64,7 +64,7 b' def _convertoutputs(repo, annotated, con'
64
64
65 def _getmaster(fctx):
65 def _getmaster(fctx):
66 """(fctx) -> str"""
66 """(fctx) -> str"""
67 return fctx._repo.ui.config('fastannotate', 'mainbranch') or 'default'
67 return fctx._repo.ui.config(b'fastannotate', b'mainbranch') or b'default'
68
68
69
69
70 def _doannotate(fctx, follow=True, diffopts=None):
70 def _doannotate(fctx, follow=True, diffopts=None):
@@ -83,7 +83,7 b' def _doannotate(fctx, follow=True, diffo'
83 except Exception:
83 except Exception:
84 ac.rebuild() # try rebuild once
84 ac.rebuild() # try rebuild once
85 fctx._repo.ui.debug(
85 fctx._repo.ui.debug(
86 'fastannotate: %s: rebuilding broken cache\n' % fctx._path
86 b'fastannotate: %s: rebuilding broken cache\n' % fctx._path
87 )
87 )
88 try:
88 try:
89 annotated, contents = ac.annotate(
89 annotated, contents = ac.annotate(
@@ -98,7 +98,7 b' def _doannotate(fctx, follow=True, diffo'
98
98
99 def _hgwebannotate(orig, fctx, ui):
99 def _hgwebannotate(orig, fctx, ui):
100 diffopts = patch.difffeatureopts(
100 diffopts = patch.difffeatureopts(
101 ui, untrusted=True, section='annotate', whitespace=True
101 ui, untrusted=True, section=b'annotate', whitespace=True
102 )
102 )
103 return _doannotate(fctx, diffopts=diffopts)
103 return _doannotate(fctx, diffopts=diffopts)
104
104
@@ -115,7 +115,7 b' def _fctxannotate('
115 return _doannotate(self, follow, diffopts)
115 return _doannotate(self, follow, diffopts)
116 except Exception as ex:
116 except Exception as ex:
117 self._repo.ui.debug(
117 self._repo.ui.debug(
118 'fastannotate: falling back to the vanilla ' 'annotate: %r\n' % ex
118 b'fastannotate: falling back to the vanilla ' b'annotate: %r\n' % ex
119 )
119 )
120 return orig(self, follow=follow, skiprevs=skiprevs, diffopts=diffopts)
120 return orig(self, follow=follow, skiprevs=skiprevs, diffopts=diffopts)
121
121
@@ -130,8 +130,8 b' def _remotefctxannotate(orig, self, foll'
130
130
131
131
132 def replacehgwebannotate():
132 def replacehgwebannotate():
133 extensions.wrapfunction(hgweb.webutil, 'annotate', _hgwebannotate)
133 extensions.wrapfunction(hgweb.webutil, b'annotate', _hgwebannotate)
134
134
135
135
136 def replacefctxannotate():
136 def replacefctxannotate():
137 extensions.wrapfunction(hgcontext.basefilectx, 'annotate', _fctxannotate)
137 extensions.wrapfunction(hgcontext.basefilectx, b'annotate', _fctxannotate)
@@ -30,30 +30,30 b' command = registrar.command(cmdtable)'
30 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
30 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
31 # be specifying the version(s) of Mercurial they are tested with, or
31 # be specifying the version(s) of Mercurial they are tested with, or
32 # leave the attribute unspecified.
32 # leave the attribute unspecified.
33 testedwith = 'ships-with-hg-core'
33 testedwith = b'ships-with-hg-core'
34
34
35
35
36 @command(
36 @command(
37 'fetch',
37 b'fetch',
38 [
38 [
39 (
39 (
40 'r',
40 b'r',
41 'rev',
41 b'rev',
42 [],
42 [],
43 _('a specific revision you would like to pull'),
43 _(b'a specific revision you would like to pull'),
44 _('REV'),
44 _(b'REV'),
45 ),
45 ),
46 ('', 'edit', None, _('invoke editor on commit messages')),
46 (b'', b'edit', None, _(b'invoke editor on commit messages')),
47 ('', 'force-editor', None, _('edit commit message (DEPRECATED)')),
47 (b'', b'force-editor', None, _(b'edit commit message (DEPRECATED)')),
48 ('', 'switch-parent', None, _('switch parents when merging')),
48 (b'', b'switch-parent', None, _(b'switch parents when merging')),
49 ]
49 ]
50 + cmdutil.commitopts
50 + cmdutil.commitopts
51 + cmdutil.commitopts2
51 + cmdutil.commitopts2
52 + cmdutil.remoteopts,
52 + cmdutil.remoteopts,
53 _('hg fetch [SOURCE]'),
53 _(b'hg fetch [SOURCE]'),
54 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
54 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
55 )
55 )
56 def fetch(ui, repo, source='default', **opts):
56 def fetch(ui, repo, source=b'default', **opts):
57 '''pull changes from a remote repository, merge new changes if needed.
57 '''pull changes from a remote repository, merge new changes if needed.
58
58
59 This finds all changes from the repository at the specified path
59 This finds all changes from the repository at the specified path
@@ -74,9 +74,9 b" def fetch(ui, repo, source='default', **"
74 '''
74 '''
75
75
76 opts = pycompat.byteskwargs(opts)
76 opts = pycompat.byteskwargs(opts)
77 date = opts.get('date')
77 date = opts.get(b'date')
78 if date:
78 if date:
79 opts['date'] = dateutil.parsedate(date)
79 opts[b'date'] = dateutil.parsedate(date)
80
80
81 parent = repo.dirstate.p1()
81 parent = repo.dirstate.p1()
82 branch = repo.dirstate.branch()
82 branch = repo.dirstate.branch()
@@ -86,8 +86,8 b" def fetch(ui, repo, source='default', **"
86 branchnode = None
86 branchnode = None
87 if parent != branchnode:
87 if parent != branchnode:
88 raise error.Abort(
88 raise error.Abort(
89 _('working directory not at branch tip'),
89 _(b'working directory not at branch tip'),
90 hint=_("use 'hg update' to check out branch tip"),
90 hint=_(b"use 'hg update' to check out branch tip"),
91 )
91 )
92
92
93 wlock = lock = None
93 wlock = lock = None
@@ -102,23 +102,23 b" def fetch(ui, repo, source='default', **"
102 if len(bheads) > 1:
102 if len(bheads) > 1:
103 raise error.Abort(
103 raise error.Abort(
104 _(
104 _(
105 'multiple heads in this branch '
105 b'multiple heads in this branch '
106 '(use "hg heads ." and "hg merge" to merge)'
106 b'(use "hg heads ." and "hg merge" to merge)'
107 )
107 )
108 )
108 )
109
109
110 other = hg.peer(repo, opts, ui.expandpath(source))
110 other = hg.peer(repo, opts, ui.expandpath(source))
111 ui.status(
111 ui.status(
112 _('pulling from %s\n') % util.hidepassword(ui.expandpath(source))
112 _(b'pulling from %s\n') % util.hidepassword(ui.expandpath(source))
113 )
113 )
114 revs = None
114 revs = None
115 if opts['rev']:
115 if opts[b'rev']:
116 try:
116 try:
117 revs = [other.lookup(rev) for rev in opts['rev']]
117 revs = [other.lookup(rev) for rev in opts[b'rev']]
118 except error.CapabilityError:
118 except error.CapabilityError:
119 err = _(
119 err = _(
120 "other repository doesn't support revision lookup, "
120 b"other repository doesn't support revision lookup, "
121 "so a rev cannot be specified."
121 b"so a rev cannot be specified."
122 )
122 )
123 raise error.Abort(err)
123 raise error.Abort(err)
124
124
@@ -146,8 +146,8 b" def fetch(ui, repo, source='default', **"
146 if len(newheads) > 1:
146 if len(newheads) > 1:
147 ui.status(
147 ui.status(
148 _(
148 _(
149 'not merging with %d other new branch heads '
149 b'not merging with %d other new branch heads '
150 '(use "hg heads ." and "hg merge" to merge them)\n'
150 b'(use "hg heads ." and "hg merge" to merge them)\n'
151 )
151 )
152 % (len(newheads) - 1)
152 % (len(newheads) - 1)
153 )
153 )
@@ -162,17 +162,17 b" def fetch(ui, repo, source='default', **"
162 # By default, we consider the repository we're pulling
162 # By default, we consider the repository we're pulling
163 # *from* as authoritative, so we merge our changes into
163 # *from* as authoritative, so we merge our changes into
164 # theirs.
164 # theirs.
165 if opts['switch_parent']:
165 if opts[b'switch_parent']:
166 firstparent, secondparent = newparent, newheads[0]
166 firstparent, secondparent = newparent, newheads[0]
167 else:
167 else:
168 firstparent, secondparent = newheads[0], newparent
168 firstparent, secondparent = newheads[0], newparent
169 ui.status(
169 ui.status(
170 _('updating to %d:%s\n')
170 _(b'updating to %d:%s\n')
171 % (repo.changelog.rev(firstparent), short(firstparent))
171 % (repo.changelog.rev(firstparent), short(firstparent))
172 )
172 )
173 hg.clean(repo, firstparent)
173 hg.clean(repo, firstparent)
174 ui.status(
174 ui.status(
175 _('merging with %d:%s\n')
175 _(b'merging with %d:%s\n')
176 % (repo.changelog.rev(secondparent), short(secondparent))
176 % (repo.changelog.rev(secondparent), short(secondparent))
177 )
177 )
178 err = hg.merge(repo, secondparent, remind=False)
178 err = hg.merge(repo, secondparent, remind=False)
@@ -180,13 +180,15 b" def fetch(ui, repo, source='default', **"
180 if not err:
180 if not err:
181 # we don't translate commit messages
181 # we don't translate commit messages
182 message = cmdutil.logmessage(ui, opts) or (
182 message = cmdutil.logmessage(ui, opts) or (
183 'Automated merge with %s' % util.removeauth(other.url())
183 b'Automated merge with %s' % util.removeauth(other.url())
184 )
184 )
185 editopt = opts.get('edit') or opts.get('force_editor')
185 editopt = opts.get(b'edit') or opts.get(b'force_editor')
186 editor = cmdutil.getcommiteditor(edit=editopt, editform='fetch')
186 editor = cmdutil.getcommiteditor(edit=editopt, editform=b'fetch')
187 n = repo.commit(message, opts['user'], opts['date'], editor=editor)
187 n = repo.commit(
188 message, opts[b'user'], opts[b'date'], editor=editor
189 )
188 ui.status(
190 ui.status(
189 _('new changeset %d:%s merges remote changes ' 'with local\n')
191 _(b'new changeset %d:%s merges remote changes ' b'with local\n')
190 % (repo.changelog.rev(n), short(n))
192 % (repo.changelog.rev(n), short(n))
191 )
193 )
192
194
@@ -157,7 +157,7 b' from mercurial import ('
157 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
157 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
158 # be specifying the version(s) of Mercurial they are tested with, or
158 # be specifying the version(s) of Mercurial they are tested with, or
159 # leave the attribute unspecified.
159 # leave the attribute unspecified.
160 testedwith = 'ships-with-hg-core'
160 testedwith = b'ships-with-hg-core'
161
161
162 cmdtable = {}
162 cmdtable = {}
163 command = registrar.command(cmdtable)
163 command = registrar.command(cmdtable)
@@ -167,61 +167,61 b' configitem = registrar.configitem(config'
167
167
168 # Register the suboptions allowed for each configured fixer, and default values.
168 # Register the suboptions allowed for each configured fixer, and default values.
169 FIXER_ATTRS = {
169 FIXER_ATTRS = {
170 'command': None,
170 b'command': None,
171 'linerange': None,
171 b'linerange': None,
172 'pattern': None,
172 b'pattern': None,
173 'priority': 0,
173 b'priority': 0,
174 'metadata': 'false',
174 b'metadata': b'false',
175 'skipclean': 'true',
175 b'skipclean': b'true',
176 'enabled': 'true',
176 b'enabled': b'true',
177 }
177 }
178
178
179 for key, default in FIXER_ATTRS.items():
179 for key, default in FIXER_ATTRS.items():
180 configitem('fix', '.*(:%s)?' % key, default=default, generic=True)
180 configitem(b'fix', b'.*(:%s)?' % key, default=default, generic=True)
181
181
182 # A good default size allows most source code files to be fixed, but avoids
182 # A good default size allows most source code files to be fixed, but avoids
183 # letting fixer tools choke on huge inputs, which could be surprising to the
183 # letting fixer tools choke on huge inputs, which could be surprising to the
184 # user.
184 # user.
185 configitem('fix', 'maxfilesize', default='2MB')
185 configitem(b'fix', b'maxfilesize', default=b'2MB')
186
186
187 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
187 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
188 # This helps users do shell scripts that stop when a fixer tool signals a
188 # This helps users do shell scripts that stop when a fixer tool signals a
189 # problem.
189 # problem.
190 configitem('fix', 'failure', default='continue')
190 configitem(b'fix', b'failure', default=b'continue')
191
191
192
192
193 def checktoolfailureaction(ui, message, hint=None):
193 def checktoolfailureaction(ui, message, hint=None):
194 """Abort with 'message' if fix.failure=abort"""
194 """Abort with 'message' if fix.failure=abort"""
195 action = ui.config('fix', 'failure')
195 action = ui.config(b'fix', b'failure')
196 if action not in ('continue', 'abort'):
196 if action not in (b'continue', b'abort'):
197 raise error.Abort(
197 raise error.Abort(
198 _('unknown fix.failure action: %s') % (action,),
198 _(b'unknown fix.failure action: %s') % (action,),
199 hint=_('use "continue" or "abort"'),
199 hint=_(b'use "continue" or "abort"'),
200 )
200 )
201 if action == 'abort':
201 if action == b'abort':
202 raise error.Abort(message, hint=hint)
202 raise error.Abort(message, hint=hint)
203
203
204
204
205 allopt = ('', 'all', False, _('fix all non-public non-obsolete revisions'))
205 allopt = (b'', b'all', False, _(b'fix all non-public non-obsolete revisions'))
206 baseopt = (
206 baseopt = (
207 '',
207 b'',
208 'base',
208 b'base',
209 [],
209 [],
210 _(
210 _(
211 'revisions to diff against (overrides automatic '
211 b'revisions to diff against (overrides automatic '
212 'selection, and applies to every revision being '
212 b'selection, and applies to every revision being '
213 'fixed)'
213 b'fixed)'
214 ),
214 ),
215 _('REV'),
215 _(b'REV'),
216 )
216 )
217 revopt = ('r', 'rev', [], _('revisions to fix'), _('REV'))
217 revopt = (b'r', b'rev', [], _(b'revisions to fix'), _(b'REV'))
218 wdiropt = ('w', 'working-dir', False, _('fix the working directory'))
218 wdiropt = (b'w', b'working-dir', False, _(b'fix the working directory'))
219 wholeopt = ('', 'whole', False, _('always fix every line of a file'))
219 wholeopt = (b'', b'whole', False, _(b'always fix every line of a file'))
220 usage = _('[OPTION]... [FILE]...')
220 usage = _(b'[OPTION]... [FILE]...')
221
221
222
222
223 @command(
223 @command(
224 'fix',
224 b'fix',
225 [allopt, baseopt, revopt, wdiropt, wholeopt],
225 [allopt, baseopt, revopt, wdiropt, wholeopt],
226 usage,
226 usage,
227 helpcategory=command.CATEGORY_FILE_CONTENTS,
227 helpcategory=command.CATEGORY_FILE_CONTENTS,
@@ -250,12 +250,12 b' def fix(ui, repo, *pats, **opts):'
250 override this default behavior, though it is not usually desirable to do so.
250 override this default behavior, though it is not usually desirable to do so.
251 """
251 """
252 opts = pycompat.byteskwargs(opts)
252 opts = pycompat.byteskwargs(opts)
253 if opts['all']:
253 if opts[b'all']:
254 if opts['rev']:
254 if opts[b'rev']:
255 raise error.Abort(_('cannot specify both "--rev" and "--all"'))
255 raise error.Abort(_(b'cannot specify both "--rev" and "--all"'))
256 opts['rev'] = ['not public() and not obsolete()']
256 opts[b'rev'] = [b'not public() and not obsolete()']
257 opts['working_dir'] = True
257 opts[b'working_dir'] = True
258 with repo.wlock(), repo.lock(), repo.transaction('fix'):
258 with repo.wlock(), repo.lock(), repo.transaction(b'fix'):
259 revstofix = getrevstofix(ui, repo, opts)
259 revstofix = getrevstofix(ui, repo, opts)
260 basectxs = getbasectxs(repo, opts, revstofix)
260 basectxs = getbasectxs(repo, opts, revstofix)
261 workqueue, numitems = getworkqueue(
261 workqueue, numitems = getworkqueue(
@@ -297,7 +297,7 b' def fix(ui, repo, *pats, **opts):'
297 wdirwritten = False
297 wdirwritten = False
298 commitorder = sorted(revstofix, reverse=True)
298 commitorder = sorted(revstofix, reverse=True)
299 with ui.makeprogress(
299 with ui.makeprogress(
300 topic=_('fixing'), unit=_('files'), total=sum(numitems.values())
300 topic=_(b'fixing'), unit=_(b'files'), total=sum(numitems.values())
301 ) as progress:
301 ) as progress:
302 for rev, path, filerevmetadata, newdata in results:
302 for rev, path, filerevmetadata, newdata in results:
303 progress.increment(item=path)
303 progress.increment(item=path)
@@ -306,12 +306,12 b' def fix(ui, repo, *pats, **opts):'
306 if newdata is not None:
306 if newdata is not None:
307 filedata[rev][path] = newdata
307 filedata[rev][path] = newdata
308 hookargs = {
308 hookargs = {
309 'rev': rev,
309 b'rev': rev,
310 'path': path,
310 b'path': path,
311 'metadata': filerevmetadata,
311 b'metadata': filerevmetadata,
312 }
312 }
313 repo.hook(
313 repo.hook(
314 'postfixfile',
314 b'postfixfile',
315 throw=False,
315 throw=False,
316 **pycompat.strkwargs(hookargs)
316 **pycompat.strkwargs(hookargs)
317 )
317 )
@@ -332,11 +332,11 b' def fix(ui, repo, *pats, **opts):'
332
332
333 cleanup(repo, replacements, wdirwritten)
333 cleanup(repo, replacements, wdirwritten)
334 hookargs = {
334 hookargs = {
335 'replacements': replacements,
335 b'replacements': replacements,
336 'wdirwritten': wdirwritten,
336 b'wdirwritten': wdirwritten,
337 'metadata': aggregatemetadata,
337 b'metadata': aggregatemetadata,
338 }
338 }
339 repo.hook('postfix', throw=True, **pycompat.strkwargs(hookargs))
339 repo.hook(b'postfix', throw=True, **pycompat.strkwargs(hookargs))
340
340
341
341
342 def cleanup(repo, replacements, wdirwritten):
342 def cleanup(repo, replacements, wdirwritten):
@@ -353,7 +353,7 b' def cleanup(repo, replacements, wdirwrit'
353 effects of the command, though we choose not to output anything here.
353 effects of the command, though we choose not to output anything here.
354 """
354 """
355 replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
355 replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
356 scmutil.cleanupnodes(repo, replacements, 'fix', fixphase=True)
356 scmutil.cleanupnodes(repo, replacements, b'fix', fixphase=True)
357
357
358
358
359 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
359 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
@@ -375,7 +375,7 b' def getworkqueue(ui, repo, pats, opts, r'
375 """
375 """
376 workqueue = []
376 workqueue = []
377 numitems = collections.defaultdict(int)
377 numitems = collections.defaultdict(int)
378 maxfilesize = ui.configbytes('fix', 'maxfilesize')
378 maxfilesize = ui.configbytes(b'fix', b'maxfilesize')
379 for rev in sorted(revstofix):
379 for rev in sorted(revstofix):
380 fixctx = repo[rev]
380 fixctx = repo[rev]
381 match = scmutil.match(fixctx, pats, opts)
381 match = scmutil.match(fixctx, pats, opts)
@@ -387,7 +387,7 b' def getworkqueue(ui, repo, pats, opts, r'
387 continue
387 continue
388 if fctx.size() > maxfilesize:
388 if fctx.size() > maxfilesize:
389 ui.warn(
389 ui.warn(
390 _('ignoring file larger than %s: %s\n')
390 _(b'ignoring file larger than %s: %s\n')
391 % (util.bytecount(maxfilesize), path)
391 % (util.bytecount(maxfilesize), path)
392 )
392 )
393 continue
393 continue
@@ -398,29 +398,29 b' def getworkqueue(ui, repo, pats, opts, r'
398
398
399 def getrevstofix(ui, repo, opts):
399 def getrevstofix(ui, repo, opts):
400 """Returns the set of revision numbers that should be fixed"""
400 """Returns the set of revision numbers that should be fixed"""
401 revs = set(scmutil.revrange(repo, opts['rev']))
401 revs = set(scmutil.revrange(repo, opts[b'rev']))
402 for rev in revs:
402 for rev in revs:
403 checkfixablectx(ui, repo, repo[rev])
403 checkfixablectx(ui, repo, repo[rev])
404 if revs:
404 if revs:
405 cmdutil.checkunfinished(repo)
405 cmdutil.checkunfinished(repo)
406 checknodescendants(repo, revs)
406 checknodescendants(repo, revs)
407 if opts.get('working_dir'):
407 if opts.get(b'working_dir'):
408 revs.add(wdirrev)
408 revs.add(wdirrev)
409 if list(merge.mergestate.read(repo).unresolved()):
409 if list(merge.mergestate.read(repo).unresolved()):
410 raise error.Abort('unresolved conflicts', hint="use 'hg resolve'")
410 raise error.Abort(b'unresolved conflicts', hint=b"use 'hg resolve'")
411 if not revs:
411 if not revs:
412 raise error.Abort(
412 raise error.Abort(
413 'no changesets specified', hint='use --rev or --working-dir'
413 b'no changesets specified', hint=b'use --rev or --working-dir'
414 )
414 )
415 return revs
415 return revs
416
416
417
417
418 def checknodescendants(repo, revs):
418 def checknodescendants(repo, revs):
419 if not obsolete.isenabled(repo, obsolete.allowunstableopt) and repo.revs(
419 if not obsolete.isenabled(repo, obsolete.allowunstableopt) and repo.revs(
420 '(%ld::) - (%ld)', revs, revs
420 b'(%ld::) - (%ld)', revs, revs
421 ):
421 ):
422 raise error.Abort(
422 raise error.Abort(
423 _('can only fix a changeset together ' 'with all its descendants')
423 _(b'can only fix a changeset together ' b'with all its descendants')
424 )
424 )
425
425
426
426
@@ -428,15 +428,18 b' def checkfixablectx(ui, repo, ctx):'
428 """Aborts if the revision shouldn't be replaced with a fixed one."""
428 """Aborts if the revision shouldn't be replaced with a fixed one."""
429 if not ctx.mutable():
429 if not ctx.mutable():
430 raise error.Abort(
430 raise error.Abort(
431 'can\'t fix immutable changeset %s' % (scmutil.formatchangeid(ctx),)
431 b'can\'t fix immutable changeset %s'
432 % (scmutil.formatchangeid(ctx),)
432 )
433 )
433 if ctx.obsolete():
434 if ctx.obsolete():
434 # It would be better to actually check if the revision has a successor.
435 # It would be better to actually check if the revision has a successor.
435 allowdivergence = ui.configbool(
436 allowdivergence = ui.configbool(
436 'experimental', 'evolution.allowdivergence'
437 b'experimental', b'evolution.allowdivergence'
437 )
438 )
438 if not allowdivergence:
439 if not allowdivergence:
439 raise error.Abort('fixing obsolete revision could cause divergence')
440 raise error.Abort(
441 b'fixing obsolete revision could cause divergence'
442 )
440
443
441
444
442 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
445 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
@@ -473,10 +476,10 b' def lineranges(opts, path, basectxs, fix'
473 Another way to understand this is that we exclude line ranges that are
476 Another way to understand this is that we exclude line ranges that are
474 common to the file in all base contexts.
477 common to the file in all base contexts.
475 """
478 """
476 if opts.get('whole'):
479 if opts.get(b'whole'):
477 # Return a range containing all lines. Rely on the diff implementation's
480 # Return a range containing all lines. Rely on the diff implementation's
478 # idea of how many lines are in the file, instead of reimplementing it.
481 # idea of how many lines are in the file, instead of reimplementing it.
479 return difflineranges('', content2)
482 return difflineranges(b'', content2)
480
483
481 rangeslist = []
484 rangeslist = []
482 for basectx in basectxs:
485 for basectx in basectxs:
@@ -484,7 +487,7 b' def lineranges(opts, path, basectxs, fix'
484 if basepath in basectx:
487 if basepath in basectx:
485 content1 = basectx[basepath].data()
488 content1 = basectx[basepath].data()
486 else:
489 else:
487 content1 = ''
490 content1 = b''
488 rangeslist.extend(difflineranges(content1, content2))
491 rangeslist.extend(difflineranges(content1, content2))
489 return unionranges(rangeslist)
492 return unionranges(rangeslist)
490
493
@@ -566,7 +569,7 b' def difflineranges(content1, content2):'
566 ranges = []
569 ranges = []
567 for lines, kind in mdiff.allblocks(content1, content2):
570 for lines, kind in mdiff.allblocks(content1, content2):
568 firstline, lastline = lines[2:4]
571 firstline, lastline = lines[2:4]
569 if kind == '!' and firstline != lastline:
572 if kind == b'!' and firstline != lastline:
570 ranges.append((firstline + 1, lastline))
573 ranges.append((firstline + 1, lastline))
571 return ranges
574 return ranges
572
575
@@ -581,8 +584,8 b' def getbasectxs(repo, opts, revstofix):'
581 """
584 """
582 # The --base flag overrides the usual logic, and we give every revision
585 # The --base flag overrides the usual logic, and we give every revision
583 # exactly the set of baserevs that the user specified.
586 # exactly the set of baserevs that the user specified.
584 if opts.get('base'):
587 if opts.get(b'base'):
585 baserevs = set(scmutil.revrange(repo, opts.get('base')))
588 baserevs = set(scmutil.revrange(repo, opts.get(b'base')))
586 if not baserevs:
589 if not baserevs:
587 baserevs = {nullrev}
590 baserevs = {nullrev}
588 basectxs = {repo[rev] for rev in baserevs}
591 basectxs = {repo[rev] for rev in baserevs}
@@ -621,7 +624,7 b' def fixfile(ui, repo, opts, fixers, fixc'
621 command = fixer.command(ui, path, ranges)
624 command = fixer.command(ui, path, ranges)
622 if command is None:
625 if command is None:
623 continue
626 continue
624 ui.debug('subprocess: %s\n' % (command,))
627 ui.debug(b'subprocess: %s\n' % (command,))
625 proc = subprocess.Popen(
628 proc = subprocess.Popen(
626 procutil.tonativestr(command),
629 procutil.tonativestr(command),
627 shell=True,
630 shell=True,
@@ -636,11 +639,11 b' def fixfile(ui, repo, opts, fixers, fixc'
636 newerdata = stdout
639 newerdata = stdout
637 if fixer.shouldoutputmetadata():
640 if fixer.shouldoutputmetadata():
638 try:
641 try:
639 metadatajson, newerdata = stdout.split('\0', 1)
642 metadatajson, newerdata = stdout.split(b'\0', 1)
640 metadata[fixername] = json.loads(metadatajson)
643 metadata[fixername] = json.loads(metadatajson)
641 except ValueError:
644 except ValueError:
642 ui.warn(
645 ui.warn(
643 _('ignored invalid output from fixer tool: %s\n')
646 _(b'ignored invalid output from fixer tool: %s\n')
644 % (fixername,)
647 % (fixername,)
645 )
648 )
646 continue
649 continue
@@ -650,14 +653,14 b' def fixfile(ui, repo, opts, fixers, fixc'
650 newdata = newerdata
653 newdata = newerdata
651 else:
654 else:
652 if not stderr:
655 if not stderr:
653 message = _('exited with status %d\n') % (proc.returncode,)
656 message = _(b'exited with status %d\n') % (proc.returncode,)
654 showstderr(ui, fixctx.rev(), fixername, message)
657 showstderr(ui, fixctx.rev(), fixername, message)
655 checktoolfailureaction(
658 checktoolfailureaction(
656 ui,
659 ui,
657 _('no fixes will be applied'),
660 _(b'no fixes will be applied'),
658 hint=_(
661 hint=_(
659 'use --config fix.failure=continue to apply any '
662 b'use --config fix.failure=continue to apply any '
660 'successful fixes anyway'
663 b'successful fixes anyway'
661 ),
664 ),
662 )
665 )
663 return metadata, newdata
666 return metadata, newdata
@@ -671,14 +674,14 b' def showstderr(ui, rev, fixername, stder'
671 space and would tend to be included in the error message if they were
674 space and would tend to be included in the error message if they were
672 relevant.
675 relevant.
673 """
676 """
674 for line in re.split('[\r\n]+', stderr):
677 for line in re.split(b'[\r\n]+', stderr):
675 if line:
678 if line:
676 ui.warn('[')
679 ui.warn(b'[')
677 if rev is None:
680 if rev is None:
678 ui.warn(_('wdir'), label='evolve.rev')
681 ui.warn(_(b'wdir'), label=b'evolve.rev')
679 else:
682 else:
680 ui.warn((str(rev)), label='evolve.rev')
683 ui.warn((str(rev)), label=b'evolve.rev')
681 ui.warn('] %s: %s\n' % (fixername, line))
684 ui.warn(b'] %s: %s\n' % (fixername, line))
682
685
683
686
684 def writeworkingdir(repo, ctx, filedata, replacements):
687 def writeworkingdir(repo, ctx, filedata, replacements):
@@ -694,7 +697,7 b' def writeworkingdir(repo, ctx, filedata,'
694 for path, data in filedata.iteritems():
697 for path, data in filedata.iteritems():
695 fctx = ctx[path]
698 fctx = ctx[path]
696 fctx.write(data, fctx.flags())
699 fctx.write(data, fctx.flags())
697 if repo.dirstate[path] == 'n':
700 if repo.dirstate[path] == b'n':
698 repo.dirstate.normallookup(path)
701 repo.dirstate.normallookup(path)
699
702
700 oldparentnodes = repo.dirstate.parents()
703 oldparentnodes = repo.dirstate.parents()
@@ -757,7 +760,7 b' def replacerev(ui, repo, ctx, filedata, '
757 )
760 )
758
761
759 extra = ctx.extra().copy()
762 extra = ctx.extra().copy()
760 extra['fix_source'] = ctx.hex()
763 extra[b'fix_source'] = ctx.hex()
761
764
762 memctx = context.memctx(
765 memctx = context.memctx(
763 repo,
766 repo,
@@ -774,7 +777,7 b' def replacerev(ui, repo, ctx, filedata, '
774 sucnode = memctx.commit()
777 sucnode = memctx.commit()
775 prenode = ctx.node()
778 prenode = ctx.node()
776 if prenode == sucnode:
779 if prenode == sucnode:
777 ui.debug('node %s already existed\n' % (ctx.hex()))
780 ui.debug(b'node %s already existed\n' % (ctx.hex()))
778 else:
781 else:
779 replacements[ctx.node()] = sucnode
782 replacements[ctx.node()] = sucnode
780
783
@@ -788,11 +791,11 b' def getfixers(ui):'
788 fixers = {}
791 fixers = {}
789 for name in fixernames(ui):
792 for name in fixernames(ui):
790 fixers[name] = Fixer()
793 fixers[name] = Fixer()
791 attrs = ui.configsuboptions('fix', name)[1]
794 attrs = ui.configsuboptions(b'fix', name)[1]
792 for key, default in FIXER_ATTRS.items():
795 for key, default in FIXER_ATTRS.items():
793 setattr(
796 setattr(
794 fixers[name],
797 fixers[name],
795 pycompat.sysstr('_' + key),
798 pycompat.sysstr(b'_' + key),
796 attrs.get(key, default),
799 attrs.get(key, default),
797 )
800 )
798 fixers[name]._priority = int(fixers[name]._priority)
801 fixers[name]._priority = int(fixers[name]._priority)
@@ -805,11 +808,11 b' def getfixers(ui):'
805 # default.
808 # default.
806 if fixers[name]._pattern is None:
809 if fixers[name]._pattern is None:
807 ui.warn(
810 ui.warn(
808 _('fixer tool has no pattern configuration: %s\n') % (name,)
811 _(b'fixer tool has no pattern configuration: %s\n') % (name,)
809 )
812 )
810 del fixers[name]
813 del fixers[name]
811 elif not fixers[name]._enabled:
814 elif not fixers[name]._enabled:
812 ui.debug('ignoring disabled fixer tool: %s\n' % (name,))
815 ui.debug(b'ignoring disabled fixer tool: %s\n' % (name,))
813 del fixers[name]
816 del fixers[name]
814 return collections.OrderedDict(
817 return collections.OrderedDict(
815 sorted(fixers.items(), key=lambda item: item[1]._priority, reverse=True)
818 sorted(fixers.items(), key=lambda item: item[1]._priority, reverse=True)
@@ -819,9 +822,9 b' def getfixers(ui):'
819 def fixernames(ui):
822 def fixernames(ui):
820 """Returns the names of [fix] config options that have suboptions"""
823 """Returns the names of [fix] config options that have suboptions"""
821 names = set()
824 names = set()
822 for k, v in ui.configitems('fix'):
825 for k, v in ui.configitems(b'fix'):
823 if ':' in k:
826 if b':' in k:
824 names.add(k.split(':', 1)[0])
827 names.add(k.split(b':', 1)[0])
825 return names
828 return names
826
829
827
830
@@ -849,7 +852,7 b' class Fixer(object):'
849 expand(
852 expand(
850 ui,
853 ui,
851 self._command,
854 self._command,
852 {'rootpath': path, 'basename': os.path.basename(path)},
855 {b'rootpath': path, b'basename': os.path.basename(path)},
853 )
856 )
854 ]
857 ]
855 if self._linerange:
858 if self._linerange:
@@ -858,6 +861,8 b' class Fixer(object):'
858 return None
861 return None
859 for first, last in ranges:
862 for first, last in ranges:
860 parts.append(
863 parts.append(
861 expand(ui, self._linerange, {'first': first, 'last': last})
864 expand(
865 ui, self._linerange, {b'first': first, b'last': last}
866 )
862 )
867 )
863 return ' '.join(parts)
868 return b' '.join(parts)
@@ -143,60 +143,60 b' from . import ('
143 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
143 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
144 # be specifying the version(s) of Mercurial they are tested with, or
144 # be specifying the version(s) of Mercurial they are tested with, or
145 # leave the attribute unspecified.
145 # leave the attribute unspecified.
146 testedwith = 'ships-with-hg-core'
146 testedwith = b'ships-with-hg-core'
147
147
148 configtable = {}
148 configtable = {}
149 configitem = registrar.configitem(configtable)
149 configitem = registrar.configitem(configtable)
150
150
151 configitem(
151 configitem(
152 'fsmonitor', 'mode', default='on',
152 b'fsmonitor', b'mode', default=b'on',
153 )
153 )
154 configitem(
154 configitem(
155 'fsmonitor', 'walk_on_invalidate', default=False,
155 b'fsmonitor', b'walk_on_invalidate', default=False,
156 )
156 )
157 configitem(
157 configitem(
158 'fsmonitor', 'timeout', default='2',
158 b'fsmonitor', b'timeout', default=b'2',
159 )
159 )
160 configitem(
160 configitem(
161 'fsmonitor', 'blacklistusers', default=list,
161 b'fsmonitor', b'blacklistusers', default=list,
162 )
162 )
163 configitem(
163 configitem(
164 'fsmonitor', 'watchman_exe', default='watchman',
164 b'fsmonitor', b'watchman_exe', default=b'watchman',
165 )
165 )
166 configitem(
166 configitem(
167 'fsmonitor', 'verbose', default=True, experimental=True,
167 b'fsmonitor', b'verbose', default=True, experimental=True,
168 )
168 )
169 configitem(
169 configitem(
170 'experimental', 'fsmonitor.transaction_notify', default=False,
170 b'experimental', b'fsmonitor.transaction_notify', default=False,
171 )
171 )
172
172
173 # This extension is incompatible with the following blacklisted extensions
173 # This extension is incompatible with the following blacklisted extensions
174 # and will disable itself when encountering one of these:
174 # and will disable itself when encountering one of these:
175 _blacklist = ['largefiles', 'eol']
175 _blacklist = [b'largefiles', b'eol']
176
176
177
177
178 def debuginstall(ui, fm):
178 def debuginstall(ui, fm):
179 fm.write(
179 fm.write(
180 "fsmonitor-watchman",
180 b"fsmonitor-watchman",
181 _("fsmonitor checking for watchman binary... (%s)\n"),
181 _(b"fsmonitor checking for watchman binary... (%s)\n"),
182 ui.configpath("fsmonitor", "watchman_exe"),
182 ui.configpath(b"fsmonitor", b"watchman_exe"),
183 )
183 )
184 root = tempfile.mkdtemp()
184 root = tempfile.mkdtemp()
185 c = watchmanclient.client(ui, root)
185 c = watchmanclient.client(ui, root)
186 err = None
186 err = None
187 try:
187 try:
188 v = c.command("version")
188 v = c.command(b"version")
189 fm.write(
189 fm.write(
190 "fsmonitor-watchman-version",
190 b"fsmonitor-watchman-version",
191 _(" watchman binary version %s\n"),
191 _(b" watchman binary version %s\n"),
192 v["version"],
192 v[b"version"],
193 )
193 )
194 except watchmanclient.Unavailable as e:
194 except watchmanclient.Unavailable as e:
195 err = str(e)
195 err = str(e)
196 fm.condwrite(
196 fm.condwrite(
197 err,
197 err,
198 "fsmonitor-watchman-error",
198 b"fsmonitor-watchman-error",
199 _(" watchman binary missing or broken: %s\n"),
199 _(b" watchman binary missing or broken: %s\n"),
200 err,
200 err,
201 )
201 )
202 return 1 if err else 0
202 return 1 if err else 0
@@ -206,16 +206,16 b' def _handleunavailable(ui, state, ex):'
206 """Exception handler for Watchman interaction exceptions"""
206 """Exception handler for Watchman interaction exceptions"""
207 if isinstance(ex, watchmanclient.Unavailable):
207 if isinstance(ex, watchmanclient.Unavailable):
208 # experimental config: fsmonitor.verbose
208 # experimental config: fsmonitor.verbose
209 if ex.warn and ui.configbool('fsmonitor', 'verbose'):
209 if ex.warn and ui.configbool(b'fsmonitor', b'verbose'):
210 if 'illegal_fstypes' not in str(ex):
210 if b'illegal_fstypes' not in str(ex):
211 ui.warn(str(ex) + '\n')
211 ui.warn(str(ex) + b'\n')
212 if ex.invalidate:
212 if ex.invalidate:
213 state.invalidate()
213 state.invalidate()
214 # experimental config: fsmonitor.verbose
214 # experimental config: fsmonitor.verbose
215 if ui.configbool('fsmonitor', 'verbose'):
215 if ui.configbool(b'fsmonitor', b'verbose'):
216 ui.log('fsmonitor', 'Watchman unavailable: %s\n', ex.msg)
216 ui.log(b'fsmonitor', b'Watchman unavailable: %s\n', ex.msg)
217 else:
217 else:
218 ui.log('fsmonitor', 'Watchman exception: %s\n', ex)
218 ui.log(b'fsmonitor', b'Watchman exception: %s\n', ex)
219
219
220
220
221 def _hashignore(ignore):
221 def _hashignore(ignore):
@@ -245,7 +245,7 b' def _watchmantofsencoding(path):'
245 try:
245 try:
246 decoded = path.decode(_watchmanencoding)
246 decoded = path.decode(_watchmanencoding)
247 except UnicodeDecodeError as e:
247 except UnicodeDecodeError as e:
248 raise error.Abort(str(e), hint='watchman encoding error')
248 raise error.Abort(str(e), hint=b'watchman encoding error')
249
249
250 try:
250 try:
251 encoded = decoded.encode(_fsencoding, 'strict')
251 encoded = decoded.encode(_fsencoding, 'strict')
@@ -263,34 +263,34 b' def overridewalk(orig, self, match, subr'
263 subset of files.'''
263 subset of files.'''
264
264
265 def bail(reason):
265 def bail(reason):
266 self._ui.debug('fsmonitor: fallback to core status, %s\n' % reason)
266 self._ui.debug(b'fsmonitor: fallback to core status, %s\n' % reason)
267 return orig(match, subrepos, unknown, ignored, full=True)
267 return orig(match, subrepos, unknown, ignored, full=True)
268
268
269 if full:
269 if full:
270 return bail('full rewalk requested')
270 return bail(b'full rewalk requested')
271 if ignored:
271 if ignored:
272 return bail('listing ignored files')
272 return bail(b'listing ignored files')
273 if not self._watchmanclient.available():
273 if not self._watchmanclient.available():
274 return bail('client unavailable')
274 return bail(b'client unavailable')
275 state = self._fsmonitorstate
275 state = self._fsmonitorstate
276 clock, ignorehash, notefiles = state.get()
276 clock, ignorehash, notefiles = state.get()
277 if not clock:
277 if not clock:
278 if state.walk_on_invalidate:
278 if state.walk_on_invalidate:
279 return bail('no clock')
279 return bail(b'no clock')
280 # Initial NULL clock value, see
280 # Initial NULL clock value, see
281 # https://facebook.github.io/watchman/docs/clockspec.html
281 # https://facebook.github.io/watchman/docs/clockspec.html
282 clock = 'c:0:0'
282 clock = b'c:0:0'
283 notefiles = []
283 notefiles = []
284
284
285 ignore = self._ignore
285 ignore = self._ignore
286 dirignore = self._dirignore
286 dirignore = self._dirignore
287 if unknown:
287 if unknown:
288 if _hashignore(ignore) != ignorehash and clock != 'c:0:0':
288 if _hashignore(ignore) != ignorehash and clock != b'c:0:0':
289 # ignore list changed -- can't rely on Watchman state any more
289 # ignore list changed -- can't rely on Watchman state any more
290 if state.walk_on_invalidate:
290 if state.walk_on_invalidate:
291 return bail('ignore rules changed')
291 return bail(b'ignore rules changed')
292 notefiles = []
292 notefiles = []
293 clock = 'c:0:0'
293 clock = b'c:0:0'
294 else:
294 else:
295 # always ignore
295 # always ignore
296 ignore = util.always
296 ignore = util.always
@@ -299,7 +299,7 b' def overridewalk(orig, self, match, subr'
299 matchfn = match.matchfn
299 matchfn = match.matchfn
300 matchalways = match.always()
300 matchalways = match.always()
301 dmap = self._map
301 dmap = self._map
302 if util.safehasattr(dmap, '_map'):
302 if util.safehasattr(dmap, b'_map'):
303 # for better performance, directly access the inner dirstate map if the
303 # for better performance, directly access the inner dirstate map if the
304 # standard dirstate implementation is in use.
304 # standard dirstate implementation is in use.
305 dmap = dmap._map
305 dmap = dmap._map
@@ -339,7 +339,7 b' def overridewalk(orig, self, match, subr'
339 if not work and (exact or skipstep3):
339 if not work and (exact or skipstep3):
340 for s in subrepos:
340 for s in subrepos:
341 del results[s]
341 del results[s]
342 del results['.hg']
342 del results[b'.hg']
343 return results
343 return results
344
344
345 # step 2: query Watchman
345 # step 2: query Watchman
@@ -349,30 +349,34 b' def overridewalk(orig, self, match, subr'
349 # overheads while transferring the data
349 # overheads while transferring the data
350 self._watchmanclient.settimeout(state.timeout + 0.1)
350 self._watchmanclient.settimeout(state.timeout + 0.1)
351 result = self._watchmanclient.command(
351 result = self._watchmanclient.command(
352 'query',
352 b'query',
353 {
353 {
354 'fields': ['mode', 'mtime', 'size', 'exists', 'name'],
354 b'fields': [b'mode', b'mtime', b'size', b'exists', b'name'],
355 'since': clock,
355 b'since': clock,
356 'expression': [
356 b'expression': [
357 'not',
357 b'not',
358 ['anyof', ['dirname', '.hg'], ['name', '.hg', 'wholename']],
358 [
359 b'anyof',
360 [b'dirname', b'.hg'],
361 [b'name', b'.hg', b'wholename'],
362 ],
359 ],
363 ],
360 'sync_timeout': int(state.timeout * 1000),
364 b'sync_timeout': int(state.timeout * 1000),
361 'empty_on_fresh_instance': state.walk_on_invalidate,
365 b'empty_on_fresh_instance': state.walk_on_invalidate,
362 },
366 },
363 )
367 )
364 except Exception as ex:
368 except Exception as ex:
365 _handleunavailable(self._ui, state, ex)
369 _handleunavailable(self._ui, state, ex)
366 self._watchmanclient.clearconnection()
370 self._watchmanclient.clearconnection()
367 return bail('exception during run')
371 return bail(b'exception during run')
368 else:
372 else:
369 # We need to propagate the last observed clock up so that we
373 # We need to propagate the last observed clock up so that we
370 # can use it for our next query
374 # can use it for our next query
371 state.setlastclock(result['clock'])
375 state.setlastclock(result[b'clock'])
372 if result['is_fresh_instance']:
376 if result[b'is_fresh_instance']:
373 if state.walk_on_invalidate:
377 if state.walk_on_invalidate:
374 state.invalidate()
378 state.invalidate()
375 return bail('fresh instance')
379 return bail(b'fresh instance')
376 fresh_instance = True
380 fresh_instance = True
377 # Ignore any prior noteable files from the state info
381 # Ignore any prior noteable files from the state info
378 notefiles = []
382 notefiles = []
@@ -382,7 +386,7 b' def overridewalk(orig, self, match, subr'
382 if normalize:
386 if normalize:
383 foldmap = dict((normcase(k), k) for k in results)
387 foldmap = dict((normcase(k), k) for k in results)
384
388
385 switch_slashes = pycompat.ossep == '\\'
389 switch_slashes = pycompat.ossep == b'\\'
386 # The order of the results is, strictly speaking, undefined.
390 # The order of the results is, strictly speaking, undefined.
387 # For case changes on a case insensitive filesystem we may receive
391 # For case changes on a case insensitive filesystem we may receive
388 # two entries, one with exists=True and another with exists=False.
392 # two entries, one with exists=True and another with exists=False.
@@ -390,22 +394,22 b' def overridewalk(orig, self, match, subr'
390 # as being happens-after the exists=False entries due to the way that
394 # as being happens-after the exists=False entries due to the way that
391 # Watchman tracks files. We use this property to reconcile deletes
395 # Watchman tracks files. We use this property to reconcile deletes
392 # for name case changes.
396 # for name case changes.
393 for entry in result['files']:
397 for entry in result[b'files']:
394 fname = entry['name']
398 fname = entry[b'name']
395 if _fixencoding:
399 if _fixencoding:
396 fname = _watchmantofsencoding(fname)
400 fname = _watchmantofsencoding(fname)
397 if switch_slashes:
401 if switch_slashes:
398 fname = fname.replace('\\', '/')
402 fname = fname.replace(b'\\', b'/')
399 if normalize:
403 if normalize:
400 normed = normcase(fname)
404 normed = normcase(fname)
401 fname = normalize(fname, True, True)
405 fname = normalize(fname, True, True)
402 foldmap[normed] = fname
406 foldmap[normed] = fname
403 fmode = entry['mode']
407 fmode = entry[b'mode']
404 fexists = entry['exists']
408 fexists = entry[b'exists']
405 kind = getkind(fmode)
409 kind = getkind(fmode)
406
410
407 if '/.hg/' in fname or fname.endswith('/.hg'):
411 if b'/.hg/' in fname or fname.endswith(b'/.hg'):
408 return bail('nested-repo-detected')
412 return bail(b'nested-repo-detected')
409
413
410 if not fexists:
414 if not fexists:
411 # if marked as deleted and we don't already have a change
415 # if marked as deleted and we don't already have a change
@@ -488,14 +492,14 b' def overridewalk(orig, self, match, subr'
488
492
489 for s in subrepos:
493 for s in subrepos:
490 del results[s]
494 del results[s]
491 del results['.hg']
495 del results[b'.hg']
492 return results
496 return results
493
497
494
498
495 def overridestatus(
499 def overridestatus(
496 orig,
500 orig,
497 self,
501 self,
498 node1='.',
502 node1=b'.',
499 node2=None,
503 node2=None,
500 match=None,
504 match=None,
501 ignored=False,
505 ignored=False,
@@ -509,22 +513,22 b' def overridestatus('
509
513
510 def _cmpsets(l1, l2):
514 def _cmpsets(l1, l2):
511 try:
515 try:
512 if 'FSMONITOR_LOG_FILE' in encoding.environ:
516 if b'FSMONITOR_LOG_FILE' in encoding.environ:
513 fn = encoding.environ['FSMONITOR_LOG_FILE']
517 fn = encoding.environ[b'FSMONITOR_LOG_FILE']
514 f = open(fn, 'wb')
518 f = open(fn, b'wb')
515 else:
519 else:
516 fn = 'fsmonitorfail.log'
520 fn = b'fsmonitorfail.log'
517 f = self.vfs.open(fn, 'wb')
521 f = self.vfs.open(fn, b'wb')
518 except (IOError, OSError):
522 except (IOError, OSError):
519 self.ui.warn(_('warning: unable to write to %s\n') % fn)
523 self.ui.warn(_(b'warning: unable to write to %s\n') % fn)
520 return
524 return
521
525
522 try:
526 try:
523 for i, (s1, s2) in enumerate(zip(l1, l2)):
527 for i, (s1, s2) in enumerate(zip(l1, l2)):
524 if set(s1) != set(s2):
528 if set(s1) != set(s2):
525 f.write('sets at position %d are unequal\n' % i)
529 f.write(b'sets at position %d are unequal\n' % i)
526 f.write('watchman returned: %s\n' % s1)
530 f.write(b'watchman returned: %s\n' % s1)
527 f.write('stat returned: %s\n' % s2)
531 f.write(b'stat returned: %s\n' % s2)
528 finally:
532 finally:
529 f.close()
533 f.close()
530
534
@@ -538,7 +542,7 b' def overridestatus('
538 ctx2 = self[node2]
542 ctx2 = self[node2]
539
543
540 working = ctx2.rev() is None
544 working = ctx2.rev() is None
541 parentworking = working and ctx1 == self['.']
545 parentworking = working and ctx1 == self[b'.']
542 match = match or matchmod.always()
546 match = match or matchmod.always()
543
547
544 # Maybe we can use this opportunity to update Watchman's state.
548 # Maybe we can use this opportunity to update Watchman's state.
@@ -552,7 +556,7 b' def overridestatus('
552 parentworking
556 parentworking
553 and match.always()
557 and match.always()
554 and not isinstance(ctx2, (context.workingcommitctx, context.memctx))
558 and not isinstance(ctx2, (context.workingcommitctx, context.memctx))
555 and 'HG_PENDING' not in encoding.environ
559 and b'HG_PENDING' not in encoding.environ
556 )
560 )
557
561
558 try:
562 try:
@@ -607,7 +611,7 b' def overridestatus('
607
611
608 # don't do paranoid checks if we're not going to query Watchman anyway
612 # don't do paranoid checks if we're not going to query Watchman anyway
609 full = listclean or match.traversedir is not None
613 full = listclean or match.traversedir is not None
610 if self._fsmonitorstate.mode == 'paranoid' and not full:
614 if self._fsmonitorstate.mode == b'paranoid' and not full:
611 # run status again and fall back to the old walk this time
615 # run status again and fall back to the old walk this time
612 self.dirstate._fsmonitordisable = True
616 self.dirstate._fsmonitordisable = True
613
617
@@ -615,7 +619,7 b' def overridestatus('
615 quiet = self.ui.quiet
619 quiet = self.ui.quiet
616 self.ui.quiet = True
620 self.ui.quiet = True
617 fout, ferr = self.ui.fout, self.ui.ferr
621 fout, ferr = self.ui.fout, self.ui.ferr
618 self.ui.fout = self.ui.ferr = open(os.devnull, 'wb')
622 self.ui.fout = self.ui.ferr = open(os.devnull, b'wb')
619
623
620 try:
624 try:
621 rv2 = orig(
625 rv2 = orig(
@@ -692,20 +696,20 b' def makedirstate(repo, dirstate):'
692 def wrapdirstate(orig, self):
696 def wrapdirstate(orig, self):
693 ds = orig(self)
697 ds = orig(self)
694 # only override the dirstate when Watchman is available for the repo
698 # only override the dirstate when Watchman is available for the repo
695 if util.safehasattr(self, '_fsmonitorstate'):
699 if util.safehasattr(self, b'_fsmonitorstate'):
696 makedirstate(self, ds)
700 makedirstate(self, ds)
697 return ds
701 return ds
698
702
699
703
700 def extsetup(ui):
704 def extsetup(ui):
701 extensions.wrapfilecache(
705 extensions.wrapfilecache(
702 localrepo.localrepository, 'dirstate', wrapdirstate
706 localrepo.localrepository, b'dirstate', wrapdirstate
703 )
707 )
704 if pycompat.isdarwin:
708 if pycompat.isdarwin:
705 # An assist for avoiding the dangling-symlink fsevents bug
709 # An assist for avoiding the dangling-symlink fsevents bug
706 extensions.wrapfunction(os, 'symlink', wrapsymlink)
710 extensions.wrapfunction(os, b'symlink', wrapsymlink)
707
711
708 extensions.wrapfunction(merge, 'update', wrapupdate)
712 extensions.wrapfunction(merge, b'update', wrapupdate)
709
713
710
714
711 def wrapsymlink(orig, source, link_name):
715 def wrapsymlink(orig, source, link_name):
@@ -756,14 +760,14 b' class state_update(object):'
756 # merge.update is going to take the wlock almost immediately. We are
760 # merge.update is going to take the wlock almost immediately. We are
757 # effectively extending the lock around several short sanity checks.
761 # effectively extending the lock around several short sanity checks.
758 if self.oldnode is None:
762 if self.oldnode is None:
759 self.oldnode = self.repo['.'].node()
763 self.oldnode = self.repo[b'.'].node()
760
764
761 if self.repo.currentwlock() is None:
765 if self.repo.currentwlock() is None:
762 if util.safehasattr(self.repo, 'wlocknostateupdate'):
766 if util.safehasattr(self.repo, b'wlocknostateupdate'):
763 self._lock = self.repo.wlocknostateupdate()
767 self._lock = self.repo.wlocknostateupdate()
764 else:
768 else:
765 self._lock = self.repo.wlock()
769 self._lock = self.repo.wlock()
766 self.need_leave = self._state('state-enter', hex(self.oldnode))
770 self.need_leave = self._state(b'state-enter', hex(self.oldnode))
767 return self
771 return self
768
772
769 def __exit__(self, type_, value, tb):
773 def __exit__(self, type_, value, tb):
@@ -773,36 +777,36 b' class state_update(object):'
773 def exit(self, abort=False):
777 def exit(self, abort=False):
774 try:
778 try:
775 if self.need_leave:
779 if self.need_leave:
776 status = 'failed' if abort else 'ok'
780 status = b'failed' if abort else b'ok'
777 if self.newnode is None:
781 if self.newnode is None:
778 self.newnode = self.repo['.'].node()
782 self.newnode = self.repo[b'.'].node()
779 if self.distance is None:
783 if self.distance is None:
780 self.distance = calcdistance(
784 self.distance = calcdistance(
781 self.repo, self.oldnode, self.newnode
785 self.repo, self.oldnode, self.newnode
782 )
786 )
783 self._state('state-leave', hex(self.newnode), status=status)
787 self._state(b'state-leave', hex(self.newnode), status=status)
784 finally:
788 finally:
785 self.need_leave = False
789 self.need_leave = False
786 if self._lock:
790 if self._lock:
787 self._lock.release()
791 self._lock.release()
788
792
789 def _state(self, cmd, commithash, status='ok'):
793 def _state(self, cmd, commithash, status=b'ok'):
790 if not util.safehasattr(self.repo, '_watchmanclient'):
794 if not util.safehasattr(self.repo, b'_watchmanclient'):
791 return False
795 return False
792 try:
796 try:
793 self.repo._watchmanclient.command(
797 self.repo._watchmanclient.command(
794 cmd,
798 cmd,
795 {
799 {
796 'name': self.name,
800 b'name': self.name,
797 'metadata': {
801 b'metadata': {
798 # the target revision
802 # the target revision
799 'rev': commithash,
803 b'rev': commithash,
800 # approximate number of commits between current and target
804 # approximate number of commits between current and target
801 'distance': self.distance if self.distance else 0,
805 b'distance': self.distance if self.distance else 0,
802 # success/failure (only really meaningful for state-leave)
806 # success/failure (only really meaningful for state-leave)
803 'status': status,
807 b'status': status,
804 # whether the working copy parent is changing
808 # whether the working copy parent is changing
805 'partial': self.partial,
809 b'partial': self.partial,
806 },
810 },
807 },
811 },
808 )
812 )
@@ -810,7 +814,7 b' class state_update(object):'
810 except Exception as e:
814 except Exception as e:
811 # Swallow any errors; fire and forget
815 # Swallow any errors; fire and forget
812 self.repo.ui.log(
816 self.repo.ui.log(
813 'watchman', 'Exception %s while running %s\n', e, cmd
817 b'watchman', b'Exception %s while running %s\n', e, cmd
814 )
818 )
815 return False
819 return False
816
820
@@ -844,7 +848,7 b' def wrapupdate('
844
848
845 distance = 0
849 distance = 0
846 partial = True
850 partial = True
847 oldnode = repo['.'].node()
851 oldnode = repo[b'.'].node()
848 newnode = repo[node].node()
852 newnode = repo[node].node()
849 if matcher is None or matcher.always():
853 if matcher is None or matcher.always():
850 partial = False
854 partial = False
@@ -852,7 +856,7 b' def wrapupdate('
852
856
853 with state_update(
857 with state_update(
854 repo,
858 repo,
855 name="hg.update",
859 name=b"hg.update",
856 oldnode=oldnode,
860 oldnode=oldnode,
857 newnode=newnode,
861 newnode=newnode,
858 distance=distance,
862 distance=distance,
@@ -873,8 +877,8 b' def wrapupdate('
873
877
874 def repo_has_depth_one_nested_repo(repo):
878 def repo_has_depth_one_nested_repo(repo):
875 for f in repo.wvfs.listdir():
879 for f in repo.wvfs.listdir():
876 if os.path.isdir(os.path.join(repo.root, f, '.hg')):
880 if os.path.isdir(os.path.join(repo.root, f, b'.hg')):
877 msg = 'fsmonitor: sub-repository %r detected, fsmonitor disabled\n'
881 msg = b'fsmonitor: sub-repository %r detected, fsmonitor disabled\n'
878 repo.ui.debug(msg % f)
882 repo.ui.debug(msg % f)
879 return True
883 return True
880 return False
884 return False
@@ -887,8 +891,8 b' def reposetup(ui, repo):'
887 if ext in exts:
891 if ext in exts:
888 ui.warn(
892 ui.warn(
889 _(
893 _(
890 'The fsmonitor extension is incompatible with the %s '
894 b'The fsmonitor extension is incompatible with the %s '
891 'extension and has been disabled.\n'
895 b'extension and has been disabled.\n'
892 )
896 )
893 % ext
897 % ext
894 )
898 )
@@ -899,14 +903,14 b' def reposetup(ui, repo):'
899 #
903 #
900 # if repo[None].substate can cause a dirstate parse, which is too
904 # if repo[None].substate can cause a dirstate parse, which is too
901 # slow. Instead, look for a file called hgsubstate,
905 # slow. Instead, look for a file called hgsubstate,
902 if repo.wvfs.exists('.hgsubstate') or repo.wvfs.exists('.hgsub'):
906 if repo.wvfs.exists(b'.hgsubstate') or repo.wvfs.exists(b'.hgsub'):
903 return
907 return
904
908
905 if repo_has_depth_one_nested_repo(repo):
909 if repo_has_depth_one_nested_repo(repo):
906 return
910 return
907
911
908 fsmonitorstate = state.state(repo)
912 fsmonitorstate = state.state(repo)
909 if fsmonitorstate.mode == 'off':
913 if fsmonitorstate.mode == b'off':
910 return
914 return
911
915
912 try:
916 try:
@@ -918,7 +922,7 b' def reposetup(ui, repo):'
918 repo._fsmonitorstate = fsmonitorstate
922 repo._fsmonitorstate = fsmonitorstate
919 repo._watchmanclient = client
923 repo._watchmanclient = client
920
924
921 dirstate, cached = localrepo.isfilecached(repo, 'dirstate')
925 dirstate, cached = localrepo.isfilecached(repo, b'dirstate')
922 if cached:
926 if cached:
923 # at this point since fsmonitorstate wasn't present,
927 # at this point since fsmonitorstate wasn't present,
924 # repo.dirstate is not a fsmonitordirstate
928 # repo.dirstate is not a fsmonitordirstate
@@ -935,7 +939,7 b' def reposetup(ui, repo):'
935 def wlock(self, *args, **kwargs):
939 def wlock(self, *args, **kwargs):
936 l = super(fsmonitorrepo, self).wlock(*args, **kwargs)
940 l = super(fsmonitorrepo, self).wlock(*args, **kwargs)
937 if not ui.configbool(
941 if not ui.configbool(
938 "experimental", "fsmonitor.transaction_notify"
942 b"experimental", b"fsmonitor.transaction_notify"
939 ):
943 ):
940 return l
944 return l
941 if l.held != 1:
945 if l.held != 1:
@@ -951,12 +955,14 b' def reposetup(ui, repo):'
951
955
952 try:
956 try:
953 l.stateupdate = None
957 l.stateupdate = None
954 l.stateupdate = state_update(self, name="hg.transaction")
958 l.stateupdate = state_update(self, name=b"hg.transaction")
955 l.stateupdate.enter()
959 l.stateupdate.enter()
956 l.releasefn = staterelease
960 l.releasefn = staterelease
957 except Exception as e:
961 except Exception as e:
958 # Swallow any errors; fire and forget
962 # Swallow any errors; fire and forget
959 self.ui.log('watchman', 'Exception in state update %s\n', e)
963 self.ui.log(
964 b'watchman', b'Exception in state update %s\n', e
965 )
960 return l
966 return l
961
967
962 repo.__class__ = fsmonitorrepo
968 repo.__class__ = fsmonitorrepo
@@ -19,7 +19,7 b' from mercurial import ('
19 )
19 )
20
20
21 _version = 4
21 _version = 4
22 _versionformat = ">I"
22 _versionformat = b">I"
23
23
24
24
25 class state(object):
25 class state(object):
@@ -30,15 +30,15 b' class state(object):'
30 self._lastclock = None
30 self._lastclock = None
31 self._identity = util.filestat(None)
31 self._identity = util.filestat(None)
32
32
33 self.mode = self._ui.config('fsmonitor', 'mode')
33 self.mode = self._ui.config(b'fsmonitor', b'mode')
34 self.walk_on_invalidate = self._ui.configbool(
34 self.walk_on_invalidate = self._ui.configbool(
35 'fsmonitor', 'walk_on_invalidate'
35 b'fsmonitor', b'walk_on_invalidate'
36 )
36 )
37 self.timeout = float(self._ui.config('fsmonitor', 'timeout'))
37 self.timeout = float(self._ui.config(b'fsmonitor', b'timeout'))
38
38
39 def get(self):
39 def get(self):
40 try:
40 try:
41 file = self._vfs('fsmonitor.state', 'rb')
41 file = self._vfs(b'fsmonitor.state', b'rb')
42 except IOError as inst:
42 except IOError as inst:
43 self._identity = util.filestat(None)
43 self._identity = util.filestat(None)
44 if inst.errno != errno.ENOENT:
44 if inst.errno != errno.ENOENT:
@@ -50,9 +50,9 b' class state(object):'
50 versionbytes = file.read(4)
50 versionbytes = file.read(4)
51 if len(versionbytes) < 4:
51 if len(versionbytes) < 4:
52 self._ui.log(
52 self._ui.log(
53 'fsmonitor',
53 b'fsmonitor',
54 'fsmonitor: state file only has %d bytes, '
54 b'fsmonitor: state file only has %d bytes, '
55 'nuking state\n' % len(versionbytes),
55 b'nuking state\n' % len(versionbytes),
56 )
56 )
57 self.invalidate()
57 self.invalidate()
58 return None, None, None
58 return None, None, None
@@ -61,21 +61,21 b' class state(object):'
61 if diskversion != _version:
61 if diskversion != _version:
62 # different version, nuke state and start over
62 # different version, nuke state and start over
63 self._ui.log(
63 self._ui.log(
64 'fsmonitor',
64 b'fsmonitor',
65 'fsmonitor: version switch from %d to '
65 b'fsmonitor: version switch from %d to '
66 '%d, nuking state\n' % (diskversion, _version),
66 b'%d, nuking state\n' % (diskversion, _version),
67 )
67 )
68 self.invalidate()
68 self.invalidate()
69 return None, None, None
69 return None, None, None
70
70
71 state = file.read().split('\0')
71 state = file.read().split(b'\0')
72 # state = hostname\0clock\0ignorehash\0 + list of files, each
72 # state = hostname\0clock\0ignorehash\0 + list of files, each
73 # followed by a \0
73 # followed by a \0
74 if len(state) < 3:
74 if len(state) < 3:
75 self._ui.log(
75 self._ui.log(
76 'fsmonitor',
76 b'fsmonitor',
77 'fsmonitor: state file truncated (expected '
77 b'fsmonitor: state file truncated (expected '
78 '3 chunks, found %d), nuking state\n',
78 b'3 chunks, found %d), nuking state\n',
79 len(state),
79 len(state),
80 )
80 )
81 self.invalidate()
81 self.invalidate()
@@ -85,9 +85,9 b' class state(object):'
85 if diskhostname != hostname:
85 if diskhostname != hostname:
86 # file got moved to a different host
86 # file got moved to a different host
87 self._ui.log(
87 self._ui.log(
88 'fsmonitor',
88 b'fsmonitor',
89 'fsmonitor: stored hostname "%s" '
89 b'fsmonitor: stored hostname "%s" '
90 'different from current "%s", nuking state\n'
90 b'different from current "%s", nuking state\n'
91 % (diskhostname, hostname),
91 % (diskhostname, hostname),
92 )
92 )
93 self.invalidate()
93 self.invalidate()
@@ -110,31 +110,33 b' class state(object):'
110
110
111 # Read the identity from the file on disk rather than from the open file
111 # Read the identity from the file on disk rather than from the open file
112 # pointer below, because the latter is actually a brand new file.
112 # pointer below, because the latter is actually a brand new file.
113 identity = util.filestat.frompath(self._vfs.join('fsmonitor.state'))
113 identity = util.filestat.frompath(self._vfs.join(b'fsmonitor.state'))
114 if identity != self._identity:
114 if identity != self._identity:
115 self._ui.debug('skip updating fsmonitor.state: identity mismatch\n')
115 self._ui.debug(
116 b'skip updating fsmonitor.state: identity mismatch\n'
117 )
116 return
118 return
117
119
118 try:
120 try:
119 file = self._vfs(
121 file = self._vfs(
120 'fsmonitor.state', 'wb', atomictemp=True, checkambig=True
122 b'fsmonitor.state', b'wb', atomictemp=True, checkambig=True
121 )
123 )
122 except (IOError, OSError):
124 except (IOError, OSError):
123 self._ui.warn(_("warning: unable to write out fsmonitor state\n"))
125 self._ui.warn(_(b"warning: unable to write out fsmonitor state\n"))
124 return
126 return
125
127
126 with file:
128 with file:
127 file.write(struct.pack(_versionformat, _version))
129 file.write(struct.pack(_versionformat, _version))
128 file.write(socket.gethostname() + '\0')
130 file.write(socket.gethostname() + b'\0')
129 file.write(clock + '\0')
131 file.write(clock + b'\0')
130 file.write(ignorehash + '\0')
132 file.write(ignorehash + b'\0')
131 if notefiles:
133 if notefiles:
132 file.write('\0'.join(notefiles))
134 file.write(b'\0'.join(notefiles))
133 file.write('\0')
135 file.write(b'\0')
134
136
135 def invalidate(self):
137 def invalidate(self):
136 try:
138 try:
137 os.unlink(os.path.join(self._rootdir, '.hg', 'fsmonitor.state'))
139 os.unlink(os.path.join(self._rootdir, b'.hg', b'fsmonitor.state'))
138 except OSError as inst:
140 except OSError as inst:
139 if inst.errno != errno.ENOENT:
141 if inst.errno != errno.ENOENT:
140 raise
142 raise
@@ -18,15 +18,15 b' class Unavailable(Exception):'
18 def __init__(self, msg, warn=True, invalidate=False):
18 def __init__(self, msg, warn=True, invalidate=False):
19 self.msg = msg
19 self.msg = msg
20 self.warn = warn
20 self.warn = warn
21 if self.msg == 'timed out waiting for response':
21 if self.msg == b'timed out waiting for response':
22 self.warn = False
22 self.warn = False
23 self.invalidate = invalidate
23 self.invalidate = invalidate
24
24
25 def __str__(self):
25 def __str__(self):
26 if self.warn:
26 if self.warn:
27 return 'warning: Watchman unavailable: %s' % self.msg
27 return b'warning: Watchman unavailable: %s' % self.msg
28 else:
28 else:
29 return 'Watchman unavailable: %s' % self.msg
29 return b'Watchman unavailable: %s' % self.msg
30
30
31
31
32 class WatchmanNoRoot(Unavailable):
32 class WatchmanNoRoot(Unavailable):
@@ -39,10 +39,10 b' class client(object):'
39 def __init__(self, ui, root, timeout=1.0):
39 def __init__(self, ui, root, timeout=1.0):
40 err = None
40 err = None
41 if not self._user:
41 if not self._user:
42 err = "couldn't get user"
42 err = b"couldn't get user"
43 warn = True
43 warn = True
44 if self._user in ui.configlist('fsmonitor', 'blacklistusers'):
44 if self._user in ui.configlist(b'fsmonitor', b'blacklistusers'):
45 err = 'user %s in blacklist' % self._user
45 err = b'user %s in blacklist' % self._user
46 warn = False
46 warn = False
47
47
48 if err:
48 if err:
@@ -60,10 +60,10 b' class client(object):'
60 self._watchmanclient.setTimeout(timeout)
60 self._watchmanclient.setTimeout(timeout)
61
61
62 def getcurrentclock(self):
62 def getcurrentclock(self):
63 result = self.command('clock')
63 result = self.command(b'clock')
64 if not util.safehasattr(result, 'clock'):
64 if not util.safehasattr(result, b'clock'):
65 raise Unavailable(
65 raise Unavailable(
66 'clock result is missing clock value', invalidate=True
66 b'clock result is missing clock value', invalidate=True
67 )
67 )
68 return result.clock
68 return result.clock
69
69
@@ -86,7 +86,9 b' class client(object):'
86 try:
86 try:
87 if self._watchmanclient is None:
87 if self._watchmanclient is None:
88 self._firsttime = False
88 self._firsttime = False
89 watchman_exe = self._ui.configpath('fsmonitor', 'watchman_exe')
89 watchman_exe = self._ui.configpath(
90 b'fsmonitor', b'watchman_exe'
91 )
90 self._watchmanclient = pywatchman.client(
92 self._watchmanclient = pywatchman.client(
91 timeout=self._timeout,
93 timeout=self._timeout,
92 useImmutableBser=True,
94 useImmutableBser=True,
@@ -94,7 +96,7 b' class client(object):'
94 )
96 )
95 return self._watchmanclient.query(*watchmanargs)
97 return self._watchmanclient.query(*watchmanargs)
96 except pywatchman.CommandError as ex:
98 except pywatchman.CommandError as ex:
97 if 'unable to resolve root' in ex.msg:
99 if b'unable to resolve root' in ex.msg:
98 raise WatchmanNoRoot(self._root, ex.msg)
100 raise WatchmanNoRoot(self._root, ex.msg)
99 raise Unavailable(ex.msg)
101 raise Unavailable(ex.msg)
100 except pywatchman.WatchmanError as ex:
102 except pywatchman.WatchmanError as ex:
@@ -107,7 +109,7 b' class client(object):'
107 except WatchmanNoRoot:
109 except WatchmanNoRoot:
108 # this 'watch' command can also raise a WatchmanNoRoot if
110 # this 'watch' command can also raise a WatchmanNoRoot if
109 # watchman refuses to accept this root
111 # watchman refuses to accept this root
110 self._command('watch')
112 self._command(b'watch')
111 return self._command(*args)
113 return self._command(*args)
112 except Unavailable:
114 except Unavailable:
113 # this is in an outer scope to catch Unavailable form any of the
115 # this is in an outer scope to catch Unavailable form any of the
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now