hgext/convert/git.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43117 8ff1ecfadcd1
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    24         self.path = path
    24         self.path = path
    25         self.node = node
    25         self.node = node
    26         self.url = url
    26         self.url = url
    27 
    27 
    28     def hgsub(self):
    28     def hgsub(self):
    29         return "%s = [git]%s" % (self.path, self.url)
    29         return b"%s = [git]%s" % (self.path, self.url)
    30 
    30 
    31     def hgsubstate(self):
    31     def hgsubstate(self):
    32         return "%s %s" % (self.node, self.path)
    32         return b"%s %s" % (self.node, self.path)
    33 
    33 
    34 
    34 
    35 # Keys in extra fields that should not be copied if the user requests.
    35 # Keys in extra fields that should not be copied if the user requests.
    36 bannedextrakeys = {
    36 bannedextrakeys = {
    37     # Git commit object built-ins.
    37     # Git commit object built-ins.
    38     'tree',
    38     b'tree',
    39     'parent',
    39     b'parent',
    40     'author',
    40     b'author',
    41     'committer',
    41     b'committer',
    42     # Mercurial built-ins.
    42     # Mercurial built-ins.
    43     'branch',
    43     b'branch',
    44     'close',
    44     b'close',
    45 }
    45 }
    46 
    46 
    47 
    47 
    48 class convert_git(common.converter_source, common.commandline):
    48 class convert_git(common.converter_source, common.commandline):
    49     # Windows does not support GIT_DIR= construct while other systems
    49     # Windows does not support GIT_DIR= construct while other systems
    50     # cannot remove environment variable. Just assume none have
    50     # cannot remove environment variable. Just assume none have
    51     # both issues.
    51     # both issues.
    52 
    52 
    53     def _gitcmd(self, cmd, *args, **kwargs):
    53     def _gitcmd(self, cmd, *args, **kwargs):
    54         return cmd('--git-dir=%s' % self.path, *args, **kwargs)
    54         return cmd(b'--git-dir=%s' % self.path, *args, **kwargs)
    55 
    55 
    56     def gitrun0(self, *args, **kwargs):
    56     def gitrun0(self, *args, **kwargs):
    57         return self._gitcmd(self.run0, *args, **kwargs)
    57         return self._gitcmd(self.run0, *args, **kwargs)
    58 
    58 
    59     def gitrun(self, *args, **kwargs):
    59     def gitrun(self, *args, **kwargs):
    68     def gitpipe(self, *args, **kwargs):
    68     def gitpipe(self, *args, **kwargs):
    69         return self._gitcmd(self._run3, *args, **kwargs)
    69         return self._gitcmd(self._run3, *args, **kwargs)
    70 
    70 
    71     def __init__(self, ui, repotype, path, revs=None):
    71     def __init__(self, ui, repotype, path, revs=None):
    72         super(convert_git, self).__init__(ui, repotype, path, revs=revs)
    72         super(convert_git, self).__init__(ui, repotype, path, revs=revs)
    73         common.commandline.__init__(self, ui, 'git')
    73         common.commandline.__init__(self, ui, b'git')
    74 
    74 
    75         # Pass an absolute path to git to prevent from ever being interpreted
    75         # Pass an absolute path to git to prevent from ever being interpreted
    76         # as a URL
    76         # as a URL
    77         path = os.path.abspath(path)
    77         path = os.path.abspath(path)
    78 
    78 
    79         if os.path.isdir(path + "/.git"):
    79         if os.path.isdir(path + b"/.git"):
    80             path += "/.git"
    80             path += b"/.git"
    81         if not os.path.exists(path + "/objects"):
    81         if not os.path.exists(path + b"/objects"):
    82             raise common.NoRepo(
    82             raise common.NoRepo(
    83                 _("%s does not look like a Git repository") % path
    83                 _(b"%s does not look like a Git repository") % path
    84             )
    84             )
    85 
    85 
    86         # The default value (50) is based on the default for 'git diff'.
    86         # The default value (50) is based on the default for 'git diff'.
    87         similarity = ui.configint('convert', 'git.similarity')
    87         similarity = ui.configint(b'convert', b'git.similarity')
    88         if similarity < 0 or similarity > 100:
    88         if similarity < 0 or similarity > 100:
    89             raise error.Abort(_('similarity must be between 0 and 100'))
    89             raise error.Abort(_(b'similarity must be between 0 and 100'))
    90         if similarity > 0:
    90         if similarity > 0:
    91             self.simopt = ['-C%d%%' % similarity]
    91             self.simopt = [b'-C%d%%' % similarity]
    92             findcopiesharder = ui.configbool('convert', 'git.findcopiesharder')
    92             findcopiesharder = ui.configbool(
       
    93                 b'convert', b'git.findcopiesharder'
       
    94             )
    93             if findcopiesharder:
    95             if findcopiesharder:
    94                 self.simopt.append('--find-copies-harder')
    96                 self.simopt.append(b'--find-copies-harder')
    95 
    97 
    96             renamelimit = ui.configint('convert', 'git.renamelimit')
    98             renamelimit = ui.configint(b'convert', b'git.renamelimit')
    97             self.simopt.append('-l%d' % renamelimit)
    99             self.simopt.append(b'-l%d' % renamelimit)
    98         else:
   100         else:
    99             self.simopt = []
   101             self.simopt = []
   100 
   102 
   101         common.checktool('git', 'git')
   103         common.checktool(b'git', b'git')
   102 
   104 
   103         self.path = path
   105         self.path = path
   104         self.submodules = []
   106         self.submodules = []
   105 
   107 
   106         self.catfilepipe = self.gitpipe('cat-file', '--batch')
   108         self.catfilepipe = self.gitpipe(b'cat-file', b'--batch')
   107 
   109 
   108         self.copyextrakeys = self.ui.configlist('convert', 'git.extrakeys')
   110         self.copyextrakeys = self.ui.configlist(b'convert', b'git.extrakeys')
   109         banned = set(self.copyextrakeys) & bannedextrakeys
   111         banned = set(self.copyextrakeys) & bannedextrakeys
   110         if banned:
   112         if banned:
   111             raise error.Abort(
   113             raise error.Abort(
   112                 _('copying of extra key is forbidden: %s')
   114                 _(b'copying of extra key is forbidden: %s')
   113                 % _(', ').join(sorted(banned))
   115                 % _(b', ').join(sorted(banned))
   114             )
   116             )
   115 
   117 
   116         committeractions = self.ui.configlist('convert', 'git.committeractions')
   118         committeractions = self.ui.configlist(
       
   119             b'convert', b'git.committeractions'
       
   120         )
   117 
   121 
   118         messagedifferent = None
   122         messagedifferent = None
   119         messagealways = None
   123         messagealways = None
   120         for a in committeractions:
   124         for a in committeractions:
   121             if a.startswith(('messagedifferent', 'messagealways')):
   125             if a.startswith((b'messagedifferent', b'messagealways')):
   122                 k = a
   126                 k = a
   123                 v = None
   127                 v = None
   124                 if '=' in a:
   128                 if b'=' in a:
   125                     k, v = a.split('=', 1)
   129                     k, v = a.split(b'=', 1)
   126 
   130 
   127                 if k == 'messagedifferent':
   131                 if k == b'messagedifferent':
   128                     messagedifferent = v or 'committer:'
   132                     messagedifferent = v or b'committer:'
   129                 elif k == 'messagealways':
   133                 elif k == b'messagealways':
   130                     messagealways = v or 'committer:'
   134                     messagealways = v or b'committer:'
   131 
   135 
   132         if messagedifferent and messagealways:
   136         if messagedifferent and messagealways:
   133             raise error.Abort(
   137             raise error.Abort(
   134                 _(
   138                 _(
   135                     'committeractions cannot define both '
   139                     b'committeractions cannot define both '
   136                     'messagedifferent and messagealways'
   140                     b'messagedifferent and messagealways'
   137                 )
   141                 )
   138             )
   142             )
   139 
   143 
   140         dropcommitter = 'dropcommitter' in committeractions
   144         dropcommitter = b'dropcommitter' in committeractions
   141         replaceauthor = 'replaceauthor' in committeractions
   145         replaceauthor = b'replaceauthor' in committeractions
   142 
   146 
   143         if dropcommitter and replaceauthor:
   147         if dropcommitter and replaceauthor:
   144             raise error.Abort(
   148             raise error.Abort(
   145                 _(
   149                 _(
   146                     'committeractions cannot define both '
   150                     b'committeractions cannot define both '
   147                     'dropcommitter and replaceauthor'
   151                     b'dropcommitter and replaceauthor'
   148                 )
   152                 )
   149             )
   153             )
   150 
   154 
   151         if dropcommitter and messagealways:
   155         if dropcommitter and messagealways:
   152             raise error.Abort(
   156             raise error.Abort(
   153                 _(
   157                 _(
   154                     'committeractions cannot define both '
   158                     b'committeractions cannot define both '
   155                     'dropcommitter and messagealways'
   159                     b'dropcommitter and messagealways'
   156                 )
   160                 )
   157             )
   161             )
   158 
   162 
   159         if not messagedifferent and not messagealways:
   163         if not messagedifferent and not messagealways:
   160             messagedifferent = 'committer:'
   164             messagedifferent = b'committer:'
   161 
   165 
   162         self.committeractions = {
   166         self.committeractions = {
   163             'dropcommitter': dropcommitter,
   167             b'dropcommitter': dropcommitter,
   164             'replaceauthor': replaceauthor,
   168             b'replaceauthor': replaceauthor,
   165             'messagedifferent': messagedifferent,
   169             b'messagedifferent': messagedifferent,
   166             'messagealways': messagealways,
   170             b'messagealways': messagealways,
   167         }
   171         }
   168 
   172 
   169     def after(self):
   173     def after(self):
   170         for f in self.catfilepipe:
   174         for f in self.catfilepipe:
   171             f.close()
   175             f.close()
   172 
   176 
   173     def getheads(self):
   177     def getheads(self):
   174         if not self.revs:
   178         if not self.revs:
   175             output, status = self.gitrun('rev-parse', '--branches', '--remotes')
   179             output, status = self.gitrun(
       
   180                 b'rev-parse', b'--branches', b'--remotes'
       
   181             )
   176             heads = output.splitlines()
   182             heads = output.splitlines()
   177             if status:
   183             if status:
   178                 raise error.Abort(_('cannot retrieve git heads'))
   184                 raise error.Abort(_(b'cannot retrieve git heads'))
   179         else:
   185         else:
   180             heads = []
   186             heads = []
   181             for rev in self.revs:
   187             for rev in self.revs:
   182                 rawhead, ret = self.gitrun('rev-parse', '--verify', rev)
   188                 rawhead, ret = self.gitrun(b'rev-parse', b'--verify', rev)
   183                 heads.append(rawhead[:-1])
   189                 heads.append(rawhead[:-1])
   184                 if ret:
   190                 if ret:
   185                     raise error.Abort(_('cannot retrieve git head "%s"') % rev)
   191                     raise error.Abort(_(b'cannot retrieve git head "%s"') % rev)
   186         return heads
   192         return heads
   187 
   193 
   188     def catfile(self, rev, ftype):
   194     def catfile(self, rev, ftype):
   189         if rev == nodemod.nullhex:
   195         if rev == nodemod.nullhex:
   190             raise IOError
   196             raise IOError
   191         self.catfilepipe[0].write(rev + '\n')
   197         self.catfilepipe[0].write(rev + b'\n')
   192         self.catfilepipe[0].flush()
   198         self.catfilepipe[0].flush()
   193         info = self.catfilepipe[1].readline().split()
   199         info = self.catfilepipe[1].readline().split()
   194         if info[1] != ftype:
   200         if info[1] != ftype:
   195             raise error.Abort(
   201             raise error.Abort(
   196                 _('cannot read %r object at %s')
   202                 _(b'cannot read %r object at %s')
   197                 % (pycompat.bytestr(ftype), rev)
   203                 % (pycompat.bytestr(ftype), rev)
   198             )
   204             )
   199         size = int(info[2])
   205         size = int(info[2])
   200         data = self.catfilepipe[1].read(size)
   206         data = self.catfilepipe[1].read(size)
   201         if len(data) < size:
   207         if len(data) < size:
   202             raise error.Abort(
   208             raise error.Abort(
   203                 _('cannot read %r object at %s: unexpected size') % (ftype, rev)
   209                 _(b'cannot read %r object at %s: unexpected size')
       
   210                 % (ftype, rev)
   204             )
   211             )
   205         # read the trailing newline
   212         # read the trailing newline
   206         self.catfilepipe[1].read(1)
   213         self.catfilepipe[1].read(1)
   207         return data
   214         return data
   208 
   215 
   209     def getfile(self, name, rev):
   216     def getfile(self, name, rev):
   210         if rev == nodemod.nullhex:
   217         if rev == nodemod.nullhex:
   211             return None, None
   218             return None, None
   212         if name == '.hgsub':
   219         if name == b'.hgsub':
   213             data = '\n'.join([m.hgsub() for m in self.submoditer()])
   220             data = b'\n'.join([m.hgsub() for m in self.submoditer()])
   214             mode = ''
   221             mode = b''
   215         elif name == '.hgsubstate':
   222         elif name == b'.hgsubstate':
   216             data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
   223             data = b'\n'.join([m.hgsubstate() for m in self.submoditer()])
   217             mode = ''
   224             mode = b''
   218         else:
   225         else:
   219             data = self.catfile(rev, "blob")
   226             data = self.catfile(rev, b"blob")
   220             mode = self.modecache[(name, rev)]
   227             mode = self.modecache[(name, rev)]
   221         return data, mode
   228         return data, mode
   222 
   229 
   223     def submoditer(self):
   230     def submoditer(self):
   224         null = nodemod.nullhex
   231         null = nodemod.nullhex
   234         """
   241         """
   235         self.submodules = []
   242         self.submodules = []
   236         c = config.config()
   243         c = config.config()
   237         # Each item in .gitmodules starts with whitespace that cant be parsed
   244         # Each item in .gitmodules starts with whitespace that cant be parsed
   238         c.parse(
   245         c.parse(
   239             '.gitmodules',
   246             b'.gitmodules',
   240             '\n'.join(line.strip() for line in content.split('\n')),
   247             b'\n'.join(line.strip() for line in content.split(b'\n')),
   241         )
   248         )
   242         for sec in c.sections():
   249         for sec in c.sections():
   243             s = c[sec]
   250             s = c[sec]
   244             if 'url' in s and 'path' in s:
   251             if b'url' in s and b'path' in s:
   245                 self.submodules.append(submodule(s['path'], '', s['url']))
   252                 self.submodules.append(submodule(s[b'path'], b'', s[b'url']))
   246 
   253 
   247     def retrievegitmodules(self, version):
   254     def retrievegitmodules(self, version):
   248         modules, ret = self.gitrun('show', '%s:%s' % (version, '.gitmodules'))
   255         modules, ret = self.gitrun(
       
   256             b'show', b'%s:%s' % (version, b'.gitmodules')
       
   257         )
   249         if ret:
   258         if ret:
   250             # This can happen if a file is in the repo that has permissions
   259             # This can happen if a file is in the repo that has permissions
   251             # 160000, but there is no .gitmodules file.
   260             # 160000, but there is no .gitmodules file.
   252             self.ui.warn(
   261             self.ui.warn(
   253                 _("warning: cannot read submodules config file in " "%s\n")
   262                 _(b"warning: cannot read submodules config file in " b"%s\n")
   254                 % version
   263                 % version
   255             )
   264             )
   256             return
   265             return
   257 
   266 
   258         try:
   267         try:
   259             self.parsegitmodules(modules)
   268             self.parsegitmodules(modules)
   260         except error.ParseError:
   269         except error.ParseError:
   261             self.ui.warn(
   270             self.ui.warn(
   262                 _("warning: unable to parse .gitmodules in %s\n") % version
   271                 _(b"warning: unable to parse .gitmodules in %s\n") % version
   263             )
   272             )
   264             return
   273             return
   265 
   274 
   266         for m in self.submodules:
   275         for m in self.submodules:
   267             node, ret = self.gitrun('rev-parse', '%s:%s' % (version, m.path))
   276             node, ret = self.gitrun(b'rev-parse', b'%s:%s' % (version, m.path))
   268             if ret:
   277             if ret:
   269                 continue
   278                 continue
   270             m.node = node.strip()
   279             m.node = node.strip()
   271 
   280 
   272     def getchanges(self, version, full):
   281     def getchanges(self, version, full):
   273         if full:
   282         if full:
   274             raise error.Abort(_("convert from git does not support --full"))
   283             raise error.Abort(_(b"convert from git does not support --full"))
   275         self.modecache = {}
   284         self.modecache = {}
   276         cmd = (
   285         cmd = (
   277             ['diff-tree', '-z', '--root', '-m', '-r'] + self.simopt + [version]
   286             [b'diff-tree', b'-z', b'--root', b'-m', b'-r']
       
   287             + self.simopt
       
   288             + [version]
   278         )
   289         )
   279         output, status = self.gitrun(*cmd)
   290         output, status = self.gitrun(*cmd)
   280         if status:
   291         if status:
   281             raise error.Abort(_('cannot read changes in %s') % version)
   292             raise error.Abort(_(b'cannot read changes in %s') % version)
   282         changes = []
   293         changes = []
   283         copies = {}
   294         copies = {}
   284         seen = set()
   295         seen = set()
   285         entry = None
   296         entry = None
   286         subexists = [False]
   297         subexists = [False]
   287         subdeleted = [False]
   298         subdeleted = [False]
   288         difftree = output.split('\x00')
   299         difftree = output.split(b'\x00')
   289         lcount = len(difftree)
   300         lcount = len(difftree)
   290         i = 0
   301         i = 0
   291 
   302 
   292         skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules')
   303         skipsubmodules = self.ui.configbool(b'convert', b'git.skipsubmodules')
   293 
   304 
   294         def add(entry, f, isdest):
   305         def add(entry, f, isdest):
   295             seen.add(f)
   306             seen.add(f)
   296             h = entry[3]
   307             h = entry[3]
   297             p = entry[1] == "100755"
   308             p = entry[1] == b"100755"
   298             s = entry[1] == "120000"
   309             s = entry[1] == b"120000"
   299             renamesource = not isdest and entry[4][0] == 'R'
   310             renamesource = not isdest and entry[4][0] == b'R'
   300 
   311 
   301             if f == '.gitmodules':
   312             if f == b'.gitmodules':
   302                 if skipsubmodules:
   313                 if skipsubmodules:
   303                     return
   314                     return
   304 
   315 
   305                 subexists[0] = True
   316                 subexists[0] = True
   306                 if entry[4] == 'D' or renamesource:
   317                 if entry[4] == b'D' or renamesource:
   307                     subdeleted[0] = True
   318                     subdeleted[0] = True
   308                     changes.append(('.hgsub', nodemod.nullhex))
   319                     changes.append((b'.hgsub', nodemod.nullhex))
   309                 else:
   320                 else:
   310                     changes.append(('.hgsub', ''))
   321                     changes.append((b'.hgsub', b''))
   311             elif entry[1] == '160000' or entry[0] == ':160000':
   322             elif entry[1] == b'160000' or entry[0] == b':160000':
   312                 if not skipsubmodules:
   323                 if not skipsubmodules:
   313                     subexists[0] = True
   324                     subexists[0] = True
   314             else:
   325             else:
   315                 if renamesource:
   326                 if renamesource:
   316                     h = nodemod.nullhex
   327                     h = nodemod.nullhex
   317                 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
   328                 self.modecache[(f, h)] = (p and b"x") or (s and b"l") or b""
   318                 changes.append((f, h))
   329                 changes.append((f, h))
   319 
   330 
   320         while i < lcount:
   331         while i < lcount:
   321             l = difftree[i]
   332             l = difftree[i]
   322             i += 1
   333             i += 1
   323             if not entry:
   334             if not entry:
   324                 if not l.startswith(':'):
   335                 if not l.startswith(b':'):
   325                     continue
   336                     continue
   326                 entry = tuple(pycompat.bytestr(p) for p in l.split())
   337                 entry = tuple(pycompat.bytestr(p) for p in l.split())
   327                 continue
   338                 continue
   328             f = l
   339             f = l
   329             if entry[4][0] == 'C':
   340             if entry[4][0] == b'C':
   330                 copysrc = f
   341                 copysrc = f
   331                 copydest = difftree[i]
   342                 copydest = difftree[i]
   332                 i += 1
   343                 i += 1
   333                 f = copydest
   344                 f = copydest
   334                 copies[copydest] = copysrc
   345                 copies[copydest] = copysrc
   335             if f not in seen:
   346             if f not in seen:
   336                 add(entry, f, False)
   347                 add(entry, f, False)
   337             # A file can be copied multiple times, or modified and copied
   348             # A file can be copied multiple times, or modified and copied
   338             # simultaneously. So f can be repeated even if fdest isn't.
   349             # simultaneously. So f can be repeated even if fdest isn't.
   339             if entry[4][0] == 'R':
   350             if entry[4][0] == b'R':
   340                 # rename: next line is the destination
   351                 # rename: next line is the destination
   341                 fdest = difftree[i]
   352                 fdest = difftree[i]
   342                 i += 1
   353                 i += 1
   343                 if fdest not in seen:
   354                 if fdest not in seen:
   344                     add(entry, fdest, True)
   355                     add(entry, fdest, True)
   345                     # .gitmodules isn't imported at all, so it being copied to
   356                     # .gitmodules isn't imported at all, so it being copied to
   346                     # and fro doesn't really make sense
   357                     # and fro doesn't really make sense
   347                     if f != '.gitmodules' and fdest != '.gitmodules':
   358                     if f != b'.gitmodules' and fdest != b'.gitmodules':
   348                         copies[fdest] = f
   359                         copies[fdest] = f
   349             entry = None
   360             entry = None
   350 
   361 
   351         if subexists[0]:
   362         if subexists[0]:
   352             if subdeleted[0]:
   363             if subdeleted[0]:
   353                 changes.append(('.hgsubstate', nodemod.nullhex))
   364                 changes.append((b'.hgsubstate', nodemod.nullhex))
   354             else:
   365             else:
   355                 self.retrievegitmodules(version)
   366                 self.retrievegitmodules(version)
   356                 changes.append(('.hgsubstate', ''))
   367                 changes.append((b'.hgsubstate', b''))
   357         return (changes, copies, set())
   368         return (changes, copies, set())
   358 
   369 
   359     def getcommit(self, version):
   370     def getcommit(self, version):
   360         c = self.catfile(version, "commit")  # read the commit hash
   371         c = self.catfile(version, b"commit")  # read the commit hash
   361         end = c.find("\n\n")
   372         end = c.find(b"\n\n")
   362         message = c[end + 2 :]
   373         message = c[end + 2 :]
   363         message = self.recode(message)
   374         message = self.recode(message)
   364         l = c[:end].splitlines()
   375         l = c[:end].splitlines()
   365         parents = []
   376         parents = []
   366         author = committer = None
   377         author = committer = None
   367         extra = {}
   378         extra = {}
   368         for e in l[1:]:
   379         for e in l[1:]:
   369             n, v = e.split(" ", 1)
   380             n, v = e.split(b" ", 1)
   370             if n == "author":
   381             if n == b"author":
   371                 p = v.split()
   382                 p = v.split()
   372                 tm, tz = p[-2:]
   383                 tm, tz = p[-2:]
   373                 author = " ".join(p[:-2])
   384                 author = b" ".join(p[:-2])
   374                 if author[0] == "<":
   385                 if author[0] == b"<":
   375                     author = author[1:-1]
   386                     author = author[1:-1]
   376                 author = self.recode(author)
   387                 author = self.recode(author)
   377             if n == "committer":
   388             if n == b"committer":
   378                 p = v.split()
   389                 p = v.split()
   379                 tm, tz = p[-2:]
   390                 tm, tz = p[-2:]
   380                 committer = " ".join(p[:-2])
   391                 committer = b" ".join(p[:-2])
   381                 if committer[0] == "<":
   392                 if committer[0] == b"<":
   382                     committer = committer[1:-1]
   393                     committer = committer[1:-1]
   383                 committer = self.recode(committer)
   394                 committer = self.recode(committer)
   384             if n == "parent":
   395             if n == b"parent":
   385                 parents.append(v)
   396                 parents.append(v)
   386             if n in self.copyextrakeys:
   397             if n in self.copyextrakeys:
   387                 extra[n] = v
   398                 extra[n] = v
   388 
   399 
   389         if self.committeractions['dropcommitter']:
   400         if self.committeractions[b'dropcommitter']:
   390             committer = None
   401             committer = None
   391         elif self.committeractions['replaceauthor']:
   402         elif self.committeractions[b'replaceauthor']:
   392             author = committer
   403             author = committer
   393 
   404 
   394         if committer:
   405         if committer:
   395             messagealways = self.committeractions['messagealways']
   406             messagealways = self.committeractions[b'messagealways']
   396             messagedifferent = self.committeractions['messagedifferent']
   407             messagedifferent = self.committeractions[b'messagedifferent']
   397             if messagealways:
   408             if messagealways:
   398                 message += '\n%s %s\n' % (messagealways, committer)
   409                 message += b'\n%s %s\n' % (messagealways, committer)
   399             elif messagedifferent and author != committer:
   410             elif messagedifferent and author != committer:
   400                 message += '\n%s %s\n' % (messagedifferent, committer)
   411                 message += b'\n%s %s\n' % (messagedifferent, committer)
   401 
   412 
   402         tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
   413         tzs, tzh, tzm = tz[-5:-4] + b"1", tz[-4:-2], tz[-2:]
   403         tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
   414         tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
   404         date = tm + " " + (b"%d" % tz)
   415         date = tm + b" " + (b"%d" % tz)
   405         saverev = self.ui.configbool('convert', 'git.saverev')
   416         saverev = self.ui.configbool(b'convert', b'git.saverev')
   406 
   417 
   407         c = common.commit(
   418         c = common.commit(
   408             parents=parents,
   419             parents=parents,
   409             date=date,
   420             date=date,
   410             author=author,
   421             author=author,
   414             saverev=saverev,
   425             saverev=saverev,
   415         )
   426         )
   416         return c
   427         return c
   417 
   428 
   418     def numcommits(self):
   429     def numcommits(self):
   419         output, ret = self.gitrunlines('rev-list', '--all')
   430         output, ret = self.gitrunlines(b'rev-list', b'--all')
   420         if ret:
   431         if ret:
   421             raise error.Abort(
   432             raise error.Abort(
   422                 _('cannot retrieve number of commits in %s') % self.path
   433                 _(b'cannot retrieve number of commits in %s') % self.path
   423             )
   434             )
   424         return len(output)
   435         return len(output)
   425 
   436 
   426     def gettags(self):
   437     def gettags(self):
   427         tags = {}
   438         tags = {}
   428         alltags = {}
   439         alltags = {}
   429         output, status = self.gitrunlines('ls-remote', '--tags', self.path)
   440         output, status = self.gitrunlines(b'ls-remote', b'--tags', self.path)
   430 
   441 
   431         if status:
   442         if status:
   432             raise error.Abort(_('cannot read tags from %s') % self.path)
   443             raise error.Abort(_(b'cannot read tags from %s') % self.path)
   433         prefix = 'refs/tags/'
   444         prefix = b'refs/tags/'
   434 
   445 
   435         # Build complete list of tags, both annotated and bare ones
   446         # Build complete list of tags, both annotated and bare ones
   436         for line in output:
   447         for line in output:
   437             line = line.strip()
   448             line = line.strip()
   438             if line.startswith("error:") or line.startswith("fatal:"):
   449             if line.startswith(b"error:") or line.startswith(b"fatal:"):
   439                 raise error.Abort(_('cannot read tags from %s') % self.path)
   450                 raise error.Abort(_(b'cannot read tags from %s') % self.path)
   440             node, tag = line.split(None, 1)
   451             node, tag = line.split(None, 1)
   441             if not tag.startswith(prefix):
   452             if not tag.startswith(prefix):
   442                 continue
   453                 continue
   443             alltags[tag[len(prefix) :]] = node
   454             alltags[tag[len(prefix) :]] = node
   444 
   455 
   445         # Filter out tag objects for annotated tag refs
   456         # Filter out tag objects for annotated tag refs
   446         for tag in alltags:
   457         for tag in alltags:
   447             if tag.endswith('^{}'):
   458             if tag.endswith(b'^{}'):
   448                 tags[tag[:-3]] = alltags[tag]
   459                 tags[tag[:-3]] = alltags[tag]
   449             else:
   460             else:
   450                 if tag + '^{}' in alltags:
   461                 if tag + b'^{}' in alltags:
   451                     continue
   462                     continue
   452                 else:
   463                 else:
   453                     tags[tag] = alltags[tag]
   464                     tags[tag] = alltags[tag]
   454 
   465 
   455         return tags
   466         return tags
   456 
   467 
   457     def getchangedfiles(self, version, i):
   468     def getchangedfiles(self, version, i):
   458         changes = []
   469         changes = []
   459         if i is None:
   470         if i is None:
   460             output, status = self.gitrunlines(
   471             output, status = self.gitrunlines(
   461                 'diff-tree', '--root', '-m', '-r', version
   472                 b'diff-tree', b'--root', b'-m', b'-r', version
   462             )
   473             )
   463             if status:
   474             if status:
   464                 raise error.Abort(_('cannot read changes in %s') % version)
   475                 raise error.Abort(_(b'cannot read changes in %s') % version)
   465             for l in output:
   476             for l in output:
   466                 if "\t" not in l:
   477                 if b"\t" not in l:
   467                     continue
   478                     continue
   468                 m, f = l[:-1].split("\t")
   479                 m, f = l[:-1].split(b"\t")
   469                 changes.append(f)
   480                 changes.append(f)
   470         else:
   481         else:
   471             output, status = self.gitrunlines(
   482             output, status = self.gitrunlines(
   472                 'diff-tree',
   483                 b'diff-tree',
   473                 '--name-only',
   484                 b'--name-only',
   474                 '--root',
   485                 b'--root',
   475                 '-r',
   486                 b'-r',
   476                 version,
   487                 version,
   477                 '%s^%d' % (version, i + 1),
   488                 b'%s^%d' % (version, i + 1),
   478                 '--',
   489                 b'--',
   479             )
   490             )
   480             if status:
   491             if status:
   481                 raise error.Abort(_('cannot read changes in %s') % version)
   492                 raise error.Abort(_(b'cannot read changes in %s') % version)
   482             changes = [f.rstrip('\n') for f in output]
   493             changes = [f.rstrip(b'\n') for f in output]
   483 
   494 
   484         return changes
   495         return changes
   485 
   496 
   486     def getbookmarks(self):
   497     def getbookmarks(self):
   487         bookmarks = {}
   498         bookmarks = {}
   488 
   499 
   489         # Handle local and remote branches
   500         # Handle local and remote branches
   490         remoteprefix = self.ui.config('convert', 'git.remoteprefix')
   501         remoteprefix = self.ui.config(b'convert', b'git.remoteprefix')
   491         reftypes = [
   502         reftypes = [
   492             # (git prefix, hg prefix)
   503             # (git prefix, hg prefix)
   493             ('refs/remotes/origin/', remoteprefix + '/'),
   504             (b'refs/remotes/origin/', remoteprefix + b'/'),
   494             ('refs/heads/', ''),
   505             (b'refs/heads/', b''),
   495         ]
   506         ]
   496 
   507 
   497         exclude = {
   508         exclude = {
   498             'refs/remotes/origin/HEAD',
   509             b'refs/remotes/origin/HEAD',
   499         }
   510         }
   500 
   511 
   501         try:
   512         try:
   502             output, status = self.gitrunlines('show-ref')
   513             output, status = self.gitrunlines(b'show-ref')
   503             for line in output:
   514             for line in output:
   504                 line = line.strip()
   515                 line = line.strip()
   505                 rev, name = line.split(None, 1)
   516                 rev, name = line.split(None, 1)
   506                 # Process each type of branch
   517                 # Process each type of branch
   507                 for gitprefix, hgprefix in reftypes:
   518                 for gitprefix, hgprefix in reftypes:
   508                     if not name.startswith(gitprefix) or name in exclude:
   519                     if not name.startswith(gitprefix) or name in exclude:
   509                         continue
   520                         continue
   510                     name = '%s%s' % (hgprefix, name[len(gitprefix) :])
   521                     name = b'%s%s' % (hgprefix, name[len(gitprefix) :])
   511                     bookmarks[name] = rev
   522                     bookmarks[name] = rev
   512         except Exception:
   523         except Exception:
   513             pass
   524             pass
   514 
   525 
   515         return bookmarks
   526         return bookmarks
   516 
   527 
   517     def checkrevformat(self, revstr, mapname='splicemap'):
   528     def checkrevformat(self, revstr, mapname=b'splicemap'):
   518         """ git revision string is a 40 byte hex """
   529         """ git revision string is a 40 byte hex """
   519         self.checkhexformat(revstr, mapname)
   530         self.checkhexformat(revstr, mapname)