##// END OF EJS Templates
formatting: byteify all mercurial/ and hgext/ string literals...
Augie Fackler -
r43347: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 59 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
60 60 # be specifying the version(s) of Mercurial they are tested with, or
61 61 # leave the attribute unspecified.
62 testedwith = 'ships-with-hg-core'
62 testedwith = b'ships-with-hg-core'
63 63
64 64 cmdtable = {}
65 65 command = registrar.command(cmdtable)
@@ -67,14 +67,14 b' command = registrar.command(cmdtable)'
67 67 configtable = {}
68 68 configitem = registrar.configitem(configtable)
69 69
70 configitem('absorb', 'add-noise', default=True)
71 configitem('absorb', 'amend-flag', default=None)
72 configitem('absorb', 'max-stack-size', default=50)
70 configitem(b'absorb', b'add-noise', default=True)
71 configitem(b'absorb', b'amend-flag', default=None)
72 configitem(b'absorb', b'max-stack-size', default=50)
73 73
74 74 colortable = {
75 'absorb.description': 'yellow',
76 'absorb.node': 'blue bold',
77 'absorb.path': 'bold',
75 b'absorb.description': b'yellow',
76 b'absorb.node': b'blue bold',
77 b'absorb.path': b'bold',
78 78 }
79 79
80 80 defaultdict = collections.defaultdict
@@ -98,7 +98,7 b' class emptyfilecontext(object):'
98 98 """minimal filecontext representing an empty file"""
99 99
100 100 def data(self):
101 return ''
101 return b''
102 102
103 103 def node(self):
104 104 return node.nullid
@@ -364,11 +364,11 b' class filefixupstate(object):'
364 364 if self.ui.debugflag:
365 365 idx = (max(rev - 1, 0)) // 2
366 366 self.ui.write(
367 _('%s: chunk %d:%d -> %d lines\n')
367 _(b'%s: chunk %d:%d -> %d lines\n')
368 368 % (node.short(self.fctxs[idx].node()), a1, a2, len(blines))
369 369 )
370 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 372 self.finalcontents = self._checkoutlinelogwithedits()
373 373 else:
374 374 self.finalcontents = self._checkoutlinelog()
@@ -434,7 +434,7 b' class filefixupstate(object):'
434 434 """like mdiff.allblocks, but only care about differences"""
435 435 blocks = mdiff.allblocks(a, b, lines1=alines, lines2=blines)
436 436 for chunk, btype in blocks:
437 if btype != '!':
437 if btype != b'!':
438 438 continue
439 439 yield chunk
440 440
@@ -443,7 +443,7 b' class filefixupstate(object):'
443 443 this is similar to running a partial "annotate".
444 444 """
445 445 llog = linelog.linelog()
446 a, alines = '', []
446 a, alines = b'', []
447 447 for i in pycompat.xrange(len(self.contents)):
448 448 b, blines = self.contents[i], self.contentlines[i]
449 449 llrev = i * 2 + 1
@@ -459,7 +459,7 b' class filefixupstate(object):'
459 459 for i in pycompat.xrange(len(self.contents)):
460 460 rev = (i + 1) * 2
461 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 463 contents.append(content)
464 464 return contents
465 465
@@ -469,8 +469,8 b' class filefixupstate(object):'
469 469 # header
470 470 editortext = (
471 471 _(
472 'HG: editing %s\nHG: "y" means the line to the right '
473 'exists in the changeset to the top\nHG:\n'
472 b'HG: editing %s\nHG: "y" means the line to the right '
473 b'exists in the changeset to the top\nHG:\n'
474 474 )
475 475 % self.fctxs[-1].path()
476 476 )
@@ -481,13 +481,13 b' class filefixupstate(object):'
481 481 if not isinstance(f, emptyfilecontext)
482 482 ]
483 483 for i, (j, f) in enumerate(visiblefctxs):
484 editortext += _('HG: %s/%s %s %s\n') % (
485 '|' * i,
486 '-' * (len(visiblefctxs) - i + 1),
484 editortext += _(b'HG: %s/%s %s %s\n') % (
485 b'|' * i,
486 b'-' * (len(visiblefctxs) - i + 1),
487 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 491 # figure out the lifetime of a line, this is relatively inefficient,
492 492 # but probably fine
493 493 lineset = defaultdict(lambda: set()) # {(llrev, linenum): {llrev}}
@@ -497,33 +497,33 b' class filefixupstate(object):'
497 497 lineset[l].add(i)
498 498 # append lines
499 499 for l in alllines:
500 editortext += ' %s : %s' % (
501 ''.join(
500 editortext += b' %s : %s' % (
501 b''.join(
502 502 [
503 ('y' if i in lineset[l] else ' ')
503 (b'y' if i in lineset[l] else b' ')
504 504 for i, _f in visiblefctxs
505 505 ]
506 506 ),
507 507 self._getline(l),
508 508 )
509 509 # run editor
510 editedtext = self.ui.edit(editortext, '', action='absorb')
510 editedtext = self.ui.edit(editortext, b'', action=b'absorb')
511 511 if not editedtext:
512 raise error.Abort(_('empty editor text'))
512 raise error.Abort(_(b'empty editor text'))
513 513 # parse edited result
514 contents = ['' for i in self.fctxs]
514 contents = [b'' for i in self.fctxs]
515 515 leftpadpos = 4
516 516 colonpos = leftpadpos + len(visiblefctxs) + 1
517 517 for l in mdiff.splitnewlines(editedtext):
518 if l.startswith('HG:'):
518 if l.startswith(b'HG:'):
519 519 continue
520 if l[colonpos - 1 : colonpos + 2] != ' : ':
521 raise error.Abort(_('malformed line: %s') % l)
520 if l[colonpos - 1 : colonpos + 2] != b' : ':
521 raise error.Abort(_(b'malformed line: %s') % l)
522 522 linecontent = l[colonpos + 2 :]
523 523 for i, ch in enumerate(
524 524 pycompat.bytestr(l[leftpadpos : colonpos - 1])
525 525 ):
526 if ch == 'y':
526 if ch == b'y':
527 527 contents[visiblefctxs[i][0]] += linecontent
528 528 # chunkstats is hard to calculate if anything changes, therefore
529 529 # set them to just a simple value (1, 1).
@@ -589,7 +589,7 b' class filefixupstate(object):'
589 589
590 590 def _showchanges(self, fm, alines, blines, chunk, fixups):
591 591 def trim(line):
592 if line.endswith('\n'):
592 if line.endswith(b'\n'):
593 593 line = line[:-1]
594 594 return line
595 595
@@ -605,25 +605,25 b' class filefixupstate(object):'
605 605
606 606 fm.startitem()
607 607 fm.write(
608 'hunk',
609 ' %s\n',
610 '@@ -%d,%d +%d,%d @@' % (a1, a2 - a1, b1, b2 - b1),
611 label='diff.hunk',
608 b'hunk',
609 b' %s\n',
610 b'@@ -%d,%d +%d,%d @@' % (a1, a2 - a1, b1, b2 - b1),
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 615 def writeline(idx, diffchar, line, linetype, linelabel):
616 616 fm.startitem()
617 node = ''
617 node = b''
618 618 if idx:
619 619 ctx = self.fctxs[idx]
620 620 fm.context(fctx=ctx)
621 621 node = ctx.hex()
622 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 624 fm.write(
625 'diffchar ' + linetype,
626 '%s%s\n',
625 b'diffchar ' + linetype,
626 b'%s%s\n',
627 627 diffchar,
628 628 line,
629 629 label=linelabel,
@@ -632,11 +632,19 b' class filefixupstate(object):'
632 632
633 633 for i in pycompat.xrange(a1, a2):
634 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 641 for i in pycompat.xrange(b1, b2):
638 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 689 self.paths = []
682 690 # but if --edit-lines is used, the user may want to edit files
683 691 # even if they are not modified
684 editopt = self.opts.get('edit_lines')
692 editopt = self.opts.get(b'edit_lines')
685 693 if not self.status.modified and editopt and match:
686 694 interestingpaths = match.files()
687 695 else:
@@ -691,7 +699,7 b' class fixupstate(object):'
691 699 # sorting is necessary to eliminate ambiguity for the "double move"
692 700 # case: "hg cp A B; hg cp A C; hg rm A", then only "B" can affect "A".
693 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 703 targetfctx = targetctx[path]
696 704 fctxs, ctx2fctx = getfilestack(self.stack, path, seenfctxs)
697 705 # ignore symbolic links or binary, or unchanged files
@@ -708,9 +716,9 b' class fixupstate(object):'
708 716 fstate = filefixupstate(fctxs, path, ui=self.ui, opts=self.opts)
709 717 if fm is not None:
710 718 fm.startitem()
711 fm.plain('showing changes for ')
712 fm.write('path', '%s\n', path, label='absorb.path')
713 fm.data(linetype='path')
719 fm.plain(b'showing changes for ')
720 fm.write(b'path', b'%s\n', path, label=b'absorb.path')
721 fm.data(linetype=b'path')
714 722 fstate.diffwith(targetfctx, fm)
715 723 self.fixupmap[path] = fstate
716 724 self.paths.append(path)
@@ -720,7 +728,7 b' class fixupstate(object):'
720 728 """apply fixups to individual filefixupstates"""
721 729 for path, state in self.fixupmap.iteritems():
722 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 732 state.apply()
725 733
726 734 @property
@@ -733,10 +741,10 b' class fixupstate(object):'
733 741
734 742 def commit(self):
735 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 745 self._commitstack()
738 746 self._movebookmarks(tr)
739 if self.repo['.'].node() in self.replacemap:
747 if self.repo[b'.'].node() in self.replacemap:
740 748 self._moveworkingdirectoryparent()
741 749 self._cleanupoldcommits()
742 750 return self.finalnode
@@ -750,14 +758,14 b' class fixupstate(object):'
750 758 for path, stat in chunkstats.iteritems():
751 759 if stat[0]:
752 760 ui.write(
753 _('%s: %d of %d chunk(s) applied\n')
761 _(b'%s: %d of %d chunk(s) applied\n')
754 762 % (path, stat[0], stat[1])
755 763 )
756 764 elif not ui.quiet:
757 765 # a summary for all files
758 766 stats = chunkstats.values()
759 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 770 def _commitstack(self):
763 771 """make new commits. update self.finalnode, self.replacemap.
@@ -777,7 +785,7 b' class fixupstate(object):'
777 785 if self._willbecomenoop(memworkingcopy, ctx, nextp1):
778 786 # changeset is no longer necessary
779 787 self.replacemap[ctx.node()] = None
780 msg = _('became empty and was dropped')
788 msg = _(b'became empty and was dropped')
781 789 else:
782 790 # changeset needs re-commit
783 791 nodestr = self._commitsingle(memworkingcopy, ctx, p1=nextp1)
@@ -785,21 +793,21 b' class fixupstate(object):'
785 793 nextp1 = lastcommitted
786 794 self.replacemap[ctx.node()] = lastcommitted.node()
787 795 if memworkingcopy:
788 msg = _('%d file(s) changed, became %s') % (
796 msg = _(b'%d file(s) changed, became %s') % (
789 797 len(memworkingcopy),
790 798 self._ctx2str(lastcommitted),
791 799 )
792 800 else:
793 msg = _('became %s') % self._ctx2str(lastcommitted)
801 msg = _(b'became %s') % self._ctx2str(lastcommitted)
794 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 804 self.finalnode = lastcommitted and lastcommitted.node()
797 805
798 806 def _ctx2str(self, ctx):
799 807 if self.ui.debugflag:
800 return '%d:%s' % (ctx.rev(), ctx.hex())
808 return b'%d:%s' % (ctx.rev(), ctx.hex())
801 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 812 def _getnewfilecontents(self, ctx):
805 813 """(ctx) -> {path: str}
@@ -832,18 +840,18 b' class fixupstate(object):'
832 840 changes.append((name, hsh))
833 841 if self.ui.verbose:
834 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 845 else:
838 846 changes.append((name, None))
839 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 849 repo._bookmarks.applychanges(repo, tr, changes)
842 850
843 851 def _moveworkingdirectoryparent(self):
844 852 if not self.finalnode:
845 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 855 ctx = self.repo[revs.first()]
848 856 self.finalnode = ctx.node()
849 857 else:
@@ -854,7 +862,7 b' class fixupstate(object):'
854 862 # be slow. in absorb's case, no need to invalidate fsmonitorstate.
855 863 noop = lambda: 0
856 864 restore = noop
857 if util.safehasattr(dirstate, '_fsmonitorstate'):
865 if util.safehasattr(dirstate, b'_fsmonitorstate'):
858 866 bak = dirstate._fsmonitorstate.invalidate
859 867
860 868 def restore():
@@ -901,8 +909,8 b' class fixupstate(object):'
901 909 """
902 910 parents = p1 and (p1, node.nullid)
903 911 extra = ctx.extra()
904 if self._useobsolete and self.ui.configbool('absorb', 'add-noise'):
905 extra['absorb_source'] = ctx.hex()
912 if self._useobsolete and self.ui.configbool(b'absorb', b'add-noise'):
913 extra[b'absorb_source'] = ctx.hex()
906 914 mctx = overlaycontext(memworkingcopy, ctx, parents, extra=extra)
907 915 return mctx.commit()
908 916
@@ -918,7 +926,7 b' class fixupstate(object):'
918 926 }
919 927 if replacements:
920 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 943 patchlines = mdiff.splitnewlines(buf.getvalue())
936 944 # hunk.prettystr() will update hunk.removed
937 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 947 return path, (a1, a2, blines)
940 948
941 949
@@ -967,7 +975,7 b' def overlaydiffcontext(ctx, chunks):'
967 975 lines = mdiff.splitnewlines(ctx[path].data())
968 976 for a1, a2, blines in patches:
969 977 lines[a1:a2] = blines
970 memworkingcopy[path] = ''.join(lines)
978 memworkingcopy[path] = b''.join(lines)
971 979 return overlaycontext(memworkingcopy, ctx)
972 980
973 981
@@ -979,18 +987,21 b' def absorb(ui, repo, stack=None, targetc'
979 987 return fixupstate.
980 988 """
981 989 if stack is None:
982 limit = ui.configint('absorb', 'max-stack-size')
983 headctx = repo['.']
990 limit = ui.configint(b'absorb', b'max-stack-size')
991 headctx = repo[b'.']
984 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 994 stack = getdraftstack(headctx, limit)
987 995 if limit and len(stack) >= limit:
988 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 1001 % limit
991 1002 )
992 1003 if not stack:
993 raise error.Abort(_('no mutable changeset to change'))
1004 raise error.Abort(_(b'no mutable changeset to change'))
994 1005 if targetctx is None: # default to working copy
995 1006 targetctx = repo[None]
996 1007 if pats is None:
@@ -999,85 +1010,89 b' def absorb(ui, repo, stack=None, targetc'
999 1010 opts = {}
1000 1011 state = fixupstate(stack, ui=ui, opts=opts)
1001 1012 matcher = scmutil.match(targetctx, pats, opts)
1002 if opts.get('interactive'):
1013 if opts.get(b'interactive'):
1003 1014 diff = patch.diff(repo, stack[-1].node(), targetctx.node(), matcher)
1004 1015 origchunks = patch.parsepatch(diff)
1005 1016 chunks = cmdutil.recordfilter(ui, origchunks, matcher)[0]
1006 1017 targetctx = overlaydiffcontext(stack[-1], chunks)
1007 1018 fm = None
1008 if opts.get('print_changes') or not opts.get('apply_changes'):
1009 fm = ui.formatter('absorb', opts)
1019 if opts.get(b'print_changes') or not opts.get(b'apply_changes'):
1020 fm = ui.formatter(b'absorb', opts)
1010 1021 state.diffwith(targetctx, matcher, fm)
1011 1022 if fm is not None:
1012 1023 fm.startitem()
1013 fm.write("count", "\n%d changesets affected\n", len(state.ctxaffected))
1014 fm.data(linetype='summary')
1024 fm.write(
1025 b"count", b"\n%d changesets affected\n", len(state.ctxaffected)
1026 )
1027 fm.data(linetype=b'summary')
1015 1028 for ctx in reversed(stack):
1016 1029 if ctx not in state.ctxaffected:
1017 1030 continue
1018 1031 fm.startitem()
1019 1032 fm.context(ctx=ctx)
1020 fm.data(linetype='changeset')
1021 fm.write('node', '%-7.7s ', ctx.hex(), label='absorb.node')
1033 fm.data(linetype=b'changeset')
1034 fm.write(b'node', b'%-7.7s ', ctx.hex(), label=b'absorb.node')
1022 1035 descfirstline = ctx.description().splitlines()[0]
1023 1036 fm.write(
1024 'descfirstline',
1025 '%s\n',
1037 b'descfirstline',
1038 b'%s\n',
1026 1039 descfirstline,
1027 label='absorb.description',
1040 label=b'absorb.description',
1028 1041 )
1029 1042 fm.end()
1030 if not opts.get('dry_run'):
1043 if not opts.get(b'dry_run'):
1031 1044 if (
1032 not opts.get('apply_changes')
1045 not opts.get(b'apply_changes')
1033 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 1053 state.apply()
1039 1054 if state.commit():
1040 1055 state.printchunkstats()
1041 1056 elif not ui.quiet:
1042 ui.write(_('nothing applied\n'))
1057 ui.write(_(b'nothing applied\n'))
1043 1058 return state
1044 1059
1045 1060
1046 1061 @command(
1047 'absorb',
1062 b'absorb',
1048 1063 [
1049 1064 (
1050 'a',
1051 'apply-changes',
1065 b'a',
1066 b'apply-changes',
1052 1067 None,
1053 _('apply changes without prompting for confirmation'),
1068 _(b'apply changes without prompting for confirmation'),
1054 1069 ),
1055 1070 (
1056 'p',
1057 'print-changes',
1071 b'p',
1072 b'print-changes',
1058 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',
1063 'interactive',
1077 b'i',
1078 b'interactive',
1064 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',
1069 'edit-lines',
1083 b'e',
1084 b'edit-lines',
1070 1085 None,
1071 1086 _(
1072 'edit what lines belong to which changesets before commit '
1073 '(EXPERIMENTAL)'
1087 b'edit what lines belong to which changesets before commit '
1088 b'(EXPERIMENTAL)'
1074 1089 ),
1075 1090 ),
1076 1091 ]
1077 1092 + commands.dryrunopts
1078 1093 + commands.templateopts
1079 1094 + commands.walkopts,
1080 _('hg absorb [OPTION] [FILE]...'),
1095 _(b'hg absorb [OPTION] [FILE]...'),
1081 1096 helpcategory=command.CATEGORY_COMMITTING,
1082 1097 helpbasic=True,
1083 1098 )
@@ -1108,7 +1123,7 b' def absorbcmd(ui, repo, *pats, **opts):'
1108 1123 opts = pycompat.byteskwargs(opts)
1109 1124
1110 1125 with repo.wlock(), repo.lock():
1111 if not opts['dry_run']:
1126 if not opts[b'dry_run']:
1112 1127 cmdutil.checkunfinished(repo)
1113 1128
1114 1129 state = absorb(ui, repo, pats=pats, opts=opts)
@@ -232,66 +232,66 b' urlreq = util.urlreq'
232 232 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
233 233 # be specifying the version(s) of Mercurial they are tested with, or
234 234 # leave the attribute unspecified.
235 testedwith = 'ships-with-hg-core'
235 testedwith = b'ships-with-hg-core'
236 236
237 237 configtable = {}
238 238 configitem = registrar.configitem(configtable)
239 239
240 240 # deprecated config: acl.config
241 241 configitem(
242 'acl', 'config', default=None,
242 b'acl', b'config', default=None,
243 243 )
244 244 configitem(
245 'acl.groups', '.*', default=None, generic=True,
245 b'acl.groups', b'.*', default=None, generic=True,
246 246 )
247 247 configitem(
248 'acl.deny.branches', '.*', default=None, generic=True,
248 b'acl.deny.branches', b'.*', default=None, generic=True,
249 249 )
250 250 configitem(
251 'acl.allow.branches', '.*', default=None, generic=True,
251 b'acl.allow.branches', b'.*', default=None, generic=True,
252 252 )
253 253 configitem(
254 'acl.deny', '.*', default=None, generic=True,
254 b'acl.deny', b'.*', default=None, generic=True,
255 255 )
256 256 configitem(
257 'acl.allow', '.*', default=None, generic=True,
257 b'acl.allow', b'.*', default=None, generic=True,
258 258 )
259 259 configitem(
260 'acl', 'sources', default=lambda: ['serve'],
260 b'acl', b'sources', default=lambda: [b'serve'],
261 261 )
262 262
263 263
264 264 def _getusers(ui, group):
265 265
266 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 268 if hgrcusers:
269 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 272 # If no users found in group definition, get users from OS-level group
273 273 try:
274 274 return util.groupmembers(group)
275 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 279 def _usermatch(ui, user, usersorgroups):
280 280
281 if usersorgroups == '*':
281 if usersorgroups == b'*':
282 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 287 # Test for excluded user or group. Format:
288 288 # if ug is a user name: !username
289 289 # if ug is a group name: !@groupname
290 290 ug = ug[1:]
291 291 if (
292 not ug.startswith('@')
292 not ug.startswith(b'@')
293 293 and user != ug
294 or ug.startswith('@')
294 or ug.startswith(b'@')
295 295 and user not in _getusers(ui, ug[1:])
296 296 ):
297 297 return True
@@ -299,7 +299,9 b' def _usermatch(ui, user, usersorgroups):'
299 299 # Test for user or group. Format:
300 300 # if ug is a user name: username
301 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 305 return True
304 306
305 307 return False
@@ -308,14 +310,14 b' def _usermatch(ui, user, usersorgroups):'
308 310 def buildmatch(ui, repo, user, key):
309 311 '''return tuple of (match function, list enabled).'''
310 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 314 return None
313 315
314 316 pats = [
315 317 pat for pat, users in ui.configitems(key) if _usermatch(ui, user, users)
316 318 ]
317 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 323 # Branch-based ACL
@@ -323,14 +325,14 b' def buildmatch(ui, repo, user, key):'
323 325 if pats:
324 326 # If there's an asterisk (meaning "any branch"), always return True;
325 327 # Otherwise, test if b is in pats
326 if '*' in pats:
328 if b'*' in pats:
327 329 return util.always
328 330 return lambda b: b in pats
329 331 return util.never
330 332
331 333 # Path-based ACL
332 334 if pats:
333 return match.match(repo.root, '', pats)
335 return match.match(repo.root, b'', pats)
334 336 return util.never
335 337
336 338
@@ -342,122 +344,128 b' def ensureenabled(ui):'
342 344 never loaded. This function ensure the extension is enabled when running
343 345 hooks.
344 346 """
345 if 'acl' in ui._knownconfig:
347 if b'acl' in ui._knownconfig:
346 348 return
347 ui.setconfig('extensions', 'acl', '', source='internal')
348 extensions.loadall(ui, ['acl'])
349 ui.setconfig(b'extensions', b'acl', b'', source=b'internal')
350 extensions.loadall(ui, [b'acl'])
349 351
350 352
351 353 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
352 354
353 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 358 raise error.Abort(
357 359 _(
358 'config error - hook type "%s" cannot stop '
359 'incoming changesets, commits, nor bookmarks'
360 b'config error - hook type "%s" cannot stop '
361 b'incoming changesets, commits, nor bookmarks'
360 362 )
361 363 % hooktype
362 364 )
363 if hooktype == 'pretxnchangegroup' and source not in ui.configlist(
364 'acl', 'sources'
365 if hooktype == b'pretxnchangegroup' and source not in ui.configlist(
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 369 return
368 370
369 371 user = None
370 if source == 'serve' and r'url' in kwargs:
371 url = kwargs[r'url'].split(':')
372 if url[0] == 'remote' and url[1].startswith('http'):
372 if source == b'serve' and r'url' in kwargs:
373 url = kwargs[r'url'].split(b':')
374 if url[0] == b'remote' and url[1].startswith(b'http'):
373 375 user = urlreq.unquote(url[3])
374 376
375 377 if user is None:
376 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 383 _pkhook(ui, repo, hooktype, node, source, user, **kwargs)
382 384 else:
383 385 _txnhook(ui, repo, hooktype, node, source, user, **kwargs)
384 386
385 387
386 388 def _pkhook(ui, repo, hooktype, node, source, user, **kwargs):
387 if kwargs[r'namespace'] == 'bookmarks':
389 if kwargs[r'namespace'] == b'bookmarks':
388 390 bookmark = kwargs[r'key']
389 391 ctx = kwargs[r'new']
390 allowbookmarks = buildmatch(ui, None, user, 'acl.allow.bookmarks')
391 denybookmarks = buildmatch(ui, None, user, 'acl.deny.bookmarks')
392 allowbookmarks = buildmatch(ui, None, user, b'acl.allow.bookmarks')
393 denybookmarks = buildmatch(ui, None, user, b'acl.deny.bookmarks')
392 394
393 395 if denybookmarks and denybookmarks(bookmark):
394 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 401 % (user, bookmark, ctx)
397 402 )
398 403 if allowbookmarks and not allowbookmarks(bookmark):
399 404 raise error.Abort(
400 405 _(
401 'acl: user "%s" not allowed on bookmark "%s"'
402 ' (changeset "%s")'
406 b'acl: user "%s" not allowed on bookmark "%s"'
407 b' (changeset "%s")'
403 408 )
404 409 % (user, bookmark, ctx)
405 410 )
406 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 413 % (ctx, bookmark)
409 414 )
410 415
411 416
412 417 def _txnhook(ui, repo, hooktype, node, source, user, **kwargs):
413 418 # deprecated config: acl.config
414 cfg = ui.config('acl', 'config')
419 cfg = ui.config(b'acl', b'config')
415 420 if cfg:
416 421 ui.readconfig(
417 422 cfg,
418 423 sections=[
419 'acl.groups',
420 'acl.allow.branches',
421 'acl.deny.branches',
422 'acl.allow',
423 'acl.deny',
424 b'acl.groups',
425 b'acl.allow.branches',
426 b'acl.deny.branches',
427 b'acl.allow',
428 b'acl.deny',
424 429 ],
425 430 )
426 431
427 allowbranches = buildmatch(ui, None, user, 'acl.allow.branches')
428 denybranches = buildmatch(ui, None, user, 'acl.deny.branches')
429 allow = buildmatch(ui, repo, user, 'acl.allow')
430 deny = buildmatch(ui, repo, user, 'acl.deny')
432 allowbranches = buildmatch(ui, None, user, b'acl.allow.branches')
433 denybranches = buildmatch(ui, None, user, b'acl.deny.branches')
434 allow = buildmatch(ui, repo, user, b'acl.allow')
435 deny = buildmatch(ui, repo, user, b'acl.deny')
431 436
432 437 for rev in pycompat.xrange(repo[node].rev(), len(repo)):
433 438 ctx = repo[rev]
434 439 branch = ctx.branch()
435 440 if denybranches and denybranches(branch):
436 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 443 % (user, branch, ctx)
439 444 )
440 445 if allowbranches and not allowbranches(branch):
441 446 raise error.Abort(
442 447 _(
443 'acl: user "%s" not allowed on branch "%s"'
444 ' (changeset "%s")'
448 b'acl: user "%s" not allowed on branch "%s"'
449 b' (changeset "%s")'
445 450 )
446 451 % (user, branch, ctx)
447 452 )
448 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 457 for f in ctx.files():
453 458 if deny and deny(f):
454 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 461 % (user, f, ctx)
457 462 )
458 463 if allow and not allow(f):
459 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 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 24 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
25 25 # be specifying the version(s) of Mercurial they are tested with, or
26 26 # leave the attribute unspecified.
27 testedwith = 'ships-with-hg-core'
27 testedwith = b'ships-with-hg-core'
28 28
29 29 cmdtable = {}
30 30 command = registrar.command(cmdtable)
31 31
32 32
33 33 @command(
34 'amend',
34 b'amend',
35 35 [
36 36 (
37 'A',
38 'addremove',
37 b'A',
38 b'addremove',
39 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')),
43 ('i', 'interactive', None, _('use interactive mode')),
42 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
43 (b'i', b'interactive', None, _(b'use interactive mode')),
44 44 (
45 45 b'',
46 46 b'close-branch',
@@ -48,13 +48,13 b' command = registrar.command(cmdtable)'
48 48 _(b'mark a branch as closed, hiding it from the branch list'),
49 49 ),
50 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 53 + cmdutil.walkopts
54 54 + cmdutil.commitopts
55 55 + cmdutil.commitopts2
56 56 + cmdutil.commitopts3,
57 _('[OPTION]... [FILE]...'),
57 _(b'[OPTION]... [FILE]...'),
58 58 helpcategory=command.CATEGORY_COMMITTING,
59 59 inferrepo=True,
60 60 )
@@ -70,7 +70,7 b' def amend(ui, repo, *pats, **opts):'
70 70 cmdutil.checknotesize(ui, opts)
71 71
72 72 with repo.wlock(), repo.lock():
73 if not opts.get('logfile'):
74 opts['message'] = opts.get('message') or repo['.'].description()
75 opts['amend'] = True
73 if not opts.get(b'logfile'):
74 opts[b'message'] = opts.get(b'message') or repo[b'.'].description()
75 opts[b'amend'] = True
76 76 return commands._docommit(ui, repo, *pats, **pycompat.strkwargs(opts))
@@ -42,14 +42,14 b' configtable = {}'
42 42 configitem = registrar.configitem(configtable)
43 43
44 44 configitem(
45 'automv', 'similarity', default=95,
45 b'automv', b'similarity', default=95,
46 46 )
47 47
48 48
49 49 def extsetup(ui):
50 entry = extensions.wrapcommand(commands.table, 'commit', mvcheck)
50 entry = extensions.wrapcommand(commands.table, b'commit', mvcheck)
51 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 57 """Hook to check for moves at commit time"""
58 58 opts = pycompat.byteskwargs(opts)
59 59 renames = None
60 disabled = opts.pop('no_automv', False)
60 disabled = opts.pop(b'no_automv', False)
61 61 if not disabled:
62 threshold = ui.configint('automv', 'similarity')
62 threshold = ui.configint(b'automv', b'similarity')
63 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 65 if threshold > 0:
66 66 match = scmutil.match(repo[None], pats, opts)
67 67 added, removed = _interestingfiles(repo, match)
@@ -87,7 +87,7 b' def _interestingfiles(repo, matcher):'
87 87 added = stat.added
88 88 removed = stat.removed
89 89
90 copy = copies.pathcopies(repo['.'], repo[None], matcher)
90 copy = copies.pathcopies(repo[b'.'], repo[None], matcher)
91 91 # remove the copy files for which we already have copy info
92 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 109 if repo.ui.verbose:
110 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 112 % (uipathfn(src), uipathfn(dst), score * 100)
113 113 )
114 114 renames[dst] = src
115 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 117 return renames
@@ -26,33 +26,33 b' from mercurial import ('
26 26 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
27 27 # be specifying the version(s) of Mercurial they are tested with, or
28 28 # leave the attribute unspecified.
29 testedwith = 'ships-with-hg-core'
29 testedwith = b'ships-with-hg-core'
30 30
31 31
32 32 def prettyedge(before, edge, after):
33 if edge == '~':
34 return '\xE2\x95\xA7' # U+2567 ╧
35 if edge == '/':
36 return '\xE2\x95\xB1' # U+2571 ╱
37 if edge == '-':
38 return '\xE2\x94\x80' # U+2500 ─
39 if edge == '|':
40 return '\xE2\x94\x82' # U+2502 │
41 if edge == ':':
42 return '\xE2\x94\x86' # U+2506 ┆
43 if edge == '\\':
44 return '\xE2\x95\xB2' # U+2572 ╲
45 if edge == '+':
46 if before == ' ' and not after == ' ':
47 return '\xE2\x94\x9C' # U+251C ├
48 if after == ' ' and not before == ' ':
49 return '\xE2\x94\xA4' # U+2524 ┤
50 return '\xE2\x94\xBC' # U+253C ┼
33 if edge == b'~':
34 return b'\xE2\x95\xA7' # U+2567 ╧
35 if edge == b'/':
36 return b'\xE2\x95\xB1' # U+2571 ╱
37 if edge == b'-':
38 return b'\xE2\x94\x80' # U+2500 ─
39 if edge == b'|':
40 return b'\xE2\x94\x82' # U+2502 │
41 if edge == b':':
42 return b'\xE2\x94\x86' # U+2506 ┆
43 if edge == b'\\':
44 return b'\xE2\x95\xB2' # U+2572 ╲
45 if edge == b'+':
46 if before == b' ' and not after == b' ':
47 return b'\xE2\x94\x9C' # U+251C ├
48 if after == b' ' and not before == b' ':
49 return b'\xE2\x94\xA4' # U+2524 ┤
50 return b'\xE2\x94\xBC' # U+253C ┼
51 51 return edge
52 52
53 53
54 54 def convertedges(line):
55 line = ' %s ' % line
55 line = b' %s ' % line
56 56 pretty = []
57 57 for idx in pycompat.xrange(len(line) - 2):
58 58 pretty.append(
@@ -62,21 +62,21 b' def convertedges(line):'
62 62 line[idx + 2 : idx + 3],
63 63 )
64 64 )
65 return ''.join(pretty)
65 return b''.join(pretty)
66 66
67 67
68 68 def getprettygraphnode(orig, *args, **kwargs):
69 69 node = orig(*args, **kwargs)
70 if node == 'o':
71 return '\xE2\x97\x8B' # U+25CB ○
72 if node == '@':
73 return '\xE2\x97\x8D' # U+25CD ◍
74 if node == '*':
75 return '\xE2\x88\x97' # U+2217 ∗
76 if node == 'x':
77 return '\xE2\x97\x8C' # U+25CC ◌
78 if node == '_':
79 return '\xE2\x95\xA4' # U+2564 ╤
70 if node == b'o':
71 return b'\xE2\x97\x8B' # U+25CB ○
72 if node == b'@':
73 return b'\xE2\x97\x8D' # U+25CD ◍
74 if node == b'*':
75 return b'\xE2\x88\x97' # U+2217 ∗
76 if node == b'x':
77 return b'\xE2\x97\x8C' # U+25CC ◌
78 if node == b'_':
79 return b'\xE2\x95\xA4' # U+2564 ╤
80 80 return node
81 81
82 82
@@ -87,21 +87,21 b' def outputprettygraph(orig, ui, graph, *'
87 87
88 88
89 89 def extsetup(ui):
90 if ui.plain('graph'):
90 if ui.plain(b'graph'):
91 91 return
92 92
93 if encoding.encoding != 'UTF-8':
94 ui.warn(_('beautifygraph: unsupported encoding, UTF-8 required\n'))
93 if encoding.encoding != b'UTF-8':
94 ui.warn(_(b'beautifygraph: unsupported encoding, UTF-8 required\n'))
95 95 return
96 96
97 97 if r'A' in encoding._wide:
98 98 ui.warn(
99 99 _(
100 'beautifygraph: unsupported terminal settings, '
101 'monospace narrow text required\n'
100 b'beautifygraph: unsupported terminal settings, '
101 b'monospace narrow text required\n'
102 102 )
103 103 )
104 104 return
105 105
106 extensions.wrapfunction(graphmod, 'outputgraph', outputprettygraph)
107 extensions.wrapfunction(templatekw, 'getgraphnode', getprettygraphnode)
106 extensions.wrapfunction(graphmod, b'outputgraph', outputprettygraph)
107 extensions.wrapfunction(templatekw, b'getgraphnode', getprettygraphnode)
@@ -63,7 +63,7 b' from mercurial.utils import ('
63 63 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
64 64 # be specifying the version(s) of Mercurial they are tested with, or
65 65 # leave the attribute unspecified.
66 testedwith = 'ships-with-hg-core'
66 testedwith = b'ships-with-hg-core'
67 67
68 68 cmdtable = {}
69 69 command = registrar.command(cmdtable)
@@ -72,27 +72,27 b' configtable = {}'
72 72 configitem = registrar.configitem(configtable)
73 73
74 74 configitem(
75 'blackbox', 'dirty', default=False,
75 b'blackbox', b'dirty', default=False,
76 76 )
77 77 configitem(
78 'blackbox', 'maxsize', default='1 MB',
78 b'blackbox', b'maxsize', default=b'1 MB',
79 79 )
80 80 configitem(
81 'blackbox', 'logsource', default=False,
81 b'blackbox', b'logsource', default=False,
82 82 )
83 83 configitem(
84 'blackbox', 'maxfiles', default=7,
84 b'blackbox', b'maxfiles', default=7,
85 85 )
86 86 configitem(
87 'blackbox', 'track', default=lambda: ['*'],
87 b'blackbox', b'track', default=lambda: [b'*'],
88 88 )
89 89 configitem(
90 'blackbox',
91 'ignore',
92 default=lambda: ['chgserver', 'cmdserver', 'extension'],
90 b'blackbox',
91 b'ignore',
92 default=lambda: [b'chgserver', b'cmdserver', b'extension'],
93 93 )
94 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 98 _lastlogger = loggingutil.proxylogger()
@@ -101,10 +101,10 b' configitem('
101 101 class blackboxlogger(object):
102 102 def __init__(self, ui, repo):
103 103 self._repo = repo
104 self._trackedevents = set(ui.configlist('blackbox', 'track'))
105 self._ignoredevents = set(ui.configlist('blackbox', 'ignore'))
106 self._maxfiles = ui.configint('blackbox', 'maxfiles')
107 self._maxsize = ui.configbytes('blackbox', 'maxsize')
104 self._trackedevents = set(ui.configlist(b'blackbox', b'track'))
105 self._ignoredevents = set(ui.configlist(b'blackbox', b'ignore'))
106 self._maxfiles = ui.configint(b'blackbox', b'maxfiles')
107 self._maxsize = ui.configbytes(b'blackbox', b'maxsize')
108 108 self._inlog = False
109 109
110 110 def tracked(self, event):
@@ -125,29 +125,29 b' class blackboxlogger(object):'
125 125 self._inlog = False
126 126
127 127 def _log(self, ui, event, msg, opts):
128 default = ui.configdate('devel', 'default-date')
129 date = dateutil.datestr(default, ui.config('blackbox', 'date-format'))
128 default = ui.configdate(b'devel', b'default-date')
129 date = dateutil.datestr(default, ui.config(b'blackbox', b'date-format'))
130 130 user = procutil.getuser()
131 pid = '%d' % procutil.getpid()
132 changed = ''
131 pid = b'%d' % procutil.getpid()
132 changed = b''
133 133 ctx = self._repo[None]
134 134 parents = ctx.parents()
135 rev = '+'.join([hex(p.node()) for p in parents])
136 if ui.configbool('blackbox', 'dirty') and ctx.dirty(
135 rev = b'+'.join([hex(p.node()) for p in parents])
136 if ui.configbool(b'blackbox', b'dirty') and ctx.dirty(
137 137 missing=True, merge=False, branch=False
138 138 ):
139 changed = '+'
140 if ui.configbool('blackbox', 'logsource'):
141 src = ' [%s]' % event
139 changed = b'+'
140 if ui.configbool(b'blackbox', b'logsource'):
141 src = b' [%s]' % event
142 142 else:
143 src = ''
143 src = b''
144 144 try:
145 fmt = '%s %s @%s%s (%s)%s> %s'
145 fmt = b'%s %s @%s%s (%s)%s> %s'
146 146 args = (date, user, rev, changed, pid, src, msg)
147 147 with loggingutil.openlogfile(
148 148 ui,
149 149 self._repo.vfs,
150 name='blackbox.log',
150 name=b'blackbox.log',
151 151 maxfiles=self._maxfiles,
152 152 maxsize=self._maxsize,
153 153 ) as fp:
@@ -156,7 +156,7 b' class blackboxlogger(object):'
156 156 # deactivate this to avoid failed logging again
157 157 self._trackedevents.clear()
158 158 ui.debug(
159 'warning: cannot write to blackbox.log: %s\n'
159 b'warning: cannot write to blackbox.log: %s\n'
160 160 % encoding.strtolocal(err.strerror)
161 161 )
162 162 return
@@ -184,13 +184,13 b' def reposetup(ui, repo):'
184 184 if _lastlogger.logger is None:
185 185 _lastlogger.logger = logger
186 186
187 repo._wlockfreeprefix.add('blackbox.log')
187 repo._wlockfreeprefix.add(b'blackbox.log')
188 188
189 189
190 190 @command(
191 'blackbox',
192 [('l', 'limit', 10, _('the number of events to show')),],
193 _('hg blackbox [OPTION]...'),
191 b'blackbox',
192 [(b'l', b'limit', 10, _(b'the number of events to show')),],
193 _(b'hg blackbox [OPTION]...'),
194 194 helpcategory=command.CATEGORY_MAINTENANCE,
195 195 helpbasic=True,
196 196 )
@@ -198,12 +198,12 b' def blackbox(ui, repo, *revs, **opts):'
198 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 202 return
203 203
204 204 limit = opts.get(r'limit')
205 fp = repo.vfs('blackbox.log', 'r')
206 lines = fp.read().split('\n')
205 fp = repo.vfs(b'blackbox.log', b'r')
206 lines = fp.read().split(b'\n')
207 207
208 208 count = 0
209 209 output = []
@@ -216,4 +216,4 b' def blackbox(ui, repo, *revs, **opts):'
216 216 count += 1
217 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 24 registrar,
25 25 )
26 26
27 MY_NAME = 'bookflow'
27 MY_NAME = b'bookflow'
28 28
29 29 configtable = {}
30 30 configitem = registrar.configitem(configtable)
31 31
32 configitem(MY_NAME, 'protect', ['@'])
33 configitem(MY_NAME, 'require-bookmark', True)
34 configitem(MY_NAME, 'enable-branches', False)
32 configitem(MY_NAME, b'protect', [b'@'])
33 configitem(MY_NAME, b'require-bookmark', True)
34 configitem(MY_NAME, b'enable-branches', False)
35 35
36 36 cmdtable = {}
37 37 command = registrar.command(cmdtable)
@@ -40,19 +40,19 b' command = registrar.command(cmdtable)'
40 40 def commit_hook(ui, repo, **kwargs):
41 41 active = repo._bookmarks.active
42 42 if active:
43 if active in ui.configlist(MY_NAME, 'protect'):
43 if active in ui.configlist(MY_NAME, b'protect'):
44 44 raise error.Abort(
45 _('cannot commit, bookmark %s is protected') % active
45 _(b'cannot commit, bookmark %s is protected') % active
46 46 )
47 47 if not cwd_at_bookmark(repo, active):
48 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):
55 raise error.Abort(_('cannot commit without an active bookmark'))
54 elif ui.configbool(MY_NAME, b'require-bookmark', True):
55 raise error.Abort(_(b'cannot commit without an active bookmark'))
56 56 return 0
57 57
58 58
@@ -74,7 +74,7 b' def bookmarks_addbookmarks('
74 74 if name in marks:
75 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 79 % name
80 80 )
@@ -92,8 +92,8 b' def commands_pull(orig, ui, repo, *args,'
92 92 if active and not cwd_at_bookmark(repo, active):
93 93 ui.warn(
94 94 _(
95 "working directory out of sync with active bookmark, run "
96 "'hg up %s'"
95 b"working directory out of sync with active bookmark, run "
96 b"'hg up %s'"
97 97 )
98 98 % active
99 99 )
@@ -104,23 +104,23 b' def commands_branch(orig, ui, repo, labe'
104 104 if label and not opts.get(r'clean') and not opts.get(r'rev'):
105 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 111 return orig(ui, repo, label, **opts)
112 112
113 113
114 114 def cwd_at_bookmark(repo, mark):
115 115 mark_id = repo._bookmarks[mark]
116 cur_id = repo.lookup('.')
116 cur_id = repo.lookup(b'.')
117 117 return cur_id == mark_id
118 118
119 119
120 120 def uisetup(ui):
121 extensions.wrapfunction(bookmarks, 'update', bookmarks_update)
122 extensions.wrapfunction(bookmarks, 'addbookmarks', bookmarks_addbookmarks)
123 extensions.wrapcommand(commands.table, 'commit', commands_commit)
124 extensions.wrapcommand(commands.table, 'pull', commands_pull)
125 if not ui.configbool(MY_NAME, 'enable-branches'):
126 extensions.wrapcommand(commands.table, 'branch', commands_branch)
121 extensions.wrapfunction(bookmarks, b'update', bookmarks_update)
122 extensions.wrapfunction(bookmarks, b'addbookmarks', bookmarks_addbookmarks)
123 extensions.wrapcommand(commands.table, b'commit', commands_commit)
124 extensions.wrapcommand(commands.table, b'pull', commands_pull)
125 if not ui.configbool(MY_NAME, b'enable-branches'):
126 extensions.wrapcommand(commands.table, b'branch', commands_branch)
@@ -319,32 +319,32 b' xmlrpclib = util.xmlrpclib'
319 319 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
320 320 # be specifying the version(s) of Mercurial they are tested with, or
321 321 # leave the attribute unspecified.
322 testedwith = 'ships-with-hg-core'
322 testedwith = b'ships-with-hg-core'
323 323
324 324 configtable = {}
325 325 configitem = registrar.configitem(configtable)
326 326
327 327 configitem(
328 'bugzilla', 'apikey', default='',
328 b'bugzilla', b'apikey', default=b'',
329 329 )
330 330 configitem(
331 'bugzilla', 'bzdir', default='/var/www/html/bugzilla',
331 b'bugzilla', b'bzdir', default=b'/var/www/html/bugzilla',
332 332 )
333 333 configitem(
334 'bugzilla', 'bzemail', default=None,
334 b'bugzilla', b'bzemail', default=None,
335 335 )
336 336 configitem(
337 'bugzilla', 'bzurl', default='http://localhost/bugzilla/',
337 b'bugzilla', b'bzurl', default=b'http://localhost/bugzilla/',
338 338 )
339 339 configitem(
340 'bugzilla', 'bzuser', default=None,
340 b'bugzilla', b'bzuser', default=None,
341 341 )
342 342 configitem(
343 'bugzilla', 'db', default='bugs',
343 b'bugzilla', b'db', default=b'bugs',
344 344 )
345 345 configitem(
346 'bugzilla',
347 'fixregexp',
346 b'bugzilla',
347 b'fixregexp',
348 348 default=(
349 349 br'fix(?:es)?\s*(?:bugs?\s*)?,?\s*'
350 350 br'(?:nos?\.?|num(?:ber)?s?)?\s*'
@@ -353,23 +353,23 b' configitem('
353 353 ),
354 354 )
355 355 configitem(
356 'bugzilla', 'fixresolution', default='FIXED',
356 b'bugzilla', b'fixresolution', default=b'FIXED',
357 357 )
358 358 configitem(
359 'bugzilla', 'fixstatus', default='RESOLVED',
359 b'bugzilla', b'fixstatus', default=b'RESOLVED',
360 360 )
361 361 configitem(
362 'bugzilla', 'host', default='localhost',
362 b'bugzilla', b'host', default=b'localhost',
363 363 )
364 364 configitem(
365 'bugzilla', 'notify', default=configitem.dynamicdefault,
365 b'bugzilla', b'notify', default=configitem.dynamicdefault,
366 366 )
367 367 configitem(
368 'bugzilla', 'password', default=None,
368 b'bugzilla', b'password', default=None,
369 369 )
370 370 configitem(
371 'bugzilla',
372 'regexp',
371 b'bugzilla',
372 b'regexp',
373 373 default=(
374 374 br'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
375 375 br'(?P<ids>(?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)'
@@ -377,25 +377,25 b' configitem('
377 377 ),
378 378 )
379 379 configitem(
380 'bugzilla', 'strip', default=0,
380 b'bugzilla', b'strip', default=0,
381 381 )
382 382 configitem(
383 'bugzilla', 'style', default=None,
383 b'bugzilla', b'style', default=None,
384 384 )
385 385 configitem(
386 'bugzilla', 'template', default=None,
386 b'bugzilla', b'template', default=None,
387 387 )
388 388 configitem(
389 'bugzilla', 'timeout', default=5,
389 b'bugzilla', b'timeout', default=5,
390 390 )
391 391 configitem(
392 'bugzilla', 'user', default='bugs',
392 b'bugzilla', b'user', default=b'bugs',
393 393 )
394 394 configitem(
395 'bugzilla', 'usermap', default=None,
395 b'bugzilla', b'usermap', default=None,
396 396 )
397 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 405 def __init__(self, ui):
406 406 self.ui = ui
407 usermap = self.ui.config('bugzilla', 'usermap')
407 usermap = self.ui.config(b'bugzilla', b'usermap')
408 408 if usermap:
409 self.ui.readconfig(usermap, sections=['usermap'])
409 self.ui.readconfig(usermap, sections=[b'usermap'])
410 410
411 411 def map_committer(self, user):
412 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 414 if committer.lower() == user.lower():
415 415 return bzuser
416 416 return user
@@ -457,7 +457,7 b' class bzmysql(bzaccess):'
457 457 @staticmethod
458 458 def sql_buglist(ids):
459 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 462 _MySQLdb = None
463 463
@@ -467,18 +467,20 b' class bzmysql(bzaccess):'
467 467
468 468 bzmysql._MySQLdb = mysql
469 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 474 bzaccess.__init__(self, ui)
473 475
474 host = self.ui.config('bugzilla', 'host')
475 user = self.ui.config('bugzilla', 'user')
476 passwd = self.ui.config('bugzilla', 'password')
477 db = self.ui.config('bugzilla', 'db')
478 timeout = int(self.ui.config('bugzilla', 'timeout'))
476 host = self.ui.config(b'bugzilla', b'host')
477 user = self.ui.config(b'bugzilla', b'user')
478 passwd = self.ui.config(b'bugzilla', b'password')
479 db = self.ui.config(b'bugzilla', b'db')
480 timeout = int(self.ui.config(b'bugzilla', b'timeout'))
479 481 self.ui.note(
480 _('connecting to %s:%s as %s, password %s\n')
481 % (host, db, user, '*' * len(passwd))
482 _(b'connecting to %s:%s as %s, password %s\n')
483 % (host, db, user, b'*' * len(passwd))
482 484 )
483 485 self.conn = bzmysql._MySQLdb.connect(
484 486 host=host, user=user, passwd=passwd, db=db, connect_timeout=timeout
@@ -486,35 +488,35 b' class bzmysql(bzaccess):'
486 488 self.cursor = self.conn.cursor()
487 489 self.longdesc_id = self.get_longdesc_id()
488 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 493 def run(self, *args, **kwargs):
492 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 496 try:
495 497 self.cursor.execute(*args, **kwargs)
496 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 500 raise
499 501
500 502 def get_longdesc_id(self):
501 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 505 ids = self.cursor.fetchall()
504 506 if len(ids) != 1:
505 raise error.Abort(_('unknown database schema'))
507 raise error.Abort(_(b'unknown database schema'))
506 508 return ids[0][0]
507 509
508 510 def filter_real_bug_ids(self, bugs):
509 511 '''filter not-existing bugs from set.'''
510 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 514 % bzmysql.sql_buglist(bugs.keys())
513 515 )
514 516 existing = [id for (id,) in self.cursor.fetchall()]
515 517 for id in bugs.keys():
516 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 520 del bugs[id]
519 521
520 522 def filter_cset_known_bug_ids(self, node, bugs):
@@ -526,36 +528,36 b' class bzmysql(bzaccess):'
526 528 )
527 529 for (id,) in self.cursor.fetchall():
528 530 self.ui.status(
529 _('bug %d already knows about changeset %s\n')
531 _(b'bug %d already knows about changeset %s\n')
530 532 % (id, short(node))
531 533 )
532 534 del bugs[id]
533 535
534 536 def notify(self, bugs, committer):
535 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 539 (user, userid) = self.get_bugzilla_user(committer)
538 540 for id in bugs.keys():
539 self.ui.status(_(' bug %s\n') % id)
540 cmdfmt = self.ui.config('bugzilla', 'notify', self.default_notify)
541 bzdir = self.ui.config('bugzilla', 'bzdir')
541 self.ui.status(_(b' bug %s\n') % id)
542 cmdfmt = self.ui.config(b'bugzilla', b'notify', self.default_notify)
543 bzdir = self.ui.config(b'bugzilla', b'bzdir')
542 544 try:
543 545 # Backwards-compatible with old notify string, which
544 546 # took one string. This will throw with a new format
545 547 # string.
546 548 cmd = cmdfmt % id
547 549 except TypeError:
548 cmd = cmdfmt % {'bzdir': bzdir, 'id': id, 'user': user}
549 self.ui.note(_('running notify command %s\n') % cmd)
550 fp = procutil.popen('(%s) 2>&1' % cmd, 'rb')
550 cmd = cmdfmt % {b'bzdir': bzdir, b'id': id, b'user': user}
551 self.ui.note(_(b'running notify command %s\n') % cmd)
552 fp = procutil.popen(b'(%s) 2>&1' % cmd, b'rb')
551 553 out = util.fromnativeeol(fp.read())
552 554 ret = fp.close()
553 555 if ret:
554 556 self.ui.warn(out)
555 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 562 def get_user_id(self, user):
561 563 '''look up numeric bugzilla user id.'''
@@ -565,7 +567,7 b' class bzmysql(bzaccess):'
565 567 try:
566 568 userid = int(user)
567 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 571 self.run(
570 572 '''select userid from profiles
571 573 where login_name like %s''',
@@ -587,16 +589,16 b' class bzmysql(bzaccess):'
587 589 userid = self.get_user_id(user)
588 590 except KeyError:
589 591 try:
590 defaultuser = self.ui.config('bugzilla', 'bzuser')
592 defaultuser = self.ui.config(b'bugzilla', b'bzuser')
591 593 if not defaultuser:
592 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 597 userid = self.get_user_id(defaultuser)
596 598 user = defaultuser
597 599 except KeyError:
598 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 602 % (user, defaultuser)
601 603 )
602 604 return (user, userid)
@@ -607,7 +609,7 b' class bzmysql(bzaccess):'
607 609 Try adding comment as committer of changeset, otherwise as
608 610 default bugzilla user.'''
609 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 614 (user, userid) = self.get_bugzilla_user(committer)
613 615 now = time.strftime(r'%Y-%m-%d %H:%M:%S')
@@ -631,7 +633,7 b' class bzmysql_2_18(bzmysql):'
631 633 def __init__(self, ui):
632 634 bzmysql.__init__(self, ui)
633 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 646 def get_longdesc_id(self):
645 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 649 ids = self.cursor.fetchall()
648 650 if len(ids) != 1:
649 raise error.Abort(_('unknown database schema'))
651 raise error.Abort(_(b'unknown database schema'))
650 652 return ids[0][0]
651 653
652 654
@@ -674,7 +676,7 b' class cookietransportrequest(object):'
674 676 def send_cookies(self, connection):
675 677 if self.cookies:
676 678 for cookie in self.cookies:
677 connection.putheader("Cookie", cookie)
679 connection.putheader(b"Cookie", cookie)
678 680
679 681 def request(self, host, handler, request_body, verbose=0):
680 682 self.verbose = verbose
@@ -702,9 +704,9 b' class cookietransportrequest(object):'
702 704 response = h._conn.getresponse()
703 705
704 706 # Add any cookie definitions to our list.
705 for header in response.msg.getallmatchingheaders("Set-Cookie"):
706 val = header.split(": ", 1)[1]
707 cookie = val.split(";", 1)[0]
707 for header in response.msg.getallmatchingheaders(b"Set-Cookie"):
708 val = header.split(b": ", 1)[1]
709 cookie = val.split(b";", 1)[0]
708 710 self.cookies.append(cookie)
709 711
710 712 if response.status != 200:
@@ -729,13 +731,13 b' class cookietransportrequest(object):'
729 731 # inheritance with a new-style class.
730 732 class cookietransport(cookietransportrequest, xmlrpclib.Transport):
731 733 def __init__(self, use_datetime=0):
732 if util.safehasattr(xmlrpclib.Transport, "__init__"):
734 if util.safehasattr(xmlrpclib.Transport, b"__init__"):
733 735 xmlrpclib.Transport.__init__(self, use_datetime)
734 736
735 737
736 738 class cookiesafetransport(cookietransportrequest, xmlrpclib.SafeTransport):
737 739 def __init__(self, use_datetime=0):
738 if util.safehasattr(xmlrpclib.Transport, "__init__"):
740 if util.safehasattr(xmlrpclib.Transport, b"__init__"):
739 741 xmlrpclib.SafeTransport.__init__(self, use_datetime)
740 742
741 743
@@ -748,26 +750,26 b' class bzxmlrpc(bzaccess):'
748 750 def __init__(self, ui):
749 751 bzaccess.__init__(self, ui)
750 752
751 bzweb = self.ui.config('bugzilla', 'bzurl')
752 bzweb = bzweb.rstrip("/") + "/xmlrpc.cgi"
753 bzweb = self.ui.config(b'bugzilla', b'bzurl')
754 bzweb = bzweb.rstrip(b"/") + b"/xmlrpc.cgi"
753 755
754 user = self.ui.config('bugzilla', 'user')
755 passwd = self.ui.config('bugzilla', 'password')
756 user = self.ui.config(b'bugzilla', b'user')
757 passwd = self.ui.config(b'bugzilla', b'password')
756 758
757 self.fixstatus = self.ui.config('bugzilla', 'fixstatus')
758 self.fixresolution = self.ui.config('bugzilla', 'fixresolution')
759 self.fixstatus = self.ui.config(b'bugzilla', b'fixstatus')
760 self.fixresolution = self.ui.config(b'bugzilla', b'fixresolution')
759 761
760 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 764 self.bzvermajor = int(ver[0])
763 765 self.bzverminor = int(ver[1])
764 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 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 773 return cookiesafetransport()
772 774 else:
773 775 return cookietransport()
@@ -775,56 +777,58 b' class bzxmlrpc(bzaccess):'
775 777 def get_bug_comments(self, id):
776 778 """Return a string with all comment text for a bug."""
777 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 786 def filter_real_bug_ids(self, bugs):
783 787 probe = self.bzproxy.Bug.get(
784 788 {
785 'ids': sorted(bugs.keys()),
786 'include_fields': [],
787 'permissive': True,
788 'token': self.bztoken,
789 b'ids': sorted(bugs.keys()),
790 b'include_fields': [],
791 b'permissive': True,
792 b'token': self.bztoken,
789 793 }
790 794 )
791 for badbug in probe['faults']:
792 id = badbug['id']
793 self.ui.status(_('bug %d does not exist\n') % id)
795 for badbug in probe[b'faults']:
796 id = badbug[b'id']
797 self.ui.status(_(b'bug %d does not exist\n') % id)
794 798 del bugs[id]
795 799
796 800 def filter_cset_known_bug_ids(self, node, bugs):
797 801 for id in sorted(bugs.keys()):
798 802 if self.get_bug_comments(id).find(short(node)) != -1:
799 803 self.ui.status(
800 _('bug %d already knows about changeset %s\n')
804 _(b'bug %d already knows about changeset %s\n')
801 805 % (id, short(node))
802 806 )
803 807 del bugs[id]
804 808
805 809 def updatebug(self, bugid, newstate, text, committer):
806 810 args = {}
807 if 'hours' in newstate:
808 args['work_time'] = newstate['hours']
811 if b'hours' in newstate:
812 args[b'work_time'] = newstate[b'hours']
809 813
810 814 if self.bzvermajor >= 4:
811 args['ids'] = [bugid]
812 args['comment'] = {'body': text}
813 if 'fix' in newstate:
814 args['status'] = self.fixstatus
815 args['resolution'] = self.fixresolution
816 args['token'] = self.bztoken
815 args[b'ids'] = [bugid]
816 args[b'comment'] = {b'body': text}
817 if b'fix' in newstate:
818 args[b'status'] = self.fixstatus
819 args[b'resolution'] = self.fixresolution
820 args[b'token'] = self.bztoken
817 821 self.bzproxy.Bug.update(args)
818 822 else:
819 if 'fix' in newstate:
823 if b'fix' in newstate:
820 824 self.ui.warn(
821 825 _(
822 "Bugzilla/XMLRPC needs Bugzilla 4.0 or later "
823 "to mark bugs fixed\n"
826 b"Bugzilla/XMLRPC needs Bugzilla 4.0 or later "
827 b"to mark bugs fixed\n"
824 828 )
825 829 )
826 args['id'] = bugid
827 args['comment'] = text
830 args[b'id'] = bugid
831 args[b'comment'] = text
828 832 self.bzproxy.Bug.add_comment(args)
829 833
830 834
@@ -851,18 +855,18 b' class bzxmlrpcemail(bzxmlrpc):'
851 855 def __init__(self, ui):
852 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 859 if not self.bzemail:
856 raise error.Abort(_("configuration 'bzemail' missing"))
860 raise error.Abort(_(b"configuration 'bzemail' missing"))
857 861 mail.validateconfig(self.ui)
858 862
859 863 def makecommandline(self, fieldname, value):
860 864 if self.bzvermajor >= 4:
861 return "@%s %s" % (fieldname, pycompat.bytestr(value))
865 return b"@%s %s" % (fieldname, pycompat.bytestr(value))
862 866 else:
863 if fieldname == "id":
864 fieldname = "bug_id"
865 return "@%s = %s" % (fieldname, pycompat.bytestr(value))
867 if fieldname == b"id":
868 fieldname = b"bug_id"
869 return b"@%s = %s" % (fieldname, pycompat.bytestr(value))
866 870
867 871 def send_bug_modify_email(self, bugid, commands, comment, committer):
868 872 '''send modification message to Bugzilla bug via email.
@@ -877,39 +881,41 b' class bzxmlrpcemail(bzxmlrpc):'
877 881 '''
878 882 user = self.map_committer(committer)
879 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']:
883 user = self.ui.config('bugzilla', 'user')
886 if not matches[b'users']:
887 user = self.ui.config(b'bugzilla', b'user')
884 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 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']
892 commands.append(self.makecommandline("id", bugid))
895 user = matches[b'users'][0][b'email']
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 900 _charsets = mail._charsets(self.ui)
897 901 user = mail.addressencode(self.ui, user, _charsets)
898 902 bzemail = mail.addressencode(self.ui, self.bzemail, _charsets)
899 903 msg = mail.mimeencode(self.ui, text, _charsets)
900 msg['From'] = user
901 msg['To'] = bzemail
902 msg['Subject'] = mail.headencode(self.ui, "Bug modification", _charsets)
904 msg[b'From'] = user
905 msg[b'To'] = bzemail
906 msg[b'Subject'] = mail.headencode(
907 self.ui, b"Bug modification", _charsets
908 )
903 909 sendmail = mail.connect(self.ui)
904 910 sendmail(user, bzemail, msg.as_string())
905 911
906 912 def updatebug(self, bugid, newstate, text, committer):
907 913 cmds = []
908 if 'hours' in newstate:
909 cmds.append(self.makecommandline("work_time", newstate['hours']))
910 if 'fix' in newstate:
911 cmds.append(self.makecommandline("bug_status", self.fixstatus))
912 cmds.append(self.makecommandline("resolution", self.fixresolution))
914 if b'hours' in newstate:
915 cmds.append(self.makecommandline(b"work_time", newstate[b'hours']))
916 if b'fix' in newstate:
917 cmds.append(self.makecommandline(b"bug_status", self.fixstatus))
918 cmds.append(self.makecommandline(b"resolution", self.fixresolution))
913 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 931 def __init__(self, ui):
926 932 bzaccess.__init__(self, ui)
927 bz = self.ui.config('bugzilla', 'bzurl')
928 self.bzroot = '/'.join([bz, 'rest'])
929 self.apikey = self.ui.config('bugzilla', 'apikey')
930 self.user = self.ui.config('bugzilla', 'user')
931 self.passwd = self.ui.config('bugzilla', 'password')
932 self.fixstatus = self.ui.config('bugzilla', 'fixstatus')
933 self.fixresolution = self.ui.config('bugzilla', 'fixresolution')
933 bz = self.ui.config(b'bugzilla', b'bzurl')
934 self.bzroot = b'/'.join([bz, b'rest'])
935 self.apikey = self.ui.config(b'bugzilla', b'apikey')
936 self.user = self.ui.config(b'bugzilla', b'user')
937 self.passwd = self.ui.config(b'bugzilla', b'password')
938 self.fixstatus = self.ui.config(b'bugzilla', b'fixstatus')
939 self.fixresolution = self.ui.config(b'bugzilla', b'fixresolution')
934 940
935 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 943 qv = {}
938 944 if self.apikey:
939 qv['api_key'] = self.apikey
945 qv[b'api_key'] = self.apikey
940 946 elif self.user and self.passwd:
941 qv['login'] = self.user
942 qv['password'] = self.passwd
947 qv[b'login'] = self.user
948 qv[b'password'] = self.passwd
943 949 if include_fields:
944 qv['include_fields'] = include_fields
950 qv[b'include_fields'] = include_fields
945 951 if qv:
946 url = '%s?%s' % (url, util.urlreq.urlencode(qv))
952 url = b'%s?%s' % (url, util.urlreq.urlencode(qv))
947 953 return url
948 954
949 955 def _fetch(self, burl):
@@ -952,30 +958,30 b' class bzrestapi(bzaccess):'
952 958 return json.loads(resp.read())
953 959 except util.urlerr.httperror as inst:
954 960 if inst.code == 401:
955 raise error.Abort(_('authorization failed'))
961 raise error.Abort(_(b'authorization failed'))
956 962 if inst.code == 404:
957 963 raise NotFound()
958 964 else:
959 965 raise
960 966
961 def _submit(self, burl, data, method='POST'):
967 def _submit(self, burl, data, method=b'POST'):
962 968 data = json.dumps(data)
963 if method == 'PUT':
969 if method == b'PUT':
964 970
965 971 class putrequest(util.urlreq.request):
966 972 def get_method(self):
967 return 'PUT'
973 return b'PUT'
968 974
969 975 request_type = putrequest
970 976 else:
971 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 979 try:
974 980 resp = url.opener(self.ui).open(req)
975 981 return json.loads(resp.read())
976 982 except util.urlerr.httperror as inst:
977 983 if inst.code == 401:
978 raise error.Abort(_('authorization failed'))
984 raise error.Abort(_(b'authorization failed'))
979 985 if inst.code == 404:
980 986 raise NotFound()
981 987 else:
@@ -985,7 +991,7 b' class bzrestapi(bzaccess):'
985 991 '''remove bug IDs that do not exist in Bugzilla from bugs.'''
986 992 badbugs = set()
987 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 995 try:
990 996 self._fetch(burl)
991 997 except NotFound:
@@ -997,12 +1003,15 b' class bzrestapi(bzaccess):'
997 1003 '''remove bug IDs where node occurs in comment text from bugs.'''
998 1004 sn = short(node)
999 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 1009 result = self._fetch(burl)
1002 comments = result['bugs'][pycompat.bytestr(bugid)]['comments']
1003 if any(sn in c['text'] for c in comments):
1010 comments = result[b'bugs'][pycompat.bytestr(bugid)][b'comments']
1011 if any(sn in c[b'text'] for c in comments):
1004 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 1016 del bugs[bugid]
1008 1017
@@ -1013,28 +1022,32 b' class bzrestapi(bzaccess):'
1013 1022 the changeset. Otherwise use the default Bugzilla user.
1014 1023 '''
1015 1024 bugmod = {}
1016 if 'hours' in newstate:
1017 bugmod['work_time'] = newstate['hours']
1018 if 'fix' in newstate:
1019 bugmod['status'] = self.fixstatus
1020 bugmod['resolution'] = self.fixresolution
1025 if b'hours' in newstate:
1026 bugmod[b'work_time'] = newstate[b'hours']
1027 if b'fix' in newstate:
1028 bugmod[b'status'] = self.fixstatus
1029 bugmod[b'resolution'] = self.fixresolution
1021 1030 if bugmod:
1022 1031 # if we have to change the bugs state do it here
1023 bugmod['comment'] = {
1024 'comment': text,
1025 'is_private': False,
1026 'is_markdown': False,
1032 bugmod[b'comment'] = {
1033 b'comment': text,
1034 b'is_private': False,
1035 b'is_markdown': False,
1027 1036 }
1028 burl = self.apiurl(('bug', bugid))
1029 self._submit(burl, bugmod, method='PUT')
1030 self.ui.debug('updated bug %s\n' % bugid)
1037 burl = self.apiurl((b'bug', bugid))
1038 self._submit(burl, bugmod, method=b'PUT')
1039 self.ui.debug(b'updated bug %s\n' % bugid)
1031 1040 else:
1032 burl = self.apiurl(('bug', bugid, 'comment'))
1041 burl = self.apiurl((b'bug', bugid, b'comment'))
1033 1042 self._submit(
1034 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 1052 def notify(self, bugs, committer):
1040 1053 '''Force sending of Bugzilla notification emails.
@@ -1049,32 +1062,32 b' class bugzilla(object):'
1049 1062 # supported versions of bugzilla. different versions have
1050 1063 # different schemas.
1051 1064 _versions = {
1052 '2.16': bzmysql,
1053 '2.18': bzmysql_2_18,
1054 '3.0': bzmysql_3_0,
1055 'xmlrpc': bzxmlrpc,
1056 'xmlrpc+email': bzxmlrpcemail,
1057 'restapi': bzrestapi,
1065 b'2.16': bzmysql,
1066 b'2.18': bzmysql_2_18,
1067 b'3.0': bzmysql_3_0,
1068 b'xmlrpc': bzxmlrpc,
1069 b'xmlrpc+email': bzxmlrpcemail,
1070 b'restapi': bzrestapi,
1058 1071 }
1059 1072
1060 1073 def __init__(self, ui, repo):
1061 1074 self.ui = ui
1062 1075 self.repo = repo
1063 1076
1064 bzversion = self.ui.config('bugzilla', 'version')
1077 bzversion = self.ui.config(b'bugzilla', b'version')
1065 1078 try:
1066 1079 bzclass = bugzilla._versions[bzversion]
1067 1080 except KeyError:
1068 1081 raise error.Abort(
1069 _('bugzilla version %s not supported') % bzversion
1082 _(b'bugzilla version %s not supported') % bzversion
1070 1083 )
1071 1084 self.bzdriver = bzclass(self.ui)
1072 1085
1073 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 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 1092 self.split_re = re.compile(br'\D+')
1080 1093
@@ -1106,25 +1119,25 b' class bugzilla(object):'
1106 1119 start = m.end()
1107 1120 if m is bugmatch:
1108 1121 bugmatch = self.bug_re.search(ctx.description(), start)
1109 if 'fix' in bugattribs:
1110 del bugattribs['fix']
1122 if b'fix' in bugattribs:
1123 del bugattribs[b'fix']
1111 1124 else:
1112 1125 fixmatch = self.fix_re.search(ctx.description(), start)
1113 bugattribs['fix'] = None
1126 bugattribs[b'fix'] = None
1114 1127
1115 1128 try:
1116 ids = m.group('ids')
1129 ids = m.group(b'ids')
1117 1130 except IndexError:
1118 1131 ids = m.group(1)
1119 1132 try:
1120 hours = float(m.group('hours'))
1121 bugattribs['hours'] = hours
1133 hours = float(m.group(b'hours'))
1134 bugattribs[b'hours'] = hours
1122 1135 except IndexError:
1123 1136 pass
1124 1137 except TypeError:
1125 1138 pass
1126 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 1142 for id in self.split_re.split(ids):
1130 1143 if not id:
@@ -1142,10 +1155,10 b' class bugzilla(object):'
1142 1155 def webroot(root):
1143 1156 '''strip leading prefix of repo root and turn into
1144 1157 url-safe path.'''
1145 count = int(self.ui.config('bugzilla', 'strip'))
1158 count = int(self.ui.config(b'bugzilla', b'strip'))
1146 1159 root = util.pconvert(root)
1147 1160 while count > 0:
1148 c = root.find('/')
1161 c = root.find(b'/')
1149 1162 if c == -1:
1150 1163 break
1151 1164 root = root[c + 1 :]
@@ -1153,13 +1166,13 b' class bugzilla(object):'
1153 1166 return root
1154 1167
1155 1168 mapfile = None
1156 tmpl = self.ui.config('bugzilla', 'template')
1169 tmpl = self.ui.config(b'bugzilla', b'template')
1157 1170 if not tmpl:
1158 mapfile = self.ui.config('bugzilla', 'style')
1171 mapfile = self.ui.config(b'bugzilla', b'style')
1159 1172 if not mapfile and not tmpl:
1160 1173 tmpl = _(
1161 'changeset {node|short} in repo {root} refers '
1162 'to bug {bug}.\ndetails:\n\t{desc|tabindent}'
1174 b'changeset {node|short} in repo {root} refers '
1175 b'to bug {bug}.\ndetails:\n\t{desc|tabindent}'
1163 1176 )
1164 1177 spec = logcmdutil.templatespec(tmpl, mapfile)
1165 1178 t = logcmdutil.changesettemplater(self.ui, self.repo, spec)
@@ -1168,7 +1181,7 b' class bugzilla(object):'
1168 1181 ctx,
1169 1182 changes=ctx.changeset(),
1170 1183 bug=pycompat.bytestr(bugid),
1171 hgweb=self.ui.config('web', 'baseurl'),
1184 hgweb=self.ui.config(b'web', b'baseurl'),
1172 1185 root=self.repo.root,
1173 1186 webroot=webroot(self.repo.root),
1174 1187 )
@@ -1188,7 +1201,7 b' def hook(ui, repo, hooktype, node=None, '
1188 1201 seen multiple times does not fill bug with duplicate data.'''
1189 1202 if node is None:
1190 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 1206 try:
1194 1207 bz = bugzilla(ui, repo)
@@ -1199,4 +1212,4 b' def hook(ui, repo, hooktype, node=None, '
1199 1212 bz.update(bug, bugs[bug], ctx)
1200 1213 bz.notify(bugs, stringutil.email(ctx.user()))
1201 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 42 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
43 43 # be specifying the version(s) of Mercurial they are tested with, or
44 44 # leave the attribute unspecified.
45 testedwith = 'ships-with-hg-core'
45 testedwith = b'ships-with-hg-core'
46 46
47 47
48 48 @command(
49 'censor',
49 b'censor',
50 50 [
51 ('r', 'rev', '', _('censor file from specified revision'), _('REV')),
52 ('t', 'tombstone', '', _('replacement tombstone data'), _('TEXT')),
51 (
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 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 64 with repo.wlock(), repo.lock():
59 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 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 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 74 wctx = repo[None]
69 75
70 76 m = scmutil.match(wctx, (path,))
71 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 79 path = m.files()[0]
74 80 flog = repo.file(path)
75 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 84 rev = scmutil.revsingle(repo, rev, rev).rev()
79 85 try:
80 86 ctx = repo[rev]
81 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 90 try:
85 91 fctx = ctx.filectx(path)
86 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 95 fnode = fctx.filenode()
90 96 heads = []
@@ -93,17 +99,17 b" def _docensor(ui, repo, path, rev='', to"
93 99 if path in hc and hc.filenode(path) == fnode:
94 100 heads.append(hc)
95 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 103 raise error.Abort(
98 _('cannot censor file in heads (%s)') % headlist,
99 hint=_('clean/delete and commit first'),
104 _(b'cannot censor file in heads (%s)') % headlist,
105 hint=_(b'clean/delete and commit first'),
100 106 )
101 107
102 108 wp = wctx.parents()
103 109 if ctx.node() in [p.node() for p in wp]:
104 110 raise error.Abort(
105 _('cannot censor working directory'),
106 hint=_('clean/delete/update first'),
111 _(b'cannot censor working directory'),
112 hint=_(b'clean/delete/update first'),
107 113 )
108 114
109 115 with repo.transaction(b'censor') as tr:
@@ -33,14 +33,22 b' command = registrar.command(cmdtable)'
33 33 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
34 34 # be specifying the version(s) of Mercurial they are tested with, or
35 35 # leave the attribute unspecified.
36 testedwith = 'ships-with-hg-core'
36 testedwith = b'ships-with-hg-core'
37 37
38 38
39 39 @command(
40 'children',
41 [('r', 'rev', '.', _('show children of the specified revision'), _('REV')),]
40 b'children',
41 [
42 (
43 b'r',
44 b'rev',
45 b'.',
46 _(b'show children of the specified revision'),
47 _(b'REV'),
48 ),
49 ]
42 50 + templateopts,
43 _('hg children [-r REV] [FILE]'),
51 _(b'hg children [-r REV] [FILE]'),
44 52 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
45 53 inferrepo=True,
46 54 )
@@ -62,7 +70,7 b' def children(ui, repo, file_=None, **opt'
62 70
63 71 """
64 72 opts = pycompat.byteskwargs(opts)
65 rev = opts.get('rev')
73 rev = opts.get(b'rev')
66 74 ctx = scmutil.revsingle(repo, rev)
67 75 if file_:
68 76 fctx = repo.filectx(file_, changeid=ctx.rev())
@@ -32,17 +32,17 b' command = registrar.command(cmdtable)'
32 32 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
33 33 # be specifying the version(s) of Mercurial they are tested with, or
34 34 # leave the attribute unspecified.
35 testedwith = 'ships-with-hg-core'
35 testedwith = b'ships-with-hg-core'
36 36
37 37
38 38 def changedlines(ui, repo, ctx1, ctx2, fns):
39 39 added, removed = 0, 0
40 40 fmatch = scmutil.matchfiles(repo, fns)
41 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
42 for l in diff.split('\n'):
43 if l.startswith("+") and not l.startswith("+++ "):
41 diff = b''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
42 for l in diff.split(b'\n'):
43 if l.startswith(b"+") and not l.startswith(b"+++ "):
44 44 added += 1
45 elif l.startswith("-") and not l.startswith("--- "):
45 elif l.startswith(b"-") and not l.startswith(b"--- "):
46 46 removed += 1
47 47 return (added, removed)
48 48
@@ -50,17 +50,17 b' def changedlines(ui, repo, ctx1, ctx2, f'
50 50 def countrate(ui, repo, amap, *pats, **opts):
51 51 """Calculate stats"""
52 52 opts = pycompat.byteskwargs(opts)
53 if opts.get('dateformat'):
53 if opts.get(b'dateformat'):
54 54
55 55 def getkey(ctx):
56 56 t, tz = ctx.date()
57 57 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
58 58 return encoding.strtolocal(
59 date.strftime(encoding.strfromlocal(opts['dateformat']))
59 date.strftime(encoding.strfromlocal(opts[b'dateformat']))
60 60 )
61 61
62 62 else:
63 tmpl = opts.get('oldtemplate') or opts.get('template')
63 tmpl = opts.get(b'oldtemplate') or opts.get(b'template')
64 64 tmpl = logcmdutil.maketemplater(ui, repo, tmpl)
65 65
66 66 def getkey(ctx):
@@ -69,12 +69,12 b' def countrate(ui, repo, amap, *pats, **o'
69 69 return ui.popbuffer()
70 70
71 71 progress = ui.makeprogress(
72 _('analyzing'), unit=_('revisions'), total=len(repo)
72 _(b'analyzing'), unit=_(b'revisions'), total=len(repo)
73 73 )
74 74 rate = {}
75 75 df = False
76 if opts.get('date'):
77 df = dateutil.matchdate(opts['date'])
76 if opts.get(b'date'):
77 df = dateutil.matchdate(opts[b'date'])
78 78
79 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 86 key = getkey(ctx).strip()
87 87 key = amap.get(key, key) # alias remap
88 if opts.get('changesets'):
88 if opts.get(b'changesets'):
89 89 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
90 90 else:
91 91 parents = ctx.parents()
92 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 94 return
95 95
96 96 ctx1 = parents[0]
@@ -108,50 +108,50 b' def countrate(ui, repo, amap, *pats, **o'
108 108
109 109
110 110 @command(
111 'churn',
111 b'churn',
112 112 [
113 113 (
114 'r',
115 'rev',
114 b'r',
115 b'rev',
116 116 [],
117 _('count rate for the specified revision or revset'),
118 _('REV'),
117 _(b'count rate for the specified revision or revset'),
118 _(b'REV'),
119 119 ),
120 120 (
121 'd',
122 'date',
123 '',
124 _('count rate for revisions matching date spec'),
125 _('DATE'),
121 b'd',
122 b'date',
123 b'',
124 _(b'count rate for revisions matching date spec'),
125 _(b'DATE'),
126 126 ),
127 127 (
128 't',
129 'oldtemplate',
130 '',
131 _('template to group changesets (DEPRECATED)'),
132 _('TEMPLATE'),
128 b't',
129 b'oldtemplate',
130 b'',
131 _(b'template to group changesets (DEPRECATED)'),
132 _(b'TEMPLATE'),
133 133 ),
134 134 (
135 'T',
136 'template',
137 '{author|email}',
138 _('template to group changesets'),
139 _('TEMPLATE'),
135 b'T',
136 b'template',
137 b'{author|email}',
138 _(b'template to group changesets'),
139 _(b'TEMPLATE'),
140 140 ),
141 141 (
142 'f',
143 'dateformat',
144 '',
145 _('strftime-compatible format for grouping by date'),
146 _('FORMAT'),
142 b'f',
143 b'dateformat',
144 b'',
145 _(b'strftime-compatible format for grouping by date'),
146 _(b'FORMAT'),
147 147 ),
148 ('c', 'changesets', False, _('count rate by number of changesets')),
149 ('s', 'sort', False, _('sort by key (default: sort by count)')),
150 ('', 'diffstat', False, _('display added/removed lines separately')),
151 ('', 'aliases', '', _('file with email aliases'), _('FILE')),
148 (b'c', b'changesets', False, _(b'count rate by number of changesets')),
149 (b's', b'sort', False, _(b'sort by key (default: sort by count)')),
150 (b'', b'diffstat', False, _(b'display added/removed lines separately')),
151 (b'', b'aliases', b'', _(b'file with email aliases'), _(b'FILE')),
152 152 ]
153 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 155 helpcategory=command.CATEGORY_MAINTENANCE,
156 156 inferrepo=True,
157 157 )
@@ -193,21 +193,21 b' def churn(ui, repo, *pats, **opts):'
193 193 '''
194 194
195 195 def pad(s, l):
196 return s + " " * (l - encoding.colwidth(s))
196 return s + b" " * (l - encoding.colwidth(s))
197 197
198 198 amap = {}
199 199 aliases = opts.get(r'aliases')
200 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
201 aliases = repo.wjoin('.hgchurn')
200 if not aliases and os.path.exists(repo.wjoin(b'.hgchurn')):
201 aliases = repo.wjoin(b'.hgchurn')
202 202 if aliases:
203 for l in open(aliases, "rb"):
203 for l in open(aliases, b"rb"):
204 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 206 amap[alias.strip()] = actual.strip()
207 207 except ValueError:
208 208 l = l.strip()
209 209 if l:
210 ui.warn(_("skipping malformed alias: %s\n") % l)
210 ui.warn(_(b"skipping malformed alias: %s\n") % l)
211 211 continue
212 212
213 213 rate = list(countrate(ui, repo, amap, *pats, **opts).items())
@@ -224,7 +224,7 b' def churn(ui, repo, *pats, **opts):'
224 224 maxname = max(len(k) for k, v in rate)
225 225
226 226 ttywidth = ui.termwidth()
227 ui.debug("assuming %i character terminal\n" % ttywidth)
227 ui.debug(b"assuming %i character terminal\n" % ttywidth)
228 228 width = ttywidth - maxname - 2 - 2 - 2
229 229
230 230 if opts.get(r'diffstat'):
@@ -232,21 +232,21 b' def churn(ui, repo, *pats, **opts):'
232 232
233 233 def format(name, diffstat):
234 234 added, removed = diffstat
235 return "%s %15s %s%s\n" % (
235 return b"%s %15s %s%s\n" % (
236 236 pad(name, maxname),
237 '+%d/-%d' % (added, removed),
238 ui.label('+' * charnum(added), 'diffstat.inserted'),
239 ui.label('-' * charnum(removed), 'diffstat.deleted'),
237 b'+%d/-%d' % (added, removed),
238 ui.label(b'+' * charnum(added), b'diffstat.inserted'),
239 ui.label(b'-' * charnum(removed), b'diffstat.deleted'),
240 240 )
241 241
242 242 else:
243 243 width -= 6
244 244
245 245 def format(name, count):
246 return "%s %6d %s\n" % (
246 return b"%s %6d %s\n" % (
247 247 pad(name, maxname),
248 248 sum(count),
249 '*' * charnum(sum(count)),
249 b'*' * charnum(sum(count)),
250 250 )
251 251
252 252 def charnum(count):
@@ -201,7 +201,7 b' from mercurial import ('
201 201 wireprotov1server,
202 202 )
203 203
204 testedwith = 'ships-with-hg-core'
204 testedwith = b'ships-with-hg-core'
205 205
206 206
207 207 def capabilities(orig, repo, proto):
@@ -210,11 +210,11 b' def capabilities(orig, repo, proto):'
210 210 # Only advertise if a manifest exists. This does add some I/O to requests.
211 211 # But this should be cheaper than a wasted network round trip due to
212 212 # missing file.
213 if repo.vfs.exists('clonebundles.manifest'):
214 caps.append('clonebundles')
213 if repo.vfs.exists(b'clonebundles.manifest'):
214 caps.append(b'clonebundles')
215 215
216 216 return caps
217 217
218 218
219 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 24 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
25 25 # be specifying the version(s) of Mercurial they are tested with, or
26 26 # leave the attribute unspecified.
27 testedwith = 'ships-with-hg-core'
27 testedwith = b'ships-with-hg-core'
28 28
29 29 commitopts = cmdutil.commitopts
30 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 34 @command(
35 'close-head|close-heads',
35 b'close-head|close-heads',
36 36 commitopts + commitopts2 + commitopts3,
37 _('[OPTION]... [REV]...'),
37 _(b'[OPTION]... [REV]...'),
38 38 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
39 39 inferrepo=True,
40 40 )
@@ -55,11 +55,11 b' def close_branch(ui, repo, *revs, **opts'
55 55 text=message,
56 56 files=[],
57 57 filectxfn=None,
58 user=opts.get('user'),
59 date=opts.get('date'),
58 user=opts.get(b'user'),
59 date=opts.get(b'date'),
60 60 extra=extra,
61 61 )
62 tr = repo.transaction('commit')
62 tr = repo.transaction(b'commit')
63 63 ret = repo.commitctx(cctx, True)
64 64 bookmarks.update(repo, [rev, None], ret)
65 65 cctx.markcommitted(ret)
@@ -67,11 +67,11 b' def close_branch(ui, repo, *revs, **opts'
67 67
68 68 opts = pycompat.byteskwargs(opts)
69 69
70 revs += tuple(opts.get('rev', []))
70 revs += tuple(opts.get(b'rev', []))
71 71 revs = scmutil.revrange(repo, revs)
72 72
73 73 if not revs:
74 raise error.Abort(_('no revisions specified'))
74 raise error.Abort(_(b'no revisions specified'))
75 75
76 76 heads = []
77 77 for branch in repo.branchmap():
@@ -79,17 +79,17 b' def close_branch(ui, repo, *revs, **opts'
79 79 heads = set(repo[h].rev() for h in heads)
80 80 for rev in revs:
81 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 84 message = cmdutil.logmessage(ui, opts)
85 85 if not message:
86 raise error.Abort(_("no commit message specified with -l or -m"))
87 extra = {'close': '1'}
86 raise error.Abort(_(b"no commit message specified with -l or -m"))
87 extra = {b'close': b'1'}
88 88
89 89 with repo.wlock(), repo.lock():
90 90 for rev in revs:
91 91 r = repo[rev]
92 92 branch = r.branch()
93 extra['branch'] = branch
93 extra[b'branch'] = branch
94 94 docommit(r)
95 95 return 0
@@ -22,57 +22,64 b' from mercurial import ('
22 22
23 23 cmdtable = {}
24 24 command = registrar.command(cmdtable)
25 testedwith = 'ships-with-hg-core'
25 testedwith = b'ships-with-hg-core'
26 26
27 27 usedinternally = {
28 'amend_source',
29 'branch',
30 'close',
31 'histedit_source',
32 'topic',
33 'rebase_source',
34 'intermediate-source',
35 '__touch-noise__',
36 'source',
37 'transplant_source',
28 b'amend_source',
29 b'branch',
30 b'close',
31 b'histedit_source',
32 b'topic',
33 b'rebase_source',
34 b'intermediate-source',
35 b'__touch-noise__',
36 b'source',
37 b'transplant_source',
38 38 }
39 39
40 40
41 41 def extsetup(ui):
42 entry = extensions.wrapcommand(commands.table, 'commit', _commit)
42 entry = extensions.wrapcommand(commands.table, b'commit', _commit)
43 43 options = entry[1]
44 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 55 def _commit(orig, ui, repo, *pats, **opts):
50 if util.safehasattr(repo, 'unfiltered'):
56 if util.safehasattr(repo, b'unfiltered'):
51 57 repo = repo.unfiltered()
52 58
53 59 class repoextra(repo.__class__):
54 60 def commit(self, *innerpats, **inneropts):
55 61 extras = opts.get(r'extra')
56 62 for raw in extras:
57 if '=' not in raw:
63 if b'=' not in raw:
58 64 msg = _(
59 "unable to parse '%s', should follow "
60 "KEY=VALUE format"
65 b"unable to parse '%s', should follow "
66 b"KEY=VALUE format"
61 67 )
62 68 raise error.Abort(msg % raw)
63 k, v = raw.split('=', 1)
69 k, v = raw.split(b'=', 1)
64 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 72 raise error.Abort(msg % raw)
67 73 if re.search(br'[^\w-]', k):
68 74 msg = _(
69 "keys can only contain ascii letters, digits,"
70 " '_' and '-'"
75 b"keys can only contain ascii letters, digits,"
76 b" '_' and '-'"
71 77 )
72 78 raise error.Abort(msg)
73 79 if k in usedinternally:
74 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 84 raise error.Abort(msg % k)
78 85 inneropts[r'extra'][k] = v
@@ -24,60 +24,72 b' command = registrar.command(cmdtable)'
24 24 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
25 25 # be specifying the version(s) of Mercurial they are tested with, or
26 26 # leave the attribute unspecified.
27 testedwith = 'ships-with-hg-core'
27 testedwith = b'ships-with-hg-core'
28 28
29 29 # Commands definition was moved elsewhere to ease demandload job.
30 30
31 31
32 32 @command(
33 'convert',
33 b'convert',
34 34 [
35 35 (
36 '',
37 'authors',
38 '',
36 b'',
37 b'authors',
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')),
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')),
44 (b's', b'source-type', b'', _(b'source repository type'), _(b'TYPE')),
48 45 (
49 '',
50 'filemap',
51 '',
52 _('remap file names using contents of file'),
53 _('FILE'),
46 b'd',
47 b'dest-type',
48 b'',
49 _(b'destination repository type'),
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 '',
57 'full',
58 None,
59 _('apply filemap changes by converting all files again'),
61 b'',
62 b'filemap',
63 b'',
64 _(b'remap file names using contents of file'),
65 _(b'FILE'),
60 66 ),
61 67 (
62 '',
63 'splicemap',
64 '',
65 _('splice synthesized history into place'),
66 _('FILE'),
68 b'',
69 b'full',
70 None,
71 _(b'apply filemap changes by converting all files again'),
67 72 ),
68 73 (
69 '',
70 'branchmap',
71 '',
72 _('change branch names while converting'),
73 _('FILE'),
74 b'',
75 b'splicemap',
76 b'',
77 _(b'splice synthesized history into place'),
78 _(b'FILE'),
74 79 ),
75 ('', 'branchsort', None, _('try to sort changesets by branches')),
76 ('', 'datesort', None, _('try to sort changesets by date')),
77 ('', 'sourcesort', None, _('preserve source changesets order')),
78 ('', 'closesort', None, _('try to reorder closed revisions')),
80 (
81 b'',
82 b'branchmap',
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 93 norepo=True,
82 94 )
83 95 def convert(ui, src, dest=None, revmapfile=None, **opts):
@@ -483,34 +495,44 b' def convert(ui, src, dest=None, revmapfi'
483 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 499 def debugsvnlog(ui, **opts):
488 500 return subversion.debugsvnlog(ui, **opts)
489 501
490 502
491 503 @command(
492 'debugcvsps',
504 b'debugcvsps',
493 505 [
494 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',
499 'revisions',
508 b'b',
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")),
504 ('x', 'new-cache', None, _("create new cvs log cache")),
505 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
506 ('', 'root', '', _('specify cvsroot')),
520 (b'u', b'update-cache', None, _(b"update cvs log cache")),
521 (b'x', b'new-cache', None, _(b"create new cvs log cache")),
522 (b'z', b'fuzz', 60, _(b'set commit time fuzz in seconds')),
523 (b'', b'root', b'', _(b'specify cvsroot')),
507 524 # Options specific to builtin cvsps
508 ('', 'parents', '', _('show parent changesets')),
509 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
525 (b'', b'parents', b'', _(b'show parent changesets')),
526 (
527 b'',
528 b'ancestors',
529 b'',
530 _(b'show current changeset in ancestor branches'),
531 ),
510 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 536 norepo=True,
515 537 )
516 538 def debugcvsps(ui, *args, **opts):
@@ -528,14 +550,14 b' def debugcvsps(ui, *args, **opts):'
528 550
529 551
530 552 def kwconverted(context, mapping, name):
531 ctx = context.resource(mapping, 'ctx')
532 rev = ctx.extra().get('convert_revision', '')
533 if rev.startswith('svn:'):
534 if name == 'svnrev':
553 ctx = context.resource(mapping, b'ctx')
554 rev = ctx.extra().get(b'convert_revision', b'')
555 if rev.startswith(b'svn:'):
556 if name == b'svnrev':
535 557 return b"%d" % subversion.revsplit(rev)[2]
536 elif name == 'svnpath':
558 elif name == b'svnpath':
537 559 return subversion.revsplit(rev)[1]
538 elif name == 'svnuuid':
560 elif name == b'svnuuid':
539 561 return subversion.revsplit(rev)[0]
540 562 return rev
541 563
@@ -543,22 +565,22 b' def kwconverted(context, mapping, name):'
543 565 templatekeyword = registrar.templatekeyword()
544 566
545 567
546 @templatekeyword('svnrev', requires={'ctx'})
568 @templatekeyword(b'svnrev', requires={b'ctx'})
547 569 def kwsvnrev(context, mapping):
548 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 575 def kwsvnpath(context, mapping):
554 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 581 def kwsvnuuid(context, mapping):
560 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 586 # tell hggettext to extract docstrings from these functions:
@@ -17,7 +17,7 b' from . import common'
17 17
18 18 # these do not work with demandimport, blacklist
19 19 demandimport.IGNORES.update(
20 ['bzrlib.transactions', 'bzrlib.urlutils', 'ElementPath',]
20 [b'bzrlib.transactions', b'bzrlib.urlutils', b'ElementPath',]
21 21 )
22 22
23 23 try:
@@ -35,7 +35,7 b' try:'
35 35 except ImportError:
36 36 pass
37 37
38 supportedkinds = ('file', 'symlink')
38 supportedkinds = (b'file', b'symlink')
39 39
40 40
41 41 class bzr_source(common.converter_source):
@@ -44,16 +44,16 b' class bzr_source(common.converter_source'
44 44 def __init__(self, ui, repotype, path, revs=None):
45 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 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 52 try:
53 53 # access bzrlib stuff
54 54 bzrdir
55 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 58 path = os.path.abspath(path)
59 59 self._checkrepotype(path)
@@ -61,10 +61,10 b' class bzr_source(common.converter_source'
61 61 self.sourcerepo = bzrdir.BzrDir.open(path).open_repository()
62 62 except errors.NoRepositoryPresent:
63 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 66 self._parentids = {}
67 self._saverev = ui.configbool('convert', 'bzr.saverev')
67 self._saverev = ui.configbool(b'convert', b'bzr.saverev')
68 68
69 69 def _checkrepotype(self, path):
70 70 # Lightweight checkouts detection is informational but probably
@@ -84,13 +84,13 b' class bzr_source(common.converter_source'
84 84 ):
85 85 self.ui.warn(
86 86 _(
87 'warning: lightweight checkouts may cause '
88 'conversion failures, try with a regular '
89 'branch instead.\n'
87 b'warning: lightweight checkouts may cause '
88 b'conversion failures, try with a regular '
89 b'branch instead.\n'
90 90 )
91 91 )
92 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 95 def before(self):
96 96 """Before the conversion begins, acquire a read lock
@@ -126,16 +126,16 b' class bzr_source(common.converter_source'
126 126 revid = info.rev_id
127 127 if revid is None:
128 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 131 heads = [revid]
132 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 134 return heads
135 135
136 136 def getfile(self, name, rev):
137 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 139 kind = None
140 140 if fileid is not None:
141 141 kind = revtree.kind(fileid)
@@ -143,11 +143,11 b' class bzr_source(common.converter_source'
143 143 # the file is not available anymore - was deleted
144 144 return None, None
145 145 mode = self._modecache[(name, rev)]
146 if kind == 'symlink':
146 if kind == b'symlink':
147 147 target = revtree.get_symlink_target(fileid)
148 148 if target is None:
149 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 152 return target, mode
153 153 else:
@@ -156,7 +156,7 b' class bzr_source(common.converter_source'
156 156
157 157 def getchanges(self, version, full):
158 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 160 self._modecache = {}
161 161 self._revtree = self.sourcerepo.revision_tree(version)
162 162 # get the parentids from the cache
@@ -176,12 +176,12 b' class bzr_source(common.converter_source'
176 176 parents = self._filterghosts(rev.parent_ids)
177 177 self._parentids[version] = parents
178 178
179 branch = self.recode(rev.properties.get('branch-nick', u'default'))
180 if branch == 'trunk':
181 branch = 'default'
179 branch = self.recode(rev.properties.get(b'branch-nick', u'default'))
180 if branch == b'trunk':
181 branch = b'default'
182 182 return common.commit(
183 183 parents=parents,
184 date='%d %d' % (rev.timestamp, -rev.timezone),
184 date=b'%d %d' % (rev.timestamp, -rev.timezone),
185 185 author=self.recode(rev.committer),
186 186 desc=self.recode(rev.message),
187 187 branch=branch,
@@ -248,13 +248,13 b' class bzr_source(common.converter_source'
248 248
249 249 # bazaar tracks directories, mercurial does not, so
250 250 # we have to rename the directory contents
251 if kind[1] == 'directory':
252 if kind[0] not in (None, 'directory'):
251 if kind[1] == b'directory':
252 if kind[0] not in (None, b'directory'):
253 253 # Replacing 'something' with a directory, record it
254 254 # so it can be removed.
255 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 258 renaming = paths[0] != paths[1]
259 259 # neither an add nor an delete - a move
260 260 # rename all directory contents manually
@@ -262,9 +262,9 b' class bzr_source(common.converter_source'
262 262 # get all child-entries of the directory
263 263 for name, entry in inventory.iter_entries(subdir):
264 264 # hg does not track directory renames
265 if entry.kind == 'directory':
265 if entry.kind == b'directory':
266 266 continue
267 frompath = self.recode(paths[0] + '/' + name)
267 frompath = self.recode(paths[0] + b'/' + name)
268 268 if frompath in seen:
269 269 # Already handled by a more specific change entry
270 270 # This is important when you have:
@@ -275,15 +275,15 b' class bzr_source(common.converter_source'
275 275 seen.add(frompath)
276 276 if not renaming:
277 277 continue
278 topath = self.recode(paths[1] + '/' + name)
278 topath = self.recode(paths[1] + b'/' + name)
279 279 # register the files as changed
280 280 changes.append((frompath, revid))
281 281 changes.append((topath, revid))
282 282 # add to mode cache
283 283 mode = (
284 (entry.executable and 'x')
285 or (entry.kind == 'symlink' and 's')
286 or ''
284 (entry.executable and b'x')
285 or (entry.kind == b'symlink' and b's')
286 or b''
287 287 )
288 288 self._modecache[(topath, revid)] = mode
289 289 # register the change as move
@@ -312,7 +312,7 b' class bzr_source(common.converter_source'
312 312
313 313 # populate the mode cache
314 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 316 self._modecache[(topath, revid)] = mode
317 317 changes.append((topath, revid))
318 318
@@ -46,7 +46,7 b' class _shlexpy3proxy(object):'
46 46
47 47 @property
48 48 def infile(self):
49 return self._l.infile or '<unknown>'
49 return self._l.infile or b'<unknown>'
50 50
51 51 @property
52 52 def lineno(self):
@@ -56,13 +56,13 b' class _shlexpy3proxy(object):'
56 56 def shlexer(data=None, filepath=None, wordchars=None, whitespace=None):
57 57 if data is None:
58 58 if pycompat.ispy3:
59 data = open(filepath, 'r', encoding=r'latin1')
59 data = open(filepath, b'r', encoding=r'latin1')
60 60 else:
61 data = open(filepath, 'r')
61 data = open(filepath, b'r')
62 62 else:
63 63 if filepath is not None:
64 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 67 if pycompat.ispy3:
68 68 data = data.decode('latin1')
@@ -87,7 +87,7 b' def encodeargs(args):'
87 87 def encodearg(s):
88 88 lines = base64.encodestring(s)
89 89 lines = [l.splitlines()[0] for l in lines]
90 return ''.join(lines)
90 return b''.join(lines)
91 91
92 92 s = pickle.dumps(args)
93 93 return encodearg(s)
@@ -109,14 +109,14 b' def checktool(exe, name=None, abort=True'
109 109 exc = error.Abort
110 110 else:
111 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 115 class NoRepo(Exception):
116 116 pass
117 117
118 118
119 SKIPREV = 'SKIP'
119 SKIPREV = b'SKIP'
120 120
121 121
122 122 class commit(object):
@@ -135,8 +135,8 b' class commit(object):'
135 135 optparents=None,
136 136 ctx=None,
137 137 ):
138 self.author = author or 'unknown'
139 self.date = date or '0 0'
138 self.author = author or b'unknown'
139 self.date = date or b'0 0'
140 140 self.desc = desc
141 141 self.parents = parents # will be converted and used as parents
142 142 self.optparents = optparents or [] # will be used if already converted
@@ -160,15 +160,15 b' class converter_source(object):'
160 160 self.revs = revs
161 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 166 """ fails if revstr is not a 40 byte hex. mercurial and git both uses
167 167 such format for their revision numbering
168 168 """
169 169 if not re.match(br'[0-9a-fA-F]{40,40}$', revstr):
170 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 172 % (mapname, revstr)
173 173 )
174 174
@@ -236,7 +236,7 b' class converter_source(object):'
236 236
237 237 def recode(self, s, encoding=None):
238 238 if not encoding:
239 encoding = self.encoding or 'utf-8'
239 encoding = self.encoding or b'utf-8'
240 240
241 241 if isinstance(s, pycompat.unicode):
242 242 return s.encode("utf-8")
@@ -292,7 +292,7 b' class converter_source(object):'
292 292 """
293 293 return {}
294 294
295 def checkrevformat(self, revstr, mapname='splicemap'):
295 def checkrevformat(self, revstr, mapname=b'splicemap'):
296 296 """revstr is a string that describes a revision in the given
297 297 source control system. Return true if revstr has correct
298 298 format.
@@ -412,20 +412,20 b' class commandline(object):'
412 412 cmdline = [self.command, cmd] + list(args)
413 413 for k, v in kwargs.iteritems():
414 414 if len(k) == 1:
415 cmdline.append('-' + k)
415 cmdline.append(b'-' + k)
416 416 else:
417 cmdline.append('--' + k.replace('_', '-'))
417 cmdline.append(b'--' + k.replace(b'_', b'-'))
418 418 try:
419 419 if len(k) == 1:
420 cmdline.append('' + v)
420 cmdline.append(b'' + v)
421 421 else:
422 cmdline[-1] += '=' + v
422 cmdline[-1] += b'=' + v
423 423 except TypeError:
424 424 pass
425 425 cmdline = [procutil.shellquote(arg) for arg in cmdline]
426 426 if not self.ui.debugflag:
427 cmdline += ['2>', pycompat.bytestr(os.devnull)]
428 cmdline = ' '.join(cmdline)
427 cmdline += [b'2>', pycompat.bytestr(os.devnull)]
428 cmdline = b' '.join(cmdline)
429 429 return cmdline
430 430
431 431 def _run(self, cmd, *args, **kwargs):
@@ -449,7 +449,7 b' class commandline(object):'
449 449
450 450 def _dorun(self, openfunc, cmd, *args, **kwargs):
451 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 453 self.prerun()
454 454 try:
455 455 return openfunc(cmdline)
@@ -466,16 +466,16 b' class commandline(object):'
466 466 p = self._run(cmd, *args, **kwargs)
467 467 output = p.stdout.readlines()
468 468 p.wait()
469 self.ui.debug(''.join(output))
469 self.ui.debug(b''.join(output))
470 470 return output, p.returncode
471 471
472 def checkexit(self, status, output=''):
472 def checkexit(self, status, output=b''):
473 473 if status:
474 474 if output:
475 self.ui.warn(_('%s error:\n') % self.command)
475 self.ui.warn(_(b'%s error:\n') % self.command)
476 476 self.ui.warn(output)
477 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 480 def run0(self, cmd, *args, **kwargs):
481 481 output, status = self.run(cmd, *args, **kwargs)
@@ -484,7 +484,7 b' class commandline(object):'
484 484
485 485 def runlines0(self, cmd, *args, **kwargs):
486 486 output, status = self.runlines(cmd, *args, **kwargs)
487 self.checkexit(status, ''.join(output))
487 self.checkexit(status, b''.join(output))
488 488 return output
489 489
490 490 @propertycache
@@ -540,7 +540,7 b' class mapfile(dict):'
540 540 if not self.path:
541 541 return
542 542 try:
543 fp = open(self.path, 'rb')
543 fp = open(self.path, b'rb')
544 544 except IOError as err:
545 545 if err.errno != errno.ENOENT:
546 546 raise
@@ -551,10 +551,10 b' class mapfile(dict):'
551 551 # Ignore blank lines
552 552 continue
553 553 try:
554 key, value = line.rsplit(' ', 1)
554 key, value = line.rsplit(b' ', 1)
555 555 except ValueError:
556 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 558 % (self.path, i + 1)
559 559 )
560 560 if key not in self:
@@ -565,13 +565,13 b' class mapfile(dict):'
565 565 def __setitem__(self, key, value):
566 566 if self.fp is None:
567 567 try:
568 self.fp = open(self.path, 'ab')
568 self.fp = open(self.path, b'ab')
569 569 except IOError as err:
570 570 raise error.Abort(
571 _('could not open map file %r: %s')
571 _(b'could not open map file %r: %s')
572 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 575 self.fp.flush()
576 576 super(mapfile, self).__setitem__(key, value)
577 577
@@ -52,7 +52,7 b' p4_source = p4.p4_source'
52 52 svn_sink = subversion.svn_sink
53 53 svn_source = subversion.svn_source
54 54
55 orig_encoding = 'ascii'
55 orig_encoding = b'ascii'
56 56
57 57
58 58 def recode(s):
@@ -90,36 +90,36 b' def mapbranch(branch, branchmap):'
90 90 # destination repository. For such commits, using a literal "default"
91 91 # in branchmap below allows the user to map "default" to an alternate
92 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 94 # At some point we used "None" literal to denote the default branch,
95 95 # attempt to use that for backward compatibility.
96 96 if not branch:
97 branch = branchmap.get('None', branch)
97 branch = branchmap.get(b'None', branch)
98 98 return branch
99 99
100 100
101 101 source_converters = [
102 ('cvs', convert_cvs, 'branchsort'),
103 ('git', convert_git, 'branchsort'),
104 ('svn', svn_source, 'branchsort'),
105 ('hg', mercurial_source, 'sourcesort'),
106 ('darcs', darcs_source, 'branchsort'),
107 ('mtn', monotone_source, 'branchsort'),
108 ('gnuarch', gnuarch_source, 'branchsort'),
109 ('bzr', bzr_source, 'branchsort'),
110 ('p4', p4_source, 'branchsort'),
102 (b'cvs', convert_cvs, b'branchsort'),
103 (b'git', convert_git, b'branchsort'),
104 (b'svn', svn_source, b'branchsort'),
105 (b'hg', mercurial_source, b'sourcesort'),
106 (b'darcs', darcs_source, b'branchsort'),
107 (b'mtn', monotone_source, b'branchsort'),
108 (b'gnuarch', gnuarch_source, b'branchsort'),
109 (b'bzr', bzr_source, b'branchsort'),
110 (b'p4', p4_source, b'branchsort'),
111 111 ]
112 112
113 113 sink_converters = [
114 ('hg', mercurial_sink),
115 ('svn', svn_sink),
114 (b'hg', mercurial_sink),
115 (b'svn', svn_sink),
116 116 ]
117 117
118 118
119 119 def convertsource(ui, path, type, revs):
120 120 exceptions = []
121 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 123 for name, source, sortmode in source_converters:
124 124 try:
125 125 if not type or name == type:
@@ -128,22 +128,22 b' def convertsource(ui, path, type, revs):'
128 128 exceptions.append(inst)
129 129 if not ui.quiet:
130 130 for inst in exceptions:
131 ui.write("%s\n" % pycompat.bytestr(inst.args[0]))
132 raise error.Abort(_('%s: missing or unsupported repository') % path)
131 ui.write(b"%s\n" % pycompat.bytestr(inst.args[0]))
132 raise error.Abort(_(b'%s: missing or unsupported repository') % path)
133 133
134 134
135 135 def convertsink(ui, path, type):
136 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 138 for name, sink in sink_converters:
139 139 try:
140 140 if not type or name == type:
141 141 return sink(ui, name, path)
142 142 except NoRepo as inst:
143 ui.note(_("convert: %s\n") % inst)
143 ui.note(_(b"convert: %s\n") % inst)
144 144 except MissingTool as inst:
145 raise error.Abort('%s\n' % inst)
146 raise error.Abort(_('%s: unknown repository type') % path)
145 raise error.Abort(b'%s\n' % inst)
146 raise error.Abort(_(b'%s: unknown repository type') % path)
147 147
148 148
149 149 class progresssource(object):
@@ -151,7 +151,7 b' class progresssource(object):'
151 151 self.ui = ui
152 152 self.source = source
153 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 157 def getfile(self, file, rev):
@@ -189,12 +189,12 b' class converter(object):'
189 189 if authorfile and os.path.exists(authorfile):
190 190 self.readauthormap(authorfile)
191 191 # Extend/Override with new author map if necessary
192 if opts.get('authormap'):
193 self.readauthormap(opts.get('authormap'))
192 if opts.get(b'authormap'):
193 self.readauthormap(opts.get(b'authormap'))
194 194 self.authorfile = self.dest.authorfile()
195 195
196 self.splicemap = self.parsesplicemap(opts.get('splicemap'))
197 self.branchmap = mapfile(ui, opts.get('branchmap'))
196 self.splicemap = self.parsesplicemap(opts.get(b'splicemap'))
197 self.branchmap = mapfile(ui, opts.get(b'branchmap'))
198 198
199 199 def parsesplicemap(self, path):
200 200 """ check and validate the splicemap format and
@@ -211,21 +211,21 b' class converter(object):'
211 211 return {}
212 212 m = {}
213 213 try:
214 fp = open(path, 'rb')
214 fp = open(path, b'rb')
215 215 for i, line in enumerate(util.iterfile(fp)):
216 216 line = line.splitlines()[0].rstrip()
217 217 if not line:
218 218 # Ignore blank lines
219 219 continue
220 220 # split line
221 lex = common.shlexer(data=line, whitespace=',')
221 lex = common.shlexer(data=line, whitespace=b',')
222 222 line = list(lex)
223 223 # check number of parents
224 224 if not (2 <= len(line) <= 3):
225 225 raise error.Abort(
226 226 _(
227 'syntax error in %s(%d): child parent1'
228 '[,parent2] expected'
227 b'syntax error in %s(%d): child parent1'
228 b'[,parent2] expected'
229 229 )
230 230 % (path, i + 1)
231 231 )
@@ -239,7 +239,7 b' class converter(object):'
239 239 # if file does not exist or error reading, exit
240 240 except IOError:
241 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 244 return m
245 245
@@ -251,7 +251,7 b' class converter(object):'
251 251 parents = {}
252 252 numcommits = self.source.numcommits()
253 253 progress = self.ui.makeprogress(
254 _('scanning'), unit=_('revisions'), total=numcommits
254 _(b'scanning'), unit=_(b'revisions'), total=numcommits
255 255 )
256 256 while visit:
257 257 n = visit.pop(0)
@@ -283,8 +283,8 b' class converter(object):'
283 283 # Could be in source but not converted during this run
284 284 self.ui.warn(
285 285 _(
286 'splice map revision %s is not being '
287 'converted, ignoring\n'
286 b'splice map revision %s is not being '
287 b'converted, ignoring\n'
288 288 )
289 289 % c
290 290 )
@@ -296,7 +296,7 b' class converter(object):'
296 296 continue
297 297 # Parent is not in dest and not being converted, not good
298 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 300 pc.append(p)
301 301 parents[c] = pc
302 302
@@ -369,7 +369,7 b' class converter(object):'
369 369 def makeclosesorter():
370 370 """Close order sort."""
371 371 keyfn = lambda n: (
372 'close' not in self.commitcache[n].extra,
372 b'close' not in self.commitcache[n].extra,
373 373 self.commitcache[n].sortkey,
374 374 )
375 375
@@ -392,16 +392,16 b' class converter(object):'
392 392
393 393 return picknext
394 394
395 if sortmode == 'branchsort':
395 if sortmode == b'branchsort':
396 396 picknext = makebranchsorter()
397 elif sortmode == 'datesort':
397 elif sortmode == b'datesort':
398 398 picknext = makedatesorter()
399 elif sortmode == 'sourcesort':
399 elif sortmode == b'sourcesort':
400 400 picknext = makesourcesorter()
401 elif sortmode == 'closesort':
401 elif sortmode == b'closesort':
402 402 picknext = makeclosesorter()
403 403 else:
404 raise error.Abort(_('unknown sort mode: %s') % sortmode)
404 raise error.Abort(_(b'unknown sort mode: %s') % sortmode)
405 405
406 406 children, actives = mapchildren(parents)
407 407
@@ -420,7 +420,7 b' class converter(object):'
420 420 pendings[c].remove(n)
421 421 except ValueError:
422 422 raise error.Abort(
423 _('cycle detected between %s and %s')
423 _(b'cycle detected between %s and %s')
424 424 % (recode(c), recode(n))
425 425 )
426 426 if not pendings[c]:
@@ -429,45 +429,47 b' class converter(object):'
429 429 pendings[c] = None
430 430
431 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 434 return s
435 435
436 436 def writeauthormap(self):
437 437 authorfile = self.authorfile
438 438 if authorfile:
439 self.ui.status(_('writing author map file %s\n') % authorfile)
440 ofile = open(authorfile, 'wb+')
439 self.ui.status(_(b'writing author map file %s\n') % authorfile)
440 ofile = open(authorfile, b'wb+')
441 441 for author in self.authors:
442 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 447 ofile.close()
446 448
447 449 def readauthormap(self, authorfile):
448 afile = open(authorfile, 'rb')
450 afile = open(authorfile, b'rb')
449 451 for line in afile:
450 452
451 453 line = line.strip()
452 if not line or line.startswith('#'):
454 if not line or line.startswith(b'#'):
453 455 continue
454 456
455 457 try:
456 srcauthor, dstauthor = line.split('=', 1)
458 srcauthor, dstauthor = line.split(b'=', 1)
457 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 461 self.ui.warn(msg % (authorfile, line.rstrip()))
460 462 continue
461 463
462 464 srcauthor = srcauthor.strip()
463 465 dstauthor = dstauthor.strip()
464 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 468 self.ui.debug(msg % (srcauthor, dstauthor))
467 469 self.authors[srcauthor] = dstauthor
468 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 473 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
472 474
473 475 afile.close()
@@ -481,7 +483,7 b' class converter(object):'
481 483
482 484 def copy(self, rev):
483 485 commit = self.commitcache[rev]
484 full = self.opts.get('full')
486 full = self.opts.get(b'full')
485 487 changes = self.source.getchanges(rev, full)
486 488 if isinstance(changes, bytes):
487 489 if changes == SKIPREV:
@@ -503,8 +505,8 b' class converter(object):'
503 505 try:
504 506 parents = self.splicemap[rev]
505 507 self.ui.status(
506 _('spliced in %s as parents of %s\n')
507 % (_(' and ').join(parents), rev)
508 _(b'spliced in %s as parents of %s\n')
509 % (_(b' and ').join(parents), rev)
508 510 )
509 511 parents = [self.map.get(p, p) for p in parents]
510 512 except KeyError:
@@ -536,34 +538,34 b' class converter(object):'
536 538 self.source.before()
537 539 self.dest.before()
538 540 self.source.setrevmap(self.map)
539 self.ui.status(_("scanning source...\n"))
541 self.ui.status(_(b"scanning source...\n"))
540 542 heads = self.source.getheads()
541 543 parents = self.walktree(heads)
542 544 self.mergesplicemap(parents, self.splicemap)
543 self.ui.status(_("sorting...\n"))
545 self.ui.status(_(b"sorting...\n"))
544 546 t = self.toposort(parents, sortmode)
545 547 num = len(t)
546 548 c = None
547 549
548 self.ui.status(_("converting...\n"))
550 self.ui.status(_(b"converting...\n"))
549 551 progress = self.ui.makeprogress(
550 _('converting'), unit=_('revisions'), total=len(t)
552 _(b'converting'), unit=_(b'revisions'), total=len(t)
551 553 )
552 554 for i, c in enumerate(t):
553 555 num -= 1
554 556 desc = self.commitcache[c].desc
555 if "\n" in desc:
557 if b"\n" in desc:
556 558 desc = desc.splitlines()[0]
557 559 # convert log message to local encoding without using
558 560 # tolocal() because the encoding.encoding convert()
559 561 # uses is 'utf-8'
560 self.ui.status("%d %s\n" % (num, recode(desc)))
561 self.ui.note(_("source: %s\n") % recode(c))
562 self.ui.status(b"%d %s\n" % (num, recode(desc)))
563 self.ui.note(_(b"source: %s\n") % recode(c))
562 564 progress.update(i)
563 565 self.copy(c)
564 566 progress.complete()
565 567
566 if not self.ui.configbool('convert', 'skiptags'):
568 if not self.ui.configbool(b'convert', b'skiptags'):
567 569 tags = self.source.gettags()
568 570 ctags = {}
569 571 for k in tags:
@@ -610,45 +612,47 b' def convert(ui, src, dest=None, revmapfi'
610 612 opts = pycompat.byteskwargs(opts)
611 613 global orig_encoding
612 614 orig_encoding = encoding.encoding
613 encoding.encoding = 'UTF-8'
615 encoding.encoding = b'UTF-8'
614 616
615 617 # support --authors as an alias for --authormap
616 if not opts.get('authormap'):
617 opts['authormap'] = opts.get('authors')
618 if not opts.get(b'authormap'):
619 opts[b'authormap'] = opts.get(b'authors')
618 620
619 621 if not dest:
620 dest = hg.defaultdest(src) + "-hg"
621 ui.status(_("assuming destination %s\n") % dest)
622 dest = hg.defaultdest(src) + b"-hg"
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 626 destc = scmutil.wrapconvertsink(destc)
625 627
626 628 try:
627 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 632 except Exception:
631 633 for path in destc.created:
632 634 shutil.rmtree(path, True)
633 635 raise
634 636
635 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
637 sortmodes = (b'branchsort', b'datesort', b'sourcesort', b'closesort')
636 638 sortmode = [m for m in sortmodes if opts.get(m)]
637 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 641 if sortmode:
640 642 sortmode = sortmode[0]
641 643 else:
642 644 sortmode = defaultsort
643 645
644 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
646 if sortmode == b'sourcesort' and not srcc.hasnativeorder():
645 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():
649 raise error.Abort(_('--closesort is not supported by this data source'))
650 if sortmode == b'closesort' and not srcc.hasnativeclose():
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 656 if fmap:
653 657 srcc = filemap.filemap_source(ui, srcc, fmap)
654 658 destc.setfilemapmode(True)
@@ -39,19 +39,19 b' class convert_cvs(converter_source):'
39 39 def __init__(self, ui, repotype, path, revs=None):
40 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 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 48 self.changeset = None
49 49 self.files = {}
50 50 self.tags = {}
51 51 self.lastbranch = {}
52 52 self.socket = None
53 self.cvsroot = open(os.path.join(cvs, "Root"), 'rb').read()[:-1]
54 self.cvsrepo = open(os.path.join(cvs, "Repository"), '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, b"Repository"), b'rb').read()[:-1]
55 55 self.encoding = encoding.encoding
56 56
57 57 self._connect()
@@ -65,7 +65,10 b' class convert_cvs(converter_source):'
65 65 if self.revs:
66 66 if len(self.revs) > 1:
67 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 73 # TODO: handle tags
71 74 try:
@@ -73,23 +76,23 b' class convert_cvs(converter_source):'
73 76 maxrev = int(self.revs[0])
74 77 except ValueError:
75 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 82 d = encoding.getcwd()
80 83 try:
81 84 os.chdir(self.path)
82 85
83 cache = 'update'
84 if not self.ui.configbool('convert', 'cvsps.cache'):
86 cache = b'update'
87 if not self.ui.configbool(b'convert', b'cvsps.cache'):
85 88 cache = None
86 89 db = cvsps.createlog(self.ui, cache=cache)
87 90 db = cvsps.createchangeset(
88 91 self.ui,
89 92 db,
90 fuzz=int(self.ui.config('convert', 'cvsps.fuzz')),
91 mergeto=self.ui.config('convert', 'cvsps.mergeto'),
92 mergefrom=self.ui.config('convert', 'cvsps.mergefrom'),
93 fuzz=int(self.ui.config(b'convert', b'cvsps.fuzz')),
94 mergeto=self.ui.config(b'convert', b'cvsps.mergeto'),
95 mergefrom=self.ui.config(b'convert', b'cvsps.mergefrom'),
93 96 )
94 97
95 98 for cs in db:
@@ -99,16 +102,16 b' class convert_cvs(converter_source):'
99 102 cs.author = self.recode(cs.author)
100 103 self.lastbranch[cs.branch] = id
101 104 cs.comment = self.recode(cs.comment)
102 if self.ui.configbool('convert', 'localtimezone'):
105 if self.ui.configbool(b'convert', b'localtimezone'):
103 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 108 self.tags.update(dict.fromkeys(cs.tags, id))
106 109
107 110 files = {}
108 111 for f in cs.entries:
109 files[f.file] = "%s%s" % (
110 '.'.join([(b"%d" % x) for x in f.revision]),
111 ['', '(DEAD)'][f.dead],
112 files[f.file] = b"%s%s" % (
113 b'.'.join([(b"%d" % x) for x in f.revision]),
114 [b'', b'(DEAD)'][f.dead],
112 115 )
113 116
114 117 # add current commit to set
@@ -117,7 +120,7 b' class convert_cvs(converter_source):'
117 120 date=date,
118 121 parents=[(b"%d" % p.id) for p in cs.parents],
119 122 desc=cs.comment,
120 branch=cs.branch or '',
123 branch=cs.branch or b'',
121 124 )
122 125 self.changeset[id] = c
123 126 self.files[id] = files
@@ -130,38 +133,38 b' class convert_cvs(converter_source):'
130 133 root = self.cvsroot
131 134 conntype = None
132 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 141 root = root[9:]
139 142 m = re.match(
140 143 r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)', root
141 144 )
142 145 if m:
143 conntype = "pserver"
146 conntype = b"pserver"
144 147 user, passw, serv, port, root = m.groups()
145 148 if not user:
146 user = "anonymous"
149 user = b"anonymous"
147 150 if not port:
148 151 port = 2401
149 152 else:
150 153 port = int(port)
151 format0 = ":pserver:%s@%s:%s" % (user, serv, root)
152 format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root)
154 format0 = b":pserver:%s@%s:%s" % (user, serv, root)
155 format1 = b":pserver:%s@%s:%d%s" % (user, serv, port, root)
153 156
154 157 if not passw:
155 passw = "A"
156 cvspass = os.path.expanduser("~/.cvspass")
158 passw = b"A"
159 cvspass = os.path.expanduser(b"~/.cvspass")
157 160 try:
158 pf = open(cvspass, 'rb')
161 pf = open(cvspass, b'rb')
159 162 for line in pf.read().splitlines():
160 part1, part2 = line.split(' ', 1)
163 part1, part2 = line.split(b' ', 1)
161 164 # /1 :pserver:user@example.com:2401/cvsroot/foo
162 165 # Ah<Z
163 if part1 == '/1':
164 part1, part2 = part2.split(' ', 1)
166 if part1 == b'/1':
167 part1, part2 = part2.split(b' ', 1)
165 168 format = format1
166 169 # :pserver:user@example.com:/cvsroot/foo Ah<Z
167 170 else:
@@ -179,72 +182,72 b' class convert_cvs(converter_source):'
179 182 sck = socket.socket()
180 183 sck.connect((serv, port))
181 184 sck.send(
182 "\n".join(
185 b"\n".join(
183 186 [
184 "BEGIN AUTH REQUEST",
187 b"BEGIN AUTH REQUEST",
185 188 root,
186 189 user,
187 190 passw,
188 "END AUTH REQUEST",
189 "",
191 b"END AUTH REQUEST",
192 b"",
190 193 ]
191 194 )
192 195 )
193 if sck.recv(128) != "I LOVE YOU\n":
194 raise error.Abort(_("CVS pserver authentication failed"))
196 if sck.recv(128) != b"I LOVE YOU\n":
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:"):
199 conntype = "local"
201 if not conntype and root.startswith(b":local:"):
202 conntype = b"local"
200 203 root = root[7:]
201 204
202 205 if not conntype:
203 206 # :ext:user@host/home/user/path/to/cvsroot
204 if root.startswith(":ext:"):
207 if root.startswith(b":ext:"):
205 208 root = root[5:]
206 209 m = re.match(br'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
207 210 # Do not take Windows path "c:\foo\bar" for a connection strings
208 211 if os.path.isdir(root) or not m:
209 conntype = "local"
212 conntype = b"local"
210 213 else:
211 conntype = "rsh"
214 conntype = b"rsh"
212 215 user, host, root = m.group(1), m.group(2), m.group(3)
213 216
214 if conntype != "pserver":
215 if conntype == "rsh":
216 rsh = encoding.environ.get("CVS_RSH") or "ssh"
217 if conntype != b"pserver":
218 if conntype == b"rsh":
219 rsh = encoding.environ.get(b"CVS_RSH") or b"ssh"
217 220 if user:
218 cmd = [rsh, '-l', user, host] + cmd
221 cmd = [rsh, b'-l', user, host] + cmd
219 222 else:
220 223 cmd = [rsh, host] + cmd
221 224
222 225 # popen2 does not support argument lists under Windows
223 226 cmd = [procutil.shellquote(arg) for arg in cmd]
224 cmd = procutil.quotecommand(' '.join(cmd))
227 cmd = procutil.quotecommand(b' '.join(cmd))
225 228 self.writep, self.readp = procutil.popen2(cmd)
226 229
227 230 self.realroot = root
228 231
229 self.writep.write("Root %s\n" % root)
232 self.writep.write(b"Root %s\n" % root)
230 233 self.writep.write(
231 "Valid-responses ok error Valid-requests Mode"
232 " M Mbinary E Checked-in Created Updated"
233 " Merged Removed\n"
234 b"Valid-responses ok error Valid-requests Mode"
235 b" M Mbinary E Checked-in Created Updated"
236 b" Merged Removed\n"
234 237 )
235 self.writep.write("valid-requests\n")
238 self.writep.write(b"valid-requests\n")
236 239 self.writep.flush()
237 240 r = self.readp.readline()
238 if not r.startswith("Valid-requests"):
241 if not r.startswith(b"Valid-requests"):
239 242 raise error.Abort(
240 243 _(
241 'unexpected response from CVS server '
242 '(expected "Valid-requests", but got %r)'
244 b'unexpected response from CVS server '
245 b'(expected "Valid-requests", but got %r)'
243 246 )
244 247 % r
245 248 )
246 if "UseUnchanged" in r:
247 self.writep.write("UseUnchanged\n")
249 if b"UseUnchanged" in r:
250 self.writep.write(b"UseUnchanged\n")
248 251 self.writep.flush()
249 252 self.readp.readline()
250 253
@@ -262,55 +265,55 b' class convert_cvs(converter_source):'
262 265 data = fp.read(min(count, chunksize))
263 266 if not data:
264 267 raise error.Abort(
265 _("%d bytes missing from remote file") % count
268 _(b"%d bytes missing from remote file") % count
266 269 )
267 270 count -= len(data)
268 271 output.write(data)
269 272 return output.getvalue()
270 273
271 274 self._parse()
272 if rev.endswith("(DEAD)"):
275 if rev.endswith(b"(DEAD)"):
273 276 return None, None
274 277
275 args = ("-N -P -kk -r %s --" % rev).split()
276 args.append(self.cvsrepo + '/' + name)
278 args = (b"-N -P -kk -r %s --" % rev).split()
279 args.append(self.cvsrepo + b'/' + name)
277 280 for x in args:
278 self.writep.write("Argument %s\n" % x)
279 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
281 self.writep.write(b"Argument %s\n" % x)
282 self.writep.write(b"Directory .\n%s\nco\n" % self.realroot)
280 283 self.writep.flush()
281 284
282 data = ""
285 data = b""
283 286 mode = None
284 287 while True:
285 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 290 self.readp.readline() # path
288 291 self.readp.readline() # entries
289 292 mode = self.readp.readline()[:-1]
290 293 count = int(self.readp.readline()[:-1])
291 294 data = chunkedread(self.readp, count)
292 elif line.startswith(" "):
295 elif line.startswith(b" "):
293 296 data += line[1:]
294 elif line.startswith("M "):
297 elif line.startswith(b"M "):
295 298 pass
296 elif line.startswith("Mbinary "):
299 elif line.startswith(b"Mbinary "):
297 300 count = int(self.readp.readline()[:-1])
298 301 data = chunkedread(self.readp, count)
299 302 else:
300 if line == "ok\n":
303 if line == b"ok\n":
301 304 if mode is None:
302 raise error.Abort(_('malformed response from CVS'))
303 return (data, "x" in mode and "x" or "")
304 elif line.startswith("E "):
305 self.ui.warn(_("cvs server: %s\n") % line[2:])
306 elif line.startswith("Remove"):
305 raise error.Abort(_(b'malformed response from CVS'))
306 return (data, b"x" in mode and b"x" or b"")
307 elif line.startswith(b"E "):
308 self.ui.warn(_(b"cvs server: %s\n") % line[2:])
309 elif line.startswith(b"Remove"):
307 310 self.readp.readline()
308 311 else:
309 raise error.Abort(_("unknown CVS response: %s") % line)
312 raise error.Abort(_(b"unknown CVS response: %s") % line)
310 313
311 314 def getchanges(self, rev, full):
312 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 317 self._parse()
315 318 return sorted(self.files[rev].iteritems()), {}, set()
316 319
@@ -92,18 +92,18 b' def getrepopath(cvspath):'
92 92 # of the '/' char after the '@' is located. The solution is the rest of the
93 93 # string after that '/' sign including it
94 94
95 parts = cvspath.split(':')
96 atposition = parts[-1].find('@')
95 parts = cvspath.split(b':')
96 atposition = parts[-1].find(b'@')
97 97 start = 0
98 98
99 99 if atposition != -1:
100 100 start = atposition
101 101
102 repopath = parts[-1][parts[-1].find('/', start) :]
102 repopath = parts[-1][parts[-1].find(b'/', start) :]
103 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 107 '''Collect the CVS rlog'''
108 108
109 109 # Because we store many duplicate commit log messages, reusing strings
@@ -111,10 +111,10 b' def createlog(ui, directory=None, root="'
111 111 _scache = {}
112 112
113 113 def scache(s):
114 "return a shared version of a string"
114 b"return a shared version of a string"
115 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 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 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 149 if directory is None:
150 150 # Current working directory
151 151
152 152 # Get the real directory in the repository
153 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 155 prefix = f.read().strip()
156 156 directory = prefix
157 if prefix == ".":
158 prefix = ""
157 if prefix == b".":
158 prefix = b""
159 159 except IOError:
160 raise logerror(_('not a CVS sandbox'))
160 raise logerror(_(b'not a CVS sandbox'))
161 161
162 162 if prefix and not prefix.endswith(pycompat.ossep):
163 163 prefix += pycompat.ossep
164 164
165 165 # Use the Root file in the sandbox, if it exists
166 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 168 except IOError:
169 169 pass
170 170
171 171 if not root:
172 root = encoding.environ.get('CVSROOT', '')
172 root = encoding.environ.get(b'CVSROOT', b'')
173 173
174 174 # read log cache if one exists
175 175 oldlog = []
176 176 date = None
177 177
178 178 if cache:
179 cachedir = os.path.expanduser('~/.hg.cvsps')
179 cachedir = os.path.expanduser(b'~/.hg.cvsps')
180 180 if not os.path.exists(cachedir):
181 181 os.mkdir(cachedir)
182 182
@@ -189,50 +189,50 b' def createlog(ui, directory=None, root="'
189 189 # and
190 190 # /pserver/user/server/path
191 191 # are mapped to different cache file names.
192 cachefile = root.split(":") + [directory, "cache"]
193 cachefile = ['-'.join(re.findall(br'\w+', s)) for s in cachefile if s]
192 cachefile = root.split(b":") + [directory, b"cache"]
193 cachefile = [b'-'.join(re.findall(br'\w+', s)) for s in cachefile if s]
194 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 199 try:
200 ui.note(_('reading cvs log cache %s\n') % cachefile)
201 oldlog = pickle.load(open(cachefile, 'rb'))
200 ui.note(_(b'reading cvs log cache %s\n') % cachefile)
201 oldlog = pickle.load(open(cachefile, b'rb'))
202 202 for e in oldlog:
203 203 if not (
204 util.safehasattr(e, 'branchpoints')
205 and util.safehasattr(e, 'commitid')
206 and util.safehasattr(e, 'mergepoint')
204 util.safehasattr(e, b'branchpoints')
205 and util.safehasattr(e, b'commitid')
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 209 oldlog = []
210 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 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 216 if oldlog:
217 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 220 # build the CVS commandline
221 cmd = ['cvs', '-q']
221 cmd = [b'cvs', b'-q']
222 222 if root:
223 cmd.append('-d%s' % root)
223 cmd.append(b'-d%s' % root)
224 224 p = util.normpath(getrepopath(root))
225 if not p.endswith('/'):
226 p += '/'
225 if not p.endswith(b'/'):
226 p += b'/'
227 227 if prefix:
228 228 # looks like normpath replaces "" by "."
229 229 prefix = p + util.normpath(prefix)
230 230 else:
231 231 prefix = p
232 cmd.append(['log', 'rlog'][rlog])
232 cmd.append([b'log', b'rlog'][rlog])
233 233 if date:
234 234 # no space between option and date string
235 cmd.append('-d>%s' % date)
235 cmd.append(b'-d>%s' % date)
236 236 cmd.append(directory)
237 237
238 238 # state machine begins here
@@ -243,17 +243,17 b' def createlog(ui, directory=None, root="'
243 243 store = False # set when a new record can be appended
244 244
245 245 cmd = [procutil.shellquote(arg) for arg in cmd]
246 ui.note(_("running %s\n") % (' '.join(cmd)))
247 ui.debug("prefix=%r directory=%r root=%r\n" % (prefix, directory, root))
246 ui.note(_(b"running %s\n") % (b' '.join(cmd)))
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 250 peek = util.fromnativeeol(pfp.readline())
251 251 while True:
252 252 line = peek
253 if line == '':
253 if line == b'':
254 254 break
255 255 peek = util.fromnativeeol(pfp.readline())
256 if line.endswith('\n'):
256 if line.endswith(b'\n'):
257 257 line = line[:-1]
258 258 # ui.debug('state=%d line=%r\n' % (state, line))
259 259
@@ -267,12 +267,12 b' def createlog(ui, directory=None, root="'
267 267 filename = util.normpath(rcs[:-2])
268 268 if filename.startswith(prefix):
269 269 filename = filename[len(prefix) :]
270 if filename.startswith('/'):
270 if filename.startswith(b'/'):
271 271 filename = filename[1:]
272 if filename.startswith('Attic/'):
272 if filename.startswith(b'Attic/'):
273 273 filename = filename[6:]
274 274 else:
275 filename = filename.replace('/Attic/', '/')
275 filename = filename.replace(b'/Attic/', b'/')
276 276 state = 2
277 277 continue
278 278 state = 1
@@ -289,7 +289,7 b' def createlog(ui, directory=None, root="'
289 289 elif state == 1:
290 290 # expect 'Working file' (only when using log instead of rlog)
291 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 293 filename = util.normpath(match.group(1))
294 294 state = 2
295 295
@@ -303,7 +303,7 b' def createlog(ui, directory=None, root="'
303 303 # read the symbolic names and store as tags
304 304 match = re_30.match(line)
305 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 308 # Convert magic branch number to an odd-numbered one
309 309 revn = len(rev)
@@ -327,7 +327,7 b' def createlog(ui, directory=None, root="'
327 327 state = 5
328 328 else:
329 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 333 elif state == 5:
@@ -335,11 +335,11 b' def createlog(ui, directory=None, root="'
335 335 # we create the logentry here from values stored in states 0 to 4,
336 336 # as this state is re-entered for subsequent revisions of a file.
337 337 match = re_50.match(line)
338 assert match, _('expected revision number')
338 assert match, _(b'expected revision number')
339 339 e = logentry(
340 340 rcs=scache(rcs),
341 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 343 branches=[],
344 344 parent=None,
345 345 commitid=None,
@@ -352,21 +352,25 b' def createlog(ui, directory=None, root="'
352 352 elif state == 6:
353 353 # expecting date, author, state, lines changed
354 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 356 d = match.group(1)
357 if d[2] == '/':
357 if d[2] == b'/':
358 358 # Y2K
359 d = '19' + d
359 d = b'19' + d
360 360
361 361 if len(d.split()) != 3:
362 362 # cvs log dates always in GMT
363 d = d + ' UTC'
363 d = d + b' UTC'
364 364 e.date = dateutil.parsedate(
365 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 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 375 if match.group(5):
372 376 if match.group(6):
@@ -382,14 +386,14 b' def createlog(ui, directory=None, root="'
382 386 e.commitid = match.group(8)
383 387
384 388 if match.group(9): # cvsnt mergepoint
385 myrev = match.group(10).split('.')
389 myrev = match.group(10).split(b'.')
386 390 if len(myrev) == 2: # head
387 e.mergepoint = 'HEAD'
391 e.mergepoint = b'HEAD'
388 392 else:
389 myrev = '.'.join(myrev[:-2] + ['0', myrev[-2]])
393 myrev = b'.'.join(myrev[:-2] + [b'0', myrev[-2]])
390 394 branches = [b for b in branchmap if branchmap[b] == myrev]
391 395 assert len(branches) == 1, (
392 'unknown branch: %s' % e.mergepoint
396 b'unknown branch: %s' % e.mergepoint
393 397 )
394 398 e.mergepoint = branches[0]
395 399
@@ -402,8 +406,8 b' def createlog(ui, directory=None, root="'
402 406 m = re_70.match(line)
403 407 if m:
404 408 e.branches = [
405 tuple([int(y) for y in x.strip().split('.')])
406 for x in m.group(1).split(';')
409 tuple([int(y) for y in x.strip().split(b'.')])
410 for x in m.group(1).split(b';')
407 411 ]
408 412 state = 8
409 413 elif re_31.match(line) and re_50.match(peek):
@@ -419,7 +423,7 b' def createlog(ui, directory=None, root="'
419 423 # store commit log message
420 424 if re_31.match(line):
421 425 cpeek = peek
422 if cpeek.endswith('\n'):
426 if cpeek.endswith(b'\n'):
423 427 cpeek = cpeek[:-1]
424 428 if re_50.match(cpeek):
425 429 state = 5
@@ -447,7 +451,7 b' def createlog(ui, directory=None, root="'
447 451 and file_added_re.match(e.comment[0])
448 452 ):
449 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 456 e.synthetic = True
453 457
@@ -455,7 +459,7 b' def createlog(ui, directory=None, root="'
455 459 # clean up the results and save in the log.
456 460 store = False
457 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 464 revn = len(e.revision)
461 465 if revn > 3 and (revn % 2) == 0:
@@ -466,7 +470,7 b' def createlog(ui, directory=None, root="'
466 470 # find the branches starting from this revision
467 471 branchpoints = set()
468 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 474 if len(revparts) < 2: # bad tags
471 475 continue
472 476 if revparts[-2] == 0 and revparts[-1] % 2 == 0:
@@ -480,11 +484,12 b' def createlog(ui, directory=None, root="'
480 484
481 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 489 if len(log) % 100 == 0:
486 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 495 log.sort(key=lambda x: (x.rcs, x.revision))
@@ -492,7 +497,7 b' def createlog(ui, directory=None, root="'
492 497 # find parent revisions of individual files
493 498 versions = {}
494 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 501 if rcs in rcsmap:
497 502 e.rcs = rcsmap[rcs]
498 503 branch = e.revision[:-1]
@@ -515,28 +520,28 b' def createlog(ui, directory=None, root="'
515 520 if oldlog and oldlog[-1].date >= log[0].date:
516 521 raise logerror(
517 522 _(
518 'log cache overlaps with new log entries,'
519 ' re-run without cache.'
523 b'log cache overlaps with new log entries,'
524 b' re-run without cache.'
520 525 )
521 526 )
522 527
523 528 log = oldlog + log
524 529
525 530 # write the new cachefile
526 ui.note(_('writing cvs log cache %s\n') % cachefile)
527 pickle.dump(log, open(cachefile, 'wb'))
531 ui.note(_(b'writing cvs log cache %s\n') % cachefile)
532 pickle.dump(log, open(cachefile, b'wb'))
528 533 else:
529 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 539 if encodings:
535 540
536 541 def revstr(r):
537 542 # this is needed, because logentry.revision is a tuple of "int"
538 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 546 for entry in log:
542 547 comment = entry.comment
@@ -547,7 +552,7 b' def createlog(ui, directory=None, root="'
547 552 )
548 553 if ui.debugflag:
549 554 ui.debug(
550 "transcoding by %s: %s of %s\n"
555 b"transcoding by %s: %s of %s\n"
551 556 % (e, revstr(entry.revision), entry.file)
552 557 )
553 558 break
@@ -557,20 +562,22 b' def createlog(ui, directory=None, root="'
557 562 raise error.Abort(
558 563 inst,
559 564 hint=_(
560 'check convert.cvsps.logencoding' ' configuration'
565 b'check convert.cvsps.logencoding' b' configuration'
561 566 ),
562 567 )
563 568 else:
564 569 raise error.Abort(
565 570 _(
566 "no encoding can transcode"
567 " CVS log message for %s of %s"
571 b"no encoding can transcode"
572 b" CVS log message for %s of %s"
568 573 )
569 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 582 return log
576 583
@@ -597,14 +604,16 b' class changeset(object):'
597 604 self.__dict__.update(entries)
598 605
599 606 def __repr__(self):
600 items = ("%s=%r" % (k, self.__dict__[k]) for k in sorted(self.__dict__))
601 return "%s(%s)" % (type(self).__name__, ", ".join(items))
607 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 613 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
605 614 '''Convert log into changesets.'''
606 615
607 ui.status(_('creating changesets\n'))
616 ui.status(_(b'creating changesets\n'))
608 617
609 618 # try to order commitids by date
610 619 mindate = {}
@@ -619,10 +628,10 b' def createchangeset(ui, log, fuzz=60, me'
619 628 log.sort(
620 629 key=lambda x: (
621 630 mindate.get(x.commitid, (-1, 0)),
622 x.commitid or '',
631 x.commitid or b'',
623 632 x.comment,
624 633 x.author,
625 x.branch or '',
634 x.branch or b'',
626 635 x.date,
627 636 x.branchpoints,
628 637 )
@@ -682,8 +691,8 b' def createchangeset(ui, log, fuzz=60, me'
682 691
683 692 files = set()
684 693 if len(changesets) % 100 == 0:
685 t = '%d %s' % (len(changesets), repr(e.comment)[1:-1])
686 ui.status(stringutil.ellipsis(t, 80) + '\n')
694 t = b'%d %s' % (len(changesets), repr(e.comment)[1:-1])
695 ui.status(stringutil.ellipsis(t, 80) + b'\n')
687 696
688 697 c.entries.append(e)
689 698 files.add(e.file)
@@ -705,9 +714,9 b' def createchangeset(ui, log, fuzz=60, me'
705 714 # Sort files in each changeset
706 715
707 716 def entitycompare(l, r):
708 'Mimic cvsps sorting order'
709 l = l.file.split('/')
710 r = r.file.split('/')
717 b'Mimic cvsps sorting order'
718 l = l.file.split(b'/')
719 r = r.file.split(b'/')
711 720 nl = len(l)
712 721 nr = len(r)
713 722 n = min(nl, nr)
@@ -842,7 +851,7 b' def createchangeset(ui, log, fuzz=60, me'
842 851 # Ensure no changeset has a synthetic changeset as a parent.
843 852 while p.synthetic:
844 853 assert len(p.parents) <= 1, _(
845 'synthetic changeset cannot have multiple parents'
854 b'synthetic changeset cannot have multiple parents'
846 855 )
847 856 if p.parents:
848 857 p = p.parents[0]
@@ -854,7 +863,7 b' def createchangeset(ui, log, fuzz=60, me'
854 863 c.parents.append(p)
855 864
856 865 if c.mergepoint:
857 if c.mergepoint == 'HEAD':
866 if c.mergepoint == b'HEAD':
858 867 c.mergepoint = None
859 868 c.parents.append(changesets[branches[c.mergepoint]])
860 869
@@ -862,15 +871,15 b' def createchangeset(ui, log, fuzz=60, me'
862 871 m = mergefrom.search(c.comment)
863 872 if m:
864 873 m = m.group(1)
865 if m == 'HEAD':
874 if m == b'HEAD':
866 875 m = None
867 876 try:
868 877 candidate = changesets[branches[m]]
869 878 except KeyError:
870 879 ui.warn(
871 880 _(
872 "warning: CVS commit message references "
873 "non-existent branch %r:\n%s\n"
881 b"warning: CVS commit message references "
882 b"non-existent branch %r:\n%s\n"
874 883 )
875 884 % (pycompat.bytestr(m), c.comment)
876 885 )
@@ -882,7 +891,7 b' def createchangeset(ui, log, fuzz=60, me'
882 891 if m:
883 892 if m.groups():
884 893 m = m.group(1)
885 if m == 'HEAD':
894 if m == b'HEAD':
886 895 m = None
887 896 else:
888 897 m = None # if no group found then merge to HEAD
@@ -892,7 +901,7 b' def createchangeset(ui, log, fuzz=60, me'
892 901 author=c.author,
893 902 branch=m,
894 903 date=c.date,
895 comment='convert-repo: CVS merge from branch %s'
904 comment=b'convert-repo: CVS merge from branch %s'
896 905 % c.branch,
897 906 entries=[],
898 907 tags=[],
@@ -927,13 +936,13 b' def createchangeset(ui, log, fuzz=60, me'
927 936 for l, r in odd:
928 937 if l.id is not None and r.id is not None:
929 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 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 947 return changesets
939 948
@@ -944,27 +953,27 b' def debugcvsps(ui, *args, **opts):'
944 953 commit log entries and dates.
945 954 '''
946 955 opts = pycompat.byteskwargs(opts)
947 if opts["new_cache"]:
948 cache = "write"
949 elif opts["update_cache"]:
950 cache = "update"
956 if opts[b"new_cache"]:
957 cache = b"write"
958 elif opts[b"update_cache"]:
959 cache = b"update"
951 960 else:
952 961 cache = None
953 962
954 revisions = opts["revisions"]
963 revisions = opts[b"revisions"]
955 964
956 965 try:
957 966 if args:
958 967 log = []
959 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 970 else:
962 log = createlog(ui, root=opts["root"], cache=cache)
971 log = createlog(ui, root=opts[b"root"], cache=cache)
963 972 except logerror as e:
964 ui.write("%r\n" % e)
973 ui.write(b"%r\n" % e)
965 974 return
966 975
967 changesets = createchangeset(ui, log, opts["fuzz"])
976 changesets = createchangeset(ui, log, opts[b"fuzz"])
968 977 del log
969 978
970 979 # Print changesets (optionally filtered)
@@ -974,7 +983,7 b' def debugcvsps(ui, *args, **opts):'
974 983 ancestors = {} # parent branch
975 984 for cs in changesets:
976 985
977 if opts["ancestors"]:
986 if opts[b"ancestors"]:
978 987 if cs.branch not in branches and cs.parents and cs.parents[0].id:
979 988 ancestors[cs.branch] = (
980 989 changesets[cs.parents[0].id - 1].branch,
@@ -983,72 +992,75 b' def debugcvsps(ui, *args, **opts):'
983 992 branches[cs.branch] = cs.id
984 993
985 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 999 continue
988 1000
989 1001 if not off:
990 1002 # Note: trailing spaces on several lines here are needed to have
991 1003 # bug-for-bug compatibility with cvsps.
992 ui.write('---------------------\n')
993 ui.write(('PatchSet %d \n' % cs.id))
1004 ui.write(b'---------------------\n')
1005 ui.write((b'PatchSet %d \n' % cs.id))
994 1006 ui.write(
995 1007 (
996 'Date: %s\n'
997 % dateutil.datestr(cs.date, '%Y/%m/%d %H:%M:%S %1%2')
1008 b'Date: %s\n'
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))
1001 ui.write(('Branch: %s\n' % (cs.branch or 'HEAD')))
1012 ui.write((b'Author: %s\n' % cs.author))
1013 ui.write((b'Branch: %s\n' % (cs.branch or b'HEAD')))
1002 1014 ui.write(
1003 1015 (
1004 'Tag%s: %s \n'
1016 b'Tag%s: %s \n'
1005 1017 % (
1006 ['', 's'][len(cs.tags) > 1],
1007 ','.join(cs.tags) or '(none)',
1018 [b'', b's'][len(cs.tags) > 1],
1019 b','.join(cs.tags) or b'(none)',
1008 1020 )
1009 1021 )
1010 1022 )
1011 1023 if cs.branchpoints:
1012 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 1028 if len(cs.parents) > 1:
1017 1029 ui.write(
1018 1030 (
1019 'Parents: %s\n'
1020 % (','.join([(b"%d" % p.id) for p in cs.parents]))
1031 b'Parents: %s\n'
1032 % (b','.join([(b"%d" % p.id) for p in cs.parents]))
1021 1033 )
1022 1034 )
1023 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 1039 b = cs.branch
1028 1040 r = []
1029 1041 while b:
1030 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 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')
1036 ui.write('%s\n\n' % cs.comment)
1037 ui.write('Members: \n')
1047 ui.write(b'Log:\n')
1048 ui.write(b'%s\n\n' % cs.comment)
1049 ui.write(b'Members: \n')
1038 1050 for f in cs.entries:
1039 1051 fn = f.file
1040 if fn.startswith(opts["prefix"]):
1041 fn = fn[len(opts["prefix"]) :]
1052 if fn.startswith(opts[b"prefix"]):
1053 fn = fn[len(opts[b"prefix"]) :]
1042 1054 ui.write(
1043 '\t%s:%s->%s%s \n'
1055 b'\t%s:%s->%s%s \n'
1044 1056 % (
1045 1057 fn,
1046 '.'.join([b"%d" % x for x in f.parent]) or 'INITIAL',
1047 '.'.join([(b"%d" % x) for x in f.revision]),
1048 ['', '(DEAD)'][f.dead],
1058 b'.'.join([b"%d" % x for x in f.parent]) or b'INITIAL',
1059 b'.'.join([(b"%d" % x) for x in f.revision]),
1060 [b'', b'(DEAD)'][f.dead],
1049 1061 )
1050 1062 )
1051 ui.write('\n')
1063 ui.write(b'\n')
1052 1064
1053 1065 # have we seen the start tag?
1054 1066 if revisions and off:
@@ -46,22 +46,22 b' except ImportError:'
46 46 class darcs_source(common.converter_source, common.commandline):
47 47 def __init__(self, ui, repotype, path, revs=None):
48 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 51 # check for _darcs, ElementTree so that we can easily skip
52 52 # test-convert-darcs if ElementTree is not around
53 if not os.path.exists(os.path.join(path, '_darcs')):
54 raise NoRepo(_("%s does not look like a darcs repository") % path)
53 if not os.path.exists(os.path.join(path, b'_darcs')):
54 raise NoRepo(_(b"%s does not look like a darcs repository") % path)
55 55
56 common.checktool('darcs')
57 version = self.run0('--version').splitlines()[0].strip()
58 if version < '2.1':
56 common.checktool(b'darcs')
57 version = self.run0(b'--version').splitlines()[0].strip()
58 if version < b'2.1':
59 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():
64 raise error.Abort(_("Python ElementTree module is not available"))
63 if b"ElementTree" not in globals():
64 raise error.Abort(_(b"Python ElementTree module is not available"))
65 65
66 66 self.path = os.path.realpath(path)
67 67
@@ -73,30 +73,33 b' class darcs_source(common.converter_sour'
73 73 # Check darcs repository format
74 74 format = self.format()
75 75 if format:
76 if format in ('darcs-1.0', 'hashed'):
76 if format in (b'darcs-1.0', b'hashed'):
77 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 82 % format
80 83 )
81 84 else:
82 self.ui.warn(_('failed to detect repository format!'))
85 self.ui.warn(_(b'failed to detect repository format!'))
83 86
84 87 def before(self):
85 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 92 self.checkexit(status)
90 93
91 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 97 tagname = None
95 98 child = None
96 for elt in tree.findall('patch'):
97 node = elt.get('hash')
98 name = elt.findtext('name', '')
99 if name.startswith('TAG '):
99 for elt in tree.findall(b'patch'):
100 node = elt.get(b'hash')
101 name = elt.findtext(b'name', b'')
102 if name.startswith(b'TAG '):
100 103 tagname = name[4:].strip()
101 104 elif tagname is not None:
102 105 self.tags[tagname] = node
@@ -107,7 +110,7 b' class darcs_source(common.converter_sour'
107 110 self.parents[child] = []
108 111
109 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 114 shutil.rmtree(self.tmppath, ignore_errors=True)
112 115
113 116 def recode(self, s, encoding=None):
@@ -125,7 +128,7 b' class darcs_source(common.converter_sour'
125 128 # While we are decoding the XML as latin-1 to be as liberal as
126 129 # possible, etree will still raise an exception if any
127 130 # non-printable characters are in the XML changelog.
128 parser = XMLParser(encoding='latin-1')
131 parser = XMLParser(encoding=b'latin-1')
129 132 p = self._run(cmd, **kwargs)
130 133 etree.parse(p.stdout, parser=parser)
131 134 p.wait()
@@ -133,20 +136,20 b' class darcs_source(common.converter_sour'
133 136 return etree.getroot()
134 137
135 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 140 self.checkexit(status)
138 141 m = re.search(r'^\s*Format:\s*(.*)$', output, re.MULTILINE)
139 142 if not m:
140 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 146 def manifest(self):
144 147 man = []
145 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 151 self.checkexit(status)
149 for line in output.split('\n'):
152 for line in output.split(b'\n'):
150 153 path = line[2:]
151 154 if path:
152 155 man.append(path)
@@ -157,14 +160,14 b' class darcs_source(common.converter_sour'
157 160
158 161 def getcommit(self, rev):
159 162 elt = self.changes[rev]
160 dateformat = '%a %b %d %H:%M:%S %Z %Y'
161 date = dateutil.strdate(elt.get('local_date'), dateformat)
162 desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
163 dateformat = b'%a %b %d %H:%M:%S %Z %Y'
164 date = dateutil.strdate(elt.get(b'local_date'), dateformat)
165 desc = elt.findtext(b'name') + b'\n' + elt.findtext(b'comment', b'')
163 166 # etree can return unicode objects for name, comment, and author,
164 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 169 return common.commit(
167 author=self.recode(elt.get('author')),
170 author=self.recode(elt.get(b'author')),
168 171 date=dateutil.datestr(date, newdateformat),
169 172 desc=self.recode(desc).strip(),
170 173 parents=self.parents[rev],
@@ -172,34 +175,34 b' class darcs_source(common.converter_sour'
172 175
173 176 def pull(self, rev):
174 177 output, status = self.run(
175 'pull',
178 b'pull',
176 179 self.path,
177 180 all=True,
178 match='hash %s' % rev,
181 match=b'hash %s' % rev,
179 182 no_test=True,
180 183 no_posthook=True,
181 external_merge='/bin/false',
184 external_merge=b'/bin/false',
182 185 repodir=self.tmppath,
183 186 )
184 187 if status:
185 if output.find('We have conflicts in') == -1:
188 if output.find(b'We have conflicts in') == -1:
186 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 191 self.checkexit(status, output)
189 192
190 193 def getchanges(self, rev, full):
191 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 196 copies = {}
194 197 changes = []
195 198 man = None
196 for elt in self.changes[rev].find('summary').getchildren():
197 if elt.tag in ('add_directory', 'remove_directory'):
199 for elt in self.changes[rev].find(b'summary').getchildren():
200 if elt.tag in (b'add_directory', b'remove_directory'):
198 201 continue
199 if elt.tag == 'move':
202 if elt.tag == b'move':
200 203 if man is None:
201 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 206 if source in man:
204 207 # File move
205 208 changes.append((source, rev))
@@ -207,11 +210,11 b' class darcs_source(common.converter_sour'
207 210 copies[dest] = source
208 211 else:
209 212 # Directory move, deduce file moves from manifest
210 source = source + '/'
213 source = source + b'/'
211 214 for f in man:
212 215 if not f.startswith(source):
213 216 continue
214 fdest = dest + '/' + f[len(source) :]
217 fdest = dest + b'/' + f[len(source) :]
215 218 changes.append((f, rev))
216 219 changes.append((fdest, rev))
217 220 copies[fdest] = f
@@ -223,7 +226,7 b' class darcs_source(common.converter_sour'
223 226
224 227 def getfile(self, name, rev):
225 228 if rev != self.lastrev:
226 raise error.Abort(_('internal calling inconsistency'))
229 raise error.Abort(_(b'internal calling inconsistency'))
227 230 path = os.path.join(self.tmppath, name)
228 231 try:
229 232 data = util.readfile(path)
@@ -232,7 +235,7 b' class darcs_source(common.converter_sour'
232 235 if inst.errno == errno.ENOENT:
233 236 return None, None
234 237 raise
235 mode = (mode & 0o111) and 'x' or ''
238 mode = (mode & 0o111) and b'x' or b''
236 239 return data, mode
237 240
238 241 def gettags(self):
@@ -30,8 +30,8 b' def rpairs(path):'
30 30 i = len(path)
31 31 while i != -1:
32 32 yield path[:i], path[i + 1 :]
33 i = path.rfind('/', 0, i)
34 yield '.', path
33 i = path.rfind(b'/', 0, i)
34 yield b'.', path
35 35
36 36
37 37 def normalize(path):
@@ -55,7 +55,7 b' class filemapper(object):'
55 55 self.targetprefixes = None
56 56 if path:
57 57 if self.parse(path):
58 raise error.Abort(_('errors in filemap'))
58 raise error.Abort(_(b'errors in filemap'))
59 59
60 60 def parse(self, path):
61 61 errs = 0
@@ -63,48 +63,48 b' class filemapper(object):'
63 63 def check(name, mapping, listname):
64 64 if not name:
65 65 self.ui.warn(
66 _('%s:%d: path to %s is missing\n')
66 _(b'%s:%d: path to %s is missing\n')
67 67 % (lex.infile, lex.lineno, listname)
68 68 )
69 69 return 1
70 70 if name in mapping:
71 71 self.ui.warn(
72 _('%s:%d: %r already in %s list\n')
72 _(b'%s:%d: %r already in %s list\n')
73 73 % (lex.infile, lex.lineno, name, listname)
74 74 )
75 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 77 self.ui.warn(
78 _('%s:%d: superfluous / in %s %r\n')
78 _(b'%s:%d: superfluous / in %s %r\n')
79 79 % (lex.infile, lex.lineno, listname, pycompat.bytestr(name))
80 80 )
81 81 return 1
82 82 return 0
83 83
84 84 lex = common.shlexer(
85 filepath=path, wordchars='!@#$%^&*()-=+[]{}|;:,./<>?'
85 filepath=path, wordchars=b'!@#$%^&*()-=+[]{}|;:,./<>?'
86 86 )
87 87 cmd = lex.get_token()
88 88 while cmd:
89 if cmd == 'include':
89 if cmd == b'include':
90 90 name = normalize(lex.get_token())
91 errs += check(name, self.exclude, 'exclude')
91 errs += check(name, self.exclude, b'exclude')
92 92 self.include[name] = name
93 elif cmd == 'exclude':
93 elif cmd == b'exclude':
94 94 name = normalize(lex.get_token())
95 errs += check(name, self.include, 'include')
96 errs += check(name, self.rename, 'rename')
95 errs += check(name, self.include, b'include')
96 errs += check(name, self.rename, b'rename')
97 97 self.exclude[name] = name
98 elif cmd == 'rename':
98 elif cmd == b'rename':
99 99 src = normalize(lex.get_token())
100 100 dest = normalize(lex.get_token())
101 errs += check(src, self.exclude, 'exclude')
101 errs += check(src, self.exclude, b'exclude')
102 102 self.rename[src] = dest
103 elif cmd == 'source':
103 elif cmd == b'source':
104 104 errs += self.parse(normalize(lex.get_token()))
105 105 else:
106 106 self.ui.warn(
107 _('%s:%d: unknown directive %r\n')
107 _(b'%s:%d: unknown directive %r\n')
108 108 % (lex.infile, lex.lineno, pycompat.bytestr(cmd))
109 109 )
110 110 errs += 1
@@ -118,7 +118,7 b' class filemapper(object):'
118 118 return mapping[pre], pre, suf
119 119 except KeyError:
120 120 pass
121 return '', name, ''
121 return b'', name, b''
122 122
123 123 def istargetfile(self, filename):
124 124 """Return true if the given target filename is covered as a destination
@@ -131,7 +131,7 b' class filemapper(object):'
131 131
132 132 # If "." is a target, then all target files are considered from the
133 133 # source.
134 if not self.targetprefixes or '.' in self.targetprefixes:
134 if not self.targetprefixes or b'.' in self.targetprefixes:
135 135 return True
136 136
137 137 filename = normalize(filename)
@@ -152,17 +152,17 b' class filemapper(object):'
152 152 if self.exclude:
153 153 exc = self.lookup(name, self.exclude)[0]
154 154 else:
155 exc = ''
155 exc = b''
156 156 if (not self.include and exc) or (len(inc) <= len(exc)):
157 157 return None
158 158 newpre, pre, suf = self.lookup(name, self.rename)
159 159 if newpre:
160 if newpre == '.':
160 if newpre == b'.':
161 161 return suf
162 162 if suf:
163 if newpre.endswith('/'):
163 if newpre.endswith(b'/'):
164 164 return newpre + suf
165 return newpre + '/' + suf
165 return newpre + b'/' + suf
166 166 return newpre
167 167 return name
168 168
@@ -204,7 +204,7 b' class filemap_source(common.converter_so'
204 204 self.seenchildren = {}
205 205 # experimental config: convert.ignoreancestorcheck
206 206 self.ignoreancestorcheck = self.ui.configbool(
207 'convert', 'ignoreancestorcheck'
207 b'convert', b'ignoreancestorcheck'
208 208 )
209 209
210 210 def before(self):
@@ -256,7 +256,7 b' class filemap_source(common.converter_so'
256 256 try:
257 257 self.origparents[rev] = self.getcommit(rev).parents
258 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 260 continue
261 261 if arg is not None:
262 262 self.children[arg] = self.children.get(arg, 0) + 1
@@ -316,7 +316,7 b' class filemap_source(common.converter_so'
316 316 try:
317 317 files = self.base.getchangedfiles(rev, i)
318 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 320 for f in files:
321 321 if self.filemapper(f):
322 322 return True
@@ -331,7 +331,7 b' class filemap_source(common.converter_so'
331 331 # close marker is significant (i.e. all of the branch ancestors weren't
332 332 # eliminated). Therefore if there *is* a close marker, getchanges()
333 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 336 def mark_not_wanted(self, rev, p):
337 337 # Mark rev as not interesting and update data structures.
@@ -363,7 +363,9 b' class filemap_source(common.converter_so'
363 363 if p in self.wantedancestors:
364 364 wrev.update(self.wantedancestors[p])
365 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 369 wrev.add(rev)
368 370 self.wantedancestors[rev] = wrev
369 371
@@ -423,7 +425,7 b' class filemap_source(common.converter_so'
423 425 self.origparents[rev] = parents
424 426
425 427 closed = False
426 if 'close' in self.commits[rev].extra:
428 if b'close' in self.commits[rev].extra:
427 429 # A branch closing revision is only useful if one of its
428 430 # parents belong to the branch being closed
429 431 pbranches = [self._cachedcommit(p).branch for p in mparents]
@@ -26,22 +26,22 b' class submodule(object):'
26 26 self.url = url
27 27
28 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 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 35 # Keys in extra fields that should not be copied if the user requests.
36 36 bannedextrakeys = {
37 37 # Git commit object built-ins.
38 'tree',
39 'parent',
40 'author',
41 'committer',
38 b'tree',
39 b'parent',
40 b'author',
41 b'committer',
42 42 # Mercurial built-ins.
43 'branch',
44 'close',
43 b'branch',
44 b'close',
45 45 }
46 46
47 47
@@ -51,7 +51,7 b' class convert_git(common.converter_sourc'
51 51 # both issues.
52 52
53 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 56 def gitrun0(self, *args, **kwargs):
57 57 return self._gitcmd(self.run0, *args, **kwargs)
@@ -70,100 +70,104 b' class convert_git(common.converter_sourc'
70 70
71 71 def __init__(self, ui, repotype, path, revs=None):
72 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 75 # Pass an absolute path to git to prevent from ever being interpreted
76 76 # as a URL
77 77 path = os.path.abspath(path)
78 78
79 if os.path.isdir(path + "/.git"):
80 path += "/.git"
81 if not os.path.exists(path + "/objects"):
79 if os.path.isdir(path + b"/.git"):
80 path += b"/.git"
81 if not os.path.exists(path + b"/objects"):
82 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 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 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 90 if similarity > 0:
91 self.simopt = ['-C%d%%' % similarity]
92 findcopiesharder = ui.configbool('convert', 'git.findcopiesharder')
91 self.simopt = [b'-C%d%%' % similarity]
92 findcopiesharder = ui.configbool(
93 b'convert', b'git.findcopiesharder'
94 )
93 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')
97 self.simopt.append('-l%d' % renamelimit)
98 renamelimit = ui.configint(b'convert', b'git.renamelimit')
99 self.simopt.append(b'-l%d' % renamelimit)
98 100 else:
99 101 self.simopt = []
100 102
101 common.checktool('git', 'git')
103 common.checktool(b'git', b'git')
102 104
103 105 self.path = path
104 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 111 banned = set(self.copyextrakeys) & bannedextrakeys
110 112 if banned:
111 113 raise error.Abort(
112 _('copying of extra key is forbidden: %s')
113 % _(', ').join(sorted(banned))
114 _(b'copying of extra key is forbidden: %s')
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 122 messagedifferent = None
119 123 messagealways = None
120 124 for a in committeractions:
121 if a.startswith(('messagedifferent', 'messagealways')):
125 if a.startswith((b'messagedifferent', b'messagealways')):
122 126 k = a
123 127 v = None
124 if '=' in a:
125 k, v = a.split('=', 1)
128 if b'=' in a:
129 k, v = a.split(b'=', 1)
126 130
127 if k == 'messagedifferent':
128 messagedifferent = v or 'committer:'
129 elif k == 'messagealways':
130 messagealways = v or 'committer:'
131 if k == b'messagedifferent':
132 messagedifferent = v or b'committer:'
133 elif k == b'messagealways':
134 messagealways = v or b'committer:'
131 135
132 136 if messagedifferent and messagealways:
133 137 raise error.Abort(
134 138 _(
135 'committeractions cannot define both '
136 'messagedifferent and messagealways'
139 b'committeractions cannot define both '
140 b'messagedifferent and messagealways'
137 141 )
138 142 )
139 143
140 dropcommitter = 'dropcommitter' in committeractions
141 replaceauthor = 'replaceauthor' in committeractions
144 dropcommitter = b'dropcommitter' in committeractions
145 replaceauthor = b'replaceauthor' in committeractions
142 146
143 147 if dropcommitter and replaceauthor:
144 148 raise error.Abort(
145 149 _(
146 'committeractions cannot define both '
147 'dropcommitter and replaceauthor'
150 b'committeractions cannot define both '
151 b'dropcommitter and replaceauthor'
148 152 )
149 153 )
150 154
151 155 if dropcommitter and messagealways:
152 156 raise error.Abort(
153 157 _(
154 'committeractions cannot define both '
155 'dropcommitter and messagealways'
158 b'committeractions cannot define both '
159 b'dropcommitter and messagealways'
156 160 )
157 161 )
158 162
159 163 if not messagedifferent and not messagealways:
160 messagedifferent = 'committer:'
164 messagedifferent = b'committer:'
161 165
162 166 self.committeractions = {
163 'dropcommitter': dropcommitter,
164 'replaceauthor': replaceauthor,
165 'messagedifferent': messagedifferent,
166 'messagealways': messagealways,
167 b'dropcommitter': dropcommitter,
168 b'replaceauthor': replaceauthor,
169 b'messagedifferent': messagedifferent,
170 b'messagealways': messagealways,
167 171 }
168 172
169 173 def after(self):
@@ -172,35 +176,38 b' class convert_git(common.converter_sourc'
172 176
173 177 def getheads(self):
174 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 182 heads = output.splitlines()
177 183 if status:
178 raise error.Abort(_('cannot retrieve git heads'))
184 raise error.Abort(_(b'cannot retrieve git heads'))
179 185 else:
180 186 heads = []
181 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 189 heads.append(rawhead[:-1])
184 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 192 return heads
187 193
188 194 def catfile(self, rev, ftype):
189 195 if rev == nodemod.nullhex:
190 196 raise IOError
191 self.catfilepipe[0].write(rev + '\n')
197 self.catfilepipe[0].write(rev + b'\n')
192 198 self.catfilepipe[0].flush()
193 199 info = self.catfilepipe[1].readline().split()
194 200 if info[1] != ftype:
195 201 raise error.Abort(
196 _('cannot read %r object at %s')
202 _(b'cannot read %r object at %s')
197 203 % (pycompat.bytestr(ftype), rev)
198 204 )
199 205 size = int(info[2])
200 206 data = self.catfilepipe[1].read(size)
201 207 if len(data) < size:
202 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 212 # read the trailing newline
206 213 self.catfilepipe[1].read(1)
@@ -209,14 +216,14 b' class convert_git(common.converter_sourc'
209 216 def getfile(self, name, rev):
210 217 if rev == nodemod.nullhex:
211 218 return None, None
212 if name == '.hgsub':
213 data = '\n'.join([m.hgsub() for m in self.submoditer()])
214 mode = ''
215 elif name == '.hgsubstate':
216 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
217 mode = ''
219 if name == b'.hgsub':
220 data = b'\n'.join([m.hgsub() for m in self.submoditer()])
221 mode = b''
222 elif name == b'.hgsubstate':
223 data = b'\n'.join([m.hgsubstate() for m in self.submoditer()])
224 mode = b''
218 225 else:
219 data = self.catfile(rev, "blob")
226 data = self.catfile(rev, b"blob")
220 227 mode = self.modecache[(name, rev)]
221 228 return data, mode
222 229
@@ -236,21 +243,23 b' class convert_git(common.converter_sourc'
236 243 c = config.config()
237 244 # Each item in .gitmodules starts with whitespace that cant be parsed
238 245 c.parse(
239 '.gitmodules',
240 '\n'.join(line.strip() for line in content.split('\n')),
246 b'.gitmodules',
247 b'\n'.join(line.strip() for line in content.split(b'\n')),
241 248 )
242 249 for sec in c.sections():
243 250 s = c[sec]
244 if 'url' in s and 'path' in s:
245 self.submodules.append(submodule(s['path'], '', s['url']))
251 if b'url' in s and b'path' in s:
252 self.submodules.append(submodule(s[b'path'], b'', s[b'url']))
246 253
247 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 258 if ret:
250 259 # This can happen if a file is in the repo that has permissions
251 260 # 160000, but there is no .gitmodules file.
252 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 263 % version
255 264 )
256 265 return
@@ -259,74 +268,76 b' class convert_git(common.converter_sourc'
259 268 self.parsegitmodules(modules)
260 269 except error.ParseError:
261 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 273 return
265 274
266 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 277 if ret:
269 278 continue
270 279 m.node = node.strip()
271 280
272 281 def getchanges(self, version, full):
273 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 284 self.modecache = {}
276 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 290 output, status = self.gitrun(*cmd)
280 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 293 changes = []
283 294 copies = {}
284 295 seen = set()
285 296 entry = None
286 297 subexists = [False]
287 298 subdeleted = [False]
288 difftree = output.split('\x00')
299 difftree = output.split(b'\x00')
289 300 lcount = len(difftree)
290 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 305 def add(entry, f, isdest):
295 306 seen.add(f)
296 307 h = entry[3]
297 p = entry[1] == "100755"
298 s = entry[1] == "120000"
299 renamesource = not isdest and entry[4][0] == 'R'
308 p = entry[1] == b"100755"
309 s = entry[1] == b"120000"
310 renamesource = not isdest and entry[4][0] == b'R'
300 311
301 if f == '.gitmodules':
312 if f == b'.gitmodules':
302 313 if skipsubmodules:
303 314 return
304 315
305 316 subexists[0] = True
306 if entry[4] == 'D' or renamesource:
317 if entry[4] == b'D' or renamesource:
307 318 subdeleted[0] = True
308 changes.append(('.hgsub', nodemod.nullhex))
319 changes.append((b'.hgsub', nodemod.nullhex))
309 320 else:
310 changes.append(('.hgsub', ''))
311 elif entry[1] == '160000' or entry[0] == ':160000':
321 changes.append((b'.hgsub', b''))
322 elif entry[1] == b'160000' or entry[0] == b':160000':
312 323 if not skipsubmodules:
313 324 subexists[0] = True
314 325 else:
315 326 if renamesource:
316 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 329 changes.append((f, h))
319 330
320 331 while i < lcount:
321 332 l = difftree[i]
322 333 i += 1
323 334 if not entry:
324 if not l.startswith(':'):
335 if not l.startswith(b':'):
325 336 continue
326 337 entry = tuple(pycompat.bytestr(p) for p in l.split())
327 338 continue
328 339 f = l
329 if entry[4][0] == 'C':
340 if entry[4][0] == b'C':
330 341 copysrc = f
331 342 copydest = difftree[i]
332 343 i += 1
@@ -336,7 +347,7 b' class convert_git(common.converter_sourc'
336 347 add(entry, f, False)
337 348 # A file can be copied multiple times, or modified and copied
338 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 351 # rename: next line is the destination
341 352 fdest = difftree[i]
342 353 i += 1
@@ -344,21 +355,21 b' class convert_git(common.converter_sourc'
344 355 add(entry, fdest, True)
345 356 # .gitmodules isn't imported at all, so it being copied to
346 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 359 copies[fdest] = f
349 360 entry = None
350 361
351 362 if subexists[0]:
352 363 if subdeleted[0]:
353 changes.append(('.hgsubstate', nodemod.nullhex))
364 changes.append((b'.hgsubstate', nodemod.nullhex))
354 365 else:
355 366 self.retrievegitmodules(version)
356 changes.append(('.hgsubstate', ''))
367 changes.append((b'.hgsubstate', b''))
357 368 return (changes, copies, set())
358 369
359 370 def getcommit(self, version):
360 c = self.catfile(version, "commit") # read the commit hash
361 end = c.find("\n\n")
371 c = self.catfile(version, b"commit") # read the commit hash
372 end = c.find(b"\n\n")
362 373 message = c[end + 2 :]
363 374 message = self.recode(message)
364 375 l = c[:end].splitlines()
@@ -366,43 +377,43 b' class convert_git(common.converter_sourc'
366 377 author = committer = None
367 378 extra = {}
368 379 for e in l[1:]:
369 n, v = e.split(" ", 1)
370 if n == "author":
380 n, v = e.split(b" ", 1)
381 if n == b"author":
371 382 p = v.split()
372 383 tm, tz = p[-2:]
373 author = " ".join(p[:-2])
374 if author[0] == "<":
384 author = b" ".join(p[:-2])
385 if author[0] == b"<":
375 386 author = author[1:-1]
376 387 author = self.recode(author)
377 if n == "committer":
388 if n == b"committer":
378 389 p = v.split()
379 390 tm, tz = p[-2:]
380 committer = " ".join(p[:-2])
381 if committer[0] == "<":
391 committer = b" ".join(p[:-2])
392 if committer[0] == b"<":
382 393 committer = committer[1:-1]
383 394 committer = self.recode(committer)
384 if n == "parent":
395 if n == b"parent":
385 396 parents.append(v)
386 397 if n in self.copyextrakeys:
387 398 extra[n] = v
388 399
389 if self.committeractions['dropcommitter']:
400 if self.committeractions[b'dropcommitter']:
390 401 committer = None
391 elif self.committeractions['replaceauthor']:
402 elif self.committeractions[b'replaceauthor']:
392 403 author = committer
393 404
394 405 if committer:
395 messagealways = self.committeractions['messagealways']
396 messagedifferent = self.committeractions['messagedifferent']
406 messagealways = self.committeractions[b'messagealways']
407 messagedifferent = self.committeractions[b'messagedifferent']
397 408 if messagealways:
398 message += '\n%s %s\n' % (messagealways, committer)
409 message += b'\n%s %s\n' % (messagealways, committer)
399 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 414 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
404 date = tm + " " + (b"%d" % tz)
405 saverev = self.ui.configbool('convert', 'git.saverev')
415 date = tm + b" " + (b"%d" % tz)
416 saverev = self.ui.configbool(b'convert', b'git.saverev')
406 417
407 418 c = common.commit(
408 419 parents=parents,
@@ -416,27 +427,27 b' class convert_git(common.converter_sourc'
416 427 return c
417 428
418 429 def numcommits(self):
419 output, ret = self.gitrunlines('rev-list', '--all')
430 output, ret = self.gitrunlines(b'rev-list', b'--all')
420 431 if ret:
421 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 435 return len(output)
425 436
426 437 def gettags(self):
427 438 tags = {}
428 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 442 if status:
432 raise error.Abort(_('cannot read tags from %s') % self.path)
433 prefix = 'refs/tags/'
443 raise error.Abort(_(b'cannot read tags from %s') % self.path)
444 prefix = b'refs/tags/'
434 445
435 446 # Build complete list of tags, both annotated and bare ones
436 447 for line in output:
437 448 line = line.strip()
438 if line.startswith("error:") or line.startswith("fatal:"):
439 raise error.Abort(_('cannot read tags from %s') % self.path)
449 if line.startswith(b"error:") or line.startswith(b"fatal:"):
450 raise error.Abort(_(b'cannot read tags from %s') % self.path)
440 451 node, tag = line.split(None, 1)
441 452 if not tag.startswith(prefix):
442 453 continue
@@ -444,10 +455,10 b' class convert_git(common.converter_sourc'
444 455
445 456 # Filter out tag objects for annotated tag refs
446 457 for tag in alltags:
447 if tag.endswith('^{}'):
458 if tag.endswith(b'^{}'):
448 459 tags[tag[:-3]] = alltags[tag]
449 460 else:
450 if tag + '^{}' in alltags:
461 if tag + b'^{}' in alltags:
451 462 continue
452 463 else:
453 464 tags[tag] = alltags[tag]
@@ -458,28 +469,28 b' class convert_git(common.converter_sourc'
458 469 changes = []
459 470 if i is None:
460 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 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 476 for l in output:
466 if "\t" not in l:
477 if b"\t" not in l:
467 478 continue
468 m, f = l[:-1].split("\t")
479 m, f = l[:-1].split(b"\t")
469 480 changes.append(f)
470 481 else:
471 482 output, status = self.gitrunlines(
472 'diff-tree',
473 '--name-only',
474 '--root',
475 '-r',
483 b'diff-tree',
484 b'--name-only',
485 b'--root',
486 b'-r',
476 487 version,
477 '%s^%d' % (version, i + 1),
478 '--',
488 b'%s^%d' % (version, i + 1),
489 b'--',
479 490 )
480 491 if status:
481 raise error.Abort(_('cannot read changes in %s') % version)
482 changes = [f.rstrip('\n') for f in output]
492 raise error.Abort(_(b'cannot read changes in %s') % version)
493 changes = [f.rstrip(b'\n') for f in output]
483 494
484 495 return changes
485 496
@@ -487,19 +498,19 b' class convert_git(common.converter_sourc'
487 498 bookmarks = {}
488 499
489 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 502 reftypes = [
492 503 # (git prefix, hg prefix)
493 ('refs/remotes/origin/', remoteprefix + '/'),
494 ('refs/heads/', ''),
504 (b'refs/remotes/origin/', remoteprefix + b'/'),
505 (b'refs/heads/', b''),
495 506 ]
496 507
497 508 exclude = {
498 'refs/remotes/origin/HEAD',
509 b'refs/remotes/origin/HEAD',
499 510 }
500 511
501 512 try:
502 output, status = self.gitrunlines('show-ref')
513 output, status = self.gitrunlines(b'show-ref')
503 514 for line in output:
504 515 line = line.strip()
505 516 rev, name = line.split(None, 1)
@@ -507,13 +518,13 b' class convert_git(common.converter_sourc'
507 518 for gitprefix, hgprefix in reftypes:
508 519 if not name.startswith(gitprefix) or name in exclude:
509 520 continue
510 name = '%s%s' % (hgprefix, name[len(gitprefix) :])
521 name = b'%s%s' % (hgprefix, name[len(gitprefix) :])
511 522 bookmarks[name] = rev
512 523 except Exception:
513 524 pass
514 525
515 526 return bookmarks
516 527
517 def checkrevformat(self, revstr, mapname='splicemap'):
528 def checkrevformat(self, revstr, mapname=b'splicemap'):
518 529 """ git revision string is a 40 byte hex """
519 530 self.checkhexformat(revstr, mapname)
@@ -31,9 +31,9 b' class gnuarch_source(common.converter_so'
31 31 class gnuarch_rev(object):
32 32 def __init__(self, rev):
33 33 self.rev = rev
34 self.summary = ''
34 self.summary = b''
35 35 self.date = None
36 self.author = ''
36 self.author = b''
37 37 self.continuationof = None
38 38 self.add_files = []
39 39 self.mod_files = []
@@ -44,20 +44,20 b' class gnuarch_source(common.converter_so'
44 44 def __init__(self, ui, repotype, path, revs=None):
45 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 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 52 # Could use checktool, but we want to check for baz or tla.
53 53 self.execmd = None
54 if procutil.findexe('baz'):
55 self.execmd = 'baz'
54 if procutil.findexe(b'baz'):
55 self.execmd = b'baz'
56 56 else:
57 if procutil.findexe('tla'):
58 self.execmd = 'tla'
57 if procutil.findexe(b'tla'):
58 self.execmd = b'tla'
59 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 62 common.commandline.__init__(self, ui, self.execmd)
63 63
@@ -76,19 +76,19 b' class gnuarch_source(common.converter_so'
76 76 def before(self):
77 77 # Get registered archives
78 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':
83 output = self.run0('tree-version', self.path)
82 if self.execmd == b'tla':
83 output = self.run0(b'tree-version', self.path)
84 84 else:
85 output = self.run0('tree-version', '-d', self.path)
85 output = self.run0(b'tree-version', b'-d', self.path)
86 86 self.treeversion = output.strip()
87 87
88 88 # Get name of temporary directory
89 version = self.treeversion.split('/')
89 version = self.treeversion.split(b'/')
90 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 94 # Generate parents dictionary
@@ -96,23 +96,25 b' class gnuarch_source(common.converter_so'
96 96 treeversion = self.treeversion
97 97 child = None
98 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 102 if archive not in self.archives:
103 103 self.ui.status(
104 104 _(
105 'tree analysis stopped because it points to '
106 'an unregistered archive %s...\n'
105 b'tree analysis stopped because it points to '
106 b'an unregistered archive %s...\n'
107 107 )
108 108 % archive
109 109 )
110 110 break
111 111
112 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 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 120 # No new iteration unless a revision has a continuation-of header
@@ -124,9 +126,9 b' class gnuarch_source(common.converter_so'
124 126 self.parents[rev] = []
125 127
126 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 130 if status:
129 catlog = self.run0('cat-archive-log', rev)
131 catlog = self.run0(b'cat-archive-log', rev)
130 132 self._parsecatlog(catlog, rev)
131 133
132 134 # Populate the parents map
@@ -140,18 +142,18 b' class gnuarch_source(common.converter_so'
140 142 # or if we have to 'jump' to a different treeversion given
141 143 # by the continuation-of header.
142 144 if self.changes[rev].continuationof:
143 treeversion = '--'.join(
144 self.changes[rev].continuationof.split('--')[:-1]
145 treeversion = b'--'.join(
146 self.changes[rev].continuationof.split(b'--')[:-1]
145 147 )
146 148 break
147 149
148 150 # If we reached a base-0 revision w/o any continuation-of
149 151 # header, it means the tree history ends here.
150 if rev[-6:] == 'base-0':
152 if rev[-6:] == b'base-0':
151 153 break
152 154
153 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 157 shutil.rmtree(self.tmppath, ignore_errors=True)
156 158
157 159 def getheads(self):
@@ -159,7 +161,7 b' class gnuarch_source(common.converter_so'
159 161
160 162 def getfile(self, name, rev):
161 163 if rev != self.lastrev:
162 raise error.Abort(_('internal calling inconsistency'))
164 raise error.Abort(_(b'internal calling inconsistency'))
163 165
164 166 if not os.path.lexists(os.path.join(self.tmppath, name)):
165 167 return None, None
@@ -168,7 +170,7 b' class gnuarch_source(common.converter_so'
168 170
169 171 def getchanges(self, rev, full):
170 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 174 self._update(rev)
173 175 changes = []
174 176 copies = {}
@@ -214,14 +216,14 b' class gnuarch_source(common.converter_so'
214 216 cmdline = [self.execmd, cmd]
215 217 cmdline += args
216 218 cmdline = [procutil.shellquote(arg) for arg in cmdline]
217 cmdline += ['>', os.devnull, '2>', os.devnull]
218 cmdline = procutil.quotecommand(' '.join(cmdline))
219 self.ui.debug(cmdline, '\n')
219 cmdline += [b'>', os.devnull, b'2>', os.devnull]
220 cmdline = procutil.quotecommand(b' '.join(cmdline))
221 self.ui.debug(cmdline, b'\n')
220 222 return os.system(pycompat.rapply(procutil.tonativestr, cmdline))
221 223
222 224 def _update(self, rev):
223 self.ui.debug('applying revision %s...\n' % rev)
224 changeset, status = self.runlines('replay', '-d', self.tmppath, rev)
225 self.ui.debug(b'applying revision %s...\n' % rev)
226 changeset, status = self.runlines(b'replay', b'-d', self.tmppath, rev)
225 227 if status:
226 228 # Something went wrong while merging (baz or tla
227 229 # issue?), get latest revision and try from there
@@ -230,7 +232,7 b' class gnuarch_source(common.converter_so'
230 232 else:
231 233 old_rev = self.parents[rev][0]
232 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 237 self._parsechangeset(changeset, rev)
236 238
@@ -239,16 +241,16 b' class gnuarch_source(common.converter_so'
239 241 if stat.S_ISLNK(mode):
240 242 data = util.readlink(os.path.join(self.tmppath, name))
241 243 if mode:
242 mode = 'l'
244 mode = b'l'
243 245 else:
244 mode = ''
246 mode = b''
245 247 else:
246 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 250 return data, mode
249 251
250 252 def _exclude(self, name):
251 exclude = ['{arch}', '.arch-ids', '.arch-inventory']
253 exclude = [b'{arch}', b'.arch-ids', b'.arch-inventory']
252 254 for exc in exclude:
253 255 if name.find(exc) != -1:
254 256 return True
@@ -282,15 +284,15 b' class gnuarch_source(common.converter_so'
282 284 return changes, copies
283 285
284 286 def _obtainrevision(self, rev):
285 self.ui.debug('obtaining revision %s...\n' % rev)
286 output = self._execute('get', rev, self.tmppath)
287 self.ui.debug(b'obtaining revision %s...\n' % rev)
288 output = self._execute(b'get', rev, self.tmppath)
287 289 self.checkexit(output)
288 self.ui.debug('analyzing revision %s...\n' % rev)
290 self.ui.debug(b'analyzing revision %s...\n' % rev)
289 291 files = self._readcontents(self.tmppath)
290 292 self.changes[rev].add_files += files
291 293
292 294 def _stripbasepath(self, path):
293 if path.startswith('./'):
295 if path.startswith(b'./'):
294 296 return path[2:]
295 297 return path
296 298
@@ -300,73 +302,73 b' class gnuarch_source(common.converter_so'
300 302
301 303 # Commit date
302 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 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 311 # Commit description
310 self.changes[rev].summary = '\n\n'.join(
311 (catlog['Summary'], catlog.get_payload())
312 self.changes[rev].summary = b'\n\n'.join(
313 (catlog[b'Summary'], catlog.get_payload())
312 314 )
313 315 self.changes[rev].summary = self.recode(self.changes[rev].summary)
314 316
315 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 319 self.changes[rev].continuationof = self.recode(
318 catlog['Continuation-of']
320 catlog[b'Continuation-of']
319 321 )
320 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 325 def _parsechangeset(self, data, rev):
324 326 for l in data:
325 327 l = l.strip()
326 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 330 file = self._stripbasepath(l[1:].strip())
329 331 if not self._exclude(file):
330 332 self.changes[rev].add_files.append(file)
331 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 335 file = self._stripbasepath(l[1:].strip())
334 336 if not self._exclude(file):
335 337 self.changes[rev].del_files.append(file)
336 338 # Modified binary file
337 elif l.startswith('Mb'):
339 elif l.startswith(b'Mb'):
338 340 file = self._stripbasepath(l[2:].strip())
339 341 if not self._exclude(file):
340 342 self.changes[rev].mod_files.append(file)
341 343 # Modified link
342 elif l.startswith('M->'):
344 elif l.startswith(b'M->'):
343 345 file = self._stripbasepath(l[3:].strip())
344 346 if not self._exclude(file):
345 347 self.changes[rev].mod_files.append(file)
346 348 # Modified file
347 elif l.startswith('M'):
349 elif l.startswith(b'M'):
348 350 file = self._stripbasepath(l[1:].strip())
349 351 if not self._exclude(file):
350 352 self.changes[rev].mod_files.append(file)
351 353 # Renamed file (or link)
352 elif l.startswith('=>'):
353 files = l[2:].strip().split(' ')
354 elif l.startswith(b'=>'):
355 files = l[2:].strip().split(b' ')
354 356 if len(files) == 1:
355 files = l[2:].strip().split('\t')
357 files = l[2:].strip().split(b'\t')
356 358 src = self._stripbasepath(files[0])
357 359 dst = self._stripbasepath(files[1])
358 360 if not self._exclude(src) and not self._exclude(dst):
359 361 self.changes[rev].ren_files[src] = dst
360 362 # Conversion from file to link or from link to file (modified)
361 elif l.startswith('ch'):
363 elif l.startswith(b'ch'):
362 364 file = self._stripbasepath(l[2:].strip())
363 365 if not self._exclude(file):
364 366 self.changes[rev].mod_files.append(file)
365 367 # Renamed directory
366 elif l.startswith('/>'):
367 dirs = l[2:].strip().split(' ')
368 elif l.startswith(b'/>'):
369 dirs = l[2:].strip().split(b' ')
368 370 if len(dirs) == 1:
369 dirs = l[2:].strip().split('\t')
371 dirs = l[2:].strip().split(b'\t')
370 372 src = self._stripbasepath(dirs[0])
371 373 dst = self._stripbasepath(dirs[1])
372 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 51 class mercurial_sink(common.converter_sink):
52 52 def __init__(self, ui, repotype, path):
53 53 common.converter_sink.__init__(self, ui, repotype, path)
54 self.branchnames = ui.configbool('convert', 'hg.usebranchnames')
55 self.clonebranches = ui.configbool('convert', 'hg.clonebranches')
56 self.tagsbranch = ui.config('convert', 'hg.tagsbranch')
54 self.branchnames = ui.configbool(b'convert', b'hg.usebranchnames')
55 self.clonebranches = ui.configbool(b'convert', b'hg.clonebranches')
56 self.tagsbranch = ui.config(b'convert', b'hg.tagsbranch')
57 57 self.lastbranch = None
58 58 if os.path.isdir(path) and len(os.listdir(path)) > 0:
59 59 try:
60 60 self.repo = hg.repository(self.ui, path)
61 61 if not self.repo.local():
62 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 65 except error.RepoError as err:
66 66 ui.traceback()
67 67 raise NoRepo(err.args[0])
68 68 else:
69 69 try:
70 ui.status(_('initializing destination %s repository\n') % path)
70 ui.status(_(b'initializing destination %s repository\n') % path)
71 71 self.repo = hg.repository(self.ui, path, create=True)
72 72 if not self.repo.local():
73 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 76 self.created.append(path)
77 77 except error.RepoError:
78 78 ui.traceback()
79 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 82 self.lock = None
83 83 self.wlock = None
@@ -85,22 +85,22 b' class mercurial_sink(common.converter_si'
85 85 self.subrevmaps = {}
86 86
87 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 89 self.wlock = self.repo.wlock()
90 90 self.lock = self.repo.lock()
91 91
92 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 94 if self.lock:
95 95 self.lock.release()
96 96 if self.wlock:
97 97 self.wlock.release()
98 98
99 99 def revmapfile(self):
100 return self.repo.vfs.join("shamap")
100 return self.repo.vfs.join(b"shamap")
101 101
102 102 def authorfile(self):
103 return self.repo.vfs.join("authormap")
103 return self.repo.vfs.join(b"authormap")
104 104
105 105 def setbranch(self, branch, pbranches):
106 106 if not self.clonebranches:
@@ -109,8 +109,8 b' class mercurial_sink(common.converter_si'
109 109 setbranch = branch != self.lastbranch
110 110 self.lastbranch = branch
111 111 if not branch:
112 branch = 'default'
113 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
112 branch = b'default'
113 pbranches = [(b[0], b[1] and b[1] or b'default') for b in pbranches]
114 114
115 115 branchpath = os.path.join(self.path, branch)
116 116 if setbranch:
@@ -135,7 +135,9 b' class mercurial_sink(common.converter_si'
135 135 for pbranch, heads in sorted(missings.iteritems()):
136 136 pbranchpath = os.path.join(self.path, pbranch)
137 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 141 exchange.pull(
140 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 146 def _rewritetags(self, source, revmap, data):
145 147 fp = stringio()
146 148 for line in data.splitlines():
147 s = line.split(' ', 1)
149 s = line.split(b' ', 1)
148 150 if len(s) != 2:
149 self.ui.warn(_('invalid tag entry: "%s"\n') % line)
150 fp.write('%s\n' % line) # Bogus, but keep for hash stability
151 self.ui.warn(_(b'invalid tag entry: "%s"\n') % line)
152 fp.write(b'%s\n' % line) # Bogus, but keep for hash stability
151 153 continue
152 154 revid = revmap.get(source.lookuprev(s[0]))
153 155 if not revid:
@@ -155,16 +157,16 b' class mercurial_sink(common.converter_si'
155 157 revid = s[0]
156 158 else:
157 159 # missing, but keep for hash stability
158 self.ui.warn(_('missing tag entry: "%s"\n') % line)
159 fp.write('%s\n' % line)
160 self.ui.warn(_(b'missing tag entry: "%s"\n') % line)
161 fp.write(b'%s\n' % line)
160 162 continue
161 fp.write('%s %s\n' % (revid, s[1]))
163 fp.write(b'%s %s\n' % (revid, s[1]))
162 164 return fp.getvalue()
163 165
164 166 def _rewritesubstate(self, source, data):
165 167 fp = stringio()
166 168 for line in data.splitlines():
167 s = line.split(' ', 1)
169 s = line.split(b' ', 1)
168 170 if len(s) != 2:
169 171 continue
170 172
@@ -174,7 +176,7 b' class mercurial_sink(common.converter_si'
174 176 revmap = self.subrevmaps.get(subpath)
175 177 if revmap is None:
176 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 181 self.subrevmaps[subpath] = revmap
180 182
@@ -182,9 +184,9 b' class mercurial_sink(common.converter_si'
182 184 # need to be converted, in which case they can be cloned
183 185 # into place instead of converted. Therefore, only warn
184 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 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 191 if self.repo.wvfs.exists(sub):
190 192 self.ui.warn(msg % subpath)
@@ -193,13 +195,13 b' class mercurial_sink(common.converter_si'
193 195 if not newid:
194 196 if len(revmap) > 0:
195 197 self.ui.warn(
196 _("%s is missing from %s/.hg/shamap\n")
198 _(b"%s is missing from %s/.hg/shamap\n")
197 199 % (revid, subpath)
198 200 )
199 201 else:
200 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 206 return fp.getvalue()
205 207
@@ -232,16 +234,16 b' class mercurial_sink(common.converter_si'
232 234
233 235 # If the file requires actual merging, abort. We don't have enough
234 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 238 raise error.Abort(
237 239 _(
238 "unable to convert merge commit "
239 "since target parents do not merge cleanly (file "
240 "%s, parents %s and %s)"
240 b"unable to convert merge commit "
241 b"since target parents do not merge cleanly (file "
242 b"%s, parents %s and %s)"
241 243 )
242 244 % (file, p1ctx, p2ctx)
243 245 )
244 elif action == 'k':
246 elif action == b'k':
245 247 # 'keep' means nothing changed from p1
246 248 continue
247 249 else:
@@ -255,7 +257,7 b' class mercurial_sink(common.converter_si'
255 257
256 258 def getfilectx(repo, memctx, f):
257 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 261 try:
260 262 return p2ctx[f]
261 263 except error.ManifestLookupError:
@@ -269,17 +271,17 b' class mercurial_sink(common.converter_si'
269 271 data, mode = source.getfile(f, v)
270 272 if data is None:
271 273 return None
272 if f == '.hgtags':
274 if f == b'.hgtags':
273 275 data = self._rewritetags(source, revmap, data)
274 if f == '.hgsubstate':
276 if f == b'.hgsubstate':
275 277 data = self._rewritesubstate(source, data)
276 278 return context.memfilectx(
277 279 self.repo,
278 280 memctx,
279 281 f,
280 282 data,
281 'l' in mode,
282 'x' in mode,
283 b'l' in mode,
284 b'x' in mode,
283 285 copies.get(f),
284 286 )
285 287
@@ -310,15 +312,15 b' class mercurial_sink(common.converter_si'
310 312
311 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 316 if sourcename:
315 extra['convert_source'] = sourcename
317 extra[b'convert_source'] = sourcename
316 318
317 319 for label in (
318 'source',
319 'transplant_source',
320 'rebase_source',
321 'intermediate-source',
320 b'source',
321 b'transplant_source',
322 b'rebase_source',
323 b'intermediate-source',
322 324 ):
323 325 node = extra.get(label)
324 326
@@ -326,20 +328,20 b' class mercurial_sink(common.converter_si'
326 328 continue
327 329
328 330 # Only transplant stores its reference in binary
329 if label == 'transplant_source':
331 if label == b'transplant_source':
330 332 node = nodemod.hex(node)
331 333
332 334 newrev = revmap.get(node)
333 335 if newrev is not None:
334 if label == 'transplant_source':
336 if label == b'transplant_source':
335 337 newrev = nodemod.bin(newrev)
336 338
337 339 extra[label] = newrev
338 340
339 341 if self.branchnames and commit.branch:
340 extra['branch'] = commit.branch
342 extra[b'branch'] = commit.branch
341 343 if commit.rev and commit.saverev:
342 extra['convert_revision'] = commit.rev
344 extra[b'convert_revision'] = commit.rev
343 345
344 346 while parents:
345 347 p1 = p2
@@ -373,14 +375,14 b' class mercurial_sink(common.converter_si'
373 375 # We won't know if the conversion changes the node until after the
374 376 # commit, so copy the source's phase for now.
375 377 self.repo.ui.setconfig(
376 'phases',
377 'new-commit',
378 b'phases',
379 b'new-commit',
378 380 phases.phasenames[commit.phase],
379 'convert',
381 b'convert',
380 382 )
381 383
382 with self.repo.transaction("convert") as tr:
383 if self.repo.ui.config('convert', 'hg.preserve-hash'):
384 with self.repo.transaction(b"convert") as tr:
385 if self.repo.ui.config(b'convert', b'hg.preserve-hash'):
384 386 origctx = commit.ctx
385 387 else:
386 388 origctx = None
@@ -396,15 +398,15 b' class mercurial_sink(common.converter_si'
396 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 402 p2 = node
401 403
402 404 if self.filemapmode and nparents == 1:
403 405 man = self.repo.manifestlog.getstorage(b'')
404 406 mnode = self.repo.changelog.read(nodemod.bin(p2))[0]
405 closed = 'close' in commit.extra
407 closed = b'close' in commit.extra
406 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 410 self.repo.rollback(force=True)
409 411 return parent
410 412 return p2
@@ -416,13 +418,13 b' class mercurial_sink(common.converter_si'
416 418 oldlines = set()
417 419 for branch, heads in self.repo.branchmap().iteritems():
418 420 for h in heads:
419 if '.hgtags' in self.repo[h]:
421 if b'.hgtags' in self.repo[h]:
420 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 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 428 if newlines == oldlines:
427 429 return None, None
428 430
@@ -430,12 +432,12 b' class mercurial_sink(common.converter_si'
430 432 oldtags = set()
431 433 newtags = set()
432 434 for line in oldlines:
433 s = line.strip().split(' ', 1)
435 s = line.strip().split(b' ', 1)
434 436 if len(s) != 2:
435 437 continue
436 438 oldtags.add(s[1])
437 439 for line in newlines:
438 s = line.strip().split(' ', 1)
440 s = line.strip().split(b' ', 1)
439 441 if len(s) != 2:
440 442 continue
441 443 if s[1] not in oldtags:
@@ -444,21 +446,21 b' class mercurial_sink(common.converter_si'
444 446 if not newtags:
445 447 return None, None
446 448
447 data = "".join(newlines)
449 data = b"".join(newlines)
448 450
449 451 def getfilectx(repo, memctx, f):
450 452 return context.memfilectx(repo, memctx, f, data, False, False, None)
451 453
452 self.ui.status(_("updating tags\n"))
453 date = "%d 0" % int(time.mktime(time.gmtime()))
454 extra = {'branch': self.tagsbranch}
454 self.ui.status(_(b"updating tags\n"))
455 date = b"%d 0" % int(time.mktime(time.gmtime()))
456 extra = {b'branch': self.tagsbranch}
455 457 ctx = context.memctx(
456 458 self.repo,
457 459 (tagparent, None),
458 "update tags",
459 [".hgtags"],
460 b"update tags",
461 [b".hgtags"],
460 462 getfilectx,
461 "convert-repo",
463 b"convert-repo",
462 464 date,
463 465 extra,
464 466 )
@@ -475,8 +477,8 b' class mercurial_sink(common.converter_si'
475 477 try:
476 478 wlock = self.repo.wlock()
477 479 lock = self.repo.lock()
478 tr = self.repo.transaction('bookmark')
479 self.ui.status(_("updating bookmarks\n"))
480 tr = self.repo.transaction(b'bookmark')
481 self.ui.status(_(b"updating bookmarks\n"))
480 482 destmarks = self.repo._bookmarks
481 483 changes = [
482 484 (bookmark, nodemod.bin(updatedbookmark[bookmark]))
@@ -495,9 +497,9 b' class mercurial_sink(common.converter_si'
495 497 if rev not in self.repo and self.clonebranches:
496 498 raise error.Abort(
497 499 _(
498 'revision %s not found in destination '
499 'repository (lookups with clonebranches=true '
500 'are not implemented)'
500 b'revision %s not found in destination '
501 b'repository (lookups with clonebranches=true '
502 b'are not implemented)'
501 503 )
502 504 % rev
503 505 )
@@ -507,9 +509,9 b' class mercurial_sink(common.converter_si'
507 509 class mercurial_source(common.converter_source):
508 510 def __init__(self, ui, repotype, path, revs=None):
509 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 513 self.ignored = set()
512 self.saverev = ui.configbool('convert', 'hg.saverev')
514 self.saverev = ui.configbool(b'convert', b'hg.saverev')
513 515 try:
514 516 self.repo = hg.repository(self.ui, path)
515 517 # try to provoke an exception if this isn't really a hg
@@ -518,21 +520,21 b' class mercurial_source(common.converter_'
518 520 raise error.RepoError
519 521 except error.RepoError:
520 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 524 self.lastrev = None
523 525 self.lastctx = None
524 526 self._changescache = None, None
525 527 self.convertfp = None
526 528 # Restrict converted revisions to startrev descendants
527 startnode = ui.config('convert', 'hg.startrev')
528 hgrevs = ui.config('convert', 'hg.revs')
529 startnode = ui.config(b'convert', b'hg.startrev')
530 hgrevs = ui.config(b'convert', b'hg.revs')
529 531 if hgrevs is None:
530 532 if startnode is not None:
531 533 try:
532 534 startnode = self.repo.lookup(startnode)
533 535 except error.RepoError:
534 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 539 startrev = self.repo.changelog.rev(startnode)
538 540 children = {startnode: 1}
@@ -548,7 +550,10 b' class mercurial_source(common.converter_'
548 550 else:
549 551 if revs or startnode is not None:
550 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 558 nodes = set()
554 559 parents = set()
@@ -635,7 +640,7 b' class mercurial_source(common.converter_'
635 640 if not self.ignoreerrors:
636 641 raise
637 642 self.ignored.add(name)
638 self.ui.warn(_('ignoring: %s\n') % e)
643 self.ui.warn(_(b'ignoring: %s\n') % e)
639 644 return copies
640 645
641 646 def getcommit(self, rev):
@@ -647,7 +652,7 b' class mercurial_source(common.converter_'
647 652
648 653 return common.commit(
649 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 656 desc=ctx.description(),
652 657 rev=crev,
653 658 parents=parents,
@@ -668,7 +673,7 b' class mercurial_source(common.converter_'
668 673 tags = [
669 674 t
670 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 678 return dict(
674 679 [
@@ -696,15 +701,15 b' class mercurial_source(common.converter_'
696 701
697 702 def converted(self, rev, destrev):
698 703 if self.convertfp is None:
699 self.convertfp = open(self.repo.vfs.join('shamap'), 'ab')
700 self.convertfp.write(util.tonativeeol('%s %s\n' % (destrev, rev)))
704 self.convertfp = open(self.repo.vfs.join(b'shamap'), b'ab')
705 self.convertfp.write(util.tonativeeol(b'%s %s\n' % (destrev, rev)))
701 706 self.convertfp.flush()
702 707
703 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 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 714 def hasnativeorder(self):
710 715 return True
@@ -721,6 +726,6 b' class mercurial_source(common.converter_'
721 726 def getbookmarks(self):
722 727 return bookmarks.listbookmarks(self.repo)
723 728
724 def checkrevformat(self, revstr, mapname='splicemap'):
729 def checkrevformat(self, revstr, mapname=b'splicemap'):
725 730 """ Mercurial, revision string is a 40 byte hex """
726 731 self.checkhexformat(revstr, mapname)
@@ -26,11 +26,11 b' class monotone_source(common.converter_s'
26 26 if revs and len(revs) > 1:
27 27 raise error.Abort(
28 28 _(
29 'monotone source does not support specifying '
30 'multiple revs'
29 b'monotone source does not support specifying '
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 35 self.ui = ui
36 36 self.path = path
@@ -38,17 +38,17 b' class monotone_source(common.converter_s'
38 38 self.revs = revs
39 39
40 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 44 # Could be a monotone repository (SQLite db file)
45 45 try:
46 f = open(path, 'rb')
46 f = open(path, b'rb')
47 47 header = f.read(16)
48 48 f.close()
49 49 except IOError:
50 header = ''
51 if header != 'SQLite format 3\x00':
50 header = b''
51 if header != b'SQLite format 3\x00':
52 52 raise norepo
53 53
54 54 # regular expressions for parsing monotone output
@@ -58,24 +58,26 b' class monotone_source(common.converter_s'
58 58 revision = br'\s+\[(\w+)\]\s*'
59 59 lines = br'(?:.|\n)+'
60 60
61 self.dir_re = re.compile(space + "dir" + name)
62 self.file_re = re.compile(space + "file" + name + "content" + revision)
61 self.dir_re = re.compile(space + b"dir" + name)
62 self.file_re = re.compile(
63 space + b"file" + name + b"content" + revision
64 )
63 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 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)
70 self.delete_re = re.compile(space + "delete" + name)
71 self.tag_re = re.compile(space + "tag" + name + "revision" + revision)
71 self.rename_re = re.compile(space + b"rename" + name + b"to" + name)
72 self.delete_re = re.compile(space + b"delete" + name)
73 self.tag_re = re.compile(space + b"tag" + name + b"revision" + revision)
72 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 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 83 # cached data
@@ -84,7 +86,7 b' class monotone_source(common.converter_s'
84 86 self.files = None
85 87 self.dirs = None
86 88
87 common.checktool('mtn', abort=False)
89 common.checktool(b'mtn', abort=False)
88 90
89 91 def mtnrun(self, *args, **kwargs):
90 92 if self.automatestdio:
@@ -94,27 +96,27 b' class monotone_source(common.converter_s'
94 96
95 97 def mtnrunsingle(self, *args, **kwargs):
96 98 kwargs[r'd'] = self.path
97 return self.run0('automate', *args, **kwargs)
99 return self.run0(b'automate', *args, **kwargs)
98 100
99 101 def mtnrunstdio(self, *args, **kwargs):
100 102 # Prepare the command in automate stdio format
101 103 kwargs = pycompat.byteskwargs(kwargs)
102 104 command = []
103 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 107 if v:
106 command.append("%d:%s" % (len(v), v))
108 command.append(b"%d:%s" % (len(v), v))
107 109 if command:
108 command.insert(0, 'o')
109 command.append('e')
110 command.insert(0, b'o')
111 command.append(b'e')
110 112
111 command.append('l')
113 command.append(b'l')
112 114 for arg in args:
113 command.append("%d:%s" % (len(arg), arg))
114 command.append('e')
115 command = ''.join(command)
115 command.append(b"%d:%s" % (len(arg), arg))
116 command.append(b'e')
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 120 self.mtnwritefp.write(command)
119 121 self.mtnwritefp.flush()
120 122
@@ -122,42 +124,44 b' class monotone_source(common.converter_s'
122 124
123 125 def mtnstdioreadpacket(self):
124 126 read = None
125 commandnbr = ''
126 while read != ':':
127 commandnbr = b''
128 while read != b':':
127 129 read = self.mtnreadfp.read(1)
128 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 132 commandnbr += read
131 133 commandnbr = commandnbr[:-1]
132 134
133 135 stream = self.mtnreadfp.read(1)
134 if stream not in 'mewptl':
135 raise error.Abort(_('bad mtn packet - bad stream type %s') % stream)
136 if stream not in b'mewptl':
137 raise error.Abort(
138 _(b'bad mtn packet - bad stream type %s') % stream
139 )
136 140
137 141 read = self.mtnreadfp.read(1)
138 if read != ':':
139 raise error.Abort(_('bad mtn packet - no divider before size'))
142 if read != b':':
143 raise error.Abort(_(b'bad mtn packet - no divider before size'))
140 144
141 145 read = None
142 lengthstr = ''
143 while read != ':':
146 lengthstr = b''
147 while read != b':':
144 148 read = self.mtnreadfp.read(1)
145 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 151 lengthstr += read
148 152 try:
149 153 length = pycompat.long(lengthstr[:-1])
150 154 except TypeError:
151 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 159 read = self.mtnreadfp.read(length)
156 160 if len(read) != length:
157 161 raise error.Abort(
158 162 _(
159 "bad mtn packet - unable to read full packet "
160 "read %s of %s"
163 b"bad mtn packet - unable to read full packet "
164 b"read %s of %s"
161 165 )
162 166 % (len(read), length)
163 167 )
@@ -169,33 +173,33 b' class monotone_source(common.converter_s'
169 173 while True:
170 174 commandnbr, stream, length, output = self.mtnstdioreadpacket()
171 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 180 # End of command
177 if output != '0':
181 if output != b'0':
178 182 raise error.Abort(
179 _("mtn command '%s' returned %s") % (command, output)
183 _(b"mtn command '%s' returned %s") % (command, output)
180 184 )
181 185 break
182 elif stream in 'ew':
186 elif stream in b'ew':
183 187 # Error, warning output
184 self.ui.warn(_('%s error:\n') % self.command)
188 self.ui.warn(_(b'%s error:\n') % self.command)
185 189 self.ui.warn(output)
186 elif stream == 'p':
190 elif stream == b'p':
187 191 # Progress messages
188 self.ui.debug('mtn: ' + output)
189 elif stream == 'm':
192 self.ui.debug(b'mtn: ' + output)
193 elif stream == b'm':
190 194 # Main stream - command output
191 195 retval.append(output)
192 196
193 return ''.join(retval)
197 return b''.join(retval)
194 198
195 199 def mtnloadmanifest(self, rev):
196 200 if self.manifest_rev == rev:
197 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 203 self.manifest_rev = rev
200 204 self.files = {}
201 205 self.dirs = {}
@@ -203,11 +207,11 b' class monotone_source(common.converter_s'
203 207 for e in self.manifest:
204 208 m = self.file_re.match(e)
205 209 if m:
206 attr = ""
210 attr = b""
207 211 name = m.group(1)
208 212 node = m.group(2)
209 213 if self.attr_execute_re.match(e):
210 attr += "x"
214 attr += b"x"
211 215 self.files[name] = (node, attr)
212 216 m = self.dir_re.match(e)
213 217 if m:
@@ -224,12 +228,12 b' class monotone_source(common.converter_s'
224 228
225 229 def mtngetcerts(self, rev):
226 230 certs = {
227 "author": "<missing>",
228 "date": "<missing>",
229 "changelog": "<missing>",
230 "branch": "<missing>",
231 b"author": b"<missing>",
232 b"date": b"<missing>",
233 b"changelog": b"<missing>",
234 b"branch": b"<missing>",
231 235 }
232 certlist = self.mtnrun("certs", rev)
236 certlist = self.mtnrun(b"certs", rev)
233 237 # mtn < 0.45:
234 238 # key "test@selenic.com"
235 239 # mtn >= 0.45:
@@ -239,28 +243,28 b' class monotone_source(common.converter_s'
239 243 m = self.cert_re.match(e)
240 244 if m:
241 245 name, value = m.groups()
242 value = value.replace(br'\"', '"')
243 value = value.replace(br'\\', '\\')
246 value = value.replace(br'\"', b'"')
247 value = value.replace(br'\\', b'\\')
244 248 certs[name] = value
245 249 # Monotone may have subsecond dates: 2005-02-05T09:39:12.364306
246 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 252 return certs
249 253
250 254 # implement the converter_source interface:
251 255
252 256 def getheads(self):
253 257 if not self.revs:
254 return self.mtnrun("leaves").splitlines()
258 return self.mtnrun(b"leaves").splitlines()
255 259 else:
256 260 return self.revs
257 261
258 262 def getchanges(self, rev, full):
259 263 if full:
260 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 268 files = {}
265 269 ignoremove = {}
266 270 renameddirs = []
@@ -298,7 +302,7 b' class monotone_source(common.converter_s'
298 302 for tofile in self.files:
299 303 if tofile in ignoremove:
300 304 continue
301 if tofile.startswith(todir + '/'):
305 if tofile.startswith(todir + b'/'):
302 306 renamed[tofile] = fromdir + tofile[len(todir) :]
303 307 # Avoid chained moves like:
304 308 # d1(/a) => d3/d1(/a)
@@ -306,9 +310,9 b' class monotone_source(common.converter_s'
306 310 ignoremove[tofile] = 1
307 311 for tofile, fromfile in renamed.items():
308 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 314 % (fromfile, tofile),
311 '\n',
315 b'\n',
312 316 )
313 317 files[tofile] = rev
314 318 copies[tofile] = fromfile
@@ -321,32 +325,32 b' class monotone_source(common.converter_s'
321 325 if not self.mtnisfile(name, rev):
322 326 return None, None
323 327 try:
324 data = self.mtnrun("get_file_of", name, r=rev)
328 data = self.mtnrun(b"get_file_of", name, r=rev)
325 329 except Exception:
326 330 return None, None
327 331 self.mtnloadmanifest(rev)
328 node, attr = self.files.get(name, (None, ""))
332 node, attr = self.files.get(name, (None, b""))
329 333 return data, attr
330 334
331 335 def getcommit(self, rev):
332 336 extra = {}
333 337 certs = self.mtngetcerts(rev)
334 if certs.get('suspend') == certs["branch"]:
335 extra['close'] = 1
336 dateformat = "%Y-%m-%dT%H:%M:%S"
338 if certs.get(b'suspend') == certs[b"branch"]:
339 extra[b'close'] = 1
340 dateformat = b"%Y-%m-%dT%H:%M:%S"
337 341 return common.commit(
338 author=certs["author"],
339 date=dateutil.datestr(dateutil.strdate(certs["date"], dateformat)),
340 desc=certs["changelog"],
342 author=certs[b"author"],
343 date=dateutil.datestr(dateutil.strdate(certs[b"date"], dateformat)),
344 desc=certs[b"changelog"],
341 345 rev=rev,
342 parents=self.mtnrun("parents", rev).splitlines(),
343 branch=certs["branch"],
346 parents=self.mtnrun(b"parents", rev).splitlines(),
347 branch=certs[b"branch"],
344 348 extra=extra,
345 349 )
346 350
347 351 def gettags(self):
348 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 354 m = self.tag_re.match(e)
351 355 if m:
352 356 tags[m.group(1)] = m.group(2)
@@ -360,42 +364,42 b' class monotone_source(common.converter_s'
360 364 def before(self):
361 365 # Check if we have a new enough version to use automate stdio
362 366 try:
363 versionstr = self.mtnrunsingle("interface_version")
367 versionstr = self.mtnrunsingle(b"interface_version")
364 368 version = float(versionstr)
365 369 except Exception:
366 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 374 if version >= 12.0:
371 375 self.automatestdio = True
372 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 380 # launch the long-running automate stdio process
377 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 384 # read the headers
381 385 read = self.mtnreadfp.readline()
382 if read != 'format-version: 2\n':
386 if read != b'format-version: 2\n':
383 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 391 read = self.mtnreadfp.readline()
388 392 if not read:
389 393 raise error.Abort(
390 394 _(
391 "failed to reach end of mtn automate "
392 "stdio headers"
395 b"failed to reach end of mtn automate "
396 b"stdio headers"
393 397 )
394 398 )
395 399 else:
396 400 self.ui.debug(
397 "mtn automate version %s - not using automate stdio "
398 "(automate >= 12.0 - mtn >= 0.46 is needed)\n" % version
401 b"mtn automate version %s - not using automate stdio "
402 b"(automate >= 12.0 - mtn >= 0.46 is needed)\n" % version
399 403 )
400 404
401 405 def after(self):
@@ -24,7 +24,7 b' from . import common'
24 24
25 25
26 26 def loaditer(f):
27 "Yield the dictionary objects generated by p4"
27 b"Yield the dictionary objects generated by p4"
28 28 try:
29 29 while True:
30 30 d = marshal.load(f)
@@ -44,7 +44,12 b' def decodefilename(filename):'
44 44 >>> decodefilename(b'//Depot/Directory/%2525/%2523/%23%40.%2A')
45 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 53 for k, v in replacements:
49 54 filename = filename.replace(k, v)
50 55 return filename
@@ -57,16 +62,16 b' class p4_source(common.converter_source)'
57 62
58 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 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 72 self.revmap = {}
68 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 76 self.re_type = re.compile(
72 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 86 if revs and len(revs) > 1:
82 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 94 def setrevmap(self, revmap):
@@ -97,18 +105,18 b' class p4_source(common.converter_source)'
97 105 self.revmap = revmap
98 106
99 107 def _parse_view(self, path):
100 "Read changes affecting the path"
101 cmd = 'p4 -G changes -s submitted %s' % procutil.shellquote(path)
102 stdout = procutil.popen(cmd, mode='rb')
108 b"Read changes affecting the path"
109 cmd = b'p4 -G changes -s submitted %s' % procutil.shellquote(path)
110 stdout = procutil.popen(cmd, mode=b'rb')
103 111 p4changes = {}
104 112 for d in loaditer(stdout):
105 c = d.get("change", None)
113 c = d.get(b"change", None)
106 114 if c:
107 115 p4changes[c] = True
108 116 return p4changes
109 117
110 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 120 p4changes = {}
113 121 changeset = {}
114 122 files_map = {}
@@ -117,29 +125,29 b' class p4_source(common.converter_source)'
117 125 depotname = {}
118 126 heads = []
119 127
120 ui.status(_('reading p4 views\n'))
128 ui.status(_(b'reading p4 views\n'))
121 129
122 130 # read client spec or view
123 if "/" in path:
131 if b"/" in path:
124 132 p4changes.update(self._parse_view(path))
125 if path.startswith("//") and path.endswith("/..."):
126 views = {path[:-3]: ""}
133 if path.startswith(b"//") and path.endswith(b"/..."):
134 views = {path[:-3]: b""}
127 135 else:
128 views = {"//": ""}
136 views = {b"//": b""}
129 137 else:
130 cmd = 'p4 -G client -o %s' % procutil.shellquote(path)
131 clientspec = marshal.load(procutil.popen(cmd, mode='rb'))
138 cmd = b'p4 -G client -o %s' % procutil.shellquote(path)
139 clientspec = marshal.load(procutil.popen(cmd, mode=b'rb'))
132 140
133 141 views = {}
134 142 for client in clientspec:
135 if client.startswith("View"):
143 if client.startswith(b"View"):
136 144 sview, cview = clientspec[client].split()
137 145 p4changes.update(self._parse_view(sview))
138 if sview.endswith("...") and cview.endswith("..."):
146 if sview.endswith(b"...") and cview.endswith(b"..."):
139 147 sview = sview[:-3]
140 148 cview = cview[:-3]
141 149 cview = cview[2:]
142 cview = cview[cview.find("/") + 1 :]
150 cview = cview[cview.find(b"/") + 1 :]
143 151 views[sview] = cview
144 152
145 153 # list of changes that affect our source files
@@ -151,10 +159,10 b' class p4_source(common.converter_source)'
151 159 vieworder.sort(key=len, reverse=True)
152 160
153 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 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 166 lastid = None
159 167 for change in p4changes:
160 168 if startrev and int(change) < int(startrev):
@@ -176,28 +184,28 b' class p4_source(common.converter_source)'
176 184
177 185 descarr = c.desc.splitlines(True)
178 186 if len(descarr) > 0:
179 shortdesc = descarr[0].rstrip('\r\n')
187 shortdesc = descarr[0].rstrip(b'\r\n')
180 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])
184 ui.status(stringutil.ellipsis(t, 80) + '\n')
191 t = b'%s %s' % (c.rev, repr(shortdesc)[1:-1])
192 ui.status(stringutil.ellipsis(t, 80) + b'\n')
185 193
186 194 files = []
187 195 copies = {}
188 196 copiedfiles = []
189 197 i = 0
190 while ("depotFile%d" % i) in d and ("rev%d" % i) in d:
191 oldname = d["depotFile%d" % i]
198 while (b"depotFile%d" % i) in d and (b"rev%d" % i) in d:
199 oldname = d[b"depotFile%d" % i]
192 200 filename = None
193 201 for v in vieworder:
194 202 if oldname.lower().startswith(v.lower()):
195 203 filename = decodefilename(views[v] + oldname[len(v) :])
196 204 break
197 205 if filename:
198 files.append((filename, d["rev%d" % i]))
206 files.append((filename, d[b"rev%d" % i]))
199 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 209 copiedfiles.append(filename)
202 210 localname[oldname] = filename
203 211 i += 1
@@ -206,23 +214,23 b' class p4_source(common.converter_source)'
206 214 for filename in copiedfiles:
207 215 oldname = depotname[filename]
208 216
209 flcmd = 'p4 -G filelog %s' % procutil.shellquote(oldname)
210 flstdout = procutil.popen(flcmd, mode='rb')
217 flcmd = b'p4 -G filelog %s' % procutil.shellquote(oldname)
218 flstdout = procutil.popen(flcmd, mode=b'rb')
211 219
212 220 copiedfilename = None
213 221 for d in loaditer(flstdout):
214 222 copiedoldname = None
215 223
216 224 i = 0
217 while ("change%d" % i) in d:
225 while (b"change%d" % i) in d:
218 226 if (
219 d["change%d" % i] == change
220 and d["action%d" % i] == "move/add"
227 d[b"change%d" % i] == change
228 and d[b"action%d" % i] == b"move/add"
221 229 ):
222 230 j = 0
223 while ("file%d,%d" % (i, j)) in d:
224 if d["how%d,%d" % (i, j)] == "moved from":
225 copiedoldname = d["file%d,%d" % (i, j)]
231 while (b"file%d,%d" % (i, j)) in d:
232 if d[b"how%d,%d" % (i, j)] == b"moved from":
233 copiedoldname = d[b"file%d,%d" % (i, j)]
226 234 break
227 235 j += 1
228 236 i += 1
@@ -235,7 +243,7 b' class p4_source(common.converter_source)'
235 243 copies[filename] = copiedfilename
236 244 else:
237 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 247 % (filename, change)
240 248 )
241 249
@@ -248,11 +256,11 b' class p4_source(common.converter_source)'
248 256 heads = [lastid]
249 257
250 258 return {
251 'changeset': changeset,
252 'files': files_map,
253 'copies': copies_map,
254 'heads': heads,
255 'depotname': depotname,
259 b'changeset': changeset,
260 b'files': files_map,
261 b'copies': copies_map,
262 b'heads': heads,
263 b'depotname': depotname,
256 264 }
257 265
258 266 @util.propertycache
@@ -261,74 +269,74 b' class p4_source(common.converter_source)'
261 269
262 270 @util.propertycache
263 271 def copies(self):
264 return self._parse_once['copies']
272 return self._parse_once[b'copies']
265 273
266 274 @util.propertycache
267 275 def files(self):
268 return self._parse_once['files']
276 return self._parse_once[b'files']
269 277
270 278 @util.propertycache
271 279 def changeset(self):
272 return self._parse_once['changeset']
280 return self._parse_once[b'changeset']
273 281
274 282 @util.propertycache
275 283 def heads(self):
276 return self._parse_once['heads']
284 return self._parse_once[b'heads']
277 285
278 286 @util.propertycache
279 287 def depotname(self):
280 return self._parse_once['depotname']
288 return self._parse_once[b'depotname']
281 289
282 290 def getheads(self):
283 291 return self.heads
284 292
285 293 def getfile(self, name, rev):
286 cmd = 'p4 -G print %s' % procutil.shellquote(
287 "%s#%s" % (self.depotname[name], rev)
294 cmd = b'p4 -G print %s' % procutil.shellquote(
295 b"%s#%s" % (self.depotname[name], rev)
288 296 )
289 297
290 298 lasterror = None
291 299 while True:
292 stdout = procutil.popen(cmd, mode='rb')
300 stdout = procutil.popen(cmd, mode=b'rb')
293 301
294 302 mode = None
295 303 contents = []
296 304 keywords = None
297 305
298 306 for d in loaditer(stdout):
299 code = d["code"]
300 data = d.get("data")
307 code = d[b"code"]
308 data = d.get(b"data")
301 309
302 if code == "error":
310 if code == b"error":
303 311 # if this is the first time error happened
304 312 # re-attempt getting the file
305 313 if not lasterror:
306 lasterror = IOError(d["generic"], data)
314 lasterror = IOError(d[b"generic"], data)
307 315 # this will exit inner-most for-loop
308 316 break
309 317 else:
310 318 raise lasterror
311 319
312 elif code == "stat":
313 action = d.get("action")
314 if action in ["purge", "delete", "move/delete"]:
320 elif code == b"stat":
321 action = d.get(b"action")
322 if action in [b"purge", b"delete", b"move/delete"]:
315 323 return None, None
316 p4type = self.re_type.match(d["type"])
324 p4type = self.re_type.match(d[b"type"])
317 325 if p4type:
318 mode = ""
319 flags = (p4type.group(1) or "") + (
320 p4type.group(3) or ""
326 mode = b""
327 flags = (p4type.group(1) or b"") + (
328 p4type.group(3) or b""
321 329 )
322 if "x" in flags:
323 mode = "x"
324 if p4type.group(2) == "symlink":
325 mode = "l"
326 if "ko" in flags:
330 if b"x" in flags:
331 mode = b"x"
332 if p4type.group(2) == b"symlink":
333 mode = b"l"
334 if b"ko" in flags:
327 335 keywords = self.re_keywords_old
328 elif "k" in flags:
336 elif b"k" in flags:
329 337 keywords = self.re_keywords
330 338
331 elif code == "text" or code == "binary":
339 elif code == b"text" or code == b"binary":
332 340 contents.append(data)
333 341
334 342 lasterror = None
@@ -339,18 +347,18 b' class p4_source(common.converter_source)'
339 347 if mode is None:
340 348 return None, None
341 349
342 contents = ''.join(contents)
350 contents = b''.join(contents)
343 351
344 352 if keywords:
345 contents = keywords.sub("$\\1$", contents)
346 if mode == "l" and contents.endswith("\n"):
353 contents = keywords.sub(b"$\\1$", contents)
354 if mode == b"l" and contents.endswith(b"\n"):
347 355 contents = contents[:-1]
348 356
349 357 return contents, mode
350 358
351 359 def getchanges(self, rev, full):
352 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 362 return self.files[rev], self.copies[rev], set()
355 363
356 364 def _construct_commit(self, obj, parents=None):
@@ -358,26 +366,26 b' class p4_source(common.converter_source)'
358 366 Constructs a common.commit object from an unmarshalled
359 367 `p4 describe` output
360 368 """
361 desc = self.recode(obj.get("desc", ""))
362 date = (int(obj["time"]), 0) # timezone not set
369 desc = self.recode(obj.get(b"desc", b""))
370 date = (int(obj[b"time"]), 0) # timezone not set
363 371 if parents is None:
364 372 parents = []
365 373
366 374 return common.commit(
367 author=self.recode(obj["user"]),
368 date=dateutil.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'),
375 author=self.recode(obj[b"user"]),
376 date=dateutil.datestr(date, b'%Y-%m-%d %H:%M:%S %1%2'),
369 377 parents=parents,
370 378 desc=desc,
371 379 branch=None,
372 rev=obj['change'],
373 extra={"p4": obj['change'], "convert_revision": obj['change']},
380 rev=obj[b'change'],
381 extra={b"p4": obj[b'change'], b"convert_revision": obj[b'change']},
374 382 )
375 383
376 384 def _fetch_revision(self, rev):
377 385 """Return an output of `p4 describe` including author, commit date as
378 386 a dictionary."""
379 cmd = "p4 -G describe -s %s" % rev
380 stdout = procutil.popen(cmd, mode='rb')
387 cmd = b"p4 -G describe -s %s" % rev
388 stdout = procutil.popen(cmd, mode=b'rb')
381 389 return marshal.load(stdout)
382 390
383 391 def getcommit(self, rev):
@@ -387,7 +395,7 b' class p4_source(common.converter_source)'
387 395 d = self._fetch_revision(rev)
388 396 return self._construct_commit(d, parents=None)
389 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 401 def gettags(self):
@@ -54,7 +54,7 b' try:'
54 54 import warnings
55 55
56 56 warnings.filterwarnings(
57 'ignore', module='svn.core', category=DeprecationWarning
57 b'ignore', module=b'svn.core', category=DeprecationWarning
58 58 )
59 59 svn.core.SubversionException # trigger import to catch error
60 60
@@ -80,16 +80,16 b' def revsplit(rev):'
80 80 >>> revsplit(b'bad')
81 81 ('', '', 0)
82 82 """
83 parts = rev.rsplit('@', 1)
83 parts = rev.rsplit(b'@', 1)
84 84 revnum = 0
85 85 if len(parts) > 1:
86 86 revnum = int(parts[1])
87 parts = parts[0].split('/', 1)
88 uuid = ''
89 mod = ''
90 if len(parts) > 1 and parts[0].startswith('svn:'):
87 parts = parts[0].split(b'/', 1)
88 uuid = b''
89 mod = b''
90 if len(parts) > 1 and parts[0].startswith(b'svn:'):
91 91 uuid = parts[0][4:]
92 mod = '/' + parts[1]
92 mod = b'/' + parts[1]
93 93 return uuid, mod, revnum
94 94
95 95
@@ -101,7 +101,7 b' def quote(s):'
101 101 # so we can extend it safely with new components. The "safe"
102 102 # characters were taken from the "svn_uri__char_validity" table in
103 103 # libsvn_subr/path.c.
104 return urlreq.quote(s, "!$&'()*+,-./:=@_~")
104 return urlreq.quote(s, b"!$&'()*+,-./:=@_~")
105 105
106 106
107 107 def geturl(path):
@@ -113,11 +113,11 b' def geturl(path):'
113 113 if os.path.isdir(path):
114 114 path = os.path.normpath(os.path.abspath(path))
115 115 if pycompat.iswindows:
116 path = '/' + util.normpath(path)
116 path = b'/' + util.normpath(path)
117 117 # Module URL is later compared with the repository URL returned
118 118 # by svn API, which is UTF-8.
119 119 path = encoding.tolocal(path)
120 path = 'file://%s' % quote(path)
120 path = b'file://%s' % quote(path)
121 121 return svn.core.svn_path_canonicalize(path)
122 122
123 123
@@ -188,7 +188,7 b' def debugsvnlog(ui, **opts):'
188 188 """
189 189 if svn is None:
190 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 194 args = decodeargs(ui.fin.read())
@@ -208,8 +208,8 b' class logstream(object):'
208 208 except EOFError:
209 209 raise error.Abort(
210 210 _(
211 'Mercurial failed to run itself, check'
212 ' hg executable is in PATH'
211 b'Mercurial failed to run itself, check'
212 b' hg executable is in PATH'
213 213 )
214 214 )
215 215 try:
@@ -217,7 +217,7 b' class logstream(object):'
217 217 except (TypeError, ValueError):
218 218 if entry is None:
219 219 break
220 raise error.Abort(_("log stream exception '%s'") % entry)
220 raise error.Abort(_(b"log stream exception '%s'") % entry)
221 221 yield entry
222 222
223 223 def close(self):
@@ -270,7 +270,7 b' class directlogstream(list):'
270 270 # looking for several svn-specific files and directories in the given
271 271 # directory.
272 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 274 if not os.path.exists(os.path.join(path, x)):
275 275 return False
276 276 return True
@@ -282,16 +282,16 b' def filecheck(ui, path, proto):'
282 282 def httpcheck(ui, path, proto):
283 283 try:
284 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 286 data = rsp.read()
287 287 except urlerr.httperror as inst:
288 288 if inst.code != 404:
289 289 # Except for 404 we cannot know for sure this is not an svn repo
290 290 ui.warn(
291 291 _(
292 'svn: cannot probe remote repository, assume it could '
293 'be a subversion repository. Use --source-type if you '
294 'know better.\n'
292 b'svn: cannot probe remote repository, assume it could '
293 b'be a subversion repository. Use --source-type if you '
294 b'know better.\n'
295 295 )
296 296 )
297 297 return True
@@ -299,38 +299,38 b' def httpcheck(ui, path, proto):'
299 299 except Exception:
300 300 # Could be urlerr.urlerror if the URL is invalid or anything else.
301 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 305 protomap = {
306 'http': httpcheck,
307 'https': httpcheck,
308 'file': filecheck,
306 b'http': httpcheck,
307 b'https': httpcheck,
308 b'file': filecheck,
309 309 }
310 310
311 311
312 312 def issvnurl(ui, url):
313 313 try:
314 proto, path = url.split('://', 1)
315 if proto == 'file':
314 proto, path = url.split(b'://', 1)
315 if proto == b'file':
316 316 if (
317 317 pycompat.iswindows
318 and path[:1] == '/'
318 and path[:1] == b'/'
319 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 323 path = urlreq.url2pathname(path)
324 324 except ValueError:
325 proto = 'file'
325 proto = b'file'
326 326 path = os.path.abspath(url)
327 if proto == 'file':
327 if proto == b'file':
328 328 path = util.pconvert(path)
329 329 check = protomap.get(proto, lambda *args: False)
330 while '/' in path:
330 while b'/' in path:
331 331 if check(ui, path, proto):
332 332 return True
333 path = path.rsplit('/', 1)[0]
333 path = path.rsplit(b'/', 1)[0]
334 334 return False
335 335
336 336
@@ -353,35 +353,35 b' class svn_source(converter_source):'
353 353 super(svn_source, self).__init__(ui, repotype, url, revs=revs)
354 354
355 355 if not (
356 url.startswith('svn://')
357 or url.startswith('svn+ssh://')
356 url.startswith(b'svn://')
357 or url.startswith(b'svn+ssh://')
358 358 or (
359 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 362 or issvnurl(ui, url)
363 363 ):
364 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 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 370 try:
371 371 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
372 372 if version < (1, 4):
373 373 raise MissingTool(
374 374 _(
375 'Subversion python bindings %d.%d found, '
376 '1.4 or later required'
375 b'Subversion python bindings %d.%d found, '
376 b'1.4 or later required'
377 377 )
378 378 % version
379 379 )
380 380 except AttributeError:
381 381 raise MissingTool(
382 382 _(
383 'Subversion python bindings are too old, 1.4 '
384 'or later required'
383 b'Subversion python bindings are too old, 1.4 '
384 b'or later required'
385 385 )
386 386 )
387 387
@@ -391,14 +391,14 b' class svn_source(converter_source):'
391 391 try:
392 392 # Support file://path@rev syntax. Useful e.g. to convert
393 393 # deleted branches.
394 at = url.rfind('@')
394 at = url.rfind(b'@')
395 395 if at >= 0:
396 396 latest = int(url[at + 1 :])
397 397 url = url[:at]
398 398 except ValueError:
399 399 pass
400 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 402 try:
403 403 self.transport = transport.SvnRaTransport(url=self.url)
404 404 self.ra = self.transport.ra
@@ -414,15 +414,15 b' class svn_source(converter_source):'
414 414 self.uuid = svn.ra.get_uuid(self.ra)
415 415 except svn.core.SubversionException:
416 416 ui.traceback()
417 svnversion = '%d.%d.%d' % (
417 svnversion = b'%d.%d.%d' % (
418 418 svn.core.SVN_VER_MAJOR,
419 419 svn.core.SVN_VER_MINOR,
420 420 svn.core.SVN_VER_MICRO,
421 421 )
422 422 raise NoRepo(
423 423 _(
424 "%s does not look like a Subversion repository "
425 "to libsvn version %s"
424 b"%s does not look like a Subversion repository "
425 b"to libsvn version %s"
426 426 )
427 427 % (self.url, svnversion)
428 428 )
@@ -431,29 +431,29 b' class svn_source(converter_source):'
431 431 if len(revs) > 1:
432 432 raise error.Abort(
433 433 _(
434 'subversion source does not support '
435 'specifying multiple revisions'
434 b'subversion source does not support '
435 b'specifying multiple revisions'
436 436 )
437 437 )
438 438 try:
439 439 latest = int(revs[0])
440 440 except ValueError:
441 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 446 if trunkcfg is None:
447 trunkcfg = 'trunk'
448 self.trunkname = trunkcfg.strip('/')
449 self.startrev = self.ui.config('convert', 'svn.startrev')
447 trunkcfg = b'trunk'
448 self.trunkname = trunkcfg.strip(b'/')
449 self.startrev = self.ui.config(b'convert', b'svn.startrev')
450 450 try:
451 451 self.startrev = int(self.startrev)
452 452 if self.startrev < 0:
453 453 self.startrev = 0
454 454 except ValueError:
455 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 459 try:
@@ -461,12 +461,14 b' class svn_source(converter_source):'
461 461 except SvnPathNotFound:
462 462 self.head = None
463 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 467 self.last_changed = self.revnum(self.head)
466 468
467 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 472 self.wc = url
471 473 else:
472 474 self.wc = None
@@ -484,7 +486,7 b' class svn_source(converter_source):'
484 486 def exists(self, path, optrev):
485 487 try:
486 488 svn.client.ls(
487 self.url.rstrip('/') + '/' + quote(path),
489 self.url.rstrip(b'/') + b'/' + quote(path),
488 490 optrev,
489 491 False,
490 492 self.ctx,
@@ -499,61 +501,62 b' class svn_source(converter_source):'
499 501 return kind == svn.core.svn_node_dir
500 502
501 503 def getcfgpath(name, rev):
502 cfgpath = self.ui.config('convert', 'svn.' + name)
503 if cfgpath is not None and cfgpath.strip() == '':
504 cfgpath = self.ui.config(b'convert', b'svn.' + name)
505 if cfgpath is not None and cfgpath.strip() == b'':
504 506 return None
505 path = (cfgpath or name).strip('/')
507 path = (cfgpath or name).strip(b'/')
506 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 510 # we are converting from inside this directory
509 511 return None
510 512 if cfgpath:
511 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 515 % (name, path)
514 516 )
515 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 519 return path
518 520
519 521 rev = optrev(self.last_changed)
520 oldmodule = ''
521 trunk = getcfgpath('trunk', rev)
522 self.tags = getcfgpath('tags', rev)
523 branches = getcfgpath('branches', rev)
522 oldmodule = b''
523 trunk = getcfgpath(b'trunk', rev)
524 self.tags = getcfgpath(b'tags', rev)
525 branches = getcfgpath(b'branches', rev)
524 526
525 527 # If the project has a trunk or branches, we will extract heads
526 528 # from them. We keep the project root otherwise.
527 529 if trunk:
528 oldmodule = self.module or ''
529 self.module += '/' + trunk
530 oldmodule = self.module or b''
531 self.module += b'/' + trunk
530 532 self.head = self.latest(self.module, self.last_changed)
531 533 if not self.head:
532 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 538 # First head in the list is the module's head
537 539 self.heads = [self.head]
538 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 543 # Check if branches bring a few more heads to the list
542 544 if branches:
543 rpath = self.url.strip('/')
545 rpath = self.url.strip(b'/')
544 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 549 for branch in sorted(branchnames):
548 module = '%s/%s/%s' % (oldmodule, branches, branch)
550 module = b'%s/%s/%s' % (oldmodule, branches, branch)
549 551 if not isdir(module, self.last_changed):
550 552 continue
551 553 brevid = self.latest(module, self.last_changed)
552 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 556 continue
555 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 561 self.heads.append(brevid)
559 562
@@ -561,14 +564,14 b' class svn_source(converter_source):'
561 564 if len(self.heads) > 1:
562 565 raise error.Abort(
563 566 _(
564 'svn: start revision is not supported '
565 'with more than one branch'
567 b'svn: start revision is not supported '
568 b'with more than one branch'
566 569 )
567 570 )
568 571 revnum = self.revnum(self.heads[0])
569 572 if revnum < self.startrev:
570 573 raise error.Abort(
571 _('svn: no revision found after start revision %d')
574 _(b'svn: no revision found after start revision %d')
572 575 % self.startrev
573 576 )
574 577
@@ -628,13 +631,13 b' class svn_source(converter_source):'
628 631 stop = revnum + 1
629 632 self._fetch_revisions(revnum, stop)
630 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 635 revcommit = self.commits[rev]
633 636 # caller caches the result, so free it here to release memory
634 637 del self.commits[rev]
635 638 return revcommit
636 639
637 def checkrevformat(self, revstr, mapname='splicemap'):
640 def checkrevformat(self, revstr, mapname=b'splicemap'):
638 641 """ fails if revision format does not match the correct format"""
639 642 if not re.match(
640 643 r'svn:[0-9a-f]{8,8}-[0-9a-f]{4,4}-'
@@ -643,12 +646,12 b' class svn_source(converter_source):'
643 646 revstr,
644 647 ):
645 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 650 % (mapname, revstr)
648 651 )
649 652
650 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 656 def gettags(self):
654 657 tags = {}
@@ -689,7 +692,7 b' class svn_source(converter_source):'
689 692 srctagspath = copies.pop()[0]
690 693
691 694 for source, sourcerev, dest in copies:
692 if not dest.startswith(tagspath + '/'):
695 if not dest.startswith(tagspath + b'/'):
693 696 continue
694 697 for tag in pendings:
695 698 if tag[0].startswith(dest):
@@ -709,14 +712,14 b' class svn_source(converter_source):'
709 712 addeds = dict(
710 713 (p, e.copyfrom_path)
711 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 717 badroots = set()
715 718 for destroot in addeds:
716 719 for source, sourcerev, dest in pendings:
717 720 if not dest.startswith(
718 destroot + '/'
719 ) or source.startswith(addeds[destroot] + '/'):
721 destroot + b'/'
722 ) or source.startswith(addeds[destroot] + b'/'):
720 723 continue
721 724 badroots.add(destroot)
722 725 break
@@ -726,13 +729,13 b' class svn_source(converter_source):'
726 729 p
727 730 for p in pendings
728 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 735 # Tell tag renamings from tag creations
733 736 renamings = []
734 737 for source, sourcerev, dest in pendings:
735 tagname = dest.split('/')[-1]
738 tagname = dest.split(b'/')[-1]
736 739 if source.startswith(srctagspath):
737 740 renamings.append([source, sourcerev, tagname])
738 741 continue
@@ -761,18 +764,18 b' class svn_source(converter_source):'
761 764 return
762 765 if self.convertfp is None:
763 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 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 772 self.convertfp.flush()
770 773
771 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 777 def revnum(self, rev):
775 return int(rev.split('@')[-1])
778 return int(rev.split(b'@')[-1])
776 779
777 780 def latest(self, path, stop=None):
778 781 """Find the latest revid affecting path, up to stop revision
@@ -800,7 +803,7 b' class svn_source(converter_source):'
800 803 continue
801 804 newpath = paths[p].copyfrom_path + path[len(p) :]
802 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 807 % (path, newpath, revnum)
805 808 )
806 809 path = newpath
@@ -813,20 +816,20 b' class svn_source(converter_source):'
813 816
814 817 if not path.startswith(self.rootmodule):
815 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 820 return None
818 821
819 822 if stop is None:
820 823 stop = svn.ra.get_latest_revnum(self.ra)
821 824 try:
822 prevmodule = self.reparent('')
823 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
825 prevmodule = self.reparent(b'')
826 dirent = svn.ra.stat(self.ra, path.strip(b'/'), stop)
824 827 self.reparent(prevmodule)
825 828 except svn.core.SubversionException:
826 829 dirent = None
827 830 if not dirent:
828 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 835 # stat() gives us the previous revision on this line of
@@ -843,11 +846,11 b' class svn_source(converter_source):'
843 846 # the whole history.
844 847 revnum, realpath = findchanges(path, stop)
845 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 850 return None
848 851
849 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 854 return None
852 855 return self.revid(revnum, realpath)
853 856
@@ -858,8 +861,8 b' class svn_source(converter_source):'
858 861 svnurl = self.baseurl + quote(module)
859 862 prevmodule = self.prevmodule
860 863 if prevmodule is None:
861 prevmodule = ''
862 self.ui.debug("reparent to %s\n" % svnurl)
864 prevmodule = b''
865 self.ui.debug(b"reparent to %s\n" % svnurl)
863 866 svn.ra.reparent(self.ra, svnurl)
864 867 self.prevmodule = module
865 868 return prevmodule
@@ -874,7 +877,7 b' class svn_source(converter_source):'
874 877 self.reparent(self.module)
875 878
876 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 882 for i, (path, ent) in enumerate(paths):
880 883 progress.update(i, item=path)
@@ -894,37 +897,37 b' class svn_source(converter_source):'
894 897 if not copyfrom_path:
895 898 continue
896 899 self.ui.debug(
897 "copied to %s from %s@%s\n"
900 b"copied to %s from %s@%s\n"
898 901 % (entrypath, copyfrom_path, ent.copyfrom_rev)
899 902 )
900 903 copies[self.recode(entrypath)] = self.recode(copyfrom_path)
901 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 906 pmodule, prevnum = revsplit(parents[0])[1:]
904 parentpath = pmodule + "/" + entrypath
907 parentpath = pmodule + b"/" + entrypath
905 908 fromkind = self._checkpath(entrypath, prevnum, pmodule)
906 909
907 910 if fromkind == svn.core.svn_node_file:
908 911 removed.add(self.recode(entrypath))
909 912 elif fromkind == svn.core.svn_node_dir:
910 oroot = parentpath.strip('/')
911 nroot = path.strip('/')
913 oroot = parentpath.strip(b'/')
914 nroot = path.strip(b'/')
912 915 children = self._iterfiles(oroot, prevnum)
913 916 for childpath in children:
914 917 childpath = childpath.replace(oroot, nroot)
915 childpath = self.getrelpath("/" + childpath, pmodule)
918 childpath = self.getrelpath(b"/" + childpath, pmodule)
916 919 if childpath:
917 920 removed.add(self.recode(childpath))
918 921 else:
919 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 925 elif kind == svn.core.svn_node_dir:
923 if ent.action == 'M':
926 if ent.action == b'M':
924 927 # If the directory just had a prop change,
925 928 # then we shouldn't need to look for its children.
926 929 continue
927 if ent.action == 'R' and parents:
930 if ent.action == b'R' and parents:
928 931 # If a directory is replacing a file, mark the previous
929 932 # file as deleted
930 933 pmodule, prevnum = revsplit(parents[0])[1:]
@@ -935,12 +938,12 b' class svn_source(converter_source):'
935 938 # We do not know what files were kept or removed,
936 939 # mark them all as changed.
937 940 for childpath in self._iterfiles(pmodule, prevnum):
938 childpath = self.getrelpath("/" + childpath)
941 childpath = self.getrelpath(b"/" + childpath)
939 942 if childpath:
940 943 changed.add(self.recode(childpath))
941 944
942 945 for childpath in self._iterfiles(path, revnum):
943 childpath = self.getrelpath("/" + childpath)
946 childpath = self.getrelpath(b"/" + childpath)
944 947 if childpath:
945 948 changed.add(self.recode(childpath))
946 949
@@ -956,12 +959,12 b' class svn_source(converter_source):'
956 959 if not copyfrompath:
957 960 continue
958 961 self.ui.debug(
959 "mark %s came from %s:%d\n"
962 b"mark %s came from %s:%d\n"
960 963 % (path, copyfrompath, ent.copyfrom_rev)
961 964 )
962 965 children = self._iterfiles(ent.copyfrom_path, ent.copyfrom_rev)
963 966 for childpath in children:
964 childpath = self.getrelpath("/" + childpath, pmodule)
967 childpath = self.getrelpath(b"/" + childpath, pmodule)
965 968 if not childpath:
966 969 continue
967 970 copytopath = path + childpath[len(copyfrompath) :]
@@ -983,7 +986,8 b' class svn_source(converter_source):'
983 986 the revision is a branch root.
984 987 """
985 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 993 branched = False
@@ -1012,11 +1016,11 b' class svn_source(converter_source):'
1012 1016 if prevnum >= self.startrev:
1013 1017 parents = [previd]
1014 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 1020 % (self.module, prevnum, prevmodule)
1017 1021 )
1018 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 1025 paths = []
1022 1026 # filter out unrelated paths
@@ -1028,22 +1032,24 b' class svn_source(converter_source):'
1028 1032 # Example SVN datetime. Includes microseconds.
1029 1033 # ISO-8601 conformant
1030 1034 # '2007-01-04T17:35:00.902377Z'
1031 date = dateutil.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
1032 if self.ui.configbool('convert', 'localtimezone'):
1035 date = dateutil.parsedate(
1036 date[:19] + b" UTC", [b"%Y-%m-%dT%H:%M:%S"]
1037 )
1038 if self.ui.configbool(b'convert', b'localtimezone'):
1033 1039 date = makedatetimestamp(date[0])
1034 1040
1035 1041 if message:
1036 1042 log = self.recode(message)
1037 1043 else:
1038 log = ''
1044 log = b''
1039 1045
1040 1046 if author:
1041 1047 author = self.recode(author)
1042 1048 else:
1043 author = ''
1049 author = b''
1044 1050
1045 1051 try:
1046 branch = self.module.split("/")[-1]
1052 branch = self.module.split(b"/")[-1]
1047 1053 if branch == self.trunkname:
1048 1054 branch = None
1049 1055 except IndexError:
@@ -1051,7 +1057,7 b' class svn_source(converter_source):'
1051 1057
1052 1058 cset = commit(
1053 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 1061 desc=log,
1056 1062 parents=parents,
1057 1063 branch=branch,
@@ -1068,7 +1074,7 b' class svn_source(converter_source):'
1068 1074 return cset, branched
1069 1075
1070 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 1078 % (self.module, from_revnum, to_revnum)
1073 1079 )
1074 1080
@@ -1083,7 +1089,7 b' class svn_source(converter_source):'
1083 1089 lastonbranch = True
1084 1090 break
1085 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 1093 # If we ever leave the loop on an empty
1088 1094 # revision, do not try to get a parent branch
1089 1095 lastonbranch = lastonbranch or revnum == 0
@@ -1114,7 +1120,7 b' class svn_source(converter_source):'
1114 1120 (inst, num) = xxx_todo_changeme.args
1115 1121 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
1116 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 1125 raise
1120 1126
@@ -1135,8 +1141,8 b' class svn_source(converter_source):'
1135 1141 io.close()
1136 1142 if isinstance(info, list):
1137 1143 info = info[-1]
1138 mode = ("svn:executable" in info) and 'x' or ''
1139 mode = ("svn:special" in info) and 'l' or mode
1144 mode = (b"svn:executable" in info) and b'x' or b''
1145 mode = (b"svn:special" in info) and b'l' or mode
1140 1146 except svn.core.SubversionException as e:
1141 1147 notfound = (
1142 1148 svn.core.SVN_ERR_FS_NOT_FOUND,
@@ -1145,20 +1151,20 b' class svn_source(converter_source):'
1145 1151 if e.apr_err in notfound: # File not found
1146 1152 return None, None
1147 1153 raise
1148 if mode == 'l':
1149 link_prefix = "link "
1154 if mode == b'l':
1155 link_prefix = b"link "
1150 1156 if data.startswith(link_prefix):
1151 1157 data = data[len(link_prefix) :]
1152 1158 return data, mode
1153 1159
1154 1160 def _iterfiles(self, path, revnum):
1155 1161 """Enumerate all files in path at revnum, recursively."""
1156 path = path.strip('/')
1162 path = path.strip(b'/')
1157 1163 pool = svn.core.Pool()
1158 rpath = '/'.join([self.baseurl, quote(path)]).strip('/')
1164 rpath = b'/'.join([self.baseurl, quote(path)]).strip(b'/')
1159 1165 entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
1160 1166 if path:
1161 path += '/'
1167 path += b'/'
1162 1168 return (
1163 1169 (path + p)
1164 1170 for p, e in entries.iteritems()
@@ -1175,24 +1181,24 b' class svn_source(converter_source):'
1175 1181 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
1176 1182 # that is to say "tests/PloneTestCase.py"
1177 1183 if path.startswith(module):
1178 relative = path.rstrip('/')[len(module) :]
1179 if relative.startswith('/'):
1184 relative = path.rstrip(b'/')[len(module) :]
1185 if relative.startswith(b'/'):
1180 1186 return relative[1:]
1181 elif relative == '':
1187 elif relative == b'':
1182 1188 return relative
1183 1189
1184 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 1192 return None
1187 1193
1188 1194 def _checkpath(self, path, revnum, module=None):
1189 1195 if module is not None:
1190 prevmodule = self.reparent('')
1191 path = module + '/' + path
1196 prevmodule = self.reparent(b'')
1197 path = module + b'/' + path
1192 1198 try:
1193 1199 # ra.check_path does not like leading slashes very much, it leads
1194 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 1202 finally:
1197 1203 if module is not None:
1198 1204 self.reparent(prevmodule)
@@ -1210,9 +1216,9 b' class svn_source(converter_source):'
1210 1216 # supplied URL
1211 1217 relpaths = []
1212 1218 for p in paths:
1213 if not p.startswith('/'):
1214 p = self.module + '/' + p
1215 relpaths.append(p.strip('/'))
1219 if not p.startswith(b'/'):
1220 p = self.module + b'/' + p
1221 relpaths.append(p.strip(b'/'))
1216 1222 args = [
1217 1223 self.baseurl,
1218 1224 relpaths,
@@ -1223,11 +1229,11 b' class svn_source(converter_source):'
1223 1229 strict_node_history,
1224 1230 ]
1225 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 1233 return directlogstream(*args)
1228 1234 arg = encodeargs(args)
1229 1235 hgexe = procutil.hgexecutable()
1230 cmd = '%s debugsvnlog' % procutil.shellquote(hgexe)
1236 cmd = b'%s debugsvnlog' % procutil.shellquote(hgexe)
1231 1237 stdin, stdout = procutil.popen2(procutil.quotecommand(cmd))
1232 1238 stdin.write(arg)
1233 1239 try:
@@ -1235,8 +1241,8 b' class svn_source(converter_source):'
1235 1241 except IOError:
1236 1242 raise error.Abort(
1237 1243 _(
1238 'Mercurial failed to run itself, check'
1239 ' hg executable is in PATH'
1244 b'Mercurial failed to run itself, check'
1245 b' hg executable is in PATH'
1240 1246 )
1241 1247 )
1242 1248 return logstream(stdout)
@@ -1272,18 +1278,18 b' class svn_sink(converter_sink, commandli'
1272 1278 os.chdir(self.cwd)
1273 1279
1274 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 1283 def revmapfile(self):
1278 return self.join('hg-shamap')
1284 return self.join(b'hg-shamap')
1279 1285
1280 1286 def authorfile(self):
1281 return self.join('hg-authormap')
1287 return self.join(b'hg-authormap')
1282 1288
1283 1289 def __init__(self, ui, repotype, path):
1284 1290
1285 1291 converter_sink.__init__(self, ui, repotype, path)
1286 commandline.__init__(self, ui, 'svn')
1292 commandline.__init__(self, ui, b'svn')
1287 1293 self.delete = []
1288 1294 self.setexec = []
1289 1295 self.delexec = []
@@ -1292,51 +1298,53 b' class svn_sink(converter_sink, commandli'
1292 1298 self.cwd = encoding.getcwd()
1293 1299
1294 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 1302 self.wc = os.path.realpath(path)
1297 self.run0('update')
1303 self.run0(b'update')
1298 1304 else:
1299 1305 if not re.search(br'^(file|http|https|svn|svn\+ssh)\://', path):
1300 1306 path = os.path.realpath(path)
1301 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 1311 ui.status(
1304 _("initializing svn repository '%s'\n")
1312 _(b"initializing svn repository '%s'\n")
1305 1313 % os.path.basename(path)
1306 1314 )
1307 commandline(ui, 'svnadmin').run0('create', path)
1315 commandline(ui, b'svnadmin').run0(b'create', path)
1308 1316 created = path
1309 1317 path = util.normpath(path)
1310 if not path.startswith('/'):
1311 path = '/' + path
1312 path = 'file://' + path
1318 if not path.startswith(b'/'):
1319 path = b'/' + path
1320 path = b'file://' + path
1313 1321
1314 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 1325 ui.status(
1318 _("initializing svn working copy '%s'\n")
1326 _(b"initializing svn working copy '%s'\n")
1319 1327 % os.path.basename(wcpath)
1320 1328 )
1321 self.run0('checkout', path, wcpath)
1329 self.run0(b'checkout', path, wcpath)
1322 1330
1323 1331 self.wc = wcpath
1324 1332 self.opener = vfsmod.vfs(self.wc)
1325 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 1335 if util.checkexec(self.wc):
1328 1336 self.is_exec = util.isexec
1329 1337 else:
1330 1338 self.is_exec = None
1331 1339
1332 1340 if created:
1333 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
1334 fp = open(hook, 'wb')
1341 hook = os.path.join(created, b'hooks', b'pre-revprop-change')
1342 fp = open(hook, b'wb')
1335 1343 fp.write(pre_revprop_change)
1336 1344 fp.close()
1337 1345 util.setflags(hook, False, True)
1338 1346
1339 output = self.run0('info')
1347 output = self.run0(b'info')
1340 1348 self.uuid = self.uuid_re.search(output).group(1).strip()
1341 1349
1342 1350 def wjoin(self, *names):
@@ -1348,7 +1356,7 b' class svn_sink(converter_sink, commandli'
1348 1356 # already tracked entries, so we have to track and filter them
1349 1357 # ourselves.
1350 1358 m = set()
1351 output = self.run0('ls', recursive=True, xml=True)
1359 output = self.run0(b'ls', recursive=True, xml=True)
1352 1360 doc = xml.dom.minidom.parseString(output)
1353 1361 for e in doc.getElementsByTagName(r'entry'):
1354 1362 for n in e.childNodes:
@@ -1367,7 +1375,7 b' class svn_sink(converter_sink, commandli'
1367 1375 return m
1368 1376
1369 1377 def putfile(self, filename, flags, data):
1370 if 'l' in flags:
1378 if b'l' in flags:
1371 1379 self.wopener.symlink(data, filename)
1372 1380 else:
1373 1381 try:
@@ -1387,12 +1395,12 b' class svn_sink(converter_sink, commandli'
1387 1395
1388 1396 if self.is_exec:
1389 1397 if wasexec:
1390 if 'x' not in flags:
1398 if b'x' not in flags:
1391 1399 self.delexec.append(filename)
1392 1400 else:
1393 if 'x' in flags:
1401 if b'x' in flags:
1394 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 1405 def _copyfile(self, source, dest):
1398 1406 # SVN's copy command pukes if the destination file exists, but
@@ -1402,13 +1410,13 b' class svn_sink(converter_sink, commandli'
1402 1410 exists = os.path.lexists(wdest)
1403 1411 if exists:
1404 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 1415 os.close(fd)
1408 1416 os.unlink(tempname)
1409 1417 os.rename(wdest, tempname)
1410 1418 try:
1411 self.run0('copy', source, dest)
1419 self.run0(b'copy', source, dest)
1412 1420 finally:
1413 1421 self.manifest.add(dest)
1414 1422 if exists:
@@ -1424,7 +1432,7 b' class svn_sink(converter_sink, commandli'
1424 1432 if os.path.isdir(self.wjoin(f)):
1425 1433 dirs.add(f)
1426 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 1436 dirs.add(f[:i])
1429 1437 return dirs
1430 1438
@@ -1434,21 +1442,21 b' class svn_sink(converter_sink, commandli'
1434 1442 ]
1435 1443 if add_dirs:
1436 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 1446 return add_dirs
1439 1447
1440 1448 def add_files(self, files):
1441 1449 files = [f for f in files if f not in self.manifest]
1442 1450 if files:
1443 1451 self.manifest.update(files)
1444 self.xargs(files, 'add', quiet=True)
1452 self.xargs(files, b'add', quiet=True)
1445 1453 return files
1446 1454
1447 1455 def addchild(self, parent, child):
1448 1456 self.childmap[parent] = child
1449 1457
1450 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 1461 def putcommit(
1454 1462 self, files, copies, parents, commit, source, revmap, full, cleanp2
@@ -1480,49 +1488,49 b' class svn_sink(converter_sink, commandli'
1480 1488 self._copyfile(s, d)
1481 1489 self.copies = []
1482 1490 if self.delete:
1483 self.xargs(self.delete, 'delete')
1491 self.xargs(self.delete, b'delete')
1484 1492 for f in self.delete:
1485 1493 self.manifest.remove(f)
1486 1494 self.delete = []
1487 1495 entries.update(self.add_files(files.difference(entries)))
1488 1496 if self.delexec:
1489 self.xargs(self.delexec, 'propdel', 'svn:executable')
1497 self.xargs(self.delexec, b'propdel', b'svn:executable')
1490 1498 self.delexec = []
1491 1499 if self.setexec:
1492 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1500 self.xargs(self.setexec, b'propset', b'svn:executable', b'*')
1493 1501 self.setexec = []
1494 1502
1495 fd, messagefile = pycompat.mkstemp(prefix='hg-convert-')
1503 fd, messagefile = pycompat.mkstemp(prefix=b'hg-convert-')
1496 1504 fp = os.fdopen(fd, r'wb')
1497 1505 fp.write(util.tonativeeol(commit.desc))
1498 1506 fp.close()
1499 1507 try:
1500 1508 output = self.run0(
1501 'commit',
1509 b'commit',
1502 1510 username=stringutil.shortuser(commit.author),
1503 1511 file=messagefile,
1504 encoding='utf-8',
1512 encoding=b'utf-8',
1505 1513 )
1506 1514 try:
1507 1515 rev = self.commit_re.search(output).group(1)
1508 1516 except AttributeError:
1509 1517 if not files:
1510 return parents[0] if parents else 'None'
1511 self.ui.warn(_('unexpected svn output:\n'))
1518 return parents[0] if parents else b'None'
1519 self.ui.warn(_(b'unexpected svn output:\n'))
1512 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 1522 if commit.rev:
1515 1523 self.run(
1516 'propset',
1517 'hg:convert-rev',
1524 b'propset',
1525 b'hg:convert-rev',
1518 1526 commit.rev,
1519 1527 revprop=True,
1520 1528 revision=rev,
1521 1529 )
1522 if commit.branch and commit.branch != 'default':
1530 if commit.branch and commit.branch != b'default':
1523 1531 self.run(
1524 'propset',
1525 'hg:convert-branch',
1532 b'propset',
1533 b'hg:convert-branch',
1526 1534 commit.branch,
1527 1535 revprop=True,
1528 1536 revision=rev,
@@ -1534,7 +1542,7 b' class svn_sink(converter_sink, commandli'
1534 1542 os.unlink(messagefile)
1535 1543
1536 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 1546 return None, None
1539 1547
1540 1548 def hascommitfrommap(self, rev):
@@ -1549,8 +1557,8 b' class svn_sink(converter_sink, commandli'
1549 1557 return True
1550 1558 raise error.Abort(
1551 1559 _(
1552 'splice map revision %s not found in subversion '
1553 'child map (revision lookups are not implemented)'
1560 b'splice map revision %s not found in subversion '
1561 b'child map (revision lookups are not implemented)'
1554 1562 )
1555 1563 % rev
1556 1564 )
@@ -54,13 +54,13 b' def _create_auth_baton(pool):'
54 54 )
55 55 if getprovider:
56 56 # Available in svn >= 1.6
57 for name in ('gnome_keyring', 'keychain', 'kwallet', 'windows'):
58 for type in ('simple', 'ssl_client_cert_pw', 'ssl_server_trust'):
57 for name in (b'gnome_keyring', b'keychain', b'kwallet', b'windows'):
58 for type in (b'simple', b'ssl_client_cert_pw', b'ssl_server_trust'):
59 59 p = getprovider(name, type, pool)
60 60 if p:
61 61 providers.append(p)
62 62 else:
63 if util.safehasattr(svn.client, 'get_windows_simple_provider'):
63 if util.safehasattr(svn.client, b'get_windows_simple_provider'):
64 64 providers.append(svn.client.get_windows_simple_provider(pool))
65 65
66 66 return svn.core.svn_auth_open(providers, pool)
@@ -75,14 +75,14 b' class SvnRaTransport(object):'
75 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 79 self.pool = Pool()
80 80 self.svn_url = url
81 self.username = ''
82 self.password = ''
81 self.username = b''
82 self.password = b''
83 83
84 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 86 self.client = svn.client.create_context(self.pool)
87 87 ab = _create_auth_baton(self.pool)
88 88 self.client.auth_baton = ab
@@ -112,41 +112,41 b' from mercurial.utils import stringutil'
112 112 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
113 113 # be specifying the version(s) of Mercurial they are tested with, or
114 114 # leave the attribute unspecified.
115 testedwith = 'ships-with-hg-core'
115 testedwith = b'ships-with-hg-core'
116 116
117 117 configtable = {}
118 118 configitem = registrar.configitem(configtable)
119 119
120 120 configitem(
121 'eol', 'fix-trailing-newline', default=False,
121 b'eol', b'fix-trailing-newline', default=False,
122 122 )
123 123 configitem(
124 'eol', 'native', default=pycompat.oslinesep,
124 b'eol', b'native', default=pycompat.oslinesep,
125 125 )
126 126 configitem(
127 'eol', 'only-consistent', default=True,
127 b'eol', b'only-consistent', default=True,
128 128 )
129 129
130 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 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 138 def tolf(s, params, ui, **kwargs):
139 139 """Filter to convert to LF EOLs."""
140 140 if stringutil.binary(s):
141 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 143 return s
144 144 if (
145 ui.configbool('eol', 'fix-trailing-newline')
145 ui.configbool(b'eol', b'fix-trailing-newline')
146 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 150 return util.tolf(s)
151 151
152 152
@@ -154,14 +154,14 b' def tocrlf(s, params, ui, **kwargs):'
154 154 """Filter to convert to CRLF EOLs."""
155 155 if stringutil.binary(s):
156 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 158 return s
159 159 if (
160 ui.configbool('eol', 'fix-trailing-newline')
160 ui.configbool(b'eol', b'fix-trailing-newline')
161 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 165 return util.tocrlf(s)
166 166
167 167
@@ -171,60 +171,68 b' def isbinary(s, params):'
171 171
172 172
173 173 filters = {
174 'to-lf': tolf,
175 'to-crlf': tocrlf,
176 'is-binary': isbinary,
174 b'to-lf': tolf,
175 b'to-crlf': tocrlf,
176 b'is-binary': isbinary,
177 177 # The following provide backwards compatibility with win32text
178 'cleverencode:': tolf,
179 'cleverdecode:': tocrlf,
178 b'cleverencode:': tolf,
179 b'cleverdecode:': tocrlf,
180 180 }
181 181
182 182
183 183 class eolfile(object):
184 184 def __init__(self, ui, root, data):
185 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
186 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
185 self._decode = {
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 196 self.cfg = config.config()
189 197 # Our files should not be touched. The pattern must be
190 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 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'
196 self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
197 iswdlf = ui.config('eol', 'native') in ('LF', '\n')
198 self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
203 isrepolf = self.cfg.get(b'repository', b'native') != b'CRLF'
204 self._encode[b'NATIVE'] = isrepolf and b'to-lf' or b'to-crlf'
205 iswdlf = ui.config(b'eol', b'native') in (b'LF', b'\n')
206 self._decode[b'NATIVE'] = iswdlf and b'to-lf' or b'to-crlf'
199 207
200 208 include = []
201 209 exclude = []
202 210 self.patterns = []
203 for pattern, style in self.cfg.items('patterns'):
211 for pattern, style in self.cfg.items(b'patterns'):
204 212 key = style.upper()
205 if key == 'BIN':
213 if key == b'BIN':
206 214 exclude.append(pattern)
207 215 else:
208 216 include.append(pattern)
209 m = match.match(root, '', [pattern])
217 m = match.match(root, b'', [pattern])
210 218 self.patterns.append((pattern, key, m))
211 219 # This will match the files for which we need to care
212 220 # about inconsistent newlines.
213 self.match = match.match(root, '', [], include, exclude)
221 self.match = match.match(root, b'', [], include, exclude)
214 222
215 223 def copytoui(self, ui):
216 224 for pattern, key, m in self.patterns:
217 225 try:
218 ui.setconfig('decode', pattern, self._decode[key], 'eol')
219 ui.setconfig('encode', pattern, self._encode[key], 'eol')
226 ui.setconfig(b'decode', pattern, self._decode[key], b'eol')
227 ui.setconfig(b'encode', pattern, self._encode[key], b'eol')
220 228 except KeyError:
221 229 ui.warn(
222 _("ignoring unknown EOL style '%s' from %s\n")
223 % (key, self.cfg.source('patterns', pattern))
230 _(b"ignoring unknown EOL style '%s' from %s\n")
231 % (key, self.cfg.source(b'patterns', pattern))
224 232 )
225 233 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
226 for k, v in self.cfg.items('eol'):
227 ui.setconfig('eol', k, v, 'eol')
234 for k, v in self.cfg.items(b'eol'):
235 ui.setconfig(b'eol', k, v, b'eol')
228 236
229 237 def checkrev(self, repo, ctx, files):
230 238 failed = []
@@ -237,9 +245,9 b' class eolfile(object):'
237 245 target = self._encode[key]
238 246 data = ctx[f].data()
239 247 if (
240 target == "to-lf"
241 and "\r\n" in data
242 or target == "to-crlf"
248 target == b"to-lf"
249 and b"\r\n" in data
250 or target == b"to-crlf"
243 251 and singlelf.search(data)
244 252 ):
245 253 failed.append((f, target, bytes(ctx)))
@@ -254,15 +262,18 b' def parseeol(ui, repo, nodes):'
254 262 if node is None:
255 263 # Cannot use workingctx.data() since it would load
256 264 # and cache the filters before we configure them.
257 data = repo.wvfs('.hgeol').read()
265 data = repo.wvfs(b'.hgeol').read()
258 266 else:
259 data = repo[node]['.hgeol'].data()
267 data = repo[node][b'.hgeol'].data()
260 268 return eolfile(ui, repo.root, data)
261 269 except (IOError, LookupError):
262 270 pass
263 271 except errormod.ParseError as inst:
264 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 277 % (inst.args[1], inst.args[0])
267 278 )
268 279 return None
@@ -276,10 +287,10 b' def ensureenabled(ui):'
276 287 never loaded. This function ensure the extension is enabled when running
277 288 hooks.
278 289 """
279 if 'eol' in ui._knownconfig:
290 if b'eol' in ui._knownconfig:
280 291 return
281 ui.setconfig('extensions', 'eol', '', source='internal')
282 extensions.loadall(ui, ['eol'])
292 ui.setconfig(b'extensions', b'eol', b'', source=b'internal')
293 extensions.loadall(ui, [b'eol'])
283 294
284 295
285 296 def _checkhook(ui, repo, node, headsonly):
@@ -302,14 +313,16 b' def _checkhook(ui, repo, node, headsonly'
302 313 failed.extend(eol.checkrev(repo, ctx, files))
303 314
304 315 if failed:
305 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
316 eols = {b'to-lf': b'CRLF', b'to-crlf': b'LF'}
306 317 msgs = []
307 318 for f, target, node in sorted(failed):
308 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 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 328 def checkallhook(ui, repo, node, hooktype, **kwargs):
@@ -333,16 +346,16 b' def preupdate(ui, repo, hooktype, parent'
333 346
334 347
335 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 352 def extsetup(ui):
340 353 try:
341 extensions.find('win32text')
354 extensions.find(b'win32text')
342 355 ui.warn(
343 356 _(
344 "the eol extension is incompatible with the "
345 "win32text extension\n"
357 b"the eol extension is incompatible with the "
358 b"win32text extension\n"
346 359 )
347 360 )
348 361 except KeyError:
@@ -357,7 +370,7 b' def reposetup(ui, repo):'
357 370 for name, fn in filters.iteritems():
358 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 375 class eolrepo(repo.__class__):
363 376 def loadeol(self, nodes):
@@ -368,37 +381,37 b' def reposetup(ui, repo):'
368 381 return eol.match
369 382
370 383 def _hgcleardirstate(self):
371 self._eolmatch = self.loadeol([None, 'tip'])
384 self._eolmatch = self.loadeol([None, b'tip'])
372 385 if not self._eolmatch:
373 386 self._eolmatch = util.never
374 387 return
375 388
376 389 oldeol = None
377 390 try:
378 cachemtime = os.path.getmtime(self.vfs.join("eol.cache"))
391 cachemtime = os.path.getmtime(self.vfs.join(b"eol.cache"))
379 392 except OSError:
380 393 cachemtime = 0
381 394 else:
382 olddata = self.vfs.read("eol.cache")
395 olddata = self.vfs.read(b"eol.cache")
383 396 if olddata:
384 397 oldeol = eolfile(self.ui, self.root, olddata)
385 398
386 399 try:
387 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
400 eolmtime = os.path.getmtime(self.wjoin(b".hgeol"))
388 401 except OSError:
389 402 eolmtime = 0
390 403
391 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 408 neweol = eolfile(self.ui, self.root, hgeoldata)
396 409
397 410 wlock = None
398 411 try:
399 412 wlock = self.wlock()
400 413 for f in self.dirstate:
401 if self.dirstate[f] != 'n':
414 if self.dirstate[f] != b'n':
402 415 continue
403 416 if oldeol is not None:
404 417 if not oldeol.match(f) and not neweol.match(f):
@@ -419,7 +432,7 b' def reposetup(ui, repo):'
419 432 # the new .hgeol file specify a different filter
420 433 self.dirstate.normallookup(f)
421 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 436 f.write(hgeoldata)
424 437 except errormod.LockUnavailable:
425 438 # If we cannot lock the repository and clear the
@@ -447,7 +460,7 b' def reposetup(ui, repo):'
447 460 continue
448 461 if inconsistenteol(data):
449 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 465 return super(eolrepo, self).commitctx(ctx, error, origctx)
453 466
@@ -118,26 +118,26 b' configtable = {}'
118 118 configitem = registrar.configitem(configtable)
119 119
120 120 configitem(
121 'extdiff', br'opts\..*', default='', generic=True,
121 b'extdiff', br'opts\..*', default=b'', generic=True,
122 122 )
123 123
124 124 configitem(
125 'extdiff', br'gui\..*', generic=True,
125 b'extdiff', br'gui\..*', generic=True,
126 126 )
127 127
128 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 132 configitem(
133 'diff-tools', br'.*\.gui$', generic=True,
133 b'diff-tools', br'.*\.gui$', generic=True,
134 134 )
135 135
136 136 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
137 137 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
138 138 # be specifying the version(s) of Mercurial they are tested with, or
139 139 # leave the attribute unspecified.
140 testedwith = 'ships-with-hg-core'
140 testedwith = b'ships-with-hg-core'
141 141
142 142
143 143 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
@@ -145,40 +145,40 b' def snapshot(ui, repo, files, node, tmpr'
145 145 if not using snapshot, -I/-X does not work and recursive diff
146 146 in tools like kdiff3 and meld displays too many files.'''
147 147 dirname = os.path.basename(repo.root)
148 if dirname == "":
149 dirname = "root"
148 if dirname == b"":
149 dirname = b"root"
150 150 if node is not None:
151 dirname = '%s.%s' % (dirname, short(node))
151 dirname = b'%s.%s' % (dirname, short(node))
152 152 base = os.path.join(tmproot, dirname)
153 153 os.mkdir(base)
154 154 fnsandstat = []
155 155
156 156 if node is not None:
157 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 159 % (len(files), short(node))
160 160 )
161 161 else:
162 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 164 % (len(files))
165 165 )
166 166
167 167 if files:
168 repo.ui.setconfig("ui", "archivemeta", False)
168 repo.ui.setconfig(b"ui", b"archivemeta", False)
169 169
170 170 archival.archive(
171 171 repo,
172 172 base,
173 173 node,
174 'files',
174 b'files',
175 175 match=scmutil.matchfiles(repo, files),
176 176 subrepos=listsubrepos,
177 177 )
178 178
179 179 for fn in sorted(files):
180 180 wfn = util.pconvert(fn)
181 ui.note(' %s\n' % wfn)
181 ui.note(b' %s\n' % wfn)
182 182
183 183 if node is None:
184 184 dest = os.path.join(base, wfn)
@@ -202,20 +202,20 b' def formatcmdline('
202 202 # When not operating in 3-way mode, an empty string is
203 203 # returned for parent2
204 204 replace = {
205 'parent': parent1,
206 'parent1': parent1,
207 'parent2': parent2,
208 'plabel1': plabel1,
209 'plabel2': plabel2,
210 'child': child,
211 'clabel': clabel,
212 'root': repo_root,
205 b'parent': parent1,
206 b'parent1': parent1,
207 b'parent2': parent2,
208 b'plabel1': plabel1,
209 b'plabel2': plabel2,
210 b'child': child,
211 b'clabel': clabel,
212 b'root': repo_root,
213 213 }
214 214
215 215 def quote(match):
216 216 pre = match.group(2)
217 217 key = match.group(3)
218 if not do3way and key == 'parent2':
218 if not do3way and key == b'parent2':
219 219 return pre
220 220 return pre + procutil.shellquote(replace[key])
221 221
@@ -225,7 +225,7 b' def formatcmdline('
225 225 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1'
226 226 )
227 227 if not do3way and not re.search(regex, cmdline):
228 cmdline += ' $parent1 $child'
228 cmdline += b' $parent1 $child'
229 229 return re.sub(regex, quote, cmdline)
230 230
231 231
@@ -273,8 +273,8 b' def _runperfilediff('
273 273 if not os.path.isfile(path1a):
274 274 path1a = os.devnull
275 275
276 path1b = ''
277 label1b = ''
276 path1b = b''
277 label1b = b''
278 278 if do3way:
279 279 path1b = os.path.join(tmproot, dir1b, commonfile)
280 280 label1b = commonfile + rev1b
@@ -286,24 +286,24 b' def _runperfilediff('
286 286
287 287 if confirm:
288 288 # Prompt before showing this diff
289 difffiles = _('diff %s (%d of %d)') % (
289 difffiles = _(b'diff %s (%d of %d)') % (
290 290 commonfile,
291 291 idx + 1,
292 292 totalfiles,
293 293 )
294 294 responses = _(
295 '[Yns?]'
296 '$$ &Yes, show diff'
297 '$$ &No, skip this diff'
298 '$$ &Skip remaining diffs'
299 '$$ &? (display help)'
295 b'[Yns?]'
296 b'$$ &Yes, show diff'
297 b'$$ &No, skip this diff'
298 b'$$ &Skip remaining diffs'
299 b'$$ &? (display help)'
300 300 )
301 r = ui.promptchoice('%s %s' % (difffiles, responses))
301 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
302 302 if r == 3: # ?
303 303 while r == 3:
304 304 for c, t in ui.extractchoices(responses)[1]:
305 ui.write('%s - %s\n' % (c, encoding.lower(t)))
306 r = ui.promptchoice('%s %s' % (difffiles, responses))
305 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
306 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
307 307 if r == 0: # yes
308 308 pass
309 309 elif r == 1: # no
@@ -331,22 +331,22 b' def _runperfilediff('
331 331 # as we know, the tool doesn't have a GUI, in which case
332 332 # we can't run multiple CLI programs at the same time.
333 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 337 else:
338 338 # Run the comparison program but don't wait, as we're
339 339 # going to rapid-fire each file diff and then wait on
340 340 # the whole group.
341 341 ui.debug(
342 'running %r in %s (backgrounded)\n'
342 b'running %r in %s (backgrounded)\n'
343 343 % (pycompat.bytestr(curcmdline), tmproot)
344 344 )
345 345 proc = _systembackground(curcmdline, cwd=tmproot)
346 346 waitprocs.append(proc)
347 347
348 348 if waitprocs:
349 with ui.timeblockedsection('extdiff'):
349 with ui.timeblockedsection(b'extdiff'):
350 350 for proc in waitprocs:
351 351 proc.wait()
352 352
@@ -360,12 +360,12 b' def dodiff(ui, repo, cmdline, pats, opts'
360 360 - just invoke the diff for a single file in the working dir
361 361 '''
362 362
363 revs = opts.get('rev')
364 change = opts.get('change')
365 do3way = '$parent2' in cmdline
363 revs = opts.get(b'rev')
364 change = opts.get(b'change')
365 do3way = b'$parent2' in cmdline
366 366
367 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 369 raise error.Abort(msg)
370 370 elif change:
371 371 ctx2 = scmutil.revsingle(repo, change, None)
@@ -377,8 +377,8 b' def dodiff(ui, repo, cmdline, pats, opts'
377 377 else:
378 378 ctx1b = repo[nullid]
379 379
380 perfile = opts.get('per_file')
381 confirm = opts.get('confirm')
380 perfile = opts.get(b'per_file')
381 confirm = opts.get(b'confirm')
382 382
383 383 node1a = ctx1a.node()
384 384 node1b = ctx1b.node()
@@ -389,17 +389,17 b' def dodiff(ui, repo, cmdline, pats, opts'
389 389 if node1b == nullid:
390 390 do3way = False
391 391
392 subrepos = opts.get('subrepos')
392 subrepos = opts.get(b'subrepos')
393 393
394 394 matcher = scmutil.match(repo[node2], pats, opts)
395 395
396 if opts.get('patch'):
396 if opts.get(b'patch'):
397 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 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 401 if node2 is None:
402 raise error.Abort(_('--patch requires two revisions'))
402 raise error.Abort(_(b'--patch requires two revisions'))
403 403 else:
404 404 mod_a, add_a, rem_a = map(
405 405 set, repo.status(node1a, node2, matcher, listsubrepos=subrepos)[:3]
@@ -416,33 +416,33 b' def dodiff(ui, repo, cmdline, pats, opts'
416 416 if not common:
417 417 return 0
418 418
419 tmproot = pycompat.mkdtemp(prefix='extdiff.')
419 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
420 420 try:
421 if not opts.get('patch'):
421 if not opts.get(b'patch'):
422 422 # Always make a copy of node1a (and node1b, if applicable)
423 423 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
424 424 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[
425 425 0
426 426 ]
427 rev1a = '@%d' % repo[node1a].rev()
427 rev1a = b'@%d' % repo[node1a].rev()
428 428 if do3way:
429 429 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
430 430 dir1b = snapshot(
431 431 ui, repo, dir1b_files, node1b, tmproot, subrepos
432 432 )[0]
433 rev1b = '@%d' % repo[node1b].rev()
433 rev1b = b'@%d' % repo[node1b].rev()
434 434 else:
435 435 dir1b = None
436 rev1b = ''
436 rev1b = b''
437 437
438 438 fnsandstat = []
439 439
440 440 # If node2 in not the wc or there is >1 change, copy it
441 dir2root = ''
442 rev2 = ''
441 dir2root = b''
442 rev2 = b''
443 443 if node2:
444 444 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
445 rev2 = '@%d' % repo[node2].rev()
445 rev2 = b'@%d' % repo[node2].rev()
446 446 elif len(common) > 1:
447 447 # we only actually need to get the files to copy back to
448 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 454 else:
455 455 # This lets the diff tool open the changed file directly
456 dir2 = ''
456 dir2 = b''
457 457 dir2root = repo.root
458 458
459 459 label1a = rev1a
@@ -476,8 +476,8 b' def dodiff(ui, repo, cmdline, pats, opts'
476 476 dir2 = os.path.join(dir2root, dir2, common_file)
477 477 label2 = common_file + rev2
478 478 else:
479 template = 'hg-%h.patch'
480 with formatter.nullformatter(ui, 'extdiff', {}) as fm:
479 template = b'hg-%h.patch'
480 with formatter.nullformatter(ui, b'extdiff', {}) as fm:
481 481 cmdutil.export(
482 482 repo,
483 483 [repo[node1a].rev(), repo[node2].rev()],
@@ -507,9 +507,9 b' def dodiff(ui, repo, cmdline, pats, opts'
507 507 clabel=label2,
508 508 )
509 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 513 else:
514 514 # Run the external tool once for each pair of files
515 515 _runperfilediff(
@@ -545,35 +545,41 b' def dodiff(ui, repo, cmdline, pats, opts'
545 545 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
546 546 ):
547 547 ui.debug(
548 'file changed while diffing. '
549 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
548 b'file changed while diffing. '
549 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
550 550 )
551 551 util.copyfile(copy_fn, working_fn)
552 552
553 553 return 1
554 554 finally:
555 ui.note(_('cleaning up temp directory\n'))
555 ui.note(_(b'cleaning up temp directory\n'))
556 556 shutil.rmtree(tmproot)
557 557
558 558
559 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 '',
566 'per-file',
562 b'o',
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 573 False,
568 _('compare each file instead of revision snapshots'),
574 _(b'compare each file instead of revision snapshots'),
569 575 ),
570 576 (
571 '',
572 'confirm',
577 b'',
578 b'confirm',
573 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 584 + cmdutil.walkopts
579 585 + cmdutil.subrepoopts
@@ -581,10 +587,10 b' extdiffopts = ('
581 587
582 588
583 589 @command(
584 'extdiff',
585 [('p', 'program', '', _('comparison program to run'), _('CMD')),]
590 b'extdiff',
591 [(b'p', b'program', b'', _(b'comparison program to run'), _(b'CMD')),]
586 592 + extdiffopts,
587 _('hg extdiff [OPT]... [FILE]...'),
593 _(b'hg extdiff [OPT]... [FILE]...'),
588 594 helpcategory=command.CATEGORY_FILE_CONTENTS,
589 595 inferrepo=True,
590 596 )
@@ -620,12 +626,12 b' def extdiff(ui, repo, *pats, **opts):'
620 626 the external program. It is ignored if --per-file isn't specified.
621 627 '''
622 628 opts = pycompat.byteskwargs(opts)
623 program = opts.get('program')
624 option = opts.get('option')
629 program = opts.get(b'program')
630 option = opts.get(b'option')
625 631 if not program:
626 program = 'diff'
627 option = option or ['-Npru']
628 cmdline = ' '.join(map(procutil.shellquote, [program] + option))
632 program = b'diff'
633 option = option or [b'-Npru']
634 cmdline = b' '.join(map(procutil.shellquote, [program] + option))
629 635 return dodiff(ui, repo, cmdline, pats, opts)
630 636
631 637
@@ -655,29 +661,29 b' class savedcmd(object):'
655 661
656 662 def __call__(self, ui, repo, *pats, **opts):
657 663 opts = pycompat.byteskwargs(opts)
658 options = ' '.join(map(procutil.shellquote, opts['option']))
664 options = b' '.join(map(procutil.shellquote, opts[b'option']))
659 665 if options:
660 options = ' ' + options
666 options = b' ' + options
661 667 return dodiff(
662 668 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
663 669 )
664 670
665 671
666 672 def uisetup(ui):
667 for cmd, path in ui.configitems('extdiff'):
673 for cmd, path in ui.configitems(b'extdiff'):
668 674 path = util.expandpath(path)
669 if cmd.startswith('cmd.'):
675 if cmd.startswith(b'cmd.'):
670 676 cmd = cmd[4:]
671 677 if not path:
672 678 path = procutil.findexe(cmd)
673 679 if path is None:
674 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 682 cmdline = procutil.shellquote(path)
677 683 if diffopts:
678 cmdline += ' ' + diffopts
679 isgui = ui.configbool('extdiff', 'gui.' + cmd)
680 elif cmd.startswith('opts.') or cmd.startswith('gui.'):
684 cmdline += b' ' + diffopts
685 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
686 elif cmd.startswith(b'opts.') or cmd.startswith(b'gui.'):
681 687 continue
682 688 else:
683 689 if path:
@@ -691,21 +697,21 b' def uisetup(ui):'
691 697 path = filemerge.findexternaltool(ui, cmd) or cmd
692 698 cmdline = procutil.shellquote(path)
693 699 diffopts = False
694 isgui = ui.configbool('extdiff', 'gui.' + cmd)
700 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
695 701 # look for diff arguments in [diff-tools] then [merge-tools]
696 702 if not diffopts:
697 key = cmd + '.diffargs'
698 for section in ('diff-tools', 'merge-tools'):
703 key = cmd + b'.diffargs'
704 for section in (b'diff-tools', b'merge-tools'):
699 705 args = ui.config(section, key)
700 706 if args:
701 cmdline += ' ' + args
707 cmdline += b' ' + args
702 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 710 break
705 711 command(
706 712 cmd,
707 713 extdiffopts[:],
708 _('hg %s [OPTION]... [FILE]...') % cmd,
714 _(b'hg %s [OPTION]... [FILE]...') % cmd,
709 715 helpcategory=command.CATEGORY_FILE_CONTENTS,
710 716 inferrepo=True,
711 717 )(savedcmd(path, cmdline, isgui))
@@ -69,44 +69,44 b' configtable = {}'
69 69 configitem = registrar.configitem(configtable)
70 70
71 71 configitem(
72 'factotum', 'executable', default='/bin/auth/factotum',
72 b'factotum', b'executable', default=b'/bin/auth/factotum',
73 73 )
74 74 configitem(
75 'factotum', 'mountpoint', default='/mnt/factotum',
75 b'factotum', b'mountpoint', default=b'/mnt/factotum',
76 76 )
77 77 configitem(
78 'factotum', 'service', default='hg',
78 b'factotum', b'service', default=b'hg',
79 79 )
80 80
81 81
82 82 def auth_getkey(self, params):
83 83 if not self.ui.interactive():
84 raise error.Abort(_('factotum not interactive'))
85 if 'user=' not in params:
86 params = '%s user?' % params
87 params = '%s !password?' % params
88 os.system(procutil.tonativestr("%s -g '%s'" % (_executable, params)))
84 raise error.Abort(_(b'factotum not interactive'))
85 if b'user=' not in params:
86 params = b'%s user?' % params
87 params = b'%s !password?' % params
88 os.system(procutil.tonativestr(b"%s -g '%s'" % (_executable, params)))
89 89
90 90
91 91 def auth_getuserpasswd(self, getkey, params):
92 params = 'proto=pass %s' % params
92 params = b'proto=pass %s' % params
93 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 95 try:
96 os.write(fd, 'start %s' % params)
96 os.write(fd, b'start %s' % params)
97 97 l = os.read(fd, ERRMAX).split()
98 if l[0] == 'ok':
99 os.write(fd, 'read')
98 if l[0] == b'ok':
99 os.write(fd, b'read')
100 100 status, user, passwd = os.read(fd, ERRMAX).split(None, 2)
101 if status == 'ok':
102 if passwd.startswith("'"):
103 if passwd.endswith("'"):
104 passwd = passwd[1:-1].replace("''", "'")
101 if status == b'ok':
102 if passwd.startswith(b"'"):
103 if passwd.endswith(b"'"):
104 passwd = passwd[1:-1].replace(b"''", b"'")
105 105 else:
106 raise error.Abort(_('malformed password string'))
106 raise error.Abort(_(b'malformed password string'))
107 107 return (user, passwd)
108 108 except (OSError, IOError):
109 raise error.Abort(_('factotum not responding'))
109 raise error.Abort(_(b'factotum not responding'))
110 110 finally:
111 111 os.close(fd)
112 112 getkey(self, params)
@@ -127,18 +127,18 b' def find_user_password(self, realm, auth'
127 127 self._writedebug(user, passwd)
128 128 return (user, passwd)
129 129
130 prefix = ''
130 prefix = b''
131 131 res = httpconnection.readauthforuri(self.ui, authuri, user)
132 132 if res:
133 133 _, auth = res
134 prefix = auth.get('prefix')
135 user, passwd = auth.get('username'), auth.get('password')
134 prefix = auth.get(b'prefix')
135 user, passwd = auth.get(b'username'), auth.get(b'password')
136 136 if not user or not passwd:
137 137 if not prefix:
138 prefix = realm.split(' ')[0].lower()
139 params = 'service=%s prefix=%s' % (_service, prefix)
138 prefix = realm.split(b' ')[0].lower()
139 params = b'service=%s prefix=%s' % (_service, prefix)
140 140 if user:
141 params = '%s user=%s' % (params, user)
141 params = b'%s user=%s' % (params, user)
142 142 user, passwd = auth_getuserpasswd(self, auth_getkey, params)
143 143
144 144 self.add_password(realm, authuri, user, passwd)
@@ -148,8 +148,8 b' def find_user_password(self, realm, auth'
148 148
149 149 def uisetup(ui):
150 150 global _executable
151 _executable = ui.config('factotum', 'executable')
151 _executable = ui.config(b'factotum', b'executable')
152 152 global _mountpoint
153 _mountpoint = ui.config('factotum', 'mountpoint')
153 _mountpoint = ui.config(b'factotum', b'mountpoint')
154 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 119 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
120 120 # be specifying the version(s) of Mercurial they are tested with, or
121 121 # leave the attribute unspecified.
122 testedwith = 'ships-with-hg-core'
122 testedwith = b'ships-with-hg-core'
123 123
124 124 cmdtable = commands.cmdtable
125 125
126 126 configtable = {}
127 127 configitem = registrar.configitem(configtable)
128 128
129 configitem('fastannotate', 'modes', default=['fastannotate'])
130 configitem('fastannotate', 'server', default=False)
131 configitem('fastannotate', 'client', default=False)
132 configitem('fastannotate', 'unfilteredrepo', default=True)
133 configitem('fastannotate', 'defaultformat', default=['number'])
134 configitem('fastannotate', 'perfhack', default=False)
135 configitem('fastannotate', 'mainbranch')
136 configitem('fastannotate', 'forcetext', default=True)
137 configitem('fastannotate', 'forcefollow', default=True)
138 configitem('fastannotate', 'clientfetchthreshold', default=10)
139 configitem('fastannotate', 'serverbuildondemand', default=True)
140 configitem('fastannotate', 'remotepath', default='default')
129 configitem(b'fastannotate', b'modes', default=[b'fastannotate'])
130 configitem(b'fastannotate', b'server', default=False)
131 configitem(b'fastannotate', b'client', default=False)
132 configitem(b'fastannotate', b'unfilteredrepo', default=True)
133 configitem(b'fastannotate', b'defaultformat', default=[b'number'])
134 configitem(b'fastannotate', b'perfhack', default=False)
135 configitem(b'fastannotate', b'mainbranch')
136 configitem(b'fastannotate', b'forcetext', default=True)
137 configitem(b'fastannotate', b'forcefollow', default=True)
138 configitem(b'fastannotate', b'clientfetchthreshold', default=10)
139 configitem(b'fastannotate', b'serverbuildondemand', default=True)
140 configitem(b'fastannotate', b'remotepath', default=b'default')
141 141
142 142
143 143 def uisetup(ui):
144 modes = set(ui.configlist('fastannotate', 'modes'))
145 if 'fctx' in modes:
146 modes.discard('hgweb')
144 modes = set(ui.configlist(b'fastannotate', b'modes'))
145 if b'fctx' in modes:
146 modes.discard(b'hgweb')
147 147 for name in modes:
148 if name == 'fastannotate':
148 if name == b'fastannotate':
149 149 commands.registercommand()
150 elif name == 'hgweb':
150 elif name == b'hgweb':
151 151 from . import support
152 152
153 153 support.replacehgwebannotate()
154 elif name == 'fctx':
154 elif name == b'fctx':
155 155 from . import support
156 156
157 157 support.replacefctxannotate()
158 158 commands.wrapdefault()
159 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 163 protocol.serveruisetup(ui)
164 164
165 165
166 166 def extsetup(ui):
167 167 # fastannotate has its own locking, without depending on repo lock
168 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 172 def reposetup(ui, repo):
173 if ui.configbool('fastannotate', 'client'):
173 if ui.configbool(b'fastannotate', b'client'):
174 174 protocol.clientreposetup(ui, repo)
@@ -34,7 +34,7 b' command = registrar.command(cmdtable)'
34 34
35 35 def _matchpaths(repo, rev, pats, opts, aopts=facontext.defaultopts):
36 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 39 # disable perfhack if:
40 40 # a) any walkopt is used
@@ -44,8 +44,8 b' def _matchpaths(repo, rev, pats, opts, a'
44 44 # cwd related to reporoot
45 45 reporoot = os.path.dirname(repo.path)
46 46 reldir = os.path.relpath(encoding.getcwd(), reporoot)
47 if reldir == '.':
48 reldir = ''
47 if reldir == b'.':
48 reldir = b''
49 49 if any(opts.get(o[1]) for o in commands.walkopts): # a)
50 50 perfhack = False
51 51 else: # b)
@@ -56,7 +56,7 b' def _matchpaths(repo, rev, pats, opts, a'
56 56 # disable perfhack on '..' since it allows escaping from the repo
57 57 if any(
58 58 (
59 '..' in f
59 b'..' in f
60 60 or not os.path.isfile(
61 61 facontext.pathhelper(repo, f, aopts).linelogpath
62 62 )
@@ -73,7 +73,7 b' def _matchpaths(repo, rev, pats, opts, a'
73 73 else:
74 74
75 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 78 ctx = scmutil.revsingle(repo, rev)
79 79 m = scmutil.match(ctx, pats, opts, badfn=bad)
@@ -83,42 +83,57 b' def _matchpaths(repo, rev, pats, opts, a'
83 83
84 84 fastannotatecommandargs = {
85 85 r'options': [
86 ('r', 'rev', '.', _('annotate the specified revision'), _('REV')),
87 ('u', 'user', None, _('list the author (long with -v)')),
88 ('f', 'file', None, _('list the filename')),
89 ('d', 'date', None, _('list the date (short with -q)')),
90 ('n', 'number', None, _('list the revision number (default)')),
91 ('c', 'changeset', None, _('list the changeset')),
86 (b'r', b'rev', b'.', _(b'annotate the specified revision'), _(b'REV')),
87 (b'u', b'user', None, _(b'list the author (long with -v)')),
88 (b'f', b'file', None, _(b'list the filename')),
89 (b'd', b'date', None, _(b'list the date (short with -q)')),
90 (b'n', b'number', None, _(b'list the revision number (default)')),
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',
94 'line-number',
99 b'e',
100 b'deleted',
95 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 '',
103 'linear',
105 b'',
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 114 None,
105 115 _(
106 'enforce linear history, ignore second parent '
107 'of merges (EXPERIMENTAL)'
116 b'enforce linear history, ignore second parent '
117 b'of merges (EXPERIMENTAL)'
108 118 ),
109 119 ),
110 ('', 'long-hash', None, _('show long changeset hash (EXPERIMENTAL)')),
111 120 (
112 '',
113 'rebuild',
121 b'',
122 b'long-hash',
114 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 133 + commands.diffwsopts
119 134 + commands.walkopts
120 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 137 r'inferrepo': True,
123 138 }
124 139
@@ -155,52 +170,55 b' def fastannotate(ui, repo, *pats, **opts'
155 170 affecting results are used.
156 171 """
157 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 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 177 repo = repo.unfiltered()
163 178
164 179 opts = pycompat.byteskwargs(opts)
165 180
166 rev = opts.get('rev', '.')
167 rebuild = opts.get('rebuild', False)
181 rev = opts.get(b'rev', b'.')
182 rebuild = opts.get(b'rebuild', False)
168 183
169 184 diffopts = patch.difffeatureopts(
170 ui, opts, section='annotate', whitespace=True
185 ui, opts, section=b'annotate', whitespace=True
171 186 )
172 187 aopts = facontext.annotateopts(
173 188 diffopts=diffopts,
174 followmerge=not opts.get('linear', False),
175 followrename=not opts.get('no_follow', False),
189 followmerge=not opts.get(b'linear', False),
190 followrename=not opts.get(b'no_follow', False),
176 191 )
177 192
178 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 197 # default 'number' for compatibility. but fastannotate is more
182 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 202 opts[name] = True
185 203
186 ui.pager('fastannotate')
187 template = opts.get('template')
188 if template == 'json':
204 ui.pager(b'fastannotate')
205 template = opts.get(b'template')
206 if template == b'json':
189 207 formatter = faformatter.jsonformatter(ui, repo, opts)
190 208 else:
191 209 formatter = faformatter.defaultformatter(ui, repo, opts)
192 showdeleted = opts.get('deleted', False)
193 showlines = not bool(opts.get('no_content'))
194 showpath = opts.get('file', False)
210 showdeleted = opts.get(b'deleted', False)
211 showlines = not bool(opts.get(b'no_content'))
212 showpath = opts.get(b'file', False)
195 213
196 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 217 # paths will be used for prefetching and the real annotating
200 218 paths = list(_matchpaths(repo, rev, pats, opts, aopts))
201 219
202 220 # for client, prefetch from the server
203 if util.safehasattr(repo, 'prefetchfastannotate'):
221 if util.safehasattr(repo, b'prefetchfastannotate'):
204 222 repo.prefetchfastannotate(paths)
205 223
206 224 for path in paths:
@@ -238,7 +256,7 b' def fastannotate(ui, repo, *pats, **opts'
238 256
239 257 _newopts = set()
240 258 _knownopts = {
241 opt[1].replace('-', '_')
259 opt[1].replace(b'-', b'_')
242 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 264 def _annotatewrapper(orig, ui, repo, *pats, **opts):
247 265 """used by wrapdefault"""
248 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 268 repo = repo.unfiltered()
251 269
252 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 272 opts[r'text'] = True
255 273
256 274 # check if we need to do prefetch (client-side)
257 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 277 paths = list(_matchpaths(repo, rev, pats, pycompat.byteskwargs(opts)))
260 278 repo.prefetchfastannotate(paths)
261 279
@@ -264,20 +282,20 b' def _annotatewrapper(orig, ui, repo, *pa'
264 282
265 283 def registercommand():
266 284 """register the fastannotate command"""
267 name = 'fastannotate|fastblame|fa'
285 name = b'fastannotate|fastblame|fa'
268 286 command(name, helpbasic=True, **fastannotatecommandargs)(fastannotate)
269 287
270 288
271 289 def wrapdefault():
272 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 294 @command(
277 'debugbuildannotatecache',
278 [('r', 'rev', '', _('build up to the specific revision'), _('REV'))]
295 b'debugbuildannotatecache',
296 [(b'r', b'rev', b'', _(b'build up to the specific revision'), _(b'REV'))]
279 297 + commands.walkopts,
280 _('[-r REV] FILE...'),
298 _(b'[-r REV] FILE...'),
281 299 )
282 300 def debugbuildannotatecache(ui, repo, *pats, **opts):
283 301 """incrementally build fastannotate cache up to REV for specified files
@@ -291,25 +309,25 b' def debugbuildannotatecache(ui, repo, *p'
291 309 options and lives in '.hg/fastannotate/default'.
292 310 """
293 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 313 if not rev:
296 314 raise error.Abort(
297 _('you need to provide a revision'),
298 hint=_('set fastannotate.mainbranch or use --rev'),
315 _(b'you need to provide a revision'),
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 319 repo = repo.unfiltered()
302 320 ctx = scmutil.revsingle(repo, rev)
303 321 m = scmutil.match(ctx, pats, opts)
304 322 paths = list(ctx.walk(m))
305 if util.safehasattr(repo, 'prefetchfastannotate'):
323 if util.safehasattr(repo, b'prefetchfastannotate'):
306 324 # client
307 if opts.get('REV'):
308 raise error.Abort(_('--rev cannot be used for client'))
325 if opts.get(b'REV'):
326 raise error.Abort(_(b'--rev cannot be used for client'))
309 327 repo.prefetchfastannotate(paths)
310 328 else:
311 329 # server, or full repo
312 progress = ui.makeprogress(_('building'), total=len(paths))
330 progress = ui.makeprogress(_(b'building'), total=len(paths))
313 331 for i, path in enumerate(paths):
314 332 progress.update(i)
315 333 with facontext.annotatecontext(repo, path) as actx:
@@ -321,7 +339,7 b' def debugbuildannotatecache(ui, repo, *p'
321 339 # the cache is broken (could happen with renaming so the
322 340 # file history gets invalidated). rebuild and try again.
323 341 ui.debug(
324 'fastannotate: %s: rebuilding broken cache\n' % path
342 b'fastannotate: %s: rebuilding broken cache\n' % path
325 343 )
326 344 actx.rebuild()
327 345 try:
@@ -331,8 +349,8 b' def debugbuildannotatecache(ui, repo, *p'
331 349 # cache for other files.
332 350 ui.warn(
333 351 _(
334 'fastannotate: %s: failed to '
335 'build cache: %r\n'
352 b'fastannotate: %s: failed to '
353 b'build cache: %r\n'
336 354 )
337 355 % (path, ex)
338 356 )
@@ -52,7 +52,7 b' def _parents(f, follow=True):'
52 52 # renamed filectx won't have a filelog yet, so set it
53 53 # from the cache to save time
54 54 for p in pl:
55 if not '_filelog' in p.__dict__:
55 if not b'_filelog' in p.__dict__:
56 56 p._filelog = _getflog(f._repo, p.path())
57 57
58 58 return pl
@@ -62,8 +62,8 b' def _parents(f, follow=True):'
62 62 # so it takes a fctx instead of a pair of text and fctx.
63 63 def _decorate(fctx):
64 64 text = fctx.data()
65 linecount = text.count('\n')
66 if text and not text.endswith('\n'):
65 linecount = text.count(b'\n')
66 if text and not text.endswith(b'\n'):
67 67 linecount += 1
68 68 return ([(fctx, i) for i in pycompat.xrange(linecount)], text)
69 69
@@ -75,7 +75,7 b' def _pair(parent, child, blocks):'
75 75 for (a1, a2, b1, b2), t in blocks:
76 76 # Changed blocks ('!') or blocks made only of blank lines ('~')
77 77 # belong to the child.
78 if t == '=':
78 if t == b'=':
79 79 child[0][b1:b2] = parent[0][a1:a2]
80 80 return child
81 81
@@ -119,7 +119,7 b' def resolvefctx(repo, rev, path, resolve'
119 119 fctx = repo.filectx(path, changeid=ctx.rev())
120 120 else:
121 121 fctx = ctx[path]
122 if adjustctx == 'linkrev':
122 if adjustctx == b'linkrev':
123 123 introrev = fctx.linkrev()
124 124 else:
125 125 introrev = fctx.introrev()
@@ -132,10 +132,10 b' def resolvefctx(repo, rev, path, resolve'
132 132 # like mercurial.store.encodedir, but use linelog suffixes: .m, .l, .lock
133 133 def encodedir(path):
134 134 return (
135 path.replace('.hg/', '.hg.hg/')
136 .replace('.l/', '.l.hg/')
137 .replace('.m/', '.m.hg/')
138 .replace('.lock/', '.lock.hg/')
135 path.replace(b'.hg/', b'.hg.hg/')
136 .replace(b'.l/', b'.l.hg/')
137 .replace(b'.m/', b'.m.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 159 defaults = {
160 'diffopts': None,
161 'followrename': True,
162 'followmerge': True,
160 b'diffopts': None,
161 b'followrename': True,
162 b'followmerge': True,
163 163 }
164 164
165 165 def __init__(self, **opts):
@@ -170,17 +170,17 b' class annotateopts(object):'
170 170 @util.propertycache
171 171 def shortstr(self):
172 172 """represent opts in a short string, suitable for a directory name"""
173 result = ''
173 result = b''
174 174 if not self.followrename:
175 result += 'r0'
175 result += b'r0'
176 176 if not self.followmerge:
177 result += 'm0'
177 result += b'm0'
178 178 if self.diffopts is not None:
179 179 assert isinstance(self.diffopts, mdiff.diffopts)
180 180 diffopthash = hashdiffopts(self.diffopts)
181 181 if diffopthash != _defaultdiffopthash:
182 result += 'i' + diffopthash
183 return result or 'default'
182 result += b'i' + diffopthash
183 return result or b'default'
184 184
185 185
186 186 defaultopts = annotateopts()
@@ -206,7 +206,7 b' class _annotatecontext(object):'
206 206 def linelog(self):
207 207 if self._linelog is None:
208 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 210 try:
211 211 self._linelog = linelogmod.linelog.fromdata(f.read())
212 212 except linelogmod.LineLogError:
@@ -226,7 +226,7 b' class _annotatecontext(object):'
226 226 self._revmap.flush()
227 227 self._revmap = None
228 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 230 f.write(self._linelog.encode())
231 231 self._linelog = None
232 232
@@ -308,11 +308,11 b' class _annotatecontext(object):'
308 308 if directly:
309 309 if self.ui.debugflag:
310 310 self.ui.debug(
311 'fastannotate: %s: using fast path '
312 '(resolved fctx: %s)\n'
311 b'fastannotate: %s: using fast path '
312 b'(resolved fctx: %s)\n'
313 313 % (
314 314 self.path,
315 stringutil.pprint(util.safehasattr(revfctx, 'node')),
315 stringutil.pprint(util.safehasattr(revfctx, b'node')),
316 316 )
317 317 )
318 318 return self.annotatedirectly(revfctx, showpath, showlines)
@@ -356,8 +356,8 b' class _annotatecontext(object):'
356 356 if masterfctx:
357 357 if masterfctx.rev() is None:
358 358 raise error.Abort(
359 _('cannot update linelog to wdir()'),
360 hint=_('set fastannotate.mainbranch'),
359 _(b'cannot update linelog to wdir()'),
360 hint=_(b'set fastannotate.mainbranch'),
361 361 )
362 362 initvisit.append(masterfctx)
363 363 visit = initvisit[:]
@@ -403,13 +403,13 b' class _annotatecontext(object):'
403 403 if self.ui.debugflag:
404 404 if newmainbranch:
405 405 self.ui.debug(
406 'fastannotate: %s: %d new changesets in the main'
407 ' branch\n' % (self.path, len(newmainbranch))
406 b'fastannotate: %s: %d new changesets in the main'
407 b' branch\n' % (self.path, len(newmainbranch))
408 408 )
409 409 elif not hist: # no joints, no updates
410 410 self.ui.debug(
411 'fastannotate: %s: linelog cannot help in '
412 'annotating this revision\n' % self.path
411 b'fastannotate: %s: linelog cannot help in '
412 b'annotating this revision\n' % self.path
413 413 )
414 414
415 415 # prepare annotateresult so we can update linelog incrementally
@@ -418,7 +418,7 b' class _annotatecontext(object):'
418 418 # 3rd DFS does the actual annotate
419 419 visit = initvisit[:]
420 420 progress = self.ui.makeprogress(
421 'building cache', total=len(newmainbranch)
421 b'building cache', total=len(newmainbranch)
422 422 )
423 423 while visit:
424 424 f = visit[-1]
@@ -463,7 +463,7 b' class _annotatecontext(object):'
463 463 if len(pl) == 2 and self.opts.followmerge: # merge
464 464 bannotated = curr[0]
465 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 467 self._appendrev(f, blocks, bannotated)
468 468 elif showpath: # not append linelog, but we need to record path
469 469 self._node2path[f.node()] = f.path()
@@ -490,7 +490,7 b' class _annotatecontext(object):'
490 490 if hsh is not None and (hsh, self.path) in self.revmap:
491 491 f = hsh
492 492 if f is None:
493 adjustctx = 'linkrev' if self._perfhack else True
493 adjustctx = b'linkrev' if self._perfhack else True
494 494 f = self._resolvefctx(rev, adjustctx=adjustctx, resolverev=True)
495 495 result = f in self.revmap
496 496 if not result and self._perfhack:
@@ -511,7 +511,7 b' class _annotatecontext(object):'
511 511 # find a chain from rev to anything in the mainbranch
512 512 if revfctx not in self.revmap:
513 513 chain = [revfctx]
514 a = ''
514 a = b''
515 515 while True:
516 516 f = chain[-1]
517 517 pl = self._parentfunc(f)
@@ -589,8 +589,8 b' class _annotatecontext(object):'
589 589 hsh = annotateresult[idxs[0]][0]
590 590 if self.ui.debugflag:
591 591 self.ui.debug(
592 'fastannotate: reading %s line #%d '
593 'to resolve lines %r\n'
592 b'fastannotate: reading %s line #%d '
593 b'to resolve lines %r\n'
594 594 % (node.short(hsh), linenum, idxs)
595 595 )
596 596 fctx = self._resolvefctx(hsh, revmap.rev2path(rev))
@@ -603,14 +603,15 b' class _annotatecontext(object):'
603 603
604 604 # run the annotate and the lines should match to the file content
605 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 609 linelog.annotate(rev)
609 610 fctx = self._resolvefctx(hsh, revmap.rev2path(rev))
610 611 annotated = linelog.annotateresult
611 612 lines = mdiff.splitnewlines(fctx.data())
612 613 if len(lines) != len(annotated):
613 raise faerror.CorruptedFileError('unexpected annotated lines')
614 raise faerror.CorruptedFileError(b'unexpected annotated lines')
614 615 # resolve lines from the annotate result
615 616 for i, line in enumerate(lines):
616 617 k = annotated[i]
@@ -633,11 +634,11 b' class _annotatecontext(object):'
633 634 llrev = self.revmap.hsh2rev(hsh)
634 635 if not llrev:
635 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 639 if (self.revmap.rev2flag(llrev) & revmapmod.sidebranchflag) != 0:
639 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 643 self.linelog.annotate(llrev)
643 644 result = [
@@ -677,7 +678,7 b' class _annotatecontext(object):'
677 678 """(fctx) -> int"""
678 679 # f should not be a linelog revision
679 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 682 # f is a fctx, allocate linelog rev on demand
682 683 hsh = f.node()
683 684 rev = revmap.hsh2rev(hsh)
@@ -690,7 +691,7 b' class _annotatecontext(object):'
690 691 siderevmap = {} # node: int
691 692 if bannotated is not None:
692 693 for (a1, a2, b1, b2), op in blocks:
693 if op != '=':
694 if op != b'=':
694 695 # f could be either linelong rev, or fctx.
695 696 siderevs += [
696 697 f
@@ -708,7 +709,7 b' class _annotatecontext(object):'
708 709 siderevmap[fctx] = llrev
709 710
710 711 for (a1, a2, b1, b2), op in reversed(blocks):
711 if op == '=':
712 if op == b'=':
712 713 continue
713 714 if bannotated is None:
714 715 linelog.replacelines(llrev, a1, a2, b1, b2)
@@ -760,7 +761,7 b' class _annotatecontext(object):'
760 761
761 762 @util.propertycache
762 763 def _perfhack(self):
763 return self.ui.configbool('fastannotate', 'perfhack')
764 return self.ui.configbool(b'fastannotate', b'perfhack')
764 765
765 766 def _resolvefctx(self, rev, path=None, **kwds):
766 767 return resolvefctx(self.repo, rev, (path or self.path), **kwds)
@@ -781,7 +782,7 b' class pathhelper(object):'
781 782 def __init__(self, repo, path, opts=defaultopts):
782 783 # different options use different directories
783 784 self._vfspath = os.path.join(
784 'fastannotate', opts.shortstr, encodedir(path)
785 b'fastannotate', opts.shortstr, encodedir(path)
785 786 )
786 787 self._repo = repo
787 788
@@ -791,14 +792,14 b' class pathhelper(object):'
791 792
792 793 @property
793 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 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 800 @property
800 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 805 @contextlib.contextmanager
@@ -831,7 +832,7 b' def annotatecontext(repo, path, opts=def'
831 832 except Exception:
832 833 if actx is not None:
833 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 836 raise
836 837 finally:
837 838 if actx is not None:
@@ -844,7 +845,7 b' def fctxannotatecontext(fctx, follow=Tru'
844 845 """
845 846 repo = fctx._repo
846 847 path = fctx._path
847 if repo.ui.configbool('fastannotate', 'forcefollow', True):
848 if repo.ui.configbool(b'fastannotate', b'forcefollow', True):
848 849 follow = True
849 850 aopts = annotateopts(diffopts=diffopts, followrename=follow)
850 851 return annotatecontext(repo, path, aopts, rebuild)
@@ -33,35 +33,35 b' class defaultformatter(object):'
33 33 hexfunc = self._hexfunc
34 34
35 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 37 orig = hexfunc
38 38 hexfunc = lambda x: None if x is None else orig(x)
39 wnode = hexfunc(repo['.'].node()) + '+'
40 wrev = '%d' % repo['.'].rev()
41 wrevpad = ''
42 if not opts.get('changeset'): # only show + if changeset is hidden
43 wrev += '+'
44 wrevpad = ' '
45 revenc = lambda x: wrev if x is None else ('%d' % x) + wrevpad
39 wnode = hexfunc(repo[b'.'].node()) + b'+'
40 wrev = b'%d' % repo[b'.'].rev()
41 wrevpad = b''
42 if not opts.get(b'changeset'): # only show + if changeset is hidden
43 wrev += b'+'
44 wrevpad = b' '
45 revenc = lambda x: wrev if x is None else (b'%d' % x) + wrevpad
46 46
47 47 def csetenc(x):
48 48 if x is None:
49 49 return wnode
50 return pycompat.bytestr(x) + ' '
50 return pycompat.bytestr(x) + b' '
51 51
52 52 else:
53 53 revenc = csetenc = pycompat.bytestr
54 54
55 55 # opt name, separator, raw value (for json/plain), encoder (for plain)
56 56 opmap = [
57 ('user', ' ', lambda x: getctx(x).user(), ui.shortuser),
58 ('number', ' ', lambda x: getctx(x).rev(), revenc),
59 ('changeset', ' ', lambda x: hexfunc(x[0]), csetenc),
60 ('date', ' ', lambda x: getctx(x).date(), datefunc),
61 ('file', ' ', lambda x: x[2], pycompat.bytestr),
62 ('line_number', ':', lambda x: x[1] + 1, pycompat.bytestr),
57 (b'user', b' ', lambda x: getctx(x).user(), ui.shortuser),
58 (b'number', b' ', lambda x: getctx(x).rev(), revenc),
59 (b'changeset', b' ', lambda x: hexfunc(x[0]), csetenc),
60 (b'date', b' ', lambda x: getctx(x).date(), datefunc),
61 (b'file', b' ', lambda x: x[2], 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 65 funcmap = [
66 66 (get, sep, fieldnamemap.get(op, op), enc)
67 67 for op, sep, get, enc in opmap
@@ -69,7 +69,7 b' class defaultformatter(object):'
69 69 ]
70 70 # no separator for first column
71 71 funcmap[0] = list(funcmap[0])
72 funcmap[0][1] = ''
72 funcmap[0][1] = b''
73 73 self.funcmap = funcmap
74 74
75 75 def write(self, annotatedresult, lines=None, existinglines=None):
@@ -83,39 +83,39 b' class defaultformatter(object):'
83 83 for f, sep, name, enc in self.funcmap:
84 84 l = [enc(f(x)) for x in annotatedresult]
85 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 87 l = l[:1]
88 88 widths = pycompat.maplist(encoding.colwidth, set(l))
89 89 maxwidth = max(widths) if widths else 0
90 90 maxwidths.append(maxwidth)
91 91
92 92 # buffered output
93 result = ''
93 result = b''
94 94 for i in pycompat.xrange(len(annotatedresult)):
95 95 for j, p in enumerate(pieces):
96 96 sep = self.funcmap[j][1]
97 padding = ' ' * (maxwidths[j] - len(p[i]))
97 padding = b' ' * (maxwidths[j] - len(p[i]))
98 98 result += sep + padding + p[i]
99 99 if lines:
100 100 if existinglines is None:
101 result += ': ' + lines[i]
101 result += b': ' + lines[i]
102 102 else: # extra formatting showing whether a line exists
103 103 key = (annotatedresult[i][0], annotatedresult[i][1])
104 104 if key in existinglines:
105 result += ': ' + lines[i]
105 result += b': ' + lines[i]
106 106 else:
107 result += ': ' + self.ui.label(
108 '-' + lines[i], 'diff.deleted'
107 result += b': ' + self.ui.label(
108 b'-' + lines[i], b'diff.deleted'
109 109 )
110 110
111 if result[-1:] != '\n':
112 result += '\n'
111 if result[-1:] != b'\n':
112 result += b'\n'
113 113
114 114 self.ui.write(result)
115 115
116 116 @util.propertycache
117 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 119 return node.hex
120 120 else:
121 121 return node.short
@@ -127,7 +127,7 b' class defaultformatter(object):'
127 127 class jsonformatter(defaultformatter):
128 128 def __init__(self, ui, repo, opts):
129 129 super(jsonformatter, self).__init__(ui, repo, opts)
130 self.ui.write('[')
130 self.ui.write(b'[')
131 131 self.needcomma = False
132 132
133 133 def write(self, annotatedresult, lines=None, existinglines=None):
@@ -139,23 +139,23 b' class jsonformatter(defaultformatter):'
139 139 for f, sep, name, enc in self.funcmap
140 140 ]
141 141 if lines is not None:
142 pieces.append(('line', lines))
142 pieces.append((b'line', lines))
143 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 148 lasti = len(annotatedresult) - 1
149 149 for i in pycompat.xrange(len(annotatedresult)):
150 result += '\n {\n'
150 result += b'\n {\n'
151 151 for j, p in enumerate(pieces):
152 152 k, vs = p
153 result += ' "%s": %s%s\n' % (
153 result += b' "%s": %s%s\n' % (
154 154 k,
155 155 templatefilters.json(vs[i], paranoid=False),
156 156 seps[j],
157 157 )
158 result += ' }%s' % ('' if i == lasti else ',')
158 result += b' }%s' % (b'' if i == lasti else b',')
159 159 if lasti >= 0:
160 160 self.needcomma = True
161 161
@@ -163,7 +163,7 b' class jsonformatter(defaultformatter):'
163 163
164 164 def _writecomma(self):
165 165 if self.needcomma:
166 self.ui.write(',')
166 self.ui.write(b',')
167 167 self.needcomma = False
168 168
169 169 @util.propertycache
@@ -171,4 +171,4 b' class jsonformatter(defaultformatter):'
171 171 return node.hex
172 172
173 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 26 def _getmaster(ui):
27 27 """get the mainbranch, and enforce it is set"""
28 master = ui.config('fastannotate', 'mainbranch')
28 master = ui.config(b'fastannotate', b'mainbranch')
29 29 if not master:
30 30 raise error.Abort(
31 31 _(
32 'fastannotate.mainbranch is required '
33 'for both the client and the server'
32 b'fastannotate.mainbranch is required '
33 b'for both the client and the server'
34 34 )
35 35 )
36 36 return master
@@ -41,7 +41,7 b' def _getmaster(ui):'
41 41
42 42 def _capabilities(orig, repo, proto):
43 43 result = orig(repo, proto)
44 result.append('getannotate')
44 result.append(b'getannotate')
45 45 return result
46 46
47 47
@@ -49,9 +49,9 b' def _getannotate(repo, proto, path, last'
49 49 # output:
50 50 # FILE := vfspath + '\0' + str(size) + '\0' + content
51 51 # OUTPUT := '' | FILE + OUTPUT
52 result = ''
52 result = b''
53 53 buildondemand = repo.ui.configbool(
54 'fastannotate', 'serverbuildondemand', True
54 b'fastannotate', b'serverbuildondemand', True
55 55 )
56 56 with context.annotatecontext(repo, path) as actx:
57 57 if buildondemand:
@@ -80,25 +80,25 b' def _getannotate(repo, proto, path, last'
80 80 for p in [actx.revmappath, actx.linelogpath]:
81 81 if not os.path.exists(p):
82 82 continue
83 with open(p, 'rb') as f:
83 with open(p, b'rb') as f:
84 84 content = f.read()
85 vfsbaselen = len(repo.vfs.base + '/')
85 vfsbaselen = len(repo.vfs.base + b'/')
86 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 88 return result
89 89
90 90
91 91 def _registerwireprotocommand():
92 if 'getannotate' in wireprotov1server.commands:
92 if b'getannotate' in wireprotov1server.commands:
93 93 return
94 wireprotov1server.wireprotocommand('getannotate', 'path lastnode')(
94 wireprotov1server.wireprotocommand(b'getannotate', b'path lastnode')(
95 95 _getannotate
96 96 )
97 97
98 98
99 99 def serveruisetup(ui):
100 100 _registerwireprotocommand()
101 extensions.wrapfunction(wireprotov1server, '_capabilities', _capabilities)
101 extensions.wrapfunction(wireprotov1server, b'_capabilities', _capabilities)
102 102
103 103
104 104 # client-side
@@ -109,15 +109,15 b' def _parseresponse(payload):'
109 109 i = 0
110 110 l = len(payload) - 1
111 111 state = 0 # 0: vfspath, 1: size
112 vfspath = size = ''
112 vfspath = size = b''
113 113 while i < l:
114 114 ch = payload[i : i + 1]
115 if ch == '\0':
115 if ch == b'\0':
116 116 if state == 1:
117 117 result[vfspath] = payload[i + 1 : i + 1 + int(size)]
118 118 i += int(size)
119 119 state = 0
120 vfspath = size = ''
120 vfspath = size = b''
121 121 elif state == 0:
122 122 state = 1
123 123 else:
@@ -133,11 +133,11 b' def peersetup(ui, peer):'
133 133 class fastannotatepeer(peer.__class__):
134 134 @wireprotov1peer.batchable
135 135 def getannotate(self, path, lastnode=None):
136 if not self.capable('getannotate'):
137 ui.warn(_('remote peer cannot provide annotate cache\n'))
136 if not self.capable(b'getannotate'):
137 ui.warn(_(b'remote peer cannot provide annotate cache\n'))
138 138 yield None, None
139 139 else:
140 args = {'path': path, 'lastnode': lastnode or ''}
140 args = {b'path': path, b'lastnode': lastnode or b''}
141 141 f = wireprotov1peer.future()
142 142 yield args, f
143 143 yield _parseresponse(f.value)
@@ -150,7 +150,7 b' def annotatepeer(repo):'
150 150 ui = repo.ui
151 151
152 152 remotepath = ui.expandpath(
153 ui.config('fastannotate', 'remotepath', 'default')
153 ui.config(b'fastannotate', b'remotepath', b'default')
154 154 )
155 155 peer = hg.peer(ui, {}, remotepath)
156 156
@@ -175,11 +175,12 b' def clientfetch(repo, paths, lastnodemap'
175 175 ui = repo.ui
176 176 results = []
177 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 179 for p in paths:
180 180 results.append(
181 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 190 r = {util.pconvert(p): v for p, v in r.iteritems()}
190 191 for path in sorted(r):
191 192 # ignore malicious paths
192 if not path.startswith('fastannotate/') or '/../' in (
193 path + '/'
193 if not path.startswith(b'fastannotate/') or b'/../' in (
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 199 continue
197 200 content = r[path]
198 201 if ui.debugflag:
199 202 ui.debug(
200 'fastannotate: writing %d bytes to %s\n'
203 b'fastannotate: writing %d bytes to %s\n'
201 204 % (len(content), path)
202 205 )
203 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 208 f.write(content)
206 209
207 210
@@ -209,7 +212,7 b' def _filterfetchpaths(repo, paths):'
209 212 """return a subset of paths whose history is long and need to fetch linelog
210 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 216 if threshold <= 0:
214 217 return paths
215 218
@@ -240,7 +243,7 b' def localreposetup(ui, repo):'
240 243 clientfetch(self, needupdatepaths, lastnodemap, peer)
241 244 except Exception as ex:
242 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 248 repo.__class__ = fastannotaterepo
246 249
@@ -70,7 +70,7 b' class revmap(object):'
70 70 # since rename does not happen frequently, do not store path for every
71 71 # revision. self._renamerevs can be used for bisecting.
72 72 self._renamerevs = [0]
73 self._renamepaths = ['']
73 self._renamepaths = [b'']
74 74 self._lastmaxrev = -1
75 75 if path:
76 76 if os.path.exists(path):
@@ -98,9 +98,13 b' class revmap(object):'
98 98 if flush is True, incrementally update the file.
99 99 """
100 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 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 108 idx = len(self._rev2hsh)
105 109 flag = 0
106 110 if sidebranch:
@@ -149,7 +153,7 b' class revmap(object):'
149 153 self._rev2hsh = [None]
150 154 self._rev2flag = [None]
151 155 self._hsh2rev = {}
152 self._rev2path = ['']
156 self._rev2path = [b'']
153 157 self._lastmaxrev = -1
154 158 if flush:
155 159 self.flush()
@@ -159,12 +163,12 b' class revmap(object):'
159 163 if not self.path:
160 164 return
161 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 167 f.write(self.HEADER)
164 168 for i in pycompat.xrange(1, len(self._rev2hsh)):
165 169 self._writerev(i, f)
166 170 else: # append incrementally
167 with open(self.path, 'ab') as f:
171 with open(self.path, b'ab') as f:
168 172 for i in pycompat.xrange(
169 173 self._lastmaxrev + 1, len(self._rev2hsh)
170 174 ):
@@ -179,7 +183,7 b' class revmap(object):'
179 183 # which is faster than both LOAD_CONST and LOAD_GLOBAL.
180 184 flaglen = 1
181 185 hshlen = _hshlen
182 with open(self.path, 'rb') as f:
186 with open(self.path, b'rb') as f:
183 187 if f.read(len(self.HEADER)) != self.HEADER:
184 188 raise error.CorruptedFileError()
185 189 self.clear(flush=False)
@@ -205,23 +209,23 b' class revmap(object):'
205 209 """append a revision data to file"""
206 210 flag = self._rev2flag[rev]
207 211 hsh = self._rev2hsh[rev]
208 f.write(struct.pack('B', flag))
212 f.write(struct.pack(b'B', flag))
209 213 if flag & renameflag:
210 214 path = self.rev2path(rev)
211 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 217 f.write(path + b'\0')
214 218 f.write(hsh)
215 219
216 220 @staticmethod
217 221 def _readcstr(f):
218 222 """read a C-language-like '\0'-terminated string"""
219 buf = ''
223 buf = b''
220 224 while True:
221 225 ch = f.read(1)
222 226 if not ch: # unexpected eof
223 227 raise error.CorruptedFileError()
224 if ch == '\0':
228 if ch == b'\0':
225 229 break
226 230 buf += ch
227 231 return buf
@@ -249,7 +253,7 b' def getlastnode(path):'
249 253 """
250 254 hsh = None
251 255 try:
252 with open(path, 'rb') as f:
256 with open(path, b'rb') as f:
253 257 f.seek(-_hshlen, io.SEEK_END)
254 258 if f.tell() > len(revmap.HEADER):
255 259 hsh = f.read(_hshlen)
@@ -64,7 +64,7 b' def _convertoutputs(repo, annotated, con'
64 64
65 65 def _getmaster(fctx):
66 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 70 def _doannotate(fctx, follow=True, diffopts=None):
@@ -83,7 +83,7 b' def _doannotate(fctx, follow=True, diffo'
83 83 except Exception:
84 84 ac.rebuild() # try rebuild once
85 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 88 try:
89 89 annotated, contents = ac.annotate(
@@ -98,7 +98,7 b' def _doannotate(fctx, follow=True, diffo'
98 98
99 99 def _hgwebannotate(orig, fctx, ui):
100 100 diffopts = patch.difffeatureopts(
101 ui, untrusted=True, section='annotate', whitespace=True
101 ui, untrusted=True, section=b'annotate', whitespace=True
102 102 )
103 103 return _doannotate(fctx, diffopts=diffopts)
104 104
@@ -115,7 +115,7 b' def _fctxannotate('
115 115 return _doannotate(self, follow, diffopts)
116 116 except Exception as ex:
117 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 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 132 def replacehgwebannotate():
133 extensions.wrapfunction(hgweb.webutil, 'annotate', _hgwebannotate)
133 extensions.wrapfunction(hgweb.webutil, b'annotate', _hgwebannotate)
134 134
135 135
136 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 30 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
31 31 # be specifying the version(s) of Mercurial they are tested with, or
32 32 # leave the attribute unspecified.
33 testedwith = 'ships-with-hg-core'
33 testedwith = b'ships-with-hg-core'
34 34
35 35
36 36 @command(
37 'fetch',
37 b'fetch',
38 38 [
39 39 (
40 'r',
41 'rev',
40 b'r',
41 b'rev',
42 42 [],
43 _('a specific revision you would like to pull'),
44 _('REV'),
43 _(b'a specific revision you would like to pull'),
44 _(b'REV'),
45 45 ),
46 ('', 'edit', None, _('invoke editor on commit messages')),
47 ('', 'force-editor', None, _('edit commit message (DEPRECATED)')),
48 ('', 'switch-parent', None, _('switch parents when merging')),
46 (b'', b'edit', None, _(b'invoke editor on commit messages')),
47 (b'', b'force-editor', None, _(b'edit commit message (DEPRECATED)')),
48 (b'', b'switch-parent', None, _(b'switch parents when merging')),
49 49 ]
50 50 + cmdutil.commitopts
51 51 + cmdutil.commitopts2
52 52 + cmdutil.remoteopts,
53 _('hg fetch [SOURCE]'),
53 _(b'hg fetch [SOURCE]'),
54 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 57 '''pull changes from a remote repository, merge new changes if needed.
58 58
59 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 76 opts = pycompat.byteskwargs(opts)
77 date = opts.get('date')
77 date = opts.get(b'date')
78 78 if date:
79 opts['date'] = dateutil.parsedate(date)
79 opts[b'date'] = dateutil.parsedate(date)
80 80
81 81 parent = repo.dirstate.p1()
82 82 branch = repo.dirstate.branch()
@@ -86,8 +86,8 b" def fetch(ui, repo, source='default', **"
86 86 branchnode = None
87 87 if parent != branchnode:
88 88 raise error.Abort(
89 _('working directory not at branch tip'),
90 hint=_("use 'hg update' to check out branch tip"),
89 _(b'working directory not at branch tip'),
90 hint=_(b"use 'hg update' to check out branch tip"),
91 91 )
92 92
93 93 wlock = lock = None
@@ -102,23 +102,23 b" def fetch(ui, repo, source='default', **"
102 102 if len(bheads) > 1:
103 103 raise error.Abort(
104 104 _(
105 'multiple heads in this branch '
106 '(use "hg heads ." and "hg merge" to merge)'
105 b'multiple heads in this branch '
106 b'(use "hg heads ." and "hg merge" to merge)'
107 107 )
108 108 )
109 109
110 110 other = hg.peer(repo, opts, ui.expandpath(source))
111 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 114 revs = None
115 if opts['rev']:
115 if opts[b'rev']:
116 116 try:
117 revs = [other.lookup(rev) for rev in opts['rev']]
117 revs = [other.lookup(rev) for rev in opts[b'rev']]
118 118 except error.CapabilityError:
119 119 err = _(
120 "other repository doesn't support revision lookup, "
121 "so a rev cannot be specified."
120 b"other repository doesn't support revision lookup, "
121 b"so a rev cannot be specified."
122 122 )
123 123 raise error.Abort(err)
124 124
@@ -146,8 +146,8 b" def fetch(ui, repo, source='default', **"
146 146 if len(newheads) > 1:
147 147 ui.status(
148 148 _(
149 'not merging with %d other new branch heads '
150 '(use "hg heads ." and "hg merge" to merge them)\n'
149 b'not merging with %d other new branch heads '
150 b'(use "hg heads ." and "hg merge" to merge them)\n'
151 151 )
152 152 % (len(newheads) - 1)
153 153 )
@@ -162,17 +162,17 b" def fetch(ui, repo, source='default', **"
162 162 # By default, we consider the repository we're pulling
163 163 # *from* as authoritative, so we merge our changes into
164 164 # theirs.
165 if opts['switch_parent']:
165 if opts[b'switch_parent']:
166 166 firstparent, secondparent = newparent, newheads[0]
167 167 else:
168 168 firstparent, secondparent = newheads[0], newparent
169 169 ui.status(
170 _('updating to %d:%s\n')
170 _(b'updating to %d:%s\n')
171 171 % (repo.changelog.rev(firstparent), short(firstparent))
172 172 )
173 173 hg.clean(repo, firstparent)
174 174 ui.status(
175 _('merging with %d:%s\n')
175 _(b'merging with %d:%s\n')
176 176 % (repo.changelog.rev(secondparent), short(secondparent))
177 177 )
178 178 err = hg.merge(repo, secondparent, remind=False)
@@ -180,13 +180,15 b" def fetch(ui, repo, source='default', **"
180 180 if not err:
181 181 # we don't translate commit messages
182 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')
186 editor = cmdutil.getcommiteditor(edit=editopt, editform='fetch')
187 n = repo.commit(message, opts['user'], opts['date'], editor=editor)
185 editopt = opts.get(b'edit') or opts.get(b'force_editor')
186 editor = cmdutil.getcommiteditor(edit=editopt, editform=b'fetch')
187 n = repo.commit(
188 message, opts[b'user'], opts[b'date'], editor=editor
189 )
188 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 192 % (repo.changelog.rev(n), short(n))
191 193 )
192 194
@@ -157,7 +157,7 b' from mercurial import ('
157 157 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
158 158 # be specifying the version(s) of Mercurial they are tested with, or
159 159 # leave the attribute unspecified.
160 testedwith = 'ships-with-hg-core'
160 testedwith = b'ships-with-hg-core'
161 161
162 162 cmdtable = {}
163 163 command = registrar.command(cmdtable)
@@ -167,61 +167,61 b' configitem = registrar.configitem(config'
167 167
168 168 # Register the suboptions allowed for each configured fixer, and default values.
169 169 FIXER_ATTRS = {
170 'command': None,
171 'linerange': None,
172 'pattern': None,
173 'priority': 0,
174 'metadata': 'false',
175 'skipclean': 'true',
176 'enabled': 'true',
170 b'command': None,
171 b'linerange': None,
172 b'pattern': None,
173 b'priority': 0,
174 b'metadata': b'false',
175 b'skipclean': b'true',
176 b'enabled': b'true',
177 177 }
178 178
179 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 182 # A good default size allows most source code files to be fixed, but avoids
183 183 # letting fixer tools choke on huge inputs, which could be surprising to the
184 184 # user.
185 configitem('fix', 'maxfilesize', default='2MB')
185 configitem(b'fix', b'maxfilesize', default=b'2MB')
186 186
187 187 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
188 188 # This helps users do shell scripts that stop when a fixer tool signals a
189 189 # problem.
190 configitem('fix', 'failure', default='continue')
190 configitem(b'fix', b'failure', default=b'continue')
191 191
192 192
193 193 def checktoolfailureaction(ui, message, hint=None):
194 194 """Abort with 'message' if fix.failure=abort"""
195 action = ui.config('fix', 'failure')
196 if action not in ('continue', 'abort'):
195 action = ui.config(b'fix', b'failure')
196 if action not in (b'continue', b'abort'):
197 197 raise error.Abort(
198 _('unknown fix.failure action: %s') % (action,),
199 hint=_('use "continue" or "abort"'),
198 _(b'unknown fix.failure action: %s') % (action,),
199 hint=_(b'use "continue" or "abort"'),
200 200 )
201 if action == 'abort':
201 if action == b'abort':
202 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 206 baseopt = (
207 '',
208 'base',
207 b'',
208 b'base',
209 209 [],
210 210 _(
211 'revisions to diff against (overrides automatic '
212 'selection, and applies to every revision being '
213 'fixed)'
211 b'revisions to diff against (overrides automatic '
212 b'selection, and applies to every revision being '
213 b'fixed)'
214 214 ),
215 _('REV'),
215 _(b'REV'),
216 216 )
217 revopt = ('r', 'rev', [], _('revisions to fix'), _('REV'))
218 wdiropt = ('w', 'working-dir', False, _('fix the working directory'))
219 wholeopt = ('', 'whole', False, _('always fix every line of a file'))
220 usage = _('[OPTION]... [FILE]...')
217 revopt = (b'r', b'rev', [], _(b'revisions to fix'), _(b'REV'))
218 wdiropt = (b'w', b'working-dir', False, _(b'fix the working directory'))
219 wholeopt = (b'', b'whole', False, _(b'always fix every line of a file'))
220 usage = _(b'[OPTION]... [FILE]...')
221 221
222 222
223 223 @command(
224 'fix',
224 b'fix',
225 225 [allopt, baseopt, revopt, wdiropt, wholeopt],
226 226 usage,
227 227 helpcategory=command.CATEGORY_FILE_CONTENTS,
@@ -250,12 +250,12 b' def fix(ui, repo, *pats, **opts):'
250 250 override this default behavior, though it is not usually desirable to do so.
251 251 """
252 252 opts = pycompat.byteskwargs(opts)
253 if opts['all']:
254 if opts['rev']:
255 raise error.Abort(_('cannot specify both "--rev" and "--all"'))
256 opts['rev'] = ['not public() and not obsolete()']
257 opts['working_dir'] = True
258 with repo.wlock(), repo.lock(), repo.transaction('fix'):
253 if opts[b'all']:
254 if opts[b'rev']:
255 raise error.Abort(_(b'cannot specify both "--rev" and "--all"'))
256 opts[b'rev'] = [b'not public() and not obsolete()']
257 opts[b'working_dir'] = True
258 with repo.wlock(), repo.lock(), repo.transaction(b'fix'):
259 259 revstofix = getrevstofix(ui, repo, opts)
260 260 basectxs = getbasectxs(repo, opts, revstofix)
261 261 workqueue, numitems = getworkqueue(
@@ -297,7 +297,7 b' def fix(ui, repo, *pats, **opts):'
297 297 wdirwritten = False
298 298 commitorder = sorted(revstofix, reverse=True)
299 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 301 ) as progress:
302 302 for rev, path, filerevmetadata, newdata in results:
303 303 progress.increment(item=path)
@@ -306,12 +306,12 b' def fix(ui, repo, *pats, **opts):'
306 306 if newdata is not None:
307 307 filedata[rev][path] = newdata
308 308 hookargs = {
309 'rev': rev,
310 'path': path,
311 'metadata': filerevmetadata,
309 b'rev': rev,
310 b'path': path,
311 b'metadata': filerevmetadata,
312 312 }
313 313 repo.hook(
314 'postfixfile',
314 b'postfixfile',
315 315 throw=False,
316 316 **pycompat.strkwargs(hookargs)
317 317 )
@@ -332,11 +332,11 b' def fix(ui, repo, *pats, **opts):'
332 332
333 333 cleanup(repo, replacements, wdirwritten)
334 334 hookargs = {
335 'replacements': replacements,
336 'wdirwritten': wdirwritten,
337 'metadata': aggregatemetadata,
335 b'replacements': replacements,
336 b'wdirwritten': wdirwritten,
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 342 def cleanup(repo, replacements, wdirwritten):
@@ -353,7 +353,7 b' def cleanup(repo, replacements, wdirwrit'
353 353 effects of the command, though we choose not to output anything here.
354 354 """
355 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 359 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
@@ -375,7 +375,7 b' def getworkqueue(ui, repo, pats, opts, r'
375 375 """
376 376 workqueue = []
377 377 numitems = collections.defaultdict(int)
378 maxfilesize = ui.configbytes('fix', 'maxfilesize')
378 maxfilesize = ui.configbytes(b'fix', b'maxfilesize')
379 379 for rev in sorted(revstofix):
380 380 fixctx = repo[rev]
381 381 match = scmutil.match(fixctx, pats, opts)
@@ -387,7 +387,7 b' def getworkqueue(ui, repo, pats, opts, r'
387 387 continue
388 388 if fctx.size() > maxfilesize:
389 389 ui.warn(
390 _('ignoring file larger than %s: %s\n')
390 _(b'ignoring file larger than %s: %s\n')
391 391 % (util.bytecount(maxfilesize), path)
392 392 )
393 393 continue
@@ -398,29 +398,29 b' def getworkqueue(ui, repo, pats, opts, r'
398 398
399 399 def getrevstofix(ui, repo, opts):
400 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 402 for rev in revs:
403 403 checkfixablectx(ui, repo, repo[rev])
404 404 if revs:
405 405 cmdutil.checkunfinished(repo)
406 406 checknodescendants(repo, revs)
407 if opts.get('working_dir'):
407 if opts.get(b'working_dir'):
408 408 revs.add(wdirrev)
409 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 411 if not revs:
412 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 415 return revs
416 416
417 417
418 418 def checknodescendants(repo, revs):
419 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 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 428 """Aborts if the revision shouldn't be replaced with a fixed one."""
429 429 if not ctx.mutable():
430 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 434 if ctx.obsolete():
434 435 # It would be better to actually check if the revision has a successor.
435 436 allowdivergence = ui.configbool(
436 'experimental', 'evolution.allowdivergence'
437 b'experimental', b'evolution.allowdivergence'
437 438 )
438 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 445 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
@@ -473,10 +476,10 b' def lineranges(opts, path, basectxs, fix'
473 476 Another way to understand this is that we exclude line ranges that are
474 477 common to the file in all base contexts.
475 478 """
476 if opts.get('whole'):
479 if opts.get(b'whole'):
477 480 # Return a range containing all lines. Rely on the diff implementation's
478 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 484 rangeslist = []
482 485 for basectx in basectxs:
@@ -484,7 +487,7 b' def lineranges(opts, path, basectxs, fix'
484 487 if basepath in basectx:
485 488 content1 = basectx[basepath].data()
486 489 else:
487 content1 = ''
490 content1 = b''
488 491 rangeslist.extend(difflineranges(content1, content2))
489 492 return unionranges(rangeslist)
490 493
@@ -566,7 +569,7 b' def difflineranges(content1, content2):'
566 569 ranges = []
567 570 for lines, kind in mdiff.allblocks(content1, content2):
568 571 firstline, lastline = lines[2:4]
569 if kind == '!' and firstline != lastline:
572 if kind == b'!' and firstline != lastline:
570 573 ranges.append((firstline + 1, lastline))
571 574 return ranges
572 575
@@ -581,8 +584,8 b' def getbasectxs(repo, opts, revstofix):'
581 584 """
582 585 # The --base flag overrides the usual logic, and we give every revision
583 586 # exactly the set of baserevs that the user specified.
584 if opts.get('base'):
585 baserevs = set(scmutil.revrange(repo, opts.get('base')))
587 if opts.get(b'base'):
588 baserevs = set(scmutil.revrange(repo, opts.get(b'base')))
586 589 if not baserevs:
587 590 baserevs = {nullrev}
588 591 basectxs = {repo[rev] for rev in baserevs}
@@ -621,7 +624,7 b' def fixfile(ui, repo, opts, fixers, fixc'
621 624 command = fixer.command(ui, path, ranges)
622 625 if command is None:
623 626 continue
624 ui.debug('subprocess: %s\n' % (command,))
627 ui.debug(b'subprocess: %s\n' % (command,))
625 628 proc = subprocess.Popen(
626 629 procutil.tonativestr(command),
627 630 shell=True,
@@ -636,11 +639,11 b' def fixfile(ui, repo, opts, fixers, fixc'
636 639 newerdata = stdout
637 640 if fixer.shouldoutputmetadata():
638 641 try:
639 metadatajson, newerdata = stdout.split('\0', 1)
642 metadatajson, newerdata = stdout.split(b'\0', 1)
640 643 metadata[fixername] = json.loads(metadatajson)
641 644 except ValueError:
642 645 ui.warn(
643 _('ignored invalid output from fixer tool: %s\n')
646 _(b'ignored invalid output from fixer tool: %s\n')
644 647 % (fixername,)
645 648 )
646 649 continue
@@ -650,14 +653,14 b' def fixfile(ui, repo, opts, fixers, fixc'
650 653 newdata = newerdata
651 654 else:
652 655 if not stderr:
653 message = _('exited with status %d\n') % (proc.returncode,)
656 message = _(b'exited with status %d\n') % (proc.returncode,)
654 657 showstderr(ui, fixctx.rev(), fixername, message)
655 658 checktoolfailureaction(
656 659 ui,
657 _('no fixes will be applied'),
660 _(b'no fixes will be applied'),
658 661 hint=_(
659 'use --config fix.failure=continue to apply any '
660 'successful fixes anyway'
662 b'use --config fix.failure=continue to apply any '
663 b'successful fixes anyway'
661 664 ),
662 665 )
663 666 return metadata, newdata
@@ -671,14 +674,14 b' def showstderr(ui, rev, fixername, stder'
671 674 space and would tend to be included in the error message if they were
672 675 relevant.
673 676 """
674 for line in re.split('[\r\n]+', stderr):
677 for line in re.split(b'[\r\n]+', stderr):
675 678 if line:
676 ui.warn('[')
679 ui.warn(b'[')
677 680 if rev is None:
678 ui.warn(_('wdir'), label='evolve.rev')
681 ui.warn(_(b'wdir'), label=b'evolve.rev')
679 682 else:
680 ui.warn((str(rev)), label='evolve.rev')
681 ui.warn('] %s: %s\n' % (fixername, line))
683 ui.warn((str(rev)), label=b'evolve.rev')
684 ui.warn(b'] %s: %s\n' % (fixername, line))
682 685
683 686
684 687 def writeworkingdir(repo, ctx, filedata, replacements):
@@ -694,7 +697,7 b' def writeworkingdir(repo, ctx, filedata,'
694 697 for path, data in filedata.iteritems():
695 698 fctx = ctx[path]
696 699 fctx.write(data, fctx.flags())
697 if repo.dirstate[path] == 'n':
700 if repo.dirstate[path] == b'n':
698 701 repo.dirstate.normallookup(path)
699 702
700 703 oldparentnodes = repo.dirstate.parents()
@@ -757,7 +760,7 b' def replacerev(ui, repo, ctx, filedata, '
757 760 )
758 761
759 762 extra = ctx.extra().copy()
760 extra['fix_source'] = ctx.hex()
763 extra[b'fix_source'] = ctx.hex()
761 764
762 765 memctx = context.memctx(
763 766 repo,
@@ -774,7 +777,7 b' def replacerev(ui, repo, ctx, filedata, '
774 777 sucnode = memctx.commit()
775 778 prenode = ctx.node()
776 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 781 else:
779 782 replacements[ctx.node()] = sucnode
780 783
@@ -788,11 +791,11 b' def getfixers(ui):'
788 791 fixers = {}
789 792 for name in fixernames(ui):
790 793 fixers[name] = Fixer()
791 attrs = ui.configsuboptions('fix', name)[1]
794 attrs = ui.configsuboptions(b'fix', name)[1]
792 795 for key, default in FIXER_ATTRS.items():
793 796 setattr(
794 797 fixers[name],
795 pycompat.sysstr('_' + key),
798 pycompat.sysstr(b'_' + key),
796 799 attrs.get(key, default),
797 800 )
798 801 fixers[name]._priority = int(fixers[name]._priority)
@@ -805,11 +808,11 b' def getfixers(ui):'
805 808 # default.
806 809 if fixers[name]._pattern is None:
807 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 813 del fixers[name]
811 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 816 del fixers[name]
814 817 return collections.OrderedDict(
815 818 sorted(fixers.items(), key=lambda item: item[1]._priority, reverse=True)
@@ -819,9 +822,9 b' def getfixers(ui):'
819 822 def fixernames(ui):
820 823 """Returns the names of [fix] config options that have suboptions"""
821 824 names = set()
822 for k, v in ui.configitems('fix'):
823 if ':' in k:
824 names.add(k.split(':', 1)[0])
825 for k, v in ui.configitems(b'fix'):
826 if b':' in k:
827 names.add(k.split(b':', 1)[0])
825 828 return names
826 829
827 830
@@ -849,7 +852,7 b' class Fixer(object):'
849 852 expand(
850 853 ui,
851 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 858 if self._linerange:
@@ -858,6 +861,8 b' class Fixer(object):'
858 861 return None
859 862 for first, last in ranges:
860 863 parts.append(
861 expand(ui, self._linerange, {'first': first, 'last': last})
864 expand(
865 ui, self._linerange, {b'first': first, b'last': last}
862 866 )
863 return ' '.join(parts)
867 )
868 return b' '.join(parts)
@@ -143,60 +143,60 b' from . import ('
143 143 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
144 144 # be specifying the version(s) of Mercurial they are tested with, or
145 145 # leave the attribute unspecified.
146 testedwith = 'ships-with-hg-core'
146 testedwith = b'ships-with-hg-core'
147 147
148 148 configtable = {}
149 149 configitem = registrar.configitem(configtable)
150 150
151 151 configitem(
152 'fsmonitor', 'mode', default='on',
152 b'fsmonitor', b'mode', default=b'on',
153 153 )
154 154 configitem(
155 'fsmonitor', 'walk_on_invalidate', default=False,
155 b'fsmonitor', b'walk_on_invalidate', default=False,
156 156 )
157 157 configitem(
158 'fsmonitor', 'timeout', default='2',
158 b'fsmonitor', b'timeout', default=b'2',
159 159 )
160 160 configitem(
161 'fsmonitor', 'blacklistusers', default=list,
161 b'fsmonitor', b'blacklistusers', default=list,
162 162 )
163 163 configitem(
164 'fsmonitor', 'watchman_exe', default='watchman',
164 b'fsmonitor', b'watchman_exe', default=b'watchman',
165 165 )
166 166 configitem(
167 'fsmonitor', 'verbose', default=True, experimental=True,
167 b'fsmonitor', b'verbose', default=True, experimental=True,
168 168 )
169 169 configitem(
170 'experimental', 'fsmonitor.transaction_notify', default=False,
170 b'experimental', b'fsmonitor.transaction_notify', default=False,
171 171 )
172 172
173 173 # This extension is incompatible with the following blacklisted extensions
174 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 178 def debuginstall(ui, fm):
179 179 fm.write(
180 "fsmonitor-watchman",
181 _("fsmonitor checking for watchman binary... (%s)\n"),
182 ui.configpath("fsmonitor", "watchman_exe"),
180 b"fsmonitor-watchman",
181 _(b"fsmonitor checking for watchman binary... (%s)\n"),
182 ui.configpath(b"fsmonitor", b"watchman_exe"),
183 183 )
184 184 root = tempfile.mkdtemp()
185 185 c = watchmanclient.client(ui, root)
186 186 err = None
187 187 try:
188 v = c.command("version")
188 v = c.command(b"version")
189 189 fm.write(
190 "fsmonitor-watchman-version",
191 _(" watchman binary version %s\n"),
192 v["version"],
190 b"fsmonitor-watchman-version",
191 _(b" watchman binary version %s\n"),
192 v[b"version"],
193 193 )
194 194 except watchmanclient.Unavailable as e:
195 195 err = str(e)
196 196 fm.condwrite(
197 197 err,
198 "fsmonitor-watchman-error",
199 _(" watchman binary missing or broken: %s\n"),
198 b"fsmonitor-watchman-error",
199 _(b" watchman binary missing or broken: %s\n"),
200 200 err,
201 201 )
202 202 return 1 if err else 0
@@ -206,16 +206,16 b' def _handleunavailable(ui, state, ex):'
206 206 """Exception handler for Watchman interaction exceptions"""
207 207 if isinstance(ex, watchmanclient.Unavailable):
208 208 # experimental config: fsmonitor.verbose
209 if ex.warn and ui.configbool('fsmonitor', 'verbose'):
210 if 'illegal_fstypes' not in str(ex):
211 ui.warn(str(ex) + '\n')
209 if ex.warn and ui.configbool(b'fsmonitor', b'verbose'):
210 if b'illegal_fstypes' not in str(ex):
211 ui.warn(str(ex) + b'\n')
212 212 if ex.invalidate:
213 213 state.invalidate()
214 214 # experimental config: fsmonitor.verbose
215 if ui.configbool('fsmonitor', 'verbose'):
216 ui.log('fsmonitor', 'Watchman unavailable: %s\n', ex.msg)
215 if ui.configbool(b'fsmonitor', b'verbose'):
216 ui.log(b'fsmonitor', b'Watchman unavailable: %s\n', ex.msg)
217 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 221 def _hashignore(ignore):
@@ -245,7 +245,7 b' def _watchmantofsencoding(path):'
245 245 try:
246 246 decoded = path.decode(_watchmanencoding)
247 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 250 try:
251 251 encoded = decoded.encode(_fsencoding, 'strict')
@@ -263,34 +263,34 b' def overridewalk(orig, self, match, subr'
263 263 subset of files.'''
264 264
265 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 267 return orig(match, subrepos, unknown, ignored, full=True)
268 268
269 269 if full:
270 return bail('full rewalk requested')
270 return bail(b'full rewalk requested')
271 271 if ignored:
272 return bail('listing ignored files')
272 return bail(b'listing ignored files')
273 273 if not self._watchmanclient.available():
274 return bail('client unavailable')
274 return bail(b'client unavailable')
275 275 state = self._fsmonitorstate
276 276 clock, ignorehash, notefiles = state.get()
277 277 if not clock:
278 278 if state.walk_on_invalidate:
279 return bail('no clock')
279 return bail(b'no clock')
280 280 # Initial NULL clock value, see
281 281 # https://facebook.github.io/watchman/docs/clockspec.html
282 clock = 'c:0:0'
282 clock = b'c:0:0'
283 283 notefiles = []
284 284
285 285 ignore = self._ignore
286 286 dirignore = self._dirignore
287 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 289 # ignore list changed -- can't rely on Watchman state any more
290 290 if state.walk_on_invalidate:
291 return bail('ignore rules changed')
291 return bail(b'ignore rules changed')
292 292 notefiles = []
293 clock = 'c:0:0'
293 clock = b'c:0:0'
294 294 else:
295 295 # always ignore
296 296 ignore = util.always
@@ -299,7 +299,7 b' def overridewalk(orig, self, match, subr'
299 299 matchfn = match.matchfn
300 300 matchalways = match.always()
301 301 dmap = self._map
302 if util.safehasattr(dmap, '_map'):
302 if util.safehasattr(dmap, b'_map'):
303 303 # for better performance, directly access the inner dirstate map if the
304 304 # standard dirstate implementation is in use.
305 305 dmap = dmap._map
@@ -339,7 +339,7 b' def overridewalk(orig, self, match, subr'
339 339 if not work and (exact or skipstep3):
340 340 for s in subrepos:
341 341 del results[s]
342 del results['.hg']
342 del results[b'.hg']
343 343 return results
344 344
345 345 # step 2: query Watchman
@@ -349,30 +349,34 b' def overridewalk(orig, self, match, subr'
349 349 # overheads while transferring the data
350 350 self._watchmanclient.settimeout(state.timeout + 0.1)
351 351 result = self._watchmanclient.command(
352 'query',
352 b'query',
353 353 {
354 'fields': ['mode', 'mtime', 'size', 'exists', 'name'],
355 'since': clock,
356 'expression': [
357 'not',
358 ['anyof', ['dirname', '.hg'], ['name', '.hg', 'wholename']],
354 b'fields': [b'mode', b'mtime', b'size', b'exists', b'name'],
355 b'since': clock,
356 b'expression': [
357 b'not',
358 [
359 b'anyof',
360 [b'dirname', b'.hg'],
361 [b'name', b'.hg', b'wholename'],
359 362 ],
360 'sync_timeout': int(state.timeout * 1000),
361 'empty_on_fresh_instance': state.walk_on_invalidate,
363 ],
364 b'sync_timeout': int(state.timeout * 1000),
365 b'empty_on_fresh_instance': state.walk_on_invalidate,
362 366 },
363 367 )
364 368 except Exception as ex:
365 369 _handleunavailable(self._ui, state, ex)
366 370 self._watchmanclient.clearconnection()
367 return bail('exception during run')
371 return bail(b'exception during run')
368 372 else:
369 373 # We need to propagate the last observed clock up so that we
370 374 # can use it for our next query
371 state.setlastclock(result['clock'])
372 if result['is_fresh_instance']:
375 state.setlastclock(result[b'clock'])
376 if result[b'is_fresh_instance']:
373 377 if state.walk_on_invalidate:
374 378 state.invalidate()
375 return bail('fresh instance')
379 return bail(b'fresh instance')
376 380 fresh_instance = True
377 381 # Ignore any prior noteable files from the state info
378 382 notefiles = []
@@ -382,7 +386,7 b' def overridewalk(orig, self, match, subr'
382 386 if normalize:
383 387 foldmap = dict((normcase(k), k) for k in results)
384 388
385 switch_slashes = pycompat.ossep == '\\'
389 switch_slashes = pycompat.ossep == b'\\'
386 390 # The order of the results is, strictly speaking, undefined.
387 391 # For case changes on a case insensitive filesystem we may receive
388 392 # two entries, one with exists=True and another with exists=False.
@@ -390,22 +394,22 b' def overridewalk(orig, self, match, subr'
390 394 # as being happens-after the exists=False entries due to the way that
391 395 # Watchman tracks files. We use this property to reconcile deletes
392 396 # for name case changes.
393 for entry in result['files']:
394 fname = entry['name']
397 for entry in result[b'files']:
398 fname = entry[b'name']
395 399 if _fixencoding:
396 400 fname = _watchmantofsencoding(fname)
397 401 if switch_slashes:
398 fname = fname.replace('\\', '/')
402 fname = fname.replace(b'\\', b'/')
399 403 if normalize:
400 404 normed = normcase(fname)
401 405 fname = normalize(fname, True, True)
402 406 foldmap[normed] = fname
403 fmode = entry['mode']
404 fexists = entry['exists']
407 fmode = entry[b'mode']
408 fexists = entry[b'exists']
405 409 kind = getkind(fmode)
406 410
407 if '/.hg/' in fname or fname.endswith('/.hg'):
408 return bail('nested-repo-detected')
411 if b'/.hg/' in fname or fname.endswith(b'/.hg'):
412 return bail(b'nested-repo-detected')
409 413
410 414 if not fexists:
411 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 493 for s in subrepos:
490 494 del results[s]
491 del results['.hg']
495 del results[b'.hg']
492 496 return results
493 497
494 498
495 499 def overridestatus(
496 500 orig,
497 501 self,
498 node1='.',
502 node1=b'.',
499 503 node2=None,
500 504 match=None,
501 505 ignored=False,
@@ -509,22 +513,22 b' def overridestatus('
509 513
510 514 def _cmpsets(l1, l2):
511 515 try:
512 if 'FSMONITOR_LOG_FILE' in encoding.environ:
513 fn = encoding.environ['FSMONITOR_LOG_FILE']
514 f = open(fn, 'wb')
516 if b'FSMONITOR_LOG_FILE' in encoding.environ:
517 fn = encoding.environ[b'FSMONITOR_LOG_FILE']
518 f = open(fn, b'wb')
515 519 else:
516 fn = 'fsmonitorfail.log'
517 f = self.vfs.open(fn, 'wb')
520 fn = b'fsmonitorfail.log'
521 f = self.vfs.open(fn, b'wb')
518 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 524 return
521 525
522 526 try:
523 527 for i, (s1, s2) in enumerate(zip(l1, l2)):
524 528 if set(s1) != set(s2):
525 f.write('sets at position %d are unequal\n' % i)
526 f.write('watchman returned: %s\n' % s1)
527 f.write('stat returned: %s\n' % s2)
529 f.write(b'sets at position %d are unequal\n' % i)
530 f.write(b'watchman returned: %s\n' % s1)
531 f.write(b'stat returned: %s\n' % s2)
528 532 finally:
529 533 f.close()
530 534
@@ -538,7 +542,7 b' def overridestatus('
538 542 ctx2 = self[node2]
539 543
540 544 working = ctx2.rev() is None
541 parentworking = working and ctx1 == self['.']
545 parentworking = working and ctx1 == self[b'.']
542 546 match = match or matchmod.always()
543 547
544 548 # Maybe we can use this opportunity to update Watchman's state.
@@ -552,7 +556,7 b' def overridestatus('
552 556 parentworking
553 557 and match.always()
554 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 562 try:
@@ -607,7 +611,7 b' def overridestatus('
607 611
608 612 # don't do paranoid checks if we're not going to query Watchman anyway
609 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 615 # run status again and fall back to the old walk this time
612 616 self.dirstate._fsmonitordisable = True
613 617
@@ -615,7 +619,7 b' def overridestatus('
615 619 quiet = self.ui.quiet
616 620 self.ui.quiet = True
617 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 624 try:
621 625 rv2 = orig(
@@ -692,20 +696,20 b' def makedirstate(repo, dirstate):'
692 696 def wrapdirstate(orig, self):
693 697 ds = orig(self)
694 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 700 makedirstate(self, ds)
697 701 return ds
698 702
699 703
700 704 def extsetup(ui):
701 705 extensions.wrapfilecache(
702 localrepo.localrepository, 'dirstate', wrapdirstate
706 localrepo.localrepository, b'dirstate', wrapdirstate
703 707 )
704 708 if pycompat.isdarwin:
705 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 715 def wrapsymlink(orig, source, link_name):
@@ -756,14 +760,14 b' class state_update(object):'
756 760 # merge.update is going to take the wlock almost immediately. We are
757 761 # effectively extending the lock around several short sanity checks.
758 762 if self.oldnode is None:
759 self.oldnode = self.repo['.'].node()
763 self.oldnode = self.repo[b'.'].node()
760 764
761 765 if self.repo.currentwlock() is None:
762 if util.safehasattr(self.repo, 'wlocknostateupdate'):
766 if util.safehasattr(self.repo, b'wlocknostateupdate'):
763 767 self._lock = self.repo.wlocknostateupdate()
764 768 else:
765 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 771 return self
768 772
769 773 def __exit__(self, type_, value, tb):
@@ -773,36 +777,36 b' class state_update(object):'
773 777 def exit(self, abort=False):
774 778 try:
775 779 if self.need_leave:
776 status = 'failed' if abort else 'ok'
780 status = b'failed' if abort else b'ok'
777 781 if self.newnode is None:
778 self.newnode = self.repo['.'].node()
782 self.newnode = self.repo[b'.'].node()
779 783 if self.distance is None:
780 784 self.distance = calcdistance(
781 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 788 finally:
785 789 self.need_leave = False
786 790 if self._lock:
787 791 self._lock.release()
788 792
789 def _state(self, cmd, commithash, status='ok'):
790 if not util.safehasattr(self.repo, '_watchmanclient'):
793 def _state(self, cmd, commithash, status=b'ok'):
794 if not util.safehasattr(self.repo, b'_watchmanclient'):
791 795 return False
792 796 try:
793 797 self.repo._watchmanclient.command(
794 798 cmd,
795 799 {
796 'name': self.name,
797 'metadata': {
800 b'name': self.name,
801 b'metadata': {
798 802 # the target revision
799 'rev': commithash,
803 b'rev': commithash,
800 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 806 # success/failure (only really meaningful for state-leave)
803 'status': status,
807 b'status': status,
804 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 814 except Exception as e:
811 815 # Swallow any errors; fire and forget
812 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 819 return False
816 820
@@ -844,7 +848,7 b' def wrapupdate('
844 848
845 849 distance = 0
846 850 partial = True
847 oldnode = repo['.'].node()
851 oldnode = repo[b'.'].node()
848 852 newnode = repo[node].node()
849 853 if matcher is None or matcher.always():
850 854 partial = False
@@ -852,7 +856,7 b' def wrapupdate('
852 856
853 857 with state_update(
854 858 repo,
855 name="hg.update",
859 name=b"hg.update",
856 860 oldnode=oldnode,
857 861 newnode=newnode,
858 862 distance=distance,
@@ -873,8 +877,8 b' def wrapupdate('
873 877
874 878 def repo_has_depth_one_nested_repo(repo):
875 879 for f in repo.wvfs.listdir():
876 if os.path.isdir(os.path.join(repo.root, f, '.hg')):
877 msg = 'fsmonitor: sub-repository %r detected, fsmonitor disabled\n'
880 if os.path.isdir(os.path.join(repo.root, f, b'.hg')):
881 msg = b'fsmonitor: sub-repository %r detected, fsmonitor disabled\n'
878 882 repo.ui.debug(msg % f)
879 883 return True
880 884 return False
@@ -887,8 +891,8 b' def reposetup(ui, repo):'
887 891 if ext in exts:
888 892 ui.warn(
889 893 _(
890 'The fsmonitor extension is incompatible with the %s '
891 'extension and has been disabled.\n'
894 b'The fsmonitor extension is incompatible with the %s '
895 b'extension and has been disabled.\n'
892 896 )
893 897 % ext
894 898 )
@@ -899,14 +903,14 b' def reposetup(ui, repo):'
899 903 #
900 904 # if repo[None].substate can cause a dirstate parse, which is too
901 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 907 return
904 908
905 909 if repo_has_depth_one_nested_repo(repo):
906 910 return
907 911
908 912 fsmonitorstate = state.state(repo)
909 if fsmonitorstate.mode == 'off':
913 if fsmonitorstate.mode == b'off':
910 914 return
911 915
912 916 try:
@@ -918,7 +922,7 b' def reposetup(ui, repo):'
918 922 repo._fsmonitorstate = fsmonitorstate
919 923 repo._watchmanclient = client
920 924
921 dirstate, cached = localrepo.isfilecached(repo, 'dirstate')
925 dirstate, cached = localrepo.isfilecached(repo, b'dirstate')
922 926 if cached:
923 927 # at this point since fsmonitorstate wasn't present,
924 928 # repo.dirstate is not a fsmonitordirstate
@@ -935,7 +939,7 b' def reposetup(ui, repo):'
935 939 def wlock(self, *args, **kwargs):
936 940 l = super(fsmonitorrepo, self).wlock(*args, **kwargs)
937 941 if not ui.configbool(
938 "experimental", "fsmonitor.transaction_notify"
942 b"experimental", b"fsmonitor.transaction_notify"
939 943 ):
940 944 return l
941 945 if l.held != 1:
@@ -951,12 +955,14 b' def reposetup(ui, repo):'
951 955
952 956 try:
953 957 l.stateupdate = None
954 l.stateupdate = state_update(self, name="hg.transaction")
958 l.stateupdate = state_update(self, name=b"hg.transaction")
955 959 l.stateupdate.enter()
956 960 l.releasefn = staterelease
957 961 except Exception as e:
958 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 966 return l
961 967
962 968 repo.__class__ = fsmonitorrepo
@@ -19,7 +19,7 b' from mercurial import ('
19 19 )
20 20
21 21 _version = 4
22 _versionformat = ">I"
22 _versionformat = b">I"
23 23
24 24
25 25 class state(object):
@@ -30,15 +30,15 b' class state(object):'
30 30 self._lastclock = None
31 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 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 39 def get(self):
40 40 try:
41 file = self._vfs('fsmonitor.state', 'rb')
41 file = self._vfs(b'fsmonitor.state', b'rb')
42 42 except IOError as inst:
43 43 self._identity = util.filestat(None)
44 44 if inst.errno != errno.ENOENT:
@@ -50,9 +50,9 b' class state(object):'
50 50 versionbytes = file.read(4)
51 51 if len(versionbytes) < 4:
52 52 self._ui.log(
53 'fsmonitor',
54 'fsmonitor: state file only has %d bytes, '
55 'nuking state\n' % len(versionbytes),
53 b'fsmonitor',
54 b'fsmonitor: state file only has %d bytes, '
55 b'nuking state\n' % len(versionbytes),
56 56 )
57 57 self.invalidate()
58 58 return None, None, None
@@ -61,21 +61,21 b' class state(object):'
61 61 if diskversion != _version:
62 62 # different version, nuke state and start over
63 63 self._ui.log(
64 'fsmonitor',
65 'fsmonitor: version switch from %d to '
66 '%d, nuking state\n' % (diskversion, _version),
64 b'fsmonitor',
65 b'fsmonitor: version switch from %d to '
66 b'%d, nuking state\n' % (diskversion, _version),
67 67 )
68 68 self.invalidate()
69 69 return None, None, None
70 70
71 state = file.read().split('\0')
71 state = file.read().split(b'\0')
72 72 # state = hostname\0clock\0ignorehash\0 + list of files, each
73 73 # followed by a \0
74 74 if len(state) < 3:
75 75 self._ui.log(
76 'fsmonitor',
77 'fsmonitor: state file truncated (expected '
78 '3 chunks, found %d), nuking state\n',
76 b'fsmonitor',
77 b'fsmonitor: state file truncated (expected '
78 b'3 chunks, found %d), nuking state\n',
79 79 len(state),
80 80 )
81 81 self.invalidate()
@@ -85,9 +85,9 b' class state(object):'
85 85 if diskhostname != hostname:
86 86 # file got moved to a different host
87 87 self._ui.log(
88 'fsmonitor',
89 'fsmonitor: stored hostname "%s" '
90 'different from current "%s", nuking state\n'
88 b'fsmonitor',
89 b'fsmonitor: stored hostname "%s" '
90 b'different from current "%s", nuking state\n'
91 91 % (diskhostname, hostname),
92 92 )
93 93 self.invalidate()
@@ -110,31 +110,33 b' class state(object):'
110 110
111 111 # Read the identity from the file on disk rather than from the open file
112 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 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 118 return
117 119
118 120 try:
119 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 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 126 return
125 127
126 128 with file:
127 129 file.write(struct.pack(_versionformat, _version))
128 file.write(socket.gethostname() + '\0')
129 file.write(clock + '\0')
130 file.write(ignorehash + '\0')
130 file.write(socket.gethostname() + b'\0')
131 file.write(clock + b'\0')
132 file.write(ignorehash + b'\0')
131 133 if notefiles:
132 file.write('\0'.join(notefiles))
133 file.write('\0')
134 file.write(b'\0'.join(notefiles))
135 file.write(b'\0')
134 136
135 137 def invalidate(self):
136 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 140 except OSError as inst:
139 141 if inst.errno != errno.ENOENT:
140 142 raise
@@ -18,15 +18,15 b' class Unavailable(Exception):'
18 18 def __init__(self, msg, warn=True, invalidate=False):
19 19 self.msg = msg
20 20 self.warn = warn
21 if self.msg == 'timed out waiting for response':
21 if self.msg == b'timed out waiting for response':
22 22 self.warn = False
23 23 self.invalidate = invalidate
24 24
25 25 def __str__(self):
26 26 if self.warn:
27 return 'warning: Watchman unavailable: %s' % self.msg
27 return b'warning: Watchman unavailable: %s' % self.msg
28 28 else:
29 return 'Watchman unavailable: %s' % self.msg
29 return b'Watchman unavailable: %s' % self.msg
30 30
31 31
32 32 class WatchmanNoRoot(Unavailable):
@@ -39,10 +39,10 b' class client(object):'
39 39 def __init__(self, ui, root, timeout=1.0):
40 40 err = None
41 41 if not self._user:
42 err = "couldn't get user"
42 err = b"couldn't get user"
43 43 warn = True
44 if self._user in ui.configlist('fsmonitor', 'blacklistusers'):
45 err = 'user %s in blacklist' % self._user
44 if self._user in ui.configlist(b'fsmonitor', b'blacklistusers'):
45 err = b'user %s in blacklist' % self._user
46 46 warn = False
47 47
48 48 if err:
@@ -60,10 +60,10 b' class client(object):'
60 60 self._watchmanclient.setTimeout(timeout)
61 61
62 62 def getcurrentclock(self):
63 result = self.command('clock')
64 if not util.safehasattr(result, 'clock'):
63 result = self.command(b'clock')
64 if not util.safehasattr(result, b'clock'):
65 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 68 return result.clock
69 69
@@ -86,7 +86,9 b' class client(object):'
86 86 try:
87 87 if self._watchmanclient is None:
88 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 92 self._watchmanclient = pywatchman.client(
91 93 timeout=self._timeout,
92 94 useImmutableBser=True,
@@ -94,7 +96,7 b' class client(object):'
94 96 )
95 97 return self._watchmanclient.query(*watchmanargs)
96 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 100 raise WatchmanNoRoot(self._root, ex.msg)
99 101 raise Unavailable(ex.msg)
100 102 except pywatchman.WatchmanError as ex:
@@ -107,7 +109,7 b' class client(object):'
107 109 except WatchmanNoRoot:
108 110 # this 'watch' command can also raise a WatchmanNoRoot if
109 111 # watchman refuses to accept this root
110 self._command('watch')
112 self._command(b'watch')
111 113 return self._command(*args)
112 114 except Unavailable:
113 115 # this is in an outer scope to catch Unavailable form any of the
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 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