mercurial/merge.py
changeset 43076 2372284d9457
parent 42972 71bb9363818c
child 43077 687b865b95ad
--- a/mercurial/merge.py	Sat Oct 05 10:29:34 2019 -0400
+++ b/mercurial/merge.py	Sun Oct 06 09:45:02 2019 -0400
@@ -23,9 +23,7 @@
     nullid,
     nullrev,
 )
-from .thirdparty import (
-    attr,
-)
+from .thirdparty import attr
 from . import (
     copies,
     encoding,
@@ -43,12 +41,14 @@
 _pack = struct.pack
 _unpack = struct.unpack
 
+
 def _droponode(data):
     # used for compatibility for v1
     bits = data.split('\0')
     bits = bits[:-2] + bits[-1:]
     return '\0'.join(bits)
 
+
 # Merge state record types. See ``mergestate`` docs for more.
 RECORD_LOCAL = b'L'
 RECORD_OTHER = b'O'
@@ -90,6 +90,7 @@
 ACTION_EXEC = b'e'
 ACTION_CREATED_MERGE = b'cm'
 
+
 class mergestate(object):
     '''track 3-way merge state of individual files
 
@@ -136,6 +137,7 @@
     The resolve command transitions between 'u' and 'r' for conflicts and
     'pu' and 'pr' for path conflicts.
     '''
+
     statepathv1 = 'merge/state'
     statepathv2 = 'merge/state2'
 
@@ -209,15 +211,21 @@
                 bits = record.split('\0', 1)
                 mdstate = bits[1]
                 if len(mdstate) != 1 or mdstate not in (
-                    MERGE_DRIVER_STATE_UNMARKED, MERGE_DRIVER_STATE_MARKED,
-                    MERGE_DRIVER_STATE_SUCCESS):
+                    MERGE_DRIVER_STATE_UNMARKED,
+                    MERGE_DRIVER_STATE_MARKED,
+                    MERGE_DRIVER_STATE_SUCCESS,
+                ):
                     # the merge driver should be idempotent, so just rerun it
                     mdstate = MERGE_DRIVER_STATE_UNMARKED
 
                 self._readmergedriver = bits[0]
                 self._mdstate = mdstate
-            elif rtype in (RECORD_MERGED, RECORD_CHANGEDELETE_CONFLICT,
-                           RECORD_PATH_CONFLICT, RECORD_MERGE_DRIVER_MERGE):
+            elif rtype in (
+                RECORD_MERGED,
+                RECORD_CHANGEDELETE_CONFLICT,
+                RECORD_PATH_CONFLICT,
+                RECORD_MERGE_DRIVER_MERGE,
+            ):
                 bits = record.split('\0')
                 self._state[bits[0]] = bits[1:]
             elif rtype == RECORD_FILE_VALUES:
@@ -276,7 +284,7 @@
             return v1records
 
     def _v1v2match(self, v1records, v2records):
-        oldv2 = set() # old format version of v2 record
+        oldv2 = set()  # old format version of v2 record
         for rec in v2records:
             if rec[0] == RECORD_LOCAL:
                 oldv2.add(rec)
@@ -336,11 +344,11 @@
             off = 0
             end = len(data)
             while off < end:
-                rtype = data[off:off + 1]
+                rtype = data[off : off + 1]
                 off += 1
-                length = _unpack('>I', data[off:(off + 4)])[0]
+                length = _unpack('>I', data[off : (off + 4)])[0]
                 off += 4
-                record = data[off:(off + length)]
+                record = data[off : (off + length)]
                 off += length
                 if rtype == RECORD_OVERRIDE:
                     rtype, record = record[0:1], record[1:]
@@ -362,11 +370,14 @@
         # - B then continues the merge and the malicious merge driver
         #  gets invoked
         configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
-        if (self._readmergedriver is not None
-            and self._readmergedriver != configmergedriver):
+        if (
+            self._readmergedriver is not None
+            and self._readmergedriver != configmergedriver
+        ):
             raise error.ConfigError(
                 _("merge driver changed since merge started"),
-                hint=_("revert merge driver change or abort merge"))
+                hint=_("revert merge driver change or abort merge"),
+            )
 
         return configmergedriver
 
