diff mercurial/patch.py @ 43077:687b865b95ad

formatting: byteify all mercurial/ and hgext/ string literals Done with python3.7 contrib/byteify-strings.py -i $(hg files 'set:mercurial/**.py - mercurial/thirdparty/** + hgext/**.py - hgext/fsmonitor/pywatchman/** - mercurial/__init__.py') black -l 80 -t py33 -S $(hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**" - hgext/fsmonitor/pywatchman/**') # skip-blame mass-reformatting only Differential Revision: https://phab.mercurial-scm.org/D6972
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:48:39 -0400
parents 2372284d9457
children eef9a2d67051
line wrap: on
line diff
--- a/mercurial/patch.py	Sun Oct 06 09:45:02 2019 -0400
+++ b/mercurial/patch.py	Sun Oct 06 09:48:39 2019 -0400
@@ -62,17 +62,17 @@
     '''return an iterator of individual patches from a stream'''
 
     def isheader(line, inheader):
-        if inheader and line.startswith((' ', '\t')):
+        if inheader and line.startswith((b' ', b'\t')):
             # continuation
             return True
-        if line.startswith((' ', '-', '+')):
+        if line.startswith((b' ', b'-', b'+')):
             # diff line - don't check for header pattern in there
             return False
-        l = line.split(': ', 1)
-        return len(l) == 2 and ' ' not in l[0]
+        l = line.split(b': ', 1)
+        return len(l) == 2 and b' ' not in l[0]
 
     def chunk(lines):
-        return stringio(''.join(lines))
+        return stringio(b''.join(lines))
 
     def hgsplit(stream, cur):
         inheader = True
@@ -80,7 +80,7 @@
         for line in stream:
             if not line.strip():
                 inheader = False
-            if not inheader and line.startswith('# HG changeset patch'):
+            if not inheader and line.startswith(b'# HG changeset patch'):
                 yield chunk(cur)
                 cur = []
                 inheader = True
@@ -92,7 +92,7 @@
 
     def mboxsplit(stream, cur):
         for line in stream:
-            if line.startswith('From '):
+            if line.startswith(b'From '):
                 for c in split(chunk(cur[1:])):
                     yield c
                 cur = []
@@ -119,7 +119,7 @@
         if not m.is_multipart():
             yield msgfp(m)
         else:
-            ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
+            ok_types = (b'text/plain', b'text/x-diff', b'text/x-patch')
             for part in m.walk():
                 ct = part.get_content_type()
                 if ct not in ok_types:
@@ -163,24 +163,24 @@
     inheader = False
     cur = []
 
-    mimeheaders = ['content-type']
-
-    if not util.safehasattr(stream, 'next'):
+    mimeheaders = [b'content-type']
+
+    if not util.safehasattr(stream, b'next'):
         # http responses, for example, have readline but not next
         stream = fiter(stream)
 
     for line in stream:
         cur.append(line)
-        if line.startswith('# HG changeset patch'):
+        if line.startswith(b'# HG changeset patch'):
             return hgsplit(stream, cur)
-        elif line.startswith('From '):
+        elif line.startswith(b'From '):
             return mboxsplit(stream, cur)
         elif isheader(line, inheader):
             inheader = True
-            if line.split(':', 1)[0].lower() in mimeheaders:
+            if line.split(b':', 1)[0].lower() in mimeheaders:
                 # let email parser handle this
                 return mimesplit(stream, cur)
-        elif line.startswith('--- ') and inheader:
+        elif line.startswith(b'--- ') and inheader:
             # No evil headers seen by diff start, split by hand
             return headersplit(stream, cur)
         # Not enough info, keep reading
@@ -192,9 +192,9 @@
 ## Some facility for extensible patch parsing:
 # list of pairs ("header to match", "data key")
 patchheadermap = [
-    ('Date', 'date'),
-    ('Branch', 'branch'),
-    ('Node ID', 'nodeid'),
+    (b'Date', b'date'),
+    (b'Branch', b'branch'),
+    (b'Node ID', b'nodeid'),
 ]
 
 
@@ -216,7 +216,7 @@
     Any item can be missing from the dictionary. If filename is missing,
     fileobj did not contain a patch. Caller must unlink filename when done.'''
 
-    fd, tmpname = pycompat.mkstemp(prefix='hg-patch-')
+    fd, tmpname = pycompat.mkstemp(prefix=b'hg-patch-')
     tmpfp = os.fdopen(fd, r'wb')
     try:
         yield _extract(ui, fileobj, tmpname, tmpfp)
@@ -242,34 +242,34 @@
     msg = mail.parse(fileobj)
 
     subject = msg[r'Subject'] and mail.headdecode(msg[r'Subject'])
-    data['user'] = msg[r'From'] and mail.headdecode(msg[r'From'])
-    if not subject and not data['user']:
+    data[b'user'] = msg[r'From'] and mail.headdecode(msg[r'From'])
+    if not subject and not data[b'user']:
         # Not an email, restore parsed headers if any
         subject = (
-            '\n'.join(
-                ': '.join(map(encoding.strtolocal, h)) for h in msg.items()
+            b'\n'.join(
+                b': '.join(map(encoding.strtolocal, h)) for h in msg.items()
             )
-            + '\n'
+            + b'\n'
         )
 
     # should try to parse msg['Date']
     parents = []
 
     if subject:
-        if subject.startswith('[PATCH'):
-            pend = subject.find(']')
+        if subject.startswith(b'[PATCH'):
+            pend = subject.find(b']')
             if pend >= 0:
                 subject = subject[pend + 1 :].lstrip()
-        subject = re.sub(br'\n[ \t]+', ' ', subject)
-        ui.debug('Subject: %s\n' % subject)
-    if data['user']:
-        ui.debug('From: %s\n' % data['user'])
+        subject = re.sub(br'\n[ \t]+', b' ', subject)
+        ui.debug(b'Subject: %s\n' % subject)
+    if data[b'user']:
+        ui.debug(b'From: %s\n' % data[b'user'])
     diffs_seen = 0
-    ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
-    message = ''
+    ok_types = (b'text/plain', b'text/x-diff', b'text/x-patch')
+    message = b''
     for part in msg.walk():
         content_type = pycompat.bytestr(part.get_content_type())
-        ui.debug('Content-Type: %s\n' % content_type)
+        ui.debug(b'Content-Type: %s\n' % content_type)
         if content_type not in ok_types:
             continue
         payload = part.get_payload(decode=True)
@@ -279,12 +279,12 @@
             hgpatchheader = False
             ignoretext = False
 
-            ui.debug('found patch at byte %d\n' % m.start(0))
+            ui.debug(b'found patch at byte %d\n' % m.start(0))
             diffs_seen += 1
             cfp = stringio()
             for line in payload[: m.start(0)].splitlines():
-                if line.startswith('# HG changeset patch') and not hgpatch:
-                    ui.debug('patch generated by hg export\n')
+                if line.startswith(b'# HG changeset patch') and not hgpatch:
+                    ui.debug(b'patch generated by hg export\n')
                     hgpatch = True
                     hgpatchheader = True
                     # drop earlier commit message content
@@ -292,43 +292,43 @@
                     cfp.truncate()
                     subject = None
                 elif hgpatchheader:
-                    if line.startswith('# User '):
-                        data['user'] = line[7:]
-                        ui.debug('From: %s\n' % data['user'])
-                    elif line.startswith("# Parent "):
+                    if line.startswith(b'# User '):
+                        data[b'user'] = line[7:]
+                        ui.debug(b'From: %s\n' % data[b'user'])
+                    elif line.startswith(b"# Parent "):
                         parents.append(line[9:].lstrip())
-                    elif line.startswith("# "):
+                    elif line.startswith(b"# "):
                         for header, key in patchheadermap:
-                            prefix = '# %s ' % header
+                            prefix = b'# %s ' % header
                             if line.startswith(prefix):
                                 data[key] = line[len(prefix) :]
-                                ui.debug('%s: %s\n' % (header, data[key]))
+                                ui.debug(b'%s: %s\n' % (header, data[key]))
                     else:
                         hgpatchheader = False
-                elif line == '---':
+                elif line == b'---':
                     ignoretext = True
                 if not hgpatchheader and not ignoretext:
                     cfp.write(line)
-                    cfp.write('\n')
+                    cfp.write(b'\n')
             message = cfp.getvalue()
             if tmpfp:
                 tmpfp.write(payload)
-                if not payload.endswith('\n'):
-                    tmpfp.write('\n')
-        elif not diffs_seen and message and content_type == 'text/plain':
-            message += '\n' + payload
+                if not payload.endswith(b'\n'):
+                    tmpfp.write(b'\n')
+        elif not diffs_seen and message and content_type == b'text/plain':
+            message += b'\n' + payload
 
     if subject and not message.startswith(subject):
-        message = '%s\n%s' % (subject, message)
-    data['message'] = message
+        message = b'%s\n%s' % (subject, message)
+    data[b'message'] = message
     tmpfp.close()
     if parents:
-        data['p1'] = parents.pop(0)
+        data[b'p1'] = parents.pop(0)
         if parents:
-            data['p2'] = parents.pop(0)
+            data[b'p2'] = parents.pop(0)
 
     if diffs_seen:
-        data['filename'] = tmpname
+        data[b'filename'] = tmpname
 
     return data
 
@@ -348,7 +348,7 @@
         self.path = path
         self.oldpath = None
         self.mode = None
-        self.op = 'MODIFY'
+        self.op = b'MODIFY'
         self.binary = False
 
     def setmode(self, mode):
@@ -365,14 +365,14 @@
         return other
 
     def _ispatchinga(self, afile):
-        if afile == '/dev/null':
-            return self.op == 'ADD'
-        return afile == 'a/' + (self.oldpath or self.path)
+        if afile == b'/dev/null':
+            return self.op == b'ADD'
+        return afile == b'a/' + (self.oldpath or self.path)
 
     def _ispatchingb(self, bfile):
-        if bfile == '/dev/null':
-            return self.op == 'DELETE'
-        return bfile == 'b/' + self.path
+        if bfile == b'/dev/null':
+            return self.op == b'DELETE'
+        return bfile == b'b/' + self.path
 
     def ispatching(self, afile, bfile):
         return self._ispatchinga(afile) and self._ispatchingb(bfile)
@@ -388,8 +388,8 @@
     gp = None
     gitpatches = []
     for line in lr:
-        line = line.rstrip(' \r\n')
-        if line.startswith('diff --git a/'):
+        line = line.rstrip(b' \r\n')
+        if line.startswith(b'diff --git a/'):
             m = gitre.match(line)
             if m:
                 if gp:
@@ -397,28 +397,28 @@
                 dst = m.group(2)
                 gp = patchmeta(dst)
         elif gp:
-            if line.startswith('--- '):
+            if line.startswith(b'--- '):
                 gitpatches.append(gp)
                 gp = None
                 continue
-            if line.startswith('rename from '):
-                gp.op = 'RENAME'
+            if line.startswith(b'rename from '):
+                gp.op = b'RENAME'
                 gp.oldpath = line[12:]
-            elif line.startswith('rename to '):
+            elif line.startswith(b'rename to '):
                 gp.path = line[10:]
-            elif line.startswith('copy from '):
-                gp.op = 'COPY'
+            elif line.startswith(b'copy from '):
+                gp.op = b'COPY'
                 gp.oldpath = line[10:]
-            elif line.startswith('copy to '):
+            elif line.startswith(b'copy to '):
                 gp.path = line[8:]
-            elif line.startswith('deleted file'):
-                gp.op = 'DELETE'
-            elif line.startswith('new file mode '):
-                gp.op = 'ADD'
+            elif line.startswith(b'deleted file'):
+                gp.op = b'DELETE'
+            elif line.startswith(b'new file mode '):
+                gp.op = b'ADD'
                 gp.setmode(int(line[-6:], 8))
-            elif line.startswith('new mode '):
+            elif line.startswith(b'new mode '):
                 gp.setmode(int(line[-6:], 8))
-            elif line.startswith('GIT binary patch'):
+            elif line.startswith(b'GIT binary patch'):
                 gp.binary = True
     if gp:
         gitpatches.append(gp)
@@ -444,7 +444,7 @@
         return self.fp.readline()
 
     def __iter__(self):
-        return iter(self.readline, '')
+        return iter(self.readline, b'')
 
 
 class abstractbackend(object):
@@ -517,16 +517,16 @@
                 self.opener.setflags(fname, False, True)
 
     def unlink(self, fname):
-        rmdir = self.ui.configbool('experimental', 'removeemptydirs')
+        rmdir = self.ui.configbool(b'experimental', b'removeemptydirs')
         self.opener.unlinkpath(fname, ignoremissing=True, rmdir=rmdir)
 
     def writerej(self, fname, failed, total, lines):
-        fname = fname + ".rej"
+        fname = fname + b".rej"
         self.ui.warn(
-            _("%d out of %d hunks FAILED -- saving rejects to file %s\n")
+            _(b"%d out of %d hunks FAILED -- saving rejects to file %s\n")
             % (failed, total, fname)
         )
-        fp = self.opener(fname, 'w')
+        fp = self.opener(fname, b'w')
         fp.writelines(lines)
         fp.close()
 
@@ -544,8 +544,8 @@
         self.copied = []
 
     def _checkknown(self, fname):
-        if self.repo.dirstate[fname] == '?' and self.exists(fname):
-            raise PatchError(_('cannot patch %s: file is not tracked') % fname)
+        if self.repo.dirstate[fname] == b'?' and self.exists(fname):
+            raise PatchError(_(b'cannot patch %s: file is not tracked') % fname)
 
     def setfile(self, fname, data, mode, copysource):
         self._checkknown(fname)
@@ -596,10 +596,10 @@
             self.size += len(data)
         else:
             if self.opener is None:
-                root = pycompat.mkdtemp(prefix='hg-patch-')
+                root = pycompat.mkdtemp(prefix=b'hg-patch-')
                 self.opener = vfsmod.vfs(root)
             # Avoid filename issues with these simple names
-            fn = '%d' % self.created
+            fn = b'%d' % self.created
             self.opener.write(fn, data)
             self.created += 1
             self.files[fname] = (fn, mode, copied)
@@ -629,7 +629,7 @@
 
     def _checkknown(self, fname):
         if fname not in self.ctx:
-            raise PatchError(_('cannot patch %s: file is not tracked') % fname)
+            raise PatchError(_(b'cannot patch %s: file is not tracked') % fname)
 
     def getfile(self, fname):
         try:
@@ -637,7 +637,7 @@
         except error.LookupError:
             return None, None
         flags = fctx.flags()
-        return fctx.data(), ('l' in flags, 'x' in flags)
+        return fctx.data(), (b'l' in flags, b'x' in flags)
 
     def setfile(self, fname, data, mode, copysource):
         if copysource:
@@ -663,11 +663,11 @@
 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
 unidesc = re.compile(br'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
 contextdesc = re.compile(br'(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
-eolmodes = ['strict', 'crlf', 'lf', 'auto']
+eolmodes = [b'strict', b'crlf', b'lf', b'auto']
 
 
 class patchfile(object):
-    def __init__(self, ui, gp, backend, store, eolmode='strict'):
+    def __init__(self, ui, gp, backend, store, eolmode=b'strict'):
         self.fname = gp.path
         self.eolmode = eolmode
         self.eol = None
@@ -678,8 +678,8 @@
         self.missing = True
         self.mode = gp.mode
         self.copysource = gp.oldpath
-        self.create = gp.op in ('ADD', 'COPY', 'RENAME')
-        self.remove = gp.op == 'DELETE'
+        self.create = gp.op in (b'ADD', b'COPY', b'RENAME')
+        self.remove = gp.op == b'DELETE'
         if self.copysource is None:
             data, mode = backend.getfile(self.fname)
         else:
@@ -693,15 +693,15 @@
                 self.mode = mode
             if self.lines:
                 # Normalize line endings
-                if self.lines[0].endswith('\r\n'):
-                    self.eol = '\r\n'
-                elif self.lines[0].endswith('\n'):
-                    self.eol = '\n'
-                if eolmode != 'strict':
+                if self.lines[0].endswith(b'\r\n'):
+                    self.eol = b'\r\n'
+                elif self.lines[0].endswith(b'\n'):
+                    self.eol = b'\n'
+                if eolmode != b'strict':
                     nlines = []
                     for l in self.lines:
-                        if l.endswith('\r\n'):
-                            l = l[:-2] + '\n'
+                        if l.endswith(b'\r\n'):
+                            l = l[:-2] + b'\n'
                         nlines.append(l)
                     self.lines = nlines
         else:
@@ -710,11 +710,11 @@
             if self.mode is None:
                 self.mode = (False, False)
         if self.missing:
-            self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
+            self.ui.warn(_(b"unable to find '%s' for patching\n") % self.fname)
             self.ui.warn(
                 _(
-                    "(use '--prefix' to apply patch relative to the "
-                    "current directory)\n"
+                    b"(use '--prefix' to apply patch relative to the "
+                    b"current directory)\n"
                 )
             )
 
@@ -728,29 +728,29 @@
         self.hunks = 0
 
     def writelines(self, fname, lines, mode):
-        if self.eolmode == 'auto':
+        if self.eolmode == b'auto':
             eol = self.eol
-        elif self.eolmode == 'crlf':
-            eol = '\r\n'
+        elif self.eolmode == b'crlf':
+            eol = b'\r\n'
         else:
-            eol = '\n'
-
-        if self.eolmode != 'strict' and eol and eol != '\n':
+            eol = b'\n'
+
+        if self.eolmode != b'strict' and eol and eol != b'\n':
             rawlines = []
             for l in lines:
-                if l and l.endswith('\n'):
+                if l and l.endswith(b'\n'):
                     l = l[:-1] + eol
                 rawlines.append(l)
             lines = rawlines
 
-        self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
+        self.backend.setfile(fname, b''.join(lines), mode, self.copysource)
 
     def printfile(self, warn):
         if self.fileprinted:
             return
         if warn or self.ui.verbose:
             self.fileprinted = True
-        s = _("patching file %s\n") % self.fname
+        s = _(b"patching file %s\n") % self.fname
         if warn:
             self.ui.warn(s)
         else:
@@ -775,18 +775,18 @@
         if not self.rej:
             return
         base = os.path.basename(self.fname)
-        lines = ["--- %s\n+++ %s\n" % (base, base)]
+        lines = [b"--- %s\n+++ %s\n" % (base, base)]
         for x in self.rej:
             for l in x.hunk:
                 lines.append(l)
-                if l[-1:] != '\n':
-                    lines.append("\n\\ No newline at end of file\n")
+                if l[-1:] != b'\n':
+                    lines.append(b"\n\\ No newline at end of file\n")
         self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
 
     def apply(self, h):
         if not h.complete():
             raise PatchError(
-                _("bad hunk #%d %s (%d %d %d %d)")
+                _(b"bad hunk #%d %s (%d %d %d %d)")
                 % (h.number, h.desc, len(h.a), h.lena, len(h.b), h.lenb)
             )
 
@@ -799,11 +799,11 @@
         if self.exists and self.create:
             if self.copysource:
                 self.ui.warn(
-                    _("cannot create %s: destination already " "exists\n")
+                    _(b"cannot create %s: destination already " b"exists\n")
                     % self.fname
                 )
             else:
-                self.ui.warn(_("file %s already exists\n") % self.fname)
+                self.ui.warn(_(b"file %s already exists\n") % self.fname)
             self.rej.append(h)
             return -1
 
@@ -819,8 +819,8 @@
 
         horig = h
         if (
-            self.eolmode in ('crlf', 'lf')
-            or self.eolmode == 'auto'
+            self.eolmode in (b'crlf', b'lf')
+            or self.eolmode == b'auto'
             and self.eol
         ):
             # If new eols are going to be normalized, then normalize
@@ -849,7 +849,9 @@
         for x, s in enumerate(self.lines):
             self.hash.setdefault(s, []).append(x)
 
-        for fuzzlen in pycompat.xrange(self.ui.configint("patch", "fuzz") + 1):
+        for fuzzlen in pycompat.xrange(
+            self.ui.configint(b"patch", b"fuzz") + 1
+        ):
             for toponly in [True, False]:
                 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
                 oldstart = oldstart + self.offset + self.skew
@@ -870,9 +872,9 @@
                         offset = l - orig_start - fuzzlen
                         if fuzzlen:
                             msg = _(
-                                "Hunk #%d succeeded at %d "
-                                "with fuzz %d "
-                                "(offset %d lines).\n"
+                                b"Hunk #%d succeeded at %d "
+                                b"with fuzz %d "
+                                b"(offset %d lines).\n"
                             )
                             self.printfile(True)
                             self.ui.warn(
@@ -880,13 +882,13 @@
                             )
                         else:
                             msg = _(
-                                "Hunk #%d succeeded at %d "
-                                "(offset %d lines).\n"
+                                b"Hunk #%d succeeded at %d "
+                                b"(offset %d lines).\n"
                             )
                             self.ui.note(msg % (h.number, l + 1, offset))
                         return fuzzlen
         self.printfile(True)
-        self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
+        self.ui.warn(_(b"Hunk #%d FAILED at %d\n") % (h.number, orig_start))
         self.rej.append(horig)
         return -1
 
@@ -901,33 +903,33 @@
     """patch header
     """
 
-    diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
-    diff_re = re.compile('diff -r .* (.*)$')
-    allhunks_re = re.compile('(?:index|deleted file) ')
-    pretty_re = re.compile('(?:new file|deleted file) ')
-    special_re = re.compile('(?:index|deleted|copy|rename|new mode) ')
-    newfile_re = re.compile('(?:new file|copy to|rename to)')
+    diffgit_re = re.compile(b'diff --git a/(.*) b/(.*)$')
+    diff_re = re.compile(b'diff -r .* (.*)$')
+    allhunks_re = re.compile(b'(?:index|deleted file) ')
+    pretty_re = re.compile(b'(?:new file|deleted file) ')
+    special_re = re.compile(b'(?:index|deleted|copy|rename|new mode) ')
+    newfile_re = re.compile(b'(?:new file|copy to|rename to)')
 
     def __init__(self, header):
         self.header = header
         self.hunks = []
 
     def binary(self):
-        return any(h.startswith('index ') for h in self.header)
+        return any(h.startswith(b'index ') for h in self.header)
 
     def pretty(self, fp):
         for h in self.header:
-            if h.startswith('index '):
-                fp.write(_('this modifies a binary file (all or nothing)\n'))
+            if h.startswith(b'index '):
+                fp.write(_(b'this modifies a binary file (all or nothing)\n'))
                 break
             if self.pretty_re.match(h):
                 fp.write(h)
                 if self.binary():
-                    fp.write(_('this is a binary file\n'))
+                    fp.write(_(b'this is a binary file\n'))
                 break
-            if h.startswith('---'):
+            if h.startswith(b'---'):
                 fp.write(
-                    _('%d hunks, %d lines changed\n')
+                    _(b'%d hunks, %d lines changed\n')
                     % (
                         len(self.hunks),
                         sum([max(h.added, h.removed) for h in self.hunks]),
@@ -937,7 +939,7 @@
             fp.write(h)
 
     def write(self, fp):
-        fp.write(''.join(self.header))
+        fp.write(b''.join(self.header))
 
     def allhunks(self):
         return any(self.allhunks_re.match(h) for h in self.header)
@@ -956,7 +958,7 @@
         return self.files()[-1]
 
     def __repr__(self):
-        return '<header %s>' % (' '.join(map(repr, self.files())))
+        return b'<header %s>' % (b' '.join(map(repr, self.files())))
 
     def isnewfile(self):
         return any(self.newfile_re.match(h) for h in self.header)
@@ -1035,8 +1037,8 @@
 
     def countchanges(self, hunk):
         """hunk -> (n+,n-)"""
-        add = len([h for h in hunk if h.startswith('+')])
-        rem = len([h for h in hunk if h.startswith('-')])
+        add = len([h for h in hunk if h.startswith(b'+')])
+        rem = len([h for h in hunk if h.startswith(b'-')])
         return add, rem
 
     def reversehunk(self):
@@ -1046,8 +1048,8 @@
         that, swap fromline/toline and +/- signs while keep other things
         unchanged.
         """
-        m = {'+': '-', '-': '+', '\\': '\\'}
-        hunk = ['%s%s' % (m[l[0:1]], l[1:]) for l in self.hunk]
+        m = {b'+': b'-', b'-': b'+', b'\\': b'\\'}
+        hunk = [b'%s%s' % (m[l[0:1]], l[1:]) for l in self.hunk]
         return recordhunk(
             self.header,
             self.toline,
@@ -1060,21 +1062,21 @@
 
     def write(self, fp):
         delta = len(self.before) + len(self.after)
-        if self.after and self.after[-1] == '\\ No newline at end of file\n':
+        if self.after and self.after[-1] == b'\\ No newline at end of file\n':
             delta -= 1
         fromlen = delta + self.removed
         tolen = delta + self.added
         fp.write(
-            '@@ -%d,%d +%d,%d @@%s\n'
+            b'@@ -%d,%d +%d,%d @@%s\n'
             % (
                 self.fromline,
                 fromlen,
                 self.toline,
                 tolen,
-                self.proc and (' ' + self.proc),
+                self.proc and (b' ' + self.proc),
             )
         )
-        fp.write(''.join(self.before + self.hunk + self.after))
+        fp.write(b''.join(self.before + self.hunk + self.after))
 
     pretty = write
 
@@ -1082,71 +1084,71 @@
         return self.header.filename()
 
     def __repr__(self):
-        return '<hunk %r@%d>' % (self.filename(), self.fromline)
+        return b'<hunk %r@%d>' % (self.filename(), self.fromline)
 
 
 def getmessages():
     return {
-        'multiple': {
-            'apply': _("apply change %d/%d to '%s'?"),
-            'discard': _("discard change %d/%d to '%s'?"),
-            'keep': _("keep change %d/%d to '%s'?"),
-            'record': _("record change %d/%d to '%s'?"),
+        b'multiple': {
+            b'apply': _(b"apply change %d/%d to '%s'?"),
+            b'discard': _(b"discard change %d/%d to '%s'?"),
+            b'keep': _(b"keep change %d/%d to '%s'?"),
+            b'record': _(b"record change %d/%d to '%s'?"),
         },
-        'single': {
-            'apply': _("apply this change to '%s'?"),
-            'discard': _("discard this change to '%s'?"),
-            'keep': _("keep this change to '%s'?"),
-            'record': _("record this change to '%s'?"),
+        b'single': {
+            b'apply': _(b"apply this change to '%s'?"),
+            b'discard': _(b"discard this change to '%s'?"),
+            b'keep': _(b"keep this change to '%s'?"),
+            b'record': _(b"record this change to '%s'?"),
         },
-        'help': {
-            'apply': _(
-                '[Ynesfdaq?]'
-                '$$ &Yes, apply this change'
-                '$$ &No, skip this change'
-                '$$ &Edit this change manually'
-                '$$ &Skip remaining changes to this file'
-                '$$ Apply remaining changes to this &file'
-                '$$ &Done, skip remaining changes and files'
-                '$$ Apply &all changes to all remaining files'
-                '$$ &Quit, applying no changes'
-                '$$ &? (display help)'
+        b'help': {
+            b'apply': _(
+                b'[Ynesfdaq?]'
+                b'$$ &Yes, apply this change'
+                b'$$ &No, skip this change'
+                b'$$ &Edit this change manually'
+                b'$$ &Skip remaining changes to this file'
+                b'$$ Apply remaining changes to this &file'
+                b'$$ &Done, skip remaining changes and files'
+                b'$$ Apply &all changes to all remaining files'
+                b'$$ &Quit, applying no changes'
+                b'$$ &? (display help)'
             ),
-            'discard': _(
-                '[Ynesfdaq?]'
-                '$$ &Yes, discard this change'
-                '$$ &No, skip this change'
-                '$$ &Edit this change manually'
-                '$$ &Skip remaining changes to this file'
-                '$$ Discard remaining changes to this &file'
-                '$$ &Done, skip remaining changes and files'
-                '$$ Discard &all changes to all remaining files'
-                '$$ &Quit, discarding no changes'
-                '$$ &? (display help)'
+            b'discard': _(
+                b'[Ynesfdaq?]'
+                b'$$ &Yes, discard this change'
+                b'$$ &No, skip this change'
+                b'$$ &Edit this change manually'
+                b'$$ &Skip remaining changes to this file'
+                b'$$ Discard remaining changes to this &file'
+                b'$$ &Done, skip remaining changes and files'
+                b'$$ Discard &all changes to all remaining files'
+                b'$$ &Quit, discarding no changes'
+                b'$$ &? (display help)'
             ),
-            'keep': _(
-                '[Ynesfdaq?]'
-                '$$ &Yes, keep this change'
-                '$$ &No, skip this change'
-                '$$ &Edit this change manually'
-                '$$ &Skip remaining changes to this file'
-                '$$ Keep remaining changes to this &file'
-                '$$ &Done, skip remaining changes and files'
-                '$$ Keep &all changes to all remaining files'
-                '$$ &Quit, keeping all changes'
-                '$$ &? (display help)'
+            b'keep': _(
+                b'[Ynesfdaq?]'
+                b'$$ &Yes, keep this change'
+                b'$$ &No, skip this change'
+                b'$$ &Edit this change manually'
+                b'$$ &Skip remaining changes to this file'
+                b'$$ Keep remaining changes to this &file'
+                b'$$ &Done, skip remaining changes and files'
+                b'$$ Keep &all changes to all remaining files'
+                b'$$ &Quit, keeping all changes'
+                b'$$ &? (display help)'
             ),
-            'record': _(
-                '[Ynesfdaq?]'
-                '$$ &Yes, record this change'
-                '$$ &No, skip this change'
-                '$$ &Edit this change manually'
-                '$$ &Skip remaining changes to this file'
-                '$$ Record remaining changes to this &file'
-                '$$ &Done, skip remaining changes and files'
-                '$$ Record &all changes to all remaining files'
-                '$$ &Quit, recording no changes'
-                '$$ &? (display help)'
+            b'record': _(
+                b'[Ynesfdaq?]'
+                b'$$ &Yes, record this change'
+                b'$$ &No, skip this change'
+                b'$$ &Edit this change manually'
+                b'$$ &Skip remaining changes to this file'
+                b'$$ Record remaining changes to this &file'
+                b'$$ &Done, skip remaining changes and files'
+                b'$$ Record &all changes to all remaining files'
+                b'$$ &Quit, recording no changes'
+                b'$$ &? (display help)'
             ),
         },
     }
@@ -1157,7 +1159,7 @@
     messages = getmessages()
 
     if operation is None:
-        operation = 'record'
+        operation = b'record'
 
     def prompt(skipfile, skipall, query, chunk):
         """prompt query, and process base inputs
@@ -1175,14 +1177,14 @@
         if skipfile is not None:
             return skipfile, skipfile, skipall, newpatches
         while True:
-            resps = messages['help'][operation]
+            resps = messages[b'help'][operation]
             # IMPORTANT: keep the last line of this prompt short (<40 english
             # chars is a good target) because of issue6158.
-            r = ui.promptchoice("%s\n(enter ? for help) %s" % (query, resps))
-            ui.write("\n")
+            r = ui.promptchoice(b"%s\n(enter ? for help) %s" % (query, resps))
+            ui.write(b"\n")
             if r == 8:  # ?
                 for c, t in ui.extractchoices(resps)[1]:
-                    ui.write('%s - %s\n' % (c, encoding.lower(t)))
+                    ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
                 continue
             elif r == 0:  # yes
                 ret = True
@@ -1190,16 +1192,16 @@
                 ret = False
             elif r == 2:  # Edit patch
                 if chunk is None:
-                    ui.write(_('cannot edit patch for whole file'))
-                    ui.write("\n")
+                    ui.write(_(b'cannot edit patch for whole file'))
+                    ui.write(b"\n")
                     continue
                 if chunk.header.binary():
-                    ui.write(_('cannot edit patch for binary file'))
-                    ui.write("\n")
+                    ui.write(_(b'cannot edit patch for binary file'))
+                    ui.write(b"\n")
                     continue
                 # Patch comment based on the Git one (based on comment at end of
                 # https://mercurial-scm.org/wiki/RecordExtension)
-                phelp = '---' + _(
+                phelp = b'---' + _(
                     """
 To remove '-' lines, make them ' ' lines (context).
 To remove '+' lines, delete them.
@@ -1213,7 +1215,7 @@
 """
                 )
                 (patchfd, patchfn) = pycompat.mkstemp(
-                    prefix="hg-editor-", suffix=".diff"
+                    prefix=b"hg-editor-", suffix=b".diff"
                 )
                 ncpatchfp = None
                 try:
@@ -1222,25 +1224,27 @@
                     chunk.header.write(f)
                     chunk.write(f)
                     f.write(
-                        ''.join(['# ' + i + '\n' for i in phelp.splitlines()])
+                        b''.join(
+                            [b'# ' + i + b'\n' for i in phelp.splitlines()]
+                        )
                     )
                     f.close()
                     # Start the editor and wait for it to complete
                     editor = ui.geteditor()
                     ret = ui.system(
-                        "%s \"%s\"" % (editor, patchfn),
-                        environ={'HGUSER': ui.username()},
-                        blockedtag='filterpatch',
+                        b"%s \"%s\"" % (editor, patchfn),
+                        environ={b'HGUSER': ui.username()},
+                        blockedtag=b'filterpatch',
                     )
                     if ret != 0:
-                        ui.warn(_("editor exited with exit code %d\n") % ret)
+                        ui.warn(_(b"editor exited with exit code %d\n") % ret)
                         continue
                     # Remove comment lines
                     patchfp = open(patchfn, r'rb')
                     ncpatchfp = stringio()
                     for line in util.iterfile(patchfp):
                         line = util.fromnativeeol(line)
-                        if not line.startswith('#'):
+                        if not line.startswith(b'#'):
                             ncpatchfp.write(line)
                     patchfp.close()
                     ncpatchfp.seek(0)
@@ -1260,7 +1264,7 @@
             elif r == 6:  # all
                 ret = skipall = True
             elif r == 7:  # quit
-                raise error.Abort(_('user quit'))
+                raise error.Abort(_(b'user quit'))
             return ret, skipfile, skipall, newpatches
 
     seen = set()
@@ -1271,15 +1275,15 @@
         pos += len(h.hunks)
         skipfile = None
         fixoffset = 0
-        hdr = ''.join(h.header)
+        hdr = b''.join(h.header)
         if hdr in seen:
             continue
         seen.add(hdr)
         if skipall is None:
             h.pretty(ui)
         files = h.files()
-        msg = _('examine changes to %s?') % _(' and ').join(
-            "'%s'" % f for f in files
+        msg = _(b'examine changes to %s?') % _(b' and ').join(
+            b"'%s'" % f for f in files
         )
         if all(match.exact(f) for f in files):
             r, skipall, np = True, None, None
@@ -1295,10 +1299,10 @@
             if skipfile is None and skipall is None:
                 chunk.pretty(ui)
             if total == 1:
-                msg = messages['single'][operation] % chunk.filename()
+                msg = messages[b'single'][operation] % chunk.filename()
             else:
                 idx = pos - len(h.hunks) + i
-                msg = messages['multiple'][operation] % (
+                msg = messages[b'multiple'][operation] % (
                     idx,
                     total,
                     chunk.filename(),
@@ -1349,8 +1353,8 @@
         def normalize(lines):
             nlines = []
             for line in lines:
-                if line.endswith('\r\n'):
-                    line = line[:-2] + '\n'
+                if line.endswith(b'\r\n'):
+                    line = line[:-2] + b'\n'
                 nlines.append(line)
             return nlines
 
@@ -1370,7 +1374,7 @@
     def read_unified_hunk(self, lr):
         m = unidesc.match(self.desc)
         if not m:
-            raise PatchError(_("bad hunk #%d") % self.number)
+            raise PatchError(_(b"bad hunk #%d") % self.number)
         self.starta, self.lena, self.startb, self.lenb = m.groups()
         if self.lena is None:
             self.lena = 1
@@ -1387,7 +1391,7 @@
                 lr, self.hunk, self.lena, self.lenb, self.a, self.b
             )
         except error.ParseError as e:
-            raise PatchError(_("bad hunk #%d: %s") % (self.number, e))
+            raise PatchError(_(b"bad hunk #%d: %s") % (self.number, e))
         # if we hit eof before finishing out the hunk, the last line will
         # be zero length.  Lets try to fix it up.
         while len(self.hunk[-1]) == 0:
@@ -1402,7 +1406,7 @@
         self.desc = lr.readline()
         m = contextdesc.match(self.desc)
         if not m:
-            raise PatchError(_("bad hunk #%d") % self.number)
+            raise PatchError(_(b"bad hunk #%d") % self.number)
         self.starta, aend = m.groups()
         self.starta = int(self.starta)
         if aend is None:
@@ -1412,18 +1416,18 @@
             self.lena += 1
         for x in pycompat.xrange(self.lena):
             l = lr.readline()
-            if l.startswith('---'):
+            if l.startswith(b'---'):
                 # lines addition, old block is empty
                 lr.push(l)
                 break
             s = l[2:]
-            if l.startswith('- ') or l.startswith('! '):
-                u = '-' + s
-            elif l.startswith('  '):
-                u = ' ' + s
+            if l.startswith(b'- ') or l.startswith(b'! '):
+                u = b'-' + s
+            elif l.startswith(b'  '):
+                u = b' ' + s
             else:
                 raise PatchError(
-                    _("bad hunk #%d old text line %d") % (self.number, x)
+                    _(b"bad hunk #%d old text line %d") % (self.number, x)
                 )
             self.a.append(u)
             self.hunk.append(u)
@@ -1436,7 +1440,7 @@
             l = lr.readline()
         m = contextdesc.match(l)
         if not m:
-            raise PatchError(_("bad hunk #%d") % self.number)
+            raise PatchError(_(b"bad hunk #%d") % self.number)
         self.startb, bend = m.groups()
         self.startb = int(self.startb)
         if bend is None:
@@ -1460,28 +1464,28 @@
                 lr.push(l)
                 break
             s = l[2:]
-            if l.startswith('+ ') or l.startswith('! '):
-                u = '+' + s
-            elif l.startswith('  '):
-                u = ' ' + s
+            if l.startswith(b'+ ') or l.startswith(b'! '):
+                u = b'+' + s
+            elif l.startswith(b'  '):
+                u = b' ' + s
             elif len(self.b) == 0:
                 # line deletions, new block is empty
                 lr.push(l)
                 break
             else:
                 raise PatchError(
-                    _("bad hunk #%d old text line %d") % (self.number, x)
+                    _(b"bad hunk #%d old text line %d") % (self.number, x)
                 )
             self.b.append(s)
             while True:
                 if hunki >= len(self.hunk):
-                    h = ""
+                    h = b""
                 else:
                     h = self.hunk[hunki]
                 hunki += 1
                 if h == u:
                     break
-                elif h.startswith('-'):
+                elif h.startswith(b'-'):
                     continue
                 else:
                     self.hunk.insert(hunki - 1, u)
@@ -1490,15 +1494,15 @@
         if not self.a:
             # this happens when lines were only added to the hunk
             for x in self.hunk:
-                if x.startswith('-') or x.startswith(' '):
+                if x.startswith(b'-') or x.startswith(b' '):
                     self.a.append(x)
         if not self.b:
             # this happens when lines were only deleted from the hunk
             for x in self.hunk:
-                if x.startswith('+') or x.startswith(' '):
+                if x.startswith(b'+') or x.startswith(b' '):
                     self.b.append(x[1:])
         # @@ -start,len +start,len @@
-        self.desc = "@@ -%d,%d +%d,%d @@\n" % (
+        self.desc = b"@@ -%d,%d +%d,%d @@\n" % (
             self.starta,
             self.lena,
             self.startb,
@@ -1528,13 +1532,13 @@
             hlen = len(self.hunk)
             for x in pycompat.xrange(hlen - 1):
                 # the hunk starts with the @@ line, so use x+1
-                if self.hunk[x + 1].startswith(' '):
+                if self.hunk[x + 1].startswith(b' '):
                     top += 1
                 else:
                     break
             if not toponly:
                 for x in pycompat.xrange(hlen - 1):
-                    if self.hunk[hlen - bot - 1].startswith(' '):
+                    if self.hunk[hlen - bot - 1].startswith(b' '):
                         bot += 1
                     else:
                         break
@@ -1557,12 +1561,12 @@
 
 
 class binhunk(object):
-    'A binary patch file.'
+    b'A binary patch file.'
 
     def __init__(self, lr, fname):
         self.text = None
         self.delta = False
-        self.hunk = ['GIT binary patch\n']
+        self.hunk = [b'GIT binary patch\n']
         self._fname = fname
         self._read(lr)
 
@@ -1571,25 +1575,25 @@
 
     def new(self, lines):
         if self.delta:
-            return [applybindelta(self.text, ''.join(lines))]
+            return [applybindelta(self.text, b''.join(lines))]
         return [self.text]
 
     def _read(self, lr):
         def getline(lr, hunk):
             l = lr.readline()
             hunk.append(l)
-            return l.rstrip('\r\n')
+            return l.rstrip(b'\r\n')
 
         while True:
             line = getline(lr, self.hunk)
             if not line:
                 raise PatchError(
-                    _('could not extract "%s" binary data') % self._fname
+                    _(b'could not extract "%s" binary data') % self._fname
                 )
-            if line.startswith('literal '):
+            if line.startswith(b'literal '):
                 size = int(line[8:].rstrip())
                 break
-            if line.startswith('delta '):
+            if line.startswith(b'delta '):
                 size = int(line[6:].rstrip())
                 self.delta = True
                 break
@@ -1597,22 +1601,22 @@
         line = getline(lr, self.hunk)
         while len(line) > 1:
             l = line[0:1]
-            if l <= 'Z' and l >= 'A':
-                l = ord(l) - ord('A') + 1
+            if l <= b'Z' and l >= b'A':
+                l = ord(l) - ord(b'A') + 1
             else:
-                l = ord(l) - ord('a') + 27
+                l = ord(l) - ord(b'a') + 27
             try:
                 dec.append(util.b85decode(line[1:])[:l])
             except ValueError as e:
                 raise PatchError(
-                    _('could not decode "%s" binary patch: %s')
+                    _(b'could not decode "%s" binary patch: %s')
                     % (self._fname, stringutil.forcebytestr(e))
                 )
             line = getline(lr, self.hunk)
-        text = zlib.decompress(''.join(dec))
+        text = zlib.decompress(b''.join(dec))
         if len(text) != size:
             raise PatchError(
-                _('"%s" length is %d bytes, should be %d')
+                _(b'"%s" length is %d bytes, should be %d')
                 % (self._fname, len(text), size)
             )
         self.text = text
@@ -1620,10 +1624,10 @@
 
 def parsefilename(str):
     # --- filename \t|space stuff
-    s = str[4:].rstrip('\r\n')
-    i = s.find('\t')
+    s = str[4:].rstrip(b'\r\n')
+    i = s.find(b'\t')
     if i < 0:
-        i = s.find(' ')
+        i = s.find(b' ')
         if i < 0:
             return s
     return s[:i]
@@ -1687,7 +1691,7 @@
 
     newhunks = []
     for c in hunks:
-        if util.safehasattr(c, 'reversehunk'):
+        if util.safehasattr(c, b'reversehunk'):
             c = c.reversehunk()
         newhunks.append(c)
     return newhunks
@@ -1743,7 +1747,7 @@
         def __init__(self):
             self.fromline = 0
             self.toline = 0
-            self.proc = ''
+            self.proc = b''
             self.header = None
             self.context = []
             self.before = []
@@ -1798,35 +1802,39 @@
             return self.headers
 
         transitions = {
-            'file': {
-                'context': addcontext,
-                'file': newfile,
-                'hunk': addhunk,
-                'range': addrange,
+            b'file': {
+                b'context': addcontext,
+                b'file': newfile,
+                b'hunk': addhunk,
+                b'range': addrange,
             },
-            'context': {
-                'file': newfile,
-                'hunk': addhunk,
-                'range': addrange,
-                'other': addother,
+            b'context': {
+                b'file': newfile,
+                b'hunk': addhunk,
+                b'range': addrange,
+                b'other': addother,
             },
-            'hunk': {'context': addcontext, 'file': newfile, 'range': addrange},
-            'range': {'context': addcontext, 'hunk': addhunk},
-            'other': {'other': addother},
+            b'hunk': {
+                b'context': addcontext,
+                b'file': newfile,
+                b'range': addrange,
+            },
+            b'range': {b'context': addcontext, b'hunk': addhunk},
+            b'other': {b'other': addother},
         }
 
     p = parser()
     fp = stringio()
-    fp.write(''.join(originalchunks))
+    fp.write(b''.join(originalchunks))
     fp.seek(0)
 
-    state = 'context'
+    state = b'context'
     for newstate, data in scanpatch(fp):
         try:
             p.transitions[state][newstate](p, data)
         except KeyError:
             raise PatchError(
-                'unhandled transition: %s -> %s' % (state, newstate)
+                b'unhandled transition: %s -> %s' % (state, newstate)
             )
         state = newstate
     del fp
@@ -1857,26 +1865,26 @@
     pathlen = len(path)
     i = 0
     if strip == 0:
-        return '', prefix + path.rstrip()
+        return b'', prefix + path.rstrip()
     count = strip
     while count > 0:
-        i = path.find('/', i)
+        i = path.find(b'/', i)
         if i == -1:
             raise PatchError(
-                _("unable to strip away %d of %d dirs from %s")
+                _(b"unable to strip away %d of %d dirs from %s")
                 % (count, strip, path)
             )
         i += 1
         # consume '//' in the path
-        while i < pathlen - 1 and path[i : i + 1] == '/':
+        while i < pathlen - 1 and path[i : i + 1] == b'/':
             i += 1
         count -= 1
     return path[:i].lstrip(), prefix + path[i:].rstrip()
 
 
 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
-    nulla = afile_orig == "/dev/null"
-    nullb = bfile_orig == "/dev/null"
+    nulla = afile_orig == b"/dev/null"
+    nullb = bfile_orig == b"/dev/null"
     create = nulla and hunk.starta == 0 and hunk.lena == 0
     remove = nullb and hunk.startb == 0 and hunk.lenb == 0
     abase, afile = pathtransform(afile_orig, strip, prefix)
@@ -1890,8 +1898,8 @@
 
     # some diff programs apparently produce patches where the afile is
     # not /dev/null, but afile starts with bfile
-    abasedir = afile[: afile.rfind('/') + 1]
-    bbasedir = bfile[: bfile.rfind('/') + 1]
+    abasedir = afile[: afile.rfind(b'/') + 1]
+    bbasedir = bfile[: bfile.rfind(b'/') + 1]
     if (
         missing
         and abasedir == bbasedir
@@ -1925,13 +1933,13 @@
         elif not nulla:
             fname = afile
         else:
-            raise PatchError(_("undefined source and destination files"))
+            raise PatchError(_(b"undefined source and destination files"))
 
     gp = patchmeta(fname)
     if create:
-        gp.op = 'ADD'
+        gp.op = b'ADD'
     elif remove:
-        gp.op = 'DELETE'
+        gp.op = b'DELETE'
     return gp
 
 
@@ -1949,7 +1957,7 @@
     def scanwhile(first, p):
         """scan lr while predicate holds"""
         lines = [first]
-        for line in iter(lr.readline, ''):
+        for line in iter(lr.readline, b''):
             if p(line):
                 lines.append(line)
             else:
@@ -1957,33 +1965,33 @@
                 break
         return lines
 
-    for line in iter(lr.readline, ''):
-        if line.startswith('diff --git a/') or line.startswith('diff -r '):
+    for line in iter(lr.readline, b''):
+        if line.startswith(b'diff --git a/') or line.startswith(b'diff -r '):
 
             def notheader(line):
                 s = line.split(None, 1)
-                return not s or s[0] not in ('---', 'diff')
+                return not s or s[0] not in (b'---', b'diff')
 
             header = scanwhile(line, notheader)
             fromfile = lr.readline()
-            if fromfile.startswith('---'):
+            if fromfile.startswith(b'---'):
                 tofile = lr.readline()
                 header += [fromfile, tofile]
             else:
                 lr.push(fromfile)
-            yield 'file', header
-        elif line.startswith(' '):
-            cs = (' ', '\\')
-            yield 'context', scanwhile(line, lambda l: l.startswith(cs))
-        elif line.startswith(('-', '+')):
-            cs = ('-', '+', '\\')
-            yield 'hunk', scanwhile(line, lambda l: l.startswith(cs))
+            yield b'file', header
+        elif line.startswith(b' '):
+            cs = (b' ', b'\\')
+            yield b'context', scanwhile(line, lambda l: l.startswith(cs))
+        elif line.startswith((b'-', b'+')):
+            cs = (b'-', b'+', b'\\')
+            yield b'hunk', scanwhile(line, lambda l: l.startswith(cs))
         else:
             m = lines_re.match(line)
             if m:
-                yield 'range', m.groups()
+                yield b'range', m.groups()
             else:
-                yield 'other', line
+                yield b'other', line
 
 
 def scangitpatch(lr, firstline):
@@ -2021,8 +2029,8 @@
     - ("git", gitchanges): current diff is in git format, gitchanges
     maps filenames to gitpatch records. Unique event.
     """
-    afile = ""
-    bfile = ""
+    afile = b""
+    bfile = b""
     state = None
     hunknum = 0
     emitfile = newfile = False
@@ -2033,66 +2041,71 @@
     context = None
     lr = linereader(fp)
 
-    for x in iter(lr.readline, ''):
+    for x in iter(lr.readline, b''):
         if state == BFILE and (
-            (not context and x.startswith('@'))
-            or (context is not False and x.startswith('***************'))
-            or x.startswith('GIT binary patch')
+            (not context and x.startswith(b'@'))
+            or (context is not False and x.startswith(b'***************'))
+            or x.startswith(b'GIT binary patch')
         ):
             gp = None
             if gitpatches and gitpatches[-1].ispatching(afile, bfile):
                 gp = gitpatches.pop()
-            if x.startswith('GIT binary patch'):
+            if x.startswith(b'GIT binary patch'):
                 h = binhunk(lr, gp.path)
             else:
-                if context is None and x.startswith('***************'):
+                if context is None and x.startswith(b'***************'):
                     context = True
                 h = hunk(x, hunknum + 1, lr, context)
             hunknum += 1
             if emitfile:
                 emitfile = False
-                yield 'file', (afile, bfile, h, gp and gp.copy() or None)
-            yield 'hunk', h
-        elif x.startswith('diff --git a/'):
-            m = gitre.match(x.rstrip(' \r\n'))
+                yield b'file', (afile, bfile, h, gp and gp.copy() or None)
+            yield b'hunk', h
+        elif x.startswith(b'diff --git a/'):
+            m = gitre.match(x.rstrip(b' \r\n'))
             if not m:
                 continue
             if gitpatches is None:
                 # scan whole input for git metadata
                 gitpatches = scangitpatch(lr, x)
-                yield 'git', [
-                    g.copy() for g in gitpatches if g.op in ('COPY', 'RENAME')
+                yield b'git', [
+                    g.copy() for g in gitpatches if g.op in (b'COPY', b'RENAME')
                 ]
                 gitpatches.reverse()
-            afile = 'a/' + m.group(1)
-            bfile = 'b/' + m.group(2)
+            afile = b'a/' + m.group(1)
+            bfile = b'b/' + m.group(2)
             while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
                 gp = gitpatches.pop()
-                yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
+                yield b'file', (
+                    b'a/' + gp.path,
+                    b'b/' + gp.path,
+                    None,
+                    gp.copy(),
+                )
             if not gitpatches:
                 raise PatchError(
-                    _('failed to synchronize metadata for "%s"') % afile[2:]
+                    _(b'failed to synchronize metadata for "%s"') % afile[2:]
                 )
             newfile = True
-        elif x.startswith('---'):
+        elif x.startswith(b'---'):
             # check for a unified diff
             l2 = lr.readline()
-            if not l2.startswith('+++'):
+            if not l2.startswith(b'+++'):
                 lr.push(l2)
                 continue
             newfile = True
             context = False
             afile = parsefilename(x)
             bfile = parsefilename(l2)
-        elif x.startswith('***'):
+        elif x.startswith(b'***'):
             # check for a context diff
             l2 = lr.readline()
-            if not l2.startswith('---'):
+            if not l2.startswith(b'---'):
                 lr.push(l2)
                 continue
             l3 = lr.readline()
             lr.push(l3)
-            if not l3.startswith("***************"):
+            if not l3.startswith(b"***************"):
                 lr.push(l2)
                 continue
             newfile = True
@@ -2108,7 +2121,7 @@
 
     while gitpatches:
         gp = gitpatches.pop()
-        yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
+        yield b'file', (b'a/' + gp.path, b'b/' + gp.path, None, gp.copy())
 
 
 def applybindelta(binchunk, data):
@@ -2124,7 +2137,7 @@
                 return i
         return i
 
-    out = ""
+    out = b""
     s = deltahead(binchunk)
     binchunk = binchunk[s:]
     s = deltahead(binchunk)
@@ -2166,11 +2179,11 @@
             out += binchunk[i:offset_end]
             i += cmd
         else:
-            raise PatchError(_('unexpected delta opcode 0'))
+            raise PatchError(_(b'unexpected delta opcode 0'))
     return out
 
 
-def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
+def applydiff(ui, fp, backend, store, strip=1, prefix=b'', eolmode=b'strict'):
     """Reads a patch from fp and tries to apply it.
 
     Returns 0 for a clean patch, -1 if any rejects were found and 1 if
@@ -2195,13 +2208,13 @@
 def _canonprefix(repo, prefix):
     if prefix:
         prefix = pathutil.canonpath(repo.root, repo.getcwd(), prefix)
-        if prefix != '':
-            prefix += '/'
+        if prefix != b'':
+            prefix += b'/'
     return prefix
 
 
 def _applydiff(
-    ui, fp, patcher, backend, store, strip=1, prefix='', eolmode='strict'
+    ui, fp, patcher, backend, store, strip=1, prefix=b'', eolmode=b'strict'
 ):
     prefix = _canonprefix(backend.repo, prefix)
 
@@ -2213,13 +2226,13 @@
     current_file = None
 
     for state, values in iterhunks(fp):
-        if state == 'hunk':
+        if state == b'hunk':
             if not current_file:
                 continue
             ret = current_file.apply(values)
             if ret > 0:
                 err = 1
-        elif state == 'file':
+        elif state == b'file':
             if current_file:
                 rejects += current_file.close()
                 current_file = None
@@ -2232,32 +2245,35 @@
                 gp = makepatchmeta(
                     backend, afile, bfile, first_hunk, strip, prefix
                 )
-            if gp.op == 'RENAME':
+            if gp.op == b'RENAME':
                 backend.unlink(gp.oldpath)
             if not first_hunk:
-                if gp.op == 'DELETE':
+                if gp.op == b'DELETE':
                     backend.unlink(gp.path)
                     continue
                 data, mode = None, None
-                if gp.op in ('RENAME', 'COPY'):
+                if gp.op in (b'RENAME', b'COPY'):
                     data, mode = store.getfile(gp.oldpath)[:2]
                     if data is None:
                         # This means that the old path does not exist
                         raise PatchError(
-                            _("source file '%s' does not exist") % gp.oldpath
+                            _(b"source file '%s' does not exist") % gp.oldpath
                         )
                 if gp.mode:
                     mode = gp.mode
-                    if gp.op == 'ADD':
+                    if gp.op == b'ADD':
                         # Added files without content have no hunk and
                         # must be created
-                        data = ''
+                        data = b''
                 if data or mode:
-                    if gp.op in ('ADD', 'RENAME', 'COPY') and backend.exists(
+                    if gp.op in (b'ADD', b'RENAME', b'COPY') and backend.exists(
                         gp.path
                     ):
                         raise PatchError(
-                            _("cannot create %s: destination " "already exists")
+                            _(
+                                b"cannot create %s: destination "
+                                b"already exists"
+                            )
                             % gp.path
                         )
                     backend.setfile(gp.path, data, mode, gp.oldpath)
@@ -2265,11 +2281,11 @@
             try:
                 current_file = patcher(ui, gp, backend, store, eolmode=eolmode)
             except PatchError as inst:
-                ui.warn(str(inst) + '\n')
+                ui.warn(str(inst) + b'\n')
                 current_file = None
                 rejects += 1
                 continue
-        elif state == 'git':
+        elif state == b'git':
             for gp in values:
                 path = pstrip(gp.oldpath)
                 data, mode = backend.getfile(path)
@@ -2282,7 +2298,7 @@
                 else:
                     store.setfile(path, data, mode)
         else:
-            raise error.Abort(_('unsupported parser state: %s') % state)
+            raise error.Abort(_(b'unsupported parser state: %s') % state)
 
     if current_file:
         rejects += current_file.close()
@@ -2300,61 +2316,61 @@
     args = []
     cwd = repo.root
     if cwd:
-        args.append('-d %s' % procutil.shellquote(cwd))
-    cmd = '%s %s -p%d < %s' % (
+        args.append(b'-d %s' % procutil.shellquote(cwd))
+    cmd = b'%s %s -p%d < %s' % (
         patcher,
-        ' '.join(args),
+        b' '.join(args),
         strip,
         procutil.shellquote(patchname),
     )
-    ui.debug('Using external patch tool: %s\n' % cmd)
-    fp = procutil.popen(cmd, 'rb')
+    ui.debug(b'Using external patch tool: %s\n' % cmd)
+    fp = procutil.popen(cmd, b'rb')
     try:
         for line in util.iterfile(fp):
             line = line.rstrip()
-            ui.note(line + '\n')
-            if line.startswith('patching file '):
+            ui.note(line + b'\n')
+            if line.startswith(b'patching file '):
                 pf = util.parsepatchoutput(line)
                 printed_file = False
                 files.add(pf)
-            elif line.find('with fuzz') >= 0:
+            elif line.find(b'with fuzz') >= 0:
                 fuzz = True
                 if not printed_file:
-                    ui.warn(pf + '\n')
+                    ui.warn(pf + b'\n')
                     printed_file = True
-                ui.warn(line + '\n')
-            elif line.find('saving rejects to file') >= 0:
-                ui.warn(line + '\n')
-            elif line.find('FAILED') >= 0:
+                ui.warn(line + b'\n')
+            elif line.find(b'saving rejects to file') >= 0:
+                ui.warn(line + b'\n')
+            elif line.find(b'FAILED') >= 0:
                 if not printed_file:
-                    ui.warn(pf + '\n')
+                    ui.warn(pf + b'\n')
                     printed_file = True
-                ui.warn(line + '\n')
+                ui.warn(line + b'\n')
     finally:
         if files:
             scmutil.marktouched(repo, files, similarity)
     code = fp.close()
     if code:
         raise PatchError(
-            _("patch command failed: %s") % procutil.explainexit(code)
+            _(b"patch command failed: %s") % procutil.explainexit(code)
         )
     return fuzz
 
 
 def patchbackend(
-    ui, backend, patchobj, strip, prefix, files=None, eolmode='strict'
+    ui, backend, patchobj, strip, prefix, files=None, eolmode=b'strict'
 ):
     if files is None:
         files = set()
     if eolmode is None:
-        eolmode = ui.config('patch', 'eol')
+        eolmode = ui.config(b'patch', b'eol')
     if eolmode.lower() not in eolmodes:
-        raise error.Abort(_('unsupported line endings type: %s') % eolmode)
+        raise error.Abort(_(b'unsupported line endings type: %s') % eolmode)
     eolmode = eolmode.lower()
 
     store = filestore()
     try:
-        fp = open(patchobj, 'rb')
+        fp = open(patchobj, b'rb')
     except TypeError:
         fp = patchobj
     try:
@@ -2367,7 +2383,7 @@
         files.update(backend.close())
         store.close()
     if ret < 0:
-        raise PatchError(_('patch failed to apply'))
+        raise PatchError(_(b'patch failed to apply'))
     return ret > 0
 
 
@@ -2376,9 +2392,9 @@
     repo,
     patchobj,
     strip,
-    prefix='',
+    prefix=b'',
     files=None,
-    eolmode='strict',
+    eolmode=b'strict',
     similarity=0,
 ):
     """use builtin patch to apply <patchobj> to the working directory.
@@ -2388,7 +2404,7 @@
 
 
 def patchrepo(
-    ui, repo, ctx, store, patchobj, strip, prefix, files=None, eolmode='strict'
+    ui, repo, ctx, store, patchobj, strip, prefix, files=None, eolmode=b'strict'
 ):
     backend = repobackend(ui, repo, ctx, store)
     return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
@@ -2399,9 +2415,9 @@
     repo,
     patchname,
     strip=1,
-    prefix='',
+    prefix=b'',
     files=None,
-    eolmode='strict',
+    eolmode=b'strict',
     similarity=0,
 ):
     """Apply <patchname> to the working directory.
@@ -2415,7 +2431,7 @@
 
     Returns whether patch was applied with fuzz factor.
     """
-    patcher = ui.config('ui', 'patch')
+    patcher = ui.config(b'ui', b'patch')
     if files is None:
         files = set()
     if patcher:
@@ -2427,13 +2443,13 @@
     )
 
 
-def changedfiles(ui, repo, patchpath, strip=1, prefix=''):
+def changedfiles(ui, repo, patchpath, strip=1, prefix=b''):
     backend = fsbackend(ui, repo.root)
     prefix = _canonprefix(repo, prefix)
-    with open(patchpath, 'rb') as fp:
+    with open(patchpath, b'rb') as fp:
         changed = set()
         for state, values in iterhunks(fp):
-            if state == 'file':
+            if state == b'file':
                 afile, bfile, first_hunk, gp = values
                 if gp:
                     gp.path = pathtransform(gp.path, strip - 1, prefix)[1]
@@ -2446,10 +2462,10 @@
                         backend, afile, bfile, first_hunk, strip, prefix
                     )
                 changed.add(gp.path)
-                if gp.op == 'RENAME':
+                if gp.op == b'RENAME':
                     changed.add(gp.oldpath)
-            elif state not in ('hunk', 'git'):
-                raise error.Abort(_('unsupported parser state: %s') % state)
+            elif state not in (b'hunk', b'git'):
+                raise error.Abort(_(b'unsupported parser state: %s') % state)
         return changed
 
 
@@ -2528,11 +2544,11 @@
             # logcmdutil.getlinerangerevs() for 'hg log -L'.
             assert (
                 fctx2 is not None
-            ), 'fctx2 unexpectly None in diff hunks filtering'
+            ), b'fctx2 unexpectly None in diff hunks filtering'
             hunks = hunksfilterfn(fctx2, hunks)
-        text = ''.join(sum((list(hlines) for hrange, hlines in hunks), []))
+        text = b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
         if hdr and (text or len(hdr) > 1):
-            yield '\n'.join(hdr) + '\n'
+            yield b'\n'.join(hdr) + b'\n'
         if text:
             yield text
 
@@ -2666,39 +2682,39 @@
     """yield tokens for a list of lines in a single hunk"""
     for line in hunklines:
         # chomp
-        chompline = line.rstrip('\r\n')
+        chompline = line.rstrip(b'\r\n')
         # highlight tabs and trailing whitespace
         stripline = chompline.rstrip()
-        if line.startswith('-'):
-            label = 'diff.deleted'
-        elif line.startswith('+'):
-            label = 'diff.inserted'
+        if line.startswith(b'-'):
+            label = b'diff.deleted'
+        elif line.startswith(b'+'):
+            label = b'diff.inserted'
         else:
-            raise error.ProgrammingError('unexpected hunk line: %s' % line)
+            raise error.ProgrammingError(b'unexpected hunk line: %s' % line)
         for token in tabsplitter.findall(stripline):
-            if token.startswith('\t'):
-                yield (token, 'diff.tab')
+            if token.startswith(b'\t'):
+                yield (token, b'diff.tab')
             else:
                 yield (token, label)
 
         if chompline != stripline:
-            yield (chompline[len(stripline) :], 'diff.trailingwhitespace')
+            yield (chompline[len(stripline) :], b'diff.trailingwhitespace')
         if chompline != line:
-            yield (line[len(chompline) :], '')
+            yield (line[len(chompline) :], b'')
 
 
 def diffsinglehunkinline(hunklines):
     """yield tokens for a list of lines in a single hunk, with inline colors"""
     # prepare deleted, and inserted content
-    a = ''
-    b = ''
+    a = b''
+    b = b''
     for line in hunklines:
-        if line[0:1] == '-':
+        if line[0:1] == b'-':
             a += line[1:]
-        elif line[0:1] == '+':
+        elif line[0:1] == b'+':
             b += line[1:]
         else:
-            raise error.ProgrammingError('unexpected hunk line: %s' % line)
+            raise error.ProgrammingError(b'unexpected hunk line: %s' % line)
     # fast path: if either side is empty, use diffsinglehunk
     if not a or not b:
         for t in diffsinglehunk(hunklines):
@@ -2708,25 +2724,25 @@
     al = wordsplitter.findall(a)
     bl = wordsplitter.findall(b)
     # re-arrange the words to lines since the diff algorithm is line-based
-    aln = [s if s == '\n' else s + '\n' for s in al]
-    bln = [s if s == '\n' else s + '\n' for s in bl]
-    an = ''.join(aln)
-    bn = ''.join(bln)
+    aln = [s if s == b'\n' else s + b'\n' for s in al]
+    bln = [s if s == b'\n' else s + b'\n' for s in bl]
+    an = b''.join(aln)
+    bn = b''.join(bln)
     # run the diff algorithm, prepare atokens and btokens
     atokens = []
     btokens = []
     blocks = mdiff.allblocks(an, bn, lines1=aln, lines2=bln)
     for (a1, a2, b1, b2), btype in blocks:
-        changed = btype == '!'
-        for token in mdiff.splitnewlines(''.join(al[a1:a2])):
+        changed = btype == b'!'
+        for token in mdiff.splitnewlines(b''.join(al[a1:a2])):
             atokens.append((changed, token))
-        for token in mdiff.splitnewlines(''.join(bl[b1:b2])):
+        for token in mdiff.splitnewlines(b''.join(bl[b1:b2])):
             btokens.append((changed, token))
 
     # yield deleted tokens, then inserted ones
     for prefix, label, tokens in [
-        ('-', 'diff.deleted', atokens),
-        ('+', 'diff.inserted', btokens),
+        (b'-', b'diff.deleted', atokens),
+        (b'+', b'diff.inserted', btokens),
     ]:
         nextisnewline = True
         for changed, token in tokens:
@@ -2734,10 +2750,10 @@
                 yield (prefix, label)
                 nextisnewline = False
             # special handling line end
-            isendofline = token.endswith('\n')
+            isendofline = token.endswith(b'\n')
             if isendofline:
                 chomp = token[:-1]  # chomp
-                if chomp.endswith('\r'):
+                if chomp.endswith(b'\r'):
                     chomp = chomp[:-1]
                 endofline = token[len(chomp) :]
                 token = chomp.rstrip()  # detect spaces at the end
@@ -2745,17 +2761,17 @@
             # scan tabs
             for maybetab in tabsplitter.findall(token):
                 if b'\t' == maybetab[0:1]:
-                    currentlabel = 'diff.tab'
+                    currentlabel = b'diff.tab'
                 else:
                     if changed:
-                        currentlabel = label + '.changed'
+                        currentlabel = label + b'.changed'
                     else:
-                        currentlabel = label + '.unchanged'
+                        currentlabel = label + b'.unchanged'
                 yield (maybetab, currentlabel)
             if isendofline:
                 if endspaces:
-                    yield (endspaces, 'diff.trailingwhitespace')
-                yield (endofline, '')
+                    yield (endspaces, b'diff.trailingwhitespace')
+                yield (endofline, b'')
                 nextisnewline = True
 
 
@@ -2766,19 +2782,19 @@
     else:
         dodiffhunk = diffsinglehunk
     headprefixes = [
-        ('diff', 'diff.diffline'),
-        ('copy', 'diff.extended'),
-        ('rename', 'diff.extended'),
-        ('old', 'diff.extended'),
-        ('new', 'diff.extended'),
-        ('deleted', 'diff.extended'),
-        ('index', 'diff.extended'),
-        ('similarity', 'diff.extended'),
-        ('---', 'diff.file_a'),
-        ('+++', 'diff.file_b'),
+        (b'diff', b'diff.diffline'),
+        (b'copy', b'diff.extended'),
+        (b'rename', b'diff.extended'),
+        (b'old', b'diff.extended'),
+        (b'new', b'diff.extended'),
+        (b'deleted', b'diff.extended'),
+        (b'index', b'diff.extended'),
+        (b'similarity', b'diff.extended'),
+        (b'---', b'diff.file_a'),
+        (b'+++', b'diff.file_b'),
     ]
     textprefixes = [
-        ('@', 'diff.hunk'),
+        (b'@', b'diff.hunk'),
         # - and + are handled by diffsinglehunk
     ]
     head = False
@@ -2793,17 +2809,19 @@
             hunkbuffer[:] = []
 
     for chunk in func(*args, **kw):
-        lines = chunk.split('\n')
+        lines = chunk.split(b'\n')
         linecount = len(lines)
         for i, line in enumerate(lines):
             if head:
-                if line.startswith('@'):
+                if line.startswith(b'@'):
                     head = False
             else:
-                if line and not line.startswith((' ', '+', '-', '@', '\\')):
+                if line and not line.startswith(
+                    (b' ', b'+', b'-', b'@', b'\\')
+                ):
                     head = True
             diffline = False
-            if not head and line and line.startswith(('+', '-')):
+            if not head and line and line.startswith((b'+', b'-')):
                 diffline = True
 
             prefixes = textprefixes
@@ -2813,7 +2831,7 @@
                 # buffered
                 bufferedline = line
                 if i + 1 < linecount:
-                    bufferedline += "\n"
+                    bufferedline += b"\n"
                 hunkbuffer.append(bufferedline)
             else:
                 # unbuffered
@@ -2826,13 +2844,13 @@
                         if line != stripline:
                             yield (
                                 line[len(stripline) :],
-                                'diff.trailingwhitespace',
+                                b'diff.trailingwhitespace',
                             )
                         break
                 else:
-                    yield (line, '')
+                    yield (line, b'')
                 if i + 1 < linecount:
-                    yield ('\n', '')
+                    yield (b'\n', b'')
         for token in consumehunkbuffer():
             yield token
 
@@ -2862,10 +2880,10 @@
                 if opts.git:
                     f1 = copy[f]
                     if f1 in removedset and f1 not in gone:
-                        copyop = 'rename'
+                        copyop = b'rename'
                         gone.add(f1)
                     else:
-                        copyop = 'copy'
+                        copyop = b'copy'
         elif f in removedset:
             f2 = None
             if opts.git:
@@ -2903,21 +2921,21 @@
 
     def gitindex(text):
         if not text:
-            text = ""
+            text = b""
         l = len(text)
-        s = hashlib.sha1('blob %d\0' % l)
+        s = hashlib.sha1(b'blob %d\0' % l)
         s.update(text)
         return hex(s.digest())
 
     if opts.noprefix:
-        aprefix = bprefix = ''
+        aprefix = bprefix = b''
     else:
-        aprefix = 'a/'
-        bprefix = 'b/'
+        aprefix = b'a/'
+        bprefix = b'b/'
 
     def diffline(f, revs):
-        revinfo = ' '.join(["-r %s" % rev for rev in revs])
-        return 'diff %s %s' % (revinfo, f)
+        revinfo = b' '.join([b"-r %s" % rev for rev in revs])
+        return b'diff %s %s' % (revinfo, f)
 
     def isempty(fctx):
         return fctx is None or fctx.size() == 0
@@ -2925,7 +2943,7 @@
     date1 = dateutil.datestr(ctx1.date())
     date2 = dateutil.datestr(ctx2.date())
 
-    gitmode = {'l': '120000', 'x': '100755', '': '100644'}
+    gitmode = {b'l': b'120000', b'x': b'100755', b'': b'100644'}
 
     if not pathfn:
         pathfn = lambda f: f
@@ -2977,23 +2995,23 @@
         header = []
         if opts.git:
             header.append(
-                'diff --git %s%s %s%s' % (aprefix, path1, bprefix, path2)
+                b'diff --git %s%s %s%s' % (aprefix, path1, bprefix, path2)
             )
             if not f1:  # added
-                header.append('new file mode %s' % gitmode[flag2])
+                header.append(b'new file mode %s' % gitmode[flag2])
             elif not f2:  # removed
-                header.append('deleted file mode %s' % gitmode[flag1])
+                header.append(b'deleted file mode %s' % gitmode[flag1])
             else:  # modified/copied/renamed
                 mode1, mode2 = gitmode[flag1], gitmode[flag2]
                 if mode1 != mode2:
-                    header.append('old mode %s' % mode1)
-                    header.append('new mode %s' % mode2)
+                    header.append(b'old mode %s' % mode1)
+                    header.append(b'new mode %s' % mode2)
                 if copyop is not None:
                     if opts.showsimilarity:
                         sim = similar.score(ctx1[path1], ctx2[path2]) * 100
-                        header.append('similarity index %d%%' % sim)
-                    header.append('%s from %s' % (copyop, path1))
-                    header.append('%s to %s' % (copyop, path2))
+                        header.append(b'similarity index %d%%' % sim)
+                    header.append(b'%s from %s' % (copyop, path1))
+                    header.append(b'%s to %s' % (copyop, path2))
         elif revs:
             header.append(diffline(path1, revs))
 
@@ -3032,7 +3050,7 @@
             text = mdiff.b85diff(content1, content2)
             if text:
                 header.append(
-                    'index %s..%s' % (gitindex(content1), gitindex(content2))
+                    b'index %s..%s' % (gitindex(content1), gitindex(content2))
                 )
             hunks = ((None, [text]),)
         else:
@@ -3041,7 +3059,7 @@
                 if flag is None:
                     flag = flag2
                 header.append(
-                    'index %s..%s %s'
+                    b'index %s..%s %s'
                     % (
                         gitindex(content1)[0 : opts.index],
                         gitindex(content2)[0 : opts.index],
@@ -3091,31 +3109,31 @@
     inheader = False
 
     for line in lines:
-        if line.startswith('diff'):
+        if line.startswith(b'diff'):
             addresult()
             # starting a new file diff
             # set numbers to 0 and reset inheader
             inheader = True
             adds, removes, isbinary = 0, 0, False
-            if line.startswith('diff --git a/'):
+            if line.startswith(b'diff --git a/'):
                 filename = gitre.search(line).group(2)
-            elif line.startswith('diff -r'):
+            elif line.startswith(b'diff -r'):
                 # format: "diff -r ... -r ... filename"
                 filename = diffre.search(line).group(1)
-        elif line.startswith('@@'):
+        elif line.startswith(b'@@'):
             inheader = False
-        elif line.startswith('+') and not inheader:
+        elif line.startswith(b'+') and not inheader:
             adds += 1
-        elif line.startswith('-') and not inheader:
+        elif line.startswith(b'-') and not inheader:
             removes += 1
-        elif line.startswith('GIT binary patch') or line.startswith(
-            'Binary file'
+        elif line.startswith(b'GIT binary patch') or line.startswith(
+            b'Binary file'
         ):
             isbinary = True
-        elif line.startswith('rename from'):
+        elif line.startswith(b'rename from'):
             filename = line[12:]
-        elif line.startswith('rename to'):
-            filename += ' => %s' % line[10:]
+        elif line.startswith(b'rename to'):
+            filename += b' => %s' % line[10:]
     addresult()
     return results
 
@@ -3142,16 +3160,16 @@
 
     for filename, adds, removes, isbinary in stats:
         if isbinary:
-            count = 'Bin'
+            count = b'Bin'
         else:
-            count = '%d' % (adds + removes)
-        pluses = '+' * scale(adds)
-        minuses = '-' * scale(removes)
+            count = b'%d' % (adds + removes)
+        pluses = b'+' * scale(adds)
+        minuses = b'-' * scale(removes)
         output.append(
-            ' %s%s |  %*s %s%s\n'
+            b' %s%s |  %*s %s%s\n'
             % (
                 filename,
-                ' ' * (maxname - encoding.colwidth(filename)),
+                b' ' * (maxname - encoding.colwidth(filename)),
                 countwidth,
                 count,
                 pluses,
@@ -3161,11 +3179,11 @@
 
     if stats:
         output.append(
-            _(' %d files changed, %d insertions(+), ' '%d deletions(-)\n')
+            _(b' %d files changed, %d insertions(+), ' b'%d deletions(-)\n')
             % (len(stats), totaladds, totalremoves)
         )
 
-    return ''.join(output)
+    return b''.join(output)
 
 
 def diffstatui(*args, **kw):
@@ -3174,15 +3192,15 @@
     '''
 
     for line in diffstat(*args, **kw).splitlines():
-        if line and line[-1] in '+-':
-            name, graph = line.rsplit(' ', 1)
-            yield (name + ' ', '')
+        if line and line[-1] in b'+-':
+            name, graph = line.rsplit(b' ', 1)
+            yield (name + b' ', b'')
             m = re.search(br'\++', graph)
             if m:
-                yield (m.group(0), 'diffstat.inserted')
+                yield (m.group(0), b'diffstat.inserted')
             m = re.search(br'-+', graph)
             if m:
-                yield (m.group(0), 'diffstat.deleted')
+                yield (m.group(0), b'diffstat.deleted')
         else:
-            yield (line, '')
-        yield ('\n', '')
+            yield (line, b'')
+        yield (b'\n', b'')