@@ -392,9 +403,12 @@
         """
         # Check local variables before looking at filesystem for performance
         # reasons.
-        return (bool(self._local) or bool(self._state) or
-                self._repo.vfs.exists(self.statepathv1) or
-                self._repo.vfs.exists(self.statepathv2))
+        return (
+            bool(self._local)
+            or bool(self._state)
+            or self._repo.vfs.exists(self.statepathv1)
+            or self._repo.vfs.exists(self.statepathv2)
+        )
 
     def commit(self):
         """Write current state on disk (if necessary)"""
@@ -408,8 +422,12 @@
         records.append((RECORD_LOCAL, hex(self._local)))
         records.append((RECORD_OTHER, hex(self._other)))
         if self.mergedriver:
-            records.append((RECORD_MERGE_DRIVER_STATE, '\0'.join([
-                self.mergedriver, self._mdstate])))
+            records.append(
+                (
+                    RECORD_MERGE_DRIVER_STATE,
+                    '\0'.join([self.mergedriver, self._mdstate]),
+                )
+            )
         # Write out state items. In all cases, the value of the state map entry
         # is written as the contents of the record. The record type depends on
         # the type of state that is stored, and capital-letter records are used
@@ -418,30 +436,36 @@
         for filename, v in self._state.iteritems():
             if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
                 # Driver-resolved merge. These are stored in 'D' records.
-                records.append((RECORD_MERGE_DRIVER_MERGE,
-                                '\0'.join([filename] + v)))
-            elif v[0] in (MERGE_RECORD_UNRESOLVED_PATH,
-                          MERGE_RECORD_RESOLVED_PATH):
+                records.append(
+                    (RECORD_MERGE_DRIVER_MERGE, '\0'.join([filename] + v))
+                )
+            elif v[0] in (
+                MERGE_RECORD_UNRESOLVED_PATH,
+                MERGE_RECORD_RESOLVED_PATH,
+            ):
                 # Path conflicts. These are stored in 'P' records.  The current
                 # resolution state ('pu' or 'pr') is stored within the record.
-                records.append((RECORD_PATH_CONFLICT,
-                                '\0'.join([filename] + v)))
+                records.append(
+                    (RECORD_PATH_CONFLICT, '\0'.join([filename] + v))
+                )
             elif v[1] == nullhex or v[6] == nullhex:
                 # Change/Delete or Delete/Change conflicts. These are stored in
                 # 'C' records. v[1] is the local file, and is nullhex when the
                 # file is deleted locally ('dc'). v[6] is the remote file, and
                 # is nullhex when the file is deleted remotely ('cd').
-                records.append((RECORD_CHANGEDELETE_CONFLICT,
-                                '\0'.join([filename] + v)))
+                records.append(
+                    (RECORD_CHANGEDELETE_CONFLICT, '\0'.join([filename] + v))
+                )
             else:
                 # Normal files.  These are stored in 'F' records.
-                records.append((RECORD_MERGED,
-                                '\0'.join([filename] + v)))
+                records.append((RECORD_MERGED, '\0'.join([filename] + v)))
         for filename, extras in sorted(self._stateextras.iteritems()):
-            rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
-                                  extras.iteritems())
-            records.append((RECORD_FILE_VALUES,
-                            '%s\0%s' % (filename, rawextras)))
+            rawextras = '\0'.join(
+                '%s\0%s' % (k, v) for k, v in extras.iteritems()
+            )
+            records.append(
+                (RECORD_FILE_VALUES, '%s\0%s' % (filename, rawextras))
+            )
         if self._labels is not None:
             labels = '\0'.join(self._labels)
             records.append((RECORD_LABELS, labels))
@@ -500,10 +524,16 @@
         else:
             localkey = mergestate.getlocalkey(fcl.path())
             self._repo.vfs.write('merge/' + localkey, fcl.data())
-        self._state[fd] = [MERGE_RECORD_UNRESOLVED, localkey, fcl.path(),
-                           fca.path(), hex(fca.filenode()),
-                           fco.path(), hex(fco.filenode()),
-                           fcl.flags()]
+        self._state[fd] = [
+            MERGE_RECORD_UNRESOLVED,
+            localkey,
+            fcl.path(),
+            fca.path(),
+            hex(fca.filenode()),
+            fco.path(),
+            hex(fco.filenode()),
+            fcl.flags(),
+        ]
         self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
         self._dirty = True
 
@@ -539,8 +569,10 @@
         """Obtain the paths of unresolved files."""
 
         for f, entry in self._state.iteritems():
-            if entry[0] in (MERGE_RECORD_UNRESOLVED,
-                            MERGE_RECORD_UNRESOLVED_PATH):
+            if entry[0] in (
+                MERGE_RECORD_UNRESOLVED,
+                MERGE_RECORD_UNRESOLVED_PATH,
+            ):
                 yield f
 
     def driverresolved(self):
@@ -555,8 +587,7 @@
 
     def _resolve(self, preresolve, dfile, wctx):
         """rerun merge process for file path `dfile`"""
-        if self[dfile] in (MERGE_RECORD_RESOLVED,
-                           MERGE_RECORD_DRIVER_RESOLVED):
+        if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED):
             return True, 0
         stateentry = self._state[dfile]
         state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
@@ -578,9 +609,12 @@
             if fca.node() == nullid and flags != flo:
                 if preresolve:
                     self._repo.ui.warn(
-                        _('warning: cannot merge flags for %s '
-                          'without common ancestor - keeping local flags\n')
-                        % afile)
+                        _(
+                            'warning: cannot merge flags for %s '
+                            'without common ancestor - keeping local flags\n'
+                        )
+                        % afile
+                    )
             elif flags == fla:
                 flags = flo
         if preresolve:
@@ -591,15 +625,27 @@
                 f.close()
             else:
                 wctx[dfile].remove(ignoremissing=True)
-            complete, r, deleted = filemerge.premerge(self._repo, wctx,
-                                                      self._local, lfile, fcd,
-                                                      fco, fca,
-                                                      labels=self._labels)
+            complete, r, deleted = filemerge.premerge(
+                self._repo,
+                wctx,
+                self._local,
+                lfile,
+                fcd,
+                fco,
+                fca,
+                labels=self._labels,
+            )
         else:
-            complete, r, deleted = filemerge.filemerge(self._repo, wctx,
-                                                       self._local, lfile, fcd,
-                                                       fco, fca,
-                                                       labels=self._labels)
+            complete, r, deleted = filemerge.filemerge(
+                self._repo,
+                wctx,
+                self._local,
+                lfile,
+                fcd,
+                fco,
+                fca,
+                labels=self._labels,
+            )
         if r is None:
             # no real conflict
             del self._state[dfile]
@@ -619,9 +665,9 @@
                     # cd: remote picked (or otherwise deleted)
                     action = ACTION_REMOVE
             else:
-                if fcd.isabsent(): # dc: remote picked
+                if fcd.isabsent():  # dc: remote picked
                     action = ACTION_GET
-                elif fco.isabsent(): # cd: local picked
+                elif fco.isabsent():  # cd: local picked
                     if dfile in self.localctx:
                         action = ACTION_ADD_MODIFIED
                     else:
@@ -704,16 +750,19 @@
         Meant for use by custom merge drivers."""
         self._results[f] = 0, ACTION_GET
 
+
 def _getcheckunknownconfig(repo, section, name):
     config = repo.ui.config(section, name)
     valid = ['abort', 'ignore', 'warn']
     if config not in valid:
         validstr = ', '.join(["'" + v + "'" for v in valid])
-        raise error.ConfigError(_("%s.%s not valid "
-                                  "('%s' is none of %s)")
-                                % (section, name, config, validstr))
+        raise error.ConfigError(
+            _("%s.%s not valid " "('%s' is none of %s)")
+            % (section, name, config, validstr)
+        )
     return config
 
+
 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
     if wctx.isinmemory():
         # Nothing to do in IMM because nothing in the "working copy" can be an
@@ -725,10 +774,13 @@
 
     if f2 is None:
         f2 = f
-    return (repo.wvfs.audit.check(f)
+    return (
+        repo.wvfs.audit.check(f)
         and repo.wvfs.isfileorlink(f)
         and repo.dirstate.normalize(f) not in repo.dirstate
-        and mctx[f2].cmp(wctx[f]))
+        and mctx[f2].cmp(wctx[f])
+    )
+
 
 class _unknowndirschecker(object):
     """
@@ -740,6 +792,7 @@
     Returns the shortest path at which a conflict occurs, or None if there is
     no conflict.
     """
+
     def __init__(self):
         # A set of paths known to be good.  This prevents repeated checking of
         # dirs.  It will be updated with any new dirs that are checked and found
@@ -763,8 +816,10 @@
             if p in self._unknowndircache:
                 continue
             if repo.wvfs.audit.check(p):
-                if (repo.wvfs.isfileorlink(p)
-                        and repo.dirstate.normalize(p) not in repo.dirstate):
+                if (
+                    repo.wvfs.isfileorlink(p)
+                    and repo.dirstate.normalize(p) not in repo.dirstate
+                ):
                     return p
                 if not repo.wvfs.lexists(p):
                     self._missingdircache.add(p)
@@ -782,6 +837,7 @@
                         return f
         return None
 
+
 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
     """
     Considers any actions that care about the presence of conflicting unknown
@@ -796,6 +852,7 @@
     ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
     pathconfig = repo.ui.configbool('experimental', 'merge.checkpathconflicts')
     if not force:
+
         def collectconflicts(conflicts, config):
             if config == 'abort':
                 abortconflicts.update(conflicts)
@@ -816,8 +873,7 @@
                     fileconflicts.add(f)
 
         allconflicts = fileconflicts | pathconflicts
-        ignoredconflicts = {c for c in allconflicts
-                            if repo.dirstate._ignore(c)}
+        ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
         unknownconflicts = allconflicts - ignoredconflicts
         collectconflicts(ignoredconflicts, ignoredconfig)
         collectconflicts(unknownconflicts, unknownconfig)
@@ -846,8 +902,11 @@
                 if not different:
                     actions[f] = (ACTION_GET, (fl2, False), 'remote created')
                 elif mergeforce or config == 'abort':
-                    actions[f] = (ACTION_MERGE, (f, f, None, False, anc),
-                                  'remote differs from untracked local')
+                    actions[f] = (
+                        ACTION_MERGE,
+                        (f, f, None, False, anc),
+                        'remote differs from untracked local',
+                    )
                 elif config == 'abort':
                     abortconflicts.add(f)
                 else:
@@ -865,8 +924,12 @@
         else:
             warn(_("%s: untracked file differs\n") % f)
     if abortconflicts:
-        raise error.Abort(_("untracked files in working directory "
-                            "differ from files in requested revision"))
+        raise error.Abort(
+            _(
+                "untracked files in working directory "
+                "differ from files in requested revision"
+            )
+        )
 
     for f in sorted(warnconflicts):
         if repo.wvfs.isfileorlink(f):
@@ -876,11 +939,15 @@
 
     for f, (m, args, msg) in actions.iteritems():
         if m == ACTION_CREATED:
-            backup = (f in fileconflicts or f in pathconflicts or
-                      any(p in pathconflicts for p in util.finddirs(f)))
-            flags, = args
+            backup = (
+                f in fileconflicts
+                or f in pathconflicts
+                or any(p in pathconflicts for p in util.finddirs(f))
+            )
+            (flags,) = args
             actions[f] = (ACTION_GET, (flags, backup), msg)
 
+
 def _forgetremoved(wctx, mctx, branchmerge):
     """
     Forget removed files
@@ -911,6 +978,7 @@
 
     return actions
 
+
 def _checkcollision(repo, wmf, actions):
     """
     Check for case-folding collisions.
@@ -934,8 +1002,14 @@
 
     if actions:
         # KEEP and EXEC are no-op
-        for m in (ACTION_ADD, ACTION_ADD_MODIFIED, ACTION_FORGET, ACTION_GET,
-                  ACTION_CHANGED_DELETED, ACTION_DELETED_CHANGED):
+        for m in (
+            ACTION_ADD,
+            ACTION_ADD_MODIFIED,
+            ACTION_FORGET,
+            ACTION_GET,
+            ACTION_CHANGED_DELETED,
+            ACTION_DELETED_CHANGED,
+        ):
             for f, args, msg in actions[m]:
                 pmmf.add(f)
         for f, args, msg in actions[ACTION_REMOVE]:
@@ -957,8 +1031,10 @@
     for f in pmmf:
         fold = util.normcase(f)
         if fold in foldmap:
-            raise error.Abort(_("case-folding collision between %s and %s")
-                             % (f, foldmap[fold]))
+            raise error.Abort(
+                _("case-folding collision between %s and %s")
+                % (f, foldmap[fold])
+            )
         foldmap[fold] = f
 
     # check case-folding of directories
@@ -966,24 +1042,29 @@
     for fold, f in sorted(foldmap.items()):
         if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
             # the folded prefix matches but actual casing is different
-            raise error.Abort(_("case-folding collision between "
-                                "%s and directory of %s") % (lastfull, f))
+            raise error.Abort(
+                _("case-folding collision between " "%s and directory of %s")
+                % (lastfull, f)
+            )
         foldprefix = fold + '/'
         unfoldprefix = f + '/'
         lastfull = f
 
+
 def driverpreprocess(repo, ms, wctx, labels=None):
     """run the preprocess step of the merge driver, if any
 
     This is currently not implemented -- it's an extension point."""
     return True
 
+
 def driverconclude(repo, ms, wctx, labels=None):
     """run the conclude step of the merge driver, if any
 
     This is currently not implemented -- it's an extension point."""
     return True
 
+
 def _filesindirs(repo, manifest, dirs):
     """
     Generator that yields pairs of all the files in the manifest that are found
@@ -996,6 +1077,7 @@
                 yield f, p
                 break
 
+
 def checkpathconflicts(repo, wctx, mctx, actions):
     """
     Check if any actions introduce path conflicts in the repository, updating
@@ -1022,8 +1104,12 @@
     deletedfiles = set()
 
     for f, (m, args, msg) in actions.items():
-        if m in (ACTION_CREATED, ACTION_DELETED_CHANGED, ACTION_MERGE,
-                 ACTION_CREATED_MERGE):
+        if m in (
+            ACTION_CREATED,
+            ACTION_DELETED_CHANGED,
+            ACTION_MERGE,
+            ACTION_CREATED_MERGE,
+        ):
             # This action may create a new local file.
             createdfiledirs.update(util.finddirs(f))
             if mf.hasdir(f):
@@ -1054,10 +1140,12 @@
                 # A file is in a directory which aliases a local file.
                 # We will need to rename the local file.
                 localconflicts.add(p)
-        if p in actions and actions[p][0] in (ACTION_CREATED,
-                                              ACTION_DELETED_CHANGED,
-                                              ACTION_MERGE,
-                                              ACTION_CREATED_MERGE):
+        if p in actions and actions[p][0] in (
+            ACTION_CREATED,
+            ACTION_DELETED_CHANGED,
+            ACTION_MERGE,
+            ACTION_CREATED_MERGE,
+        ):
             # The file is in a directory which aliases a remote file.
             # This is an internal inconsistency within the remote
             # manifest.
@@ -1068,10 +1156,12 @@
         if p not in deletedfiles:
             ctxname = bytes(wctx).rstrip('+')
             pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
-            actions[pnew] = (ACTION_PATH_CONFLICT_RESOLVE, (p,),
-                             'local path conflict')
-            actions[p] = (ACTION_PATH_CONFLICT, (pnew, 'l'),
-                          'path conflict')
+            actions[pnew] = (
+                ACTION_PATH_CONFLICT_RESOLVE,
+                (p,),
+                'local path conflict',
+            )
+            actions[p] = (ACTION_PATH_CONFLICT, (pnew, 'l'), 'path conflict')
 
     if remoteconflicts:
         # Check if all files in the conflicting directories have been removed.
@@ -1086,10 +1176,16 @@
                 else:
                     # Action was create, change to renamed get action.
                     fl = args[0]
-                    actions[pnew] = (ACTION_LOCAL_DIR_RENAME_GET, (p, fl),
-                                     'remote path conflict')
-                actions[p] = (ACTION_PATH_CONFLICT, (pnew, ACTION_REMOVE),
-                              'path conflict')
+                    actions[pnew] = (
+                        ACTION_LOCAL_DIR_RENAME_GET,
+                        (p, fl),
+                        'remote path conflict',
+                    )
+                actions[p] = (
+                    ACTION_PATH_CONFLICT,
+                    (pnew, ACTION_REMOVE),
+                    'path conflict',
+                )
                 remoteconflicts.remove(p)
                 break
 
@@ -1098,6 +1194,7 @@
             repo.ui.warn(_("%s: is both a file and a directory\n") % p)
         raise error.Abort(_("destination manifest contains path conflicts"))
 
+
 def _filternarrowactions(narrowmatch, branchmerge, actions):
     """
     Filters out actions that can ignored because the repo is narrowed.
@@ -1105,7 +1202,7 @@
     Raise an exception if the merge cannot be completed because the repo is
     narrowed.
     """
-    nooptypes = {'k'} # TODO: handle with nonconflicttypes
+    nooptypes = {'k'}  # TODO: handle with nonconflicttypes
     nonconflicttypes = set('a am c cm f g r e'.split())
     # We mutate the items in the dict during iteration, so iterate
     # over a copy.
@@ -1113,20 +1210,36 @@
         if narrowmatch(f):
             pass
         elif not branchmerge:
-            del actions[f] # just updating, ignore changes outside clone
+            del actions[f]  # just updating, ignore changes outside clone
         elif action[0] in nooptypes:
-            del actions[f] # merge does not affect file
+            del actions[f]  # merge does not affect file
         elif action[0] in nonconflicttypes:
-            raise error.Abort(_('merge affects file \'%s\' outside narrow, '
-                                'which is not yet supported') % f,
-                              hint=_('merging in the other direction '
-                                     'may work'))
+            raise error.Abort(
+                _(
+                    'merge affects file \'%s\' outside narrow, '
+                    'which is not yet supported'
+                )
+                % f,
+                hint=_('merging in the other direction ' 'may work'),
+            )
         else:
-            raise error.Abort(_('conflict in file \'%s\' is outside '
-                                'narrow clone') % f)
+            raise error.Abort(
+                _('conflict in file \'%s\' is outside ' 'narrow clone') % f
+            )
+
 
-def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
-                  acceptremote, followcopies, forcefulldiff=False):
+def manifestmerge(
+    repo,
+    wctx,
+    p2,
+    pa,
+    branchmerge,
+    force,
+    matcher,
+    acceptremote,
+    followcopies,
+    forcefulldiff=False,
+):
     """
     Merge wctx and p2 with ancestor pa and generate merge action list
 
@@ -1140,8 +1253,10 @@
     copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
 
     # manifests fetched in order are going to be faster, so prime the caches
-    [x.manifest() for x in
-     sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
+    [
+        x.manifest()
+        for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
+    ]
 
     if followcopies:
         ret = copies.mergecopies(repo, wctx, p2, pa)
@@ -1151,8 +1266,9 @@
     boolf = pycompat.bytestr(bool(force))
     boolm = pycompat.bytestr(bool(matcher))
     repo.ui.note(_("resolving manifests\n"))
-    repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
-                  % (boolbm, boolf, boolm))
+    repo.ui.debug(
+        " branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
+    )
     repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
 
     m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
@@ -1170,7 +1286,7 @@
     # - ma is the same as m1 or m2, which we're just going to diff again later
     # - The caller specifically asks for a full diff, which is useful during bid
     #   merge.
-    if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
+    if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
         # Identify which files are relevant to the merge, so we can limit the
         # total m1-vs-m2 diff to just those files. This has significant
         # performance benefits in large repositories.
@@ -1189,57 +1305,80 @@
 
     actions = {}
     for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
-        if n1 and n2: # file exists on both local and remote side
+        if n1 and n2:  # file exists on both local and remote side
             if f not in ma:
                 fa = copy.get(f, None)
                 if fa is not None:
-                    actions[f] = (ACTION_MERGE, (f, f, fa, False, pa.node()),
-                                  'both renamed from %s' % fa)
+                    actions[f] = (
+                        ACTION_MERGE,
+                        (f, f, fa, False, pa.node()),
+                        'both renamed from %s' % fa,
+                    )
                 else:
-                    actions[f] = (ACTION_MERGE, (f, f, None, False, pa.node()),
-                                  'both created')
+                    actions[f] = (
+                        ACTION_MERGE,
+                        (f, f, None, False, pa.node()),
+                        'both created',
+                    )
             else:
                 a = ma[f]
                 fla = ma.flags(f)
                 nol = 'l' not in fl1 + fl2 + fla
                 if n2 == a and fl2 == fla:
                     actions[f] = (ACTION_KEEP, (), 'remote unchanged')
-                elif n1 == a and fl1 == fla: # local unchanged - use remote
-                    if n1 == n2: # optimization: keep local content
+                elif n1 == a and fl1 == fla:  # local unchanged - use remote
+                    if n1 == n2:  # optimization: keep local content
                         actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
                     else:
-                        actions[f] = (ACTION_GET, (fl2, False),
-                                      'remote is newer')
-                elif nol and n2 == a: # remote only changed 'x'
+                        actions[f] = (
+                            ACTION_GET,
+                            (fl2, False),
+                            'remote is newer',
+                        )
+                elif nol and n2 == a:  # remote only changed 'x'
                     actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
-                elif nol and n1 == a: # local only changed 'x'
+                elif nol and n1 == a:  # local only changed 'x'
                     actions[f] = (ACTION_GET, (fl1, False), 'remote is newer')
-                else: # both changed something
-                    actions[f] = (ACTION_MERGE, (f, f, f, False, pa.node()),
-                                  'versions differ')
-        elif n1: # file exists only on local side
+                else:  # both changed something
+                    actions[f] = (
+                        ACTION_MERGE,
+                        (f, f, f, False, pa.node()),
+                        'versions differ',
+                    )
+        elif n1:  # file exists only on local side
             if f in copied:
-                pass # we'll deal with it on m2 side
-            elif f in movewithdir: # directory rename, move local
+                pass  # we'll deal with it on m2 side
+            elif f in movewithdir:  # directory rename, move local
                 f2 = movewithdir[f]
                 if f2 in m2:
-                    actions[f2] = (ACTION_MERGE, (f, f2, None, True, pa.node()),
-                                   'remote directory rename, both created')
+                    actions[f2] = (
+                        ACTION_MERGE,
+                        (f, f2, None, True, pa.node()),
+                        'remote directory rename, both created',
+                    )
                 else:
-                    actions[f2] = (ACTION_DIR_RENAME_MOVE_LOCAL, (f, fl1),
-                                   'remote directory rename - move from %s' % f)
+                    actions[f2] = (
+                        ACTION_DIR_RENAME_MOVE_LOCAL,
+                        (f, fl1),
+                        'remote directory rename - move from %s' % f,
+                    )
             elif f in copy:
                 f2 = copy[f]
-                actions[f] = (ACTION_MERGE, (f, f2, f2, False, pa.node()),
-                              'local copied/moved from %s' % f2)
-            elif f in ma: # clean, a different, no remote
+                actions[f] = (
+                    ACTION_MERGE,
+                    (f, f2, f2, False, pa.node()),
+                    'local copied/moved from %s' % f2,
+                )
+            elif f in ma:  # clean, a different, no remote
                 if n1 != ma[f]:
                     if acceptremote:
                         actions[f] = (ACTION_REMOVE, None, 'remote delete')
                     else:
-                        actions[f] = (ACTION_CHANGED_DELETED,
-                                      (f, None, f, False, pa.node()),
-                                      'prompt changed/deleted')
+                        actions[f] = (
+                            ACTION_CHANGED_DELETED,
+                            (f, None, f, False, pa.node()),
+                            'prompt changed/deleted',
+                        )
                 elif n1 == addednodeid:
                     # This extra 'a' is added by working copy manifest to mark
                     # the file as locally added. We should forget it instead of
@@ -1247,26 +1386,37 @@
                     actions[f] = (ACTION_FORGET, None, 'remote deleted')
                 else:
                     actions[f] = (ACTION_REMOVE, None, 'other deleted')
-        elif n2: # file exists only on remote side
+        elif n2:  # file exists only on remote side
             if f in copied:
-                pass # we'll deal with it on m1 side
+                pass  # we'll deal with it on m1 side
             elif f in movewithdir:
                 f2 = movewithdir[f]
                 if f2 in m1:
-                    actions[f2] = (ACTION_MERGE,
-                                   (f2, f, None, False, pa.node()),
-                                   'local directory rename, both created')
+                    actions[f2] = (
+                        ACTION_MERGE,
+                        (f2, f, None, False, pa.node()),
+                        'local directory rename, both created',
+                    )
                 else:
-                    actions[f2] = (ACTION_LOCAL_DIR_RENAME_GET, (f, fl2),
-                                   'local directory rename - get from %s' % f)
+                    actions[f2] = (
+                        ACTION_LOCAL_DIR_RENAME_GET,
+                        (f, fl2),
+                        'local directory rename - get from %s' % f,
+                    )
             elif f in copy:
                 f2 = copy[f]
                 if f2 in m2:
-                    actions[f] = (ACTION_MERGE, (f2, f, f2, False, pa.node()),
-                                  'remote copied from %s' % f2)
+                    actions[f] = (
+                        ACTION_MERGE,
+                        (f2, f, f2, False, pa.node()),
+                        'remote copied from %s' % f2,
+                    )
                 else:
-                    actions[f] = (ACTION_MERGE, (f2, f, f2, True, pa.node()),
-                                  'remote moved from %s' % f2)
+                    actions[f] = (
+                        ACTION_MERGE,
+                        (f2, f, f2, True, pa.node()),
+                        'remote moved from %s' % f2,
+                    )
             elif f not in ma:
                 # local unknown, remote created: the logic is described by the
                 # following table:
@@ -1284,25 +1434,32 @@
                 elif not branchmerge:
                     actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
                 else:
-                    actions[f] = (ACTION_CREATED_MERGE, (fl2, pa.node()),
-                                  'remote created, get or merge')
+                    actions[f] = (
+                        ACTION_CREATED_MERGE,
+                        (fl2, pa.node()),
+                        'remote created, get or merge',
+                    )
             elif n2 != ma[f]:
                 df = None
                 for d in dirmove:
                     if f.startswith(d):
                         # new file added in a directory that was moved
-                        df = dirmove[d] + f[len(d):]
+                        df = dirmove[d] + f[len(d) :]
                         break
                 if df is not None and df in m1:
-                    actions[df] = (ACTION_MERGE, (df, f, f, False, pa.node()),
-                                   'local directory rename - respect move '
-                                   'from %s' % f)
+                    actions[df] = (
+                        ACTION_MERGE,
+                        (df, f, f, False, pa.node()),
+                        'local directory rename - respect move ' 'from %s' % f,
+                    )
                 elif acceptremote:
                     actions[f] = (ACTION_CREATED, (fl2,), 'remote recreating')
                 else:
-                    actions[f] = (ACTION_DELETED_CHANGED,
-                                  (None, f, f, False, pa.node()),
-                                  'prompt deleted/changed')
+                    actions[f] = (
+                        ACTION_DELETED_CHANGED,
+                        (None, f, f, False, pa.node()),
+                        'prompt deleted/changed',
+                    )
 
     if repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
         # If we are merging, look for path conflicts.
@@ -1315,48 +1472,88 @@
 
     return actions, diverge, renamedelete
 
+
 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
     """Resolves false conflicts where the nodeid changed but the content
        remained the same."""
     # We force a copy of actions.items() because we're going to mutate
     # actions as we resolve trivial conflicts.
     for f, (m, args, msg) in list(actions.items()):
-        if (m == ACTION_CHANGED_DELETED and f in ancestor
-            and not wctx[f].cmp(ancestor[f])):
+        if (
+            m == ACTION_CHANGED_DELETED
+            and f in ancestor
+            and not wctx[f].cmp(ancestor[f])
+        ):
             # local did change but ended up with same content
             actions[f] = ACTION_REMOVE, None, 'prompt same'
-        elif (m == ACTION_DELETED_CHANGED and f in ancestor
-              and not mctx[f].cmp(ancestor[f])):
+        elif (
+            m == ACTION_DELETED_CHANGED
+            and f in ancestor
+            and not mctx[f].cmp(ancestor[f])
+        ):
             # remote did change but ended up with same content
-            del actions[f] # don't get = keep local deleted
+            del actions[f]  # don't get = keep local deleted
+
 
-def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
-                     acceptremote, followcopies, matcher=None,
-                     mergeforce=False):
+def calculateupdates(
+    repo,
+    wctx,
+    mctx,
+    ancestors,
+    branchmerge,
+    force,
+    acceptremote,
+    followcopies,
+    matcher=None,
+    mergeforce=False,
+):
     """Calculate the actions needed to merge mctx into wctx using ancestors"""
     # Avoid cycle.
     from . import sparse
 
-    if len(ancestors) == 1: # default
+    if len(ancestors) == 1:  # default
         actions, diverge, renamedelete = manifestmerge(
-            repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
-            acceptremote, followcopies)
+            repo,
+            wctx,
+            mctx,
+            ancestors[0],
+            branchmerge,
+            force,
+            matcher,
+            acceptremote,
+            followcopies,
+        )
         _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
 
-    else: # only when merge.preferancestor=* - the default
+    else:  # only when merge.preferancestor=* - the default
         repo.ui.note(
-            _("note: merging %s and %s using bids from ancestors %s\n") %
-            (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
-                                            for anc in ancestors)))
+            _("note: merging %s and %s using bids from ancestors %s\n")
+            % (
+                wctx,
+                mctx,
+                _(' and ').join(pycompat.bytestr(anc) for anc in ancestors),
+            )
+        )
 
         # Call for bids
-        fbids = {} # mapping filename to bids (action method to list af actions)
+        fbids = (
+            {}
+        )  # mapping filename to bids (action method to list af actions)
         diverge, renamedelete = None, None
         for ancestor in ancestors:
             repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
             actions, diverge1, renamedelete1 = manifestmerge(
-                repo, wctx, mctx, ancestor, branchmerge, force, matcher,
-                acceptremote, followcopies, forcefulldiff=True)
+                repo,
+                wctx,
+                mctx,
+                ancestor,
+                branchmerge,
+                force,
+                matcher,
+                acceptremote,
+                followcopies,
+                forcefulldiff=True,
+            )
             _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
 
             # Track the shortest set of warning on the theory that bid
@@ -1384,9 +1581,9 @@
         for f, bids in sorted(fbids.items()):
             # bids is a mapping from action method to list af actions
             # Consensus?
-            if len(bids) == 1: # all bids are the same kind of method
+            if len(bids) == 1:  # all bids are the same kind of method
                 m, l = list(bids.items())[0]
-                if all(a == l[0] for a in l[1:]): # len(bids) is > 1
+                if all(a == l[0] for a in l[1:]):  # len(bids) is > 1
                     repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
                     actions[f] = l[0]
                     continue
@@ -1410,8 +1607,9 @@
                     repo.ui.note('  %s -> %s\n' % (msg, m))
             # Pick random action. TODO: Instead, prompt user when resolving
             m, l = list(bids.items())[0]
-            repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
-                         (f, m))
+            repo.ui.warn(
+                _(' %s: ambiguous merge - picked %s action\n') % (f, m)
+            )
             actions[f] = l[0]
             continue
         repo.ui.note(_('end of auction\n\n'))
@@ -1420,12 +1618,14 @@
         fractions = _forgetremoved(wctx, mctx, branchmerge)
         actions.update(fractions)
 
-    prunedactions = sparse.filterupdatesactions(repo, wctx, mctx, branchmerge,
-                                                actions)
+    prunedactions = sparse.filterupdatesactions(
+        repo, wctx, mctx, branchmerge, actions
+    )
     _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
 
     return prunedactions, diverge, renamedelete
 
+
 def _getcwd():
     try:
         return encoding.getcwd()
@@ -1434,6 +1634,7 @@
             return None
         raise
 
+
 def batchremove(repo, wctx, actions):
     """apply removes to the working directory
 
@@ -1450,8 +1651,9 @@
         try:
             wctx[f].remove(ignoremissing=True)
         except OSError as inst:
-            repo.ui.warn(_("update failed to remove %s: %s!\n") %
-                         (f, inst.strerror))
+            repo.ui.warn(
+                _("update failed to remove %s: %s!\n") % (f, inst.strerror)
+            )
         if i == 100:
             yield i, f
             i = 0
@@ -1462,8 +1664,14 @@
     if cwd and not _getcwd():
         # cwd was removed in the course of removing files; print a helpful
         # warning.
-        repo.ui.warn(_("current directory was removed\n"
-                       "(consider changing to repo root: %s)\n") % repo.root)
+        repo.ui.warn(
+            _(
+                "current directory was removed\n"
+                "(consider changing to repo root: %s)\n"
+            )
+            % repo.root
+        )
+
 
 def batchget(repo, mctx, wctx, wantfiledata, actions):
     """apply gets to the working directory
@@ -1502,14 +1710,17 @@
             wfctx = wctx[f]
             wfctx.clearunknown()
             atomictemp = ui.configbool("experimental", "update.atomic-file")
-            size = wfctx.write(fctx(f).data(), flags,
-                               backgroundclose=True,
-                               atomictemp=atomictemp)
+            size = wfctx.write(
+                fctx(f).data(),
+                flags,
+                backgroundclose=True,
+                atomictemp=atomictemp,
+            )
             if wantfiledata:
                 s = wfctx.lstat()
                 mode = s.st_mode
                 mtime = s[stat.ST_MTIME]
-                filedata[f] = ((mode, size, mtime)) # for dirstate.normal
+                filedata[f] = (mode, size, mtime)  # for dirstate.normal
             if i == 100:
                 yield False, (i, f)
                 i = 0
@@ -1518,6 +1729,7 @@
         yield False, (i, f)
     yield True, filedata
 
+
 def _prefetchfiles(repo, ctx, actions):
     """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
     of merge actions.  ``ctx`` is the context being merged in."""
@@ -1525,13 +1737,23 @@
     # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
     # don't touch the context to be merged in.  'cd' is skipped, because
     # changed/deleted never resolves to something from the remote side.
-    oplist = [actions[a] for a in (ACTION_GET, ACTION_DELETED_CHANGED,
-                                   ACTION_LOCAL_DIR_RENAME_GET, ACTION_MERGE)]
+    oplist = [
+        actions[a]
+        for a in (
+            ACTION_GET,
+            ACTION_DELETED_CHANGED,
+            ACTION_LOCAL_DIR_RENAME_GET,
+            ACTION_MERGE,
+        )
+    ]
     prefetch = scmutil.prefetchfiles
     matchfiles = scmutil.matchfiles
-    prefetch(repo, [ctx.rev()],
-             matchfiles(repo,
-                        [f for sublist in oplist for f, args, msg in sublist]))
+    prefetch(
+        repo,
+        [ctx.rev()],
+        matchfiles(repo, [f for sublist in oplist for f, args, msg in sublist]),
+    )
+
 
 @attr.s(frozen=True)
 class updateresult(object):
@@ -1541,30 +1763,40 @@
     unresolvedcount = attr.ib()
 
     def isempty(self):
-        return not (self.updatedcount or self.mergedcount
-                    or self.removedcount or self.unresolvedcount)
+        return not (
+            self.updatedcount
+            or self.mergedcount
+            or self.removedcount
+            or self.unresolvedcount
+        )
+
 
 def emptyactions():
     """create an actions dict, to be populated and passed to applyupdates()"""
-    return dict((m, [])
-                for m in (
-                    ACTION_ADD,
-                    ACTION_ADD_MODIFIED,
-                    ACTION_FORGET,
-                    ACTION_GET,
-                    ACTION_CHANGED_DELETED,
-                    ACTION_DELETED_CHANGED,
-                    ACTION_REMOVE,
-                    ACTION_DIR_RENAME_MOVE_LOCAL,
-                    ACTION_LOCAL_DIR_RENAME_GET,
-                    ACTION_MERGE,
-                    ACTION_EXEC,
-                    ACTION_KEEP,
-                    ACTION_PATH_CONFLICT,
-                    ACTION_PATH_CONFLICT_RESOLVE))
+    return dict(
+        (m, [])
+        for m in (
+            ACTION_ADD,
+            ACTION_ADD_MODIFIED,
+            ACTION_FORGET,
+            ACTION_GET,
+            ACTION_CHANGED_DELETED,
+            ACTION_DELETED_CHANGED,
+            ACTION_REMOVE,
+            ACTION_DIR_RENAME_MOVE_LOCAL,
+            ACTION_LOCAL_DIR_RENAME_GET,
+            ACTION_MERGE,
+            ACTION_EXEC,
+            ACTION_KEEP,
+            ACTION_PATH_CONFLICT,
+            ACTION_PATH_CONFLICT_RESOLVE,
+        )
+    )
 
-def applyupdates(repo, actions, wctx, mctx, overwrite, wantfiledata,
-                 labels=None):
+
+def applyupdates(
+    repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
+):
     """apply the merge action list to the working directory
 
     wctx is the working copy context
@@ -1590,7 +1822,7 @@
     mergeactions.extend(actions[ACTION_MERGE])
     for f, args, msg in mergeactions:
         f1, f2, fa, move, anc = args
-        if f == '.hgsubstate': # merged internally
+        if f == '.hgsubstate':  # merged internally
             continue
         if f1 is None:
             fcl = filemerge.absentfilectx(wctx, fa)
@@ -1618,10 +1850,10 @@
             wctx[f].audit()
             wctx[f].remove()
 
-    numupdates = sum(len(l) for m, l in actions.items()
-                     if m != ACTION_KEEP)
-    progress = repo.ui.makeprogress(_('updating'), unit=_('files'),
-                                    total=numupdates)
+    numupdates = sum(len(l) for m, l in actions.items() if m != ACTION_KEEP)
+    progress = repo.ui.makeprogress(
+        _('updating'), unit=_('files'), total=numupdates
+    )
 
     if [a for a in actions[ACTION_REMOVE] if a[0] == '.hgsubstate']:
         subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
@@ -1630,8 +1862,13 @@
     for f, args, msg in actions[ACTION_PATH_CONFLICT]:
         f1, fo = args
         s = repo.ui.status
-        s(_("%s: path conflict - a file or link has the same name as a "
-            "directory\n") % f)
+        s(
+            _(
+                "%s: path conflict - a file or link has the same name as a "
+                "directory\n"
+            )
+            % f
+        )
         if fo == 'l':
             s(_("the local file has been renamed to %s\n") % f1)
         else:
@@ -1645,8 +1882,9 @@
     cost = 0 if wctx.isinmemory() else 0.001
 
     # remove in parallel (must come before resolving path conflicts and getting)
-    prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
-                         actions[ACTION_REMOVE])
+    prog = worker.worker(
+        repo.ui, cost, batchremove, (repo, wctx), actions[ACTION_REMOVE]
+    )
     for i, item in prog:
         progress.increment(step=i, item=item)
     removed = len(actions[ACTION_REMOVE])
@@ -1654,7 +1892,7 @@
     # resolve path conflicts (must come before getting)
     for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
         repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
-        f0, = args
+        (f0,) = args
         if wctx[f0].lexists():
             repo.ui.note(_("moving %s to %s\n") % (f0, f))
             wctx[f].audit()
@@ -1663,13 +1901,18 @@
         progress.increment(item=f)
 
     # get in parallel.
-    threadsafe = repo.ui.configbool('experimental',
-                                    'worker.wdir-get-thread-safe')
-    prog = worker.worker(repo.ui, cost, batchget,
-                         (repo, mctx, wctx, wantfiledata),
-                         actions[ACTION_GET],
-                         threadsafe=threadsafe,
-                         hasretval=True)
+    threadsafe = repo.ui.configbool(
+        'experimental', 'worker.wdir-get-thread-safe'
+    )
+    prog = worker.worker(
+        repo.ui,
+        cost,
+        batchget,
+        (repo, mctx, wctx, wantfiledata),
+        actions[ACTION_GET],
+        threadsafe=threadsafe,
+        hasretval=True,
+    )
     getfiledata = {}
     for final, res in prog:
         if final:
@@ -1726,7 +1969,7 @@
     for f, args, msg in actions[ACTION_EXEC]:
         repo.ui.debug(" %s: %s -> e\n" % (f, msg))
         progress.increment(item=f)
-        flags, = args
+        (flags,) = args
         wctx[f].audit()
         wctx[f].setflags('l' in flags, 'x' in flags)
         updated += 1
@@ -1738,8 +1981,9 @@
 
     if usemergedriver:
         if wctx.isinmemory():
-            raise error.InMemoryMergeConflictsError("in-memory merge does not "
-                                                    "support mergedriver")
+            raise error.InMemoryMergeConflictsError(
+                "in-memory merge does not " "support mergedriver"
+            )
         ms.commit()
         proceed = driverpreprocess(repo, ms, wctx, labels=labels)
         # the driver might leave some files unresolved
@@ -1747,8 +1991,9 @@
         if not proceed:
             # XXX setting unresolved to at least 1 is a hack to make sure we
             # error out
-            return updateresult(updated, merged, removed,
-                                max(len(unresolvedf), 1))
+            return updateresult(
+                updated, merged, removed, max(len(unresolvedf), 1)
+            )
         newactions = []
         for f, args, msg in mergeactions:
             if f in unresolvedf:
@@ -1761,9 +2006,10 @@
         for f, args, msg in mergeactions:
             repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
             progress.increment(item=f)
-            if f == '.hgsubstate': # subrepo states need updating
-                subrepoutil.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
-                                     overwrite, labels)
+            if f == '.hgsubstate':  # subrepo states need updating
+                subrepoutil.submerge(
+                    repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
+                )
                 continue
             wctx[f].audit()
             complete, r = ms.preresolve(f, wctx)
@@ -1782,8 +2028,11 @@
 
     unresolved = ms.unresolvedcount()
 
-    if (usemergedriver and not unresolved
-        and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS):
+    if (
+        usemergedriver
+        and not unresolved
+        and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS
+    ):
         if not driverconclude(repo, ms, wctx, labels=labels):
             # XXX setting unresolved to at least 1 is a hack to make sure we
             # error out
@@ -1823,13 +2072,15 @@
             # those lists aren't consulted again.
             mfiles.difference_update(a[0] for a in acts)
 
-        actions[ACTION_MERGE] = [a for a in actions[ACTION_MERGE]
-                                 if a[0] in mfiles]
+        actions[ACTION_MERGE] = [
+            a for a in actions[ACTION_MERGE] if a[0] in mfiles
+        ]
 
     progress.complete()
     assert len(getfiledata) == (len(actions[ACTION_GET]) if wantfiledata else 0)
     return updateresult(updated, merged, removed, unresolved), getfiledata
 
+
 def recordupdates(repo, actions, branchmerge, getfiledata):
     "record merge actions to the dirstate"
     # remove (must come first)
@@ -1845,7 +2096,7 @@
 
     # resolve path conflicts
     for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
-        f0, = args
+        (f0,) = args
         origf0 = repo.dirstate.copied(f0) or f0
         repo.dirstate.add(f)
         repo.dirstate.copy(origf0, f)
@@ -1888,7 +2139,7 @@
             # We've done a branch merge, mark this file as merged
             # so that we properly record the merger later
             repo.dirstate.merge(f)
-            if f1 != f2: # copy/rename
+            if f1 != f2:  # copy/rename
                 if move:
                     repo.dirstate.remove(f1)
                 if f1 != f:
@@ -1901,7 +2152,7 @@
             # of that file some time in the past. Thus our
             # merge will appear as a normal local file
             # modification.
-            if f2 == f: # file not locally copied/moved
+            if f2 == f:  # file not locally copied/moved
                 repo.dirstate.normallookup(f)
             if move:
                 repo.dirstate.drop(f1)
@@ -1926,14 +2177,26 @@
         else:
             repo.dirstate.normal(f)
 
+
 UPDATECHECK_ABORT = 'abort'  # handled at higher layers
 UPDATECHECK_NONE = 'none'
 UPDATECHECK_LINEAR = 'linear'
 UPDATECHECK_NO_CONFLICT = 'noconflict'
 
-def update(repo, node, branchmerge, force, ancestor=None,
-           mergeancestor=False, labels=None, matcher=None, mergeforce=False,
-           updatecheck=None, wc=None):
+
+def update(
+    repo,
+    node,
+    branchmerge,
+    force,
+    ancestor=None,
+    mergeancestor=False,
+    labels=None,
+    matcher=None,
+    mergeforce=False,
+    updatecheck=None,
+    wc=None,
+):
     """
     Perform a merge between the working directory and the given node
 
@@ -1998,15 +2261,22 @@
         # updatecheck='abort' to better suppport some of these callers.
         if updatecheck is None:
             updatecheck = UPDATECHECK_LINEAR
-        if updatecheck not in (UPDATECHECK_NONE,
-                               UPDATECHECK_LINEAR,
-                               UPDATECHECK_NO_CONFLICT,
+        if updatecheck not in (
+            UPDATECHECK_NONE,
+            UPDATECHECK_LINEAR,
+            UPDATECHECK_NO_CONFLICT,
         ):
-            raise ValueError(r'Invalid updatecheck %r (can accept %r)' % (
-                updatecheck, (UPDATECHECK_NONE,
-                               UPDATECHECK_LINEAR,
-                               UPDATECHECK_NO_CONFLICT,
-                )))
+            raise ValueError(
+                r'Invalid updatecheck %r (can accept %r)'
+                % (
+                    updatecheck,
+                    (
+                        UPDATECHECK_NONE,
+                        UPDATECHECK_LINEAR,
+                        UPDATECHECK_NO_CONFLICT,
+                    ),
+                )
+            )
     # If we're doing a partial update, we need to skip updating
     # the dirstate, so make a note of any partial-ness to the
     # update here.
@@ -2038,33 +2308,44 @@
                 raise error.Abort(_("outstanding uncommitted merge"))
             ms = mergestate.read(repo)
             if list(ms.unresolved()):
-                raise error.Abort(_("outstanding merge conflicts"),
-                                  hint=_("use 'hg resolve' to resolve"))
+                raise error.Abort(
+                    _("outstanding merge conflicts"),
+                    hint=_("use 'hg resolve' to resolve"),
+                )
         if branchmerge:
             if pas == [p2]:
-                raise error.Abort(_("merging with a working directory ancestor"
-                                   " has no effect"))
+                raise error.Abort(
+                    _(
+                        "merging with a working directory ancestor"
+                        " has no effect"
+                    )
+                )
             elif pas == [p1]:
                 if not mergeancestor and wc.branch() == p2.branch():
-                    raise error.Abort(_("nothing to merge"),
-                                     hint=_("use 'hg update' "
-                                            "or check 'hg heads'"))
+                    raise error.Abort(
+                        _("nothing to merge"),
+                        hint=_("use 'hg update' " "or check 'hg heads'"),
+                    )
             if not force and (wc.files() or wc.deleted()):
-                raise error.Abort(_("uncommitted changes"),
-                                 hint=_("use 'hg status' to list changes"))
+                raise error.Abort(
+                    _("uncommitted changes"),
+                    hint=_("use 'hg status' to list changes"),
+                )
             if not wc.isinmemory():
                 for s in sorted(wc.substate):
                     wc.sub(s).bailifchanged()
 
         elif not overwrite:
-            if p1 == p2: # no-op update
+            if p1 == p2:  # no-op update
                 # call the hooks and exit early
                 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
                 repo.hook('update', parent1=xp2, parent2='', error=0)
                 return updateresult(0, 0, 0, 0)
 
-            if (updatecheck == UPDATECHECK_LINEAR and
-                    pas not in ([p1], [p2])):  # nonlinear
+            if updatecheck == UPDATECHECK_LINEAR and pas not in (
+                [p1],
+                [p2],
+            ):  # nonlinear
                 dirty = wc.dirty(missing=True)
                 if dirty:
                     # Branching is a bit strange to ensure we do the minimal
@@ -2072,7 +2353,7 @@
                     foreground = obsutil.foreground(repo, [p1.node()])
                     # note: the <node> variable contains a random identifier
                     if repo[node].node() in foreground:
-                        pass # allow updating to successors
+                        pass  # allow updating to successors
                     else:
                         msg = _("uncommitted changes")
                         hint = _("commit or update --clean to discard changes")
@@ -2097,13 +2378,27 @@
 
         ### calculate phase
         actionbyfile, diverge, renamedelete = calculateupdates(
-            repo, wc, p2, pas, branchmerge, force, mergeancestor,
-            followcopies, matcher=matcher, mergeforce=mergeforce)
+            repo,
+            wc,
+            p2,
+            pas,
+            branchmerge,
+            force,
+            mergeancestor,
+            followcopies,
+            matcher=matcher,
+            mergeforce=mergeforce,
+        )
 
         if updatecheck == UPDATECHECK_NO_CONFLICT:
             for f, (m, args, msg) in actionbyfile.iteritems():
-                if m not in (ACTION_GET, ACTION_KEEP, ACTION_EXEC,
-                             ACTION_REMOVE, ACTION_PATH_CONFLICT_RESOLVE):
+                if m not in (
+                    ACTION_GET,
+                    ACTION_KEEP,
+                    ACTION_EXEC,
+                    ACTION_REMOVE,
+                    ACTION_PATH_CONFLICT_RESOLVE,
+                ):
                     msg = _("conflicting changes")
                     hint = _("commit or update --clean to discard changes")
                     raise error.Abort(msg, hint=hint)
@@ -2118,9 +2413,14 @@
             prompts['f'] = f
             if m == ACTION_CHANGED_DELETED:
                 if repo.ui.promptchoice(
-                    _("local%(l)s changed %(f)s which other%(o)s deleted\n"
-                      "use (c)hanged version or (d)elete?"
-                      "$$ &Changed $$ &Delete") % prompts, 0):
+                    _(
+                        "local%(l)s changed %(f)s which other%(o)s deleted\n"
+                        "use (c)hanged version or (d)elete?"
+                        "$$ &Changed $$ &Delete"
+                    )
+                    % prompts,
+                    0,
+                ):
                     actionbyfile[f] = (ACTION_REMOVE, None, 'prompt delete')
                 elif f in p1:
                     actionbyfile[f] = (ACTION_ADD_MODIFIED, None, 'prompt keep')
@@ -2129,12 +2429,23 @@
             elif m == ACTION_DELETED_CHANGED:
                 f1, f2, fa, move, anc = args
                 flags = p2[f2].flags()
-                if repo.ui.promptchoice(
-                    _("other%(o)s changed %(f)s which local%(l)s deleted\n"
-                      "use (c)hanged version or leave (d)eleted?"
-                      "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
-                    actionbyfile[f] = (ACTION_GET, (flags, False),
-                                       'prompt recreating')
+                if (
+                    repo.ui.promptchoice(
+                        _(
+                            "other%(o)s changed %(f)s which local%(l)s deleted\n"
+                            "use (c)hanged version or leave (d)eleted?"
+                            "$$ &Changed $$ &Deleted"
+                        )
+                        % prompts,
+                        0,
+                    )
+                    == 0
+                ):
+                    actionbyfile[f] = (
+                        ACTION_GET,
+                        (flags, False),
+                        'prompt recreating',
+                    )
                 else:
                     del actionbyfile[f]
 
@@ -2147,28 +2458,39 @@
 
         if not util.fscasesensitive(repo.path):
             # check collision between files only in p2 for clean update
-            if (not branchmerge and
-                (force or not wc.dirty(missing=True, branch=False))):
+            if not branchmerge and (
+                force or not wc.dirty(missing=True, branch=False)
+            ):
                 _checkcollision(repo, p2.manifest(), None)
             else:
                 _checkcollision(repo, wc.manifest(), actions)
 
         # divergent renames
         for f, fl in sorted(diverge.iteritems()):
-            repo.ui.warn(_("note: possible conflict - %s was renamed "
-                           "multiple times to:\n") % f)
+            repo.ui.warn(
+                _(
+                    "note: possible conflict - %s was renamed "
+                    "multiple times to:\n"
+                )
+                % f
+            )
             for nf in sorted(fl):
                 repo.ui.warn(" %s\n" % nf)
 
         # rename and delete
         for f, fl in sorted(renamedelete.iteritems()):
-            repo.ui.warn(_("note: possible conflict - %s was deleted "
-                           "and renamed to:\n") % f)
+            repo.ui.warn(
+                _(
+                    "note: possible conflict - %s was deleted "
+                    "and renamed to:\n"
+                )
+                % f
+            )
             for nf in sorted(fl):
                 repo.ui.warn(" %s\n" % nf)
 
         ### apply phase
-        if not branchmerge: # just jump to the new rev
+        if not branchmerge:  # just jump to the new rev
             fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
         if not partial and not wc.isinmemory():
             repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
@@ -2186,11 +2508,13 @@
         # We only allow on Linux and MacOS because that's where fsmonitor is
         # considered stable.
         fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused')
-        fsmonitorthreshold = repo.ui.configint('fsmonitor',
-                                               'warn_update_file_count')
+        fsmonitorthreshold = repo.ui.configint(
+            'fsmonitor', 'warn_update_file_count'
+        )
         try:
             # avoid cycle: extensions -> cmdutil -> merge
             from . import extensions
+
             extensions.find('fsmonitor')
             fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off'
             # We intentionally don't look at whether fsmonitor has disabled
@@ -2199,20 +2523,26 @@
         except KeyError:
             fsmonitorenabled = False
 
-        if (fsmonitorwarning
-                and not fsmonitorenabled
-                and p1.node() == nullid
-                and len(actions[ACTION_GET]) >= fsmonitorthreshold
-                and pycompat.sysplatform.startswith(('linux', 'darwin'))):
+        if (
+            fsmonitorwarning
+            and not fsmonitorenabled
+            and p1.node() == nullid
+            and len(actions[ACTION_GET]) >= fsmonitorthreshold
+            and pycompat.sysplatform.startswith(('linux', 'darwin'))
+        ):
             repo.ui.warn(
-                _('(warning: large working directory being used without '
-                  'fsmonitor enabled; enable fsmonitor to improve performance; '
-                  'see "hg help -e fsmonitor")\n'))
+                _(
+                    '(warning: large working directory being used without '
+                    'fsmonitor enabled; enable fsmonitor to improve performance; '
+                    'see "hg help -e fsmonitor")\n'
+                )
+            )
 
         updatedirstate = not partial and not wc.isinmemory()
         wantfiledata = updatedirstate and not branchmerge
-        stats, getfiledata = applyupdates(repo, actions, wc, p2, overwrite,
-                                          wantfiledata, labels=labels)
+        stats, getfiledata = applyupdates(
+            repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
+        )
 
         if updatedirstate:
             with repo.dirstate.parentchange():
@@ -2230,12 +2560,15 @@
         sparse.prunetemporaryincludes(repo)
 
     if not partial:
-        repo.hook('update', parent1=xp1, parent2=xp2,
-                  error=stats.unresolvedcount)
+        repo.hook(
+            'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
+        )
     return stats
 
-def graft(repo, ctx, pctx, labels=None, keepparent=False,
-          keepconflictparent=False):
+
+def graft(
+    repo, ctx, pctx, labels=None, keepparent=False, keepconflictparent=False
+):
     """Do a graft-like merge.
 
     This is a merge where the merge ancestor is chosen such that one
@@ -2259,9 +2592,15 @@
     # which local deleted".
     mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
 
-    stats = update(repo, ctx.node(), True, True, pctx.node(),
-                   mergeancestor=mergeancestor, labels=labels)
-
+    stats = update(
+        repo,
+        ctx.node(),
+        True,
+        True,
+        pctx.node(),
+        mergeancestor=mergeancestor,
+        labels=labels,
+    )
 
     if keepconflictparent and stats.unresolvedcount:
         pother = ctx.node()
@@ -2279,8 +2618,16 @@
         copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
     return stats
 
-def purge(repo, matcher, ignored=False, removeemptydirs=True,
-          removefiles=True, abortonerror=False, noop=False):
+
+def purge(
+    repo,
+    matcher,
+    ignored=False,
+    removeemptydirs=True,
+    removefiles=True,
+    abortonerror=False,
+    noop=False,
+):
     """Purge the working directory of untracked files.
 
     ``matcher`` is a matcher configured to scan the working directory -