merge with stable
authorAugie Fackler <augie@google.com>
Wed, 22 Jul 2020 22:09:38 -0400
changeset 45205 4b923da0cb86
parent 45204 ce9ee81df9ff (diff)
parent 45191 fc54f52779dd (current diff)
child 45206 db0be4678399
merge with stable
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/commit.py	Wed Jul 22 22:09:38 2020 -0400
@@ -0,0 +1,354 @@
+# commit.py - fonction to perform commit
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+import errno
+import weakref
+
+from .i18n import _
+from .node import (
+    hex,
+    nullid,
+    nullrev,
+)
+
+from . import (
+    context,
+    mergestate,
+    metadata,
+    phases,
+    scmutil,
+    subrepoutil,
+)
+
+
+def commitctx(repo, ctx, error=False, origctx=None):
+    """Add a new revision to the target repository.
+    Revision information is passed via the context argument.
+
+    ctx.files() should list all files involved in this commit, i.e.
+    modified/added/removed files. On merge, it may be wider than the
+    ctx.files() to be committed, since any file nodes derived directly
+    from p1 or p2 are excluded from the committed ctx.files().
+
+    origctx is for convert to work around the problem that bug
+    fixes to the files list in changesets change hashes. For
+    convert to be the identity, it can pass an origctx and this
+    function will use the same files list when it makes sense to
+    do so.
+    """
+    repo = repo.unfiltered()
+
+    p1, p2 = ctx.p1(), ctx.p2()
+    user = ctx.user()
+
+    if repo.filecopiesmode == b'changeset-sidedata':
+        writechangesetcopy = True
+        writefilecopymeta = True
+        writecopiesto = None
+    else:
+        writecopiesto = repo.ui.config(b'experimental', b'copies.write-to')
+        writefilecopymeta = writecopiesto != b'changeset-only'
+        writechangesetcopy = writecopiesto in (
+            b'changeset-only',
+            b'compatibility',
+        )
+    p1copies, p2copies = None, None
+    if writechangesetcopy:
+        p1copies = ctx.p1copies()
+        p2copies = ctx.p2copies()
+    filesadded, filesremoved = None, None
+    with repo.lock(), repo.transaction(b"commit") as tr:
+        trp = weakref.proxy(tr)
+
+        if ctx.manifestnode():
+            # reuse an existing manifest revision
+            repo.ui.debug(b'reusing known manifest\n')
+            mn = ctx.manifestnode()
+            files = ctx.files()
+            if writechangesetcopy:
+                filesadded = ctx.filesadded()
+                filesremoved = ctx.filesremoved()
+        elif not ctx.files():
+            repo.ui.debug(b'reusing manifest from p1 (no file change)\n')
+            mn = p1.manifestnode()
+            files = []
+        else:
+            m1ctx = p1.manifestctx()
+            m2ctx = p2.manifestctx()
+            mctx = m1ctx.copy()
+
+            m = mctx.read()
+            m1 = m1ctx.read()
+            m2 = m2ctx.read()
+
+            # check in files
+            added = []
+            filesadded = []
+            removed = list(ctx.removed())
+            touched = []
+            linkrev = len(repo)
+            repo.ui.note(_(b"committing files:\n"))
+            uipathfn = scmutil.getuipathfn(repo)
+            for f in sorted(ctx.modified() + ctx.added()):
+                repo.ui.note(uipathfn(f) + b"\n")
+                try:
+                    fctx = ctx[f]
+                    if fctx is None:
+                        removed.append(f)
+                    else:
+                        added.append(f)
+                        m[f], is_touched = _filecommit(
+                            repo, fctx, m1, m2, linkrev, trp, writefilecopymeta,
+                        )
+                        if is_touched:
+                            touched.append(f)
+                            if writechangesetcopy and is_touched == 'added':
+                                filesadded.append(f)
+                        m.setflag(f, fctx.flags())
+                except OSError:
+                    repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
+                    raise
+                except IOError as inst:
+                    errcode = getattr(inst, 'errno', errno.ENOENT)
+                    if error or errcode and errcode != errno.ENOENT:
+                        repo.ui.warn(
+                            _(b"trouble committing %s!\n") % uipathfn(f)
+                        )
+                    raise
+
+            # update manifest
+            removed = [f for f in removed if f in m1 or f in m2]
+            drop = sorted([f for f in removed if f in m])
+            for f in drop:
+                del m[f]
+            if p2.rev() != nullrev:
+                rf = metadata.get_removal_filter(ctx, (p1, p2, m1, m2))
+                removed = [f for f in removed if not rf(f)]
+
+            touched.extend(removed)
+
+            if writechangesetcopy:
+                filesremoved = removed
+
+            files = touched
+            md = None
+            if not files:
+                # if no "files" actually changed in terms of the changelog,
+                # try hard to detect unmodified manifest entry so that the
+                # exact same commit can be reproduced later on convert.
+                md = m1.diff(m, scmutil.matchfiles(repo, ctx.files()))
+            if not files and md:
+                repo.ui.debug(
+                    b'not reusing manifest (no file change in '
+                    b'changelog, but manifest differs)\n'
+                )
+            if files or md:
+                repo.ui.note(_(b"committing manifest\n"))
+                # we're using narrowmatch here since it's already applied at
+                # other stages (such as dirstate.walk), so we're already
+                # ignoring things outside of narrowspec in most cases. The
+                # one case where we might have files outside the narrowspec
+                # at this point is merges, and we already error out in the
+                # case where the merge has files outside of the narrowspec,
+                # so this is safe.
+                mn = mctx.write(
+                    trp,
+                    linkrev,
+                    p1.manifestnode(),
+                    p2.manifestnode(),
+                    added,
+                    drop,
+                    match=repo.narrowmatch(),
+                )
+            else:
+                repo.ui.debug(
+                    b'reusing manifest from p1 (listed files '
+                    b'actually unchanged)\n'
+                )
+                mn = p1.manifestnode()
+
+        if writecopiesto == b'changeset-only':
+            # If writing only to changeset extras, use None to indicate that
+            # no entry should be written. If writing to both, write an empty
+            # entry to prevent the reader from falling back to reading
+            # filelogs.
+            p1copies = p1copies or None
+            p2copies = p2copies or None
+            filesadded = filesadded or None
+            filesremoved = filesremoved or None
+
+        if origctx and origctx.manifestnode() == mn:
+            files = origctx.files()
+
+        # update changelog
+        repo.ui.note(_(b"committing changelog\n"))
+        repo.changelog.delayupdate(tr)
+        n = repo.changelog.add(
+            mn,
+            files,
+            ctx.description(),
+            trp,
+            p1.node(),
+            p2.node(),
+            user,
+            ctx.date(),
+            ctx.extra().copy(),
+            p1copies,
+            p2copies,
+            filesadded,
+            filesremoved,
+        )
+        xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
+        repo.hook(
+            b'pretxncommit', throw=True, node=hex(n), parent1=xp1, parent2=xp2,
+        )
+        # set the new commit is proper phase
+        targetphase = subrepoutil.newcommitphase(repo.ui, ctx)
+        if targetphase:
+            # retract boundary do not alter parent changeset.
+            # if a parent have higher the resulting phase will
+            # be compliant anyway
+            #
+            # if minimal phase was 0 we don't need to retract anything
+            phases.registernew(repo, tr, targetphase, [n])
+        return n
+
+
+def _filecommit(
+    repo, fctx, manifest1, manifest2, linkrev, tr, includecopymeta,
+):
+    """
+    commit an individual file as part of a larger transaction
+
+    input:
+
+        fctx:       a file context with the content we are trying to commit
+        manifest1:  manifest of changeset first parent
+        manifest2:  manifest of changeset second parent
+        linkrev:    revision number of the changeset being created
+        tr:         current transation
+        individual: boolean, set to False to skip storing the copy data
+                    (only used by the Google specific feature of using
+                    changeset extra as copy source of truth).
+
+    output: (filenode, touched)
+
+        filenode: the filenode that should be used by this changeset
+        touched:  one of: None, 'added' or 'modified'
+    """
+
+    fname = fctx.path()
+    fparent1 = manifest1.get(fname, nullid)
+    fparent2 = manifest2.get(fname, nullid)
+    touched = None
+    if fparent1 == fparent2 == nullid:
+        touched = 'added'
+
+    if isinstance(fctx, context.filectx):
+        # This block fast path most comparisons which are usually done. It
+        # assumes that bare filectx is used and no merge happened, hence no
+        # need to create a new file revision in this case.
+        node = fctx.filenode()
+        if node in [fparent1, fparent2]:
+            repo.ui.debug(b'reusing %s filelog entry\n' % fname)
+            if (
+                fparent1 != nullid and manifest1.flags(fname) != fctx.flags()
+            ) or (
+                fparent2 != nullid and manifest2.flags(fname) != fctx.flags()
+            ):
+                touched = 'modified'
+            return node, touched
+
+    flog = repo.file(fname)
+    meta = {}
+    cfname = fctx.copysource()
+    fnode = None
+
+    if cfname and cfname != fname:
+        # Mark the new revision of this file as a copy of another
+        # file.  This copy data will effectively act as a parent
+        # of this new revision.  If this is a merge, the first
+        # parent will be the nullid (meaning "look up the copy data")
+        # and the second one will be the other parent.  For example:
+        #
+        # 0 --- 1 --- 3   rev1 changes file foo
+        #   \       /     rev2 renames foo to bar and changes it
+        #    \- 2 -/      rev3 should have bar with all changes and
+        #                      should record that bar descends from
+        #                      bar in rev2 and foo in rev1
+        #
+        # this allows this merge to succeed:
+        #
+        # 0 --- 1 --- 3   rev4 reverts the content change from rev2
+        #   \       /     merging rev3 and rev4 should use bar@rev2
+        #    \- 2 --- 4        as the merge base
+        #
+
+        cnode = manifest1.get(cfname)
+        newfparent = fparent2
+
+        if manifest2:  # branch merge
+            if fparent2 == nullid or cnode is None:  # copied on remote side
+                if cfname in manifest2:
+                    cnode = manifest2[cfname]
+                    newfparent = fparent1
+
+        # Here, we used to search backwards through history to try to find
+        # where the file copy came from if the source of a copy was not in
+        # the parent directory. However, this doesn't actually make sense to
+        # do (what does a copy from something not in your working copy even
+        # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
+        # the user that copy information was dropped, so if they didn't
+        # expect this outcome it can be fixed, but this is the correct
+        # behavior in this circumstance.
+
+        if cnode:
+            repo.ui.debug(b" %s: copy %s:%s\n" % (fname, cfname, hex(cnode)))
+            if includecopymeta:
+                meta[b"copy"] = cfname
+                meta[b"copyrev"] = hex(cnode)
+            fparent1, fparent2 = nullid, newfparent
+        else:
+            repo.ui.warn(
+                _(
+                    b"warning: can't find ancestor for '%s' "
+                    b"copied from '%s'!\n"
+                )
+                % (fname, cfname)
+            )
+
+    elif fparent1 == nullid:
+        fparent1, fparent2 = fparent2, nullid
+    elif fparent2 != nullid:
+        # is one parent an ancestor of the other?
+        fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
+        if fparent1 in fparentancestors:
+            fparent1, fparent2 = fparent2, nullid
+        elif fparent2 in fparentancestors:
+            fparent2 = nullid
+        elif not fparentancestors:
+            # TODO: this whole if-else might be simplified much more
+            ms = mergestate.mergestate.read(repo)
+            if (
+                fname in ms
+                and ms[fname] == mergestate.MERGE_RECORD_MERGED_OTHER
+            ):
+                fparent1, fparent2 = fparent2, nullid
+
+    # is the file changed?
+    text = fctx.data()
+    if fparent2 != nullid or meta or flog.cmp(fparent1, text):
+        if touched is None:  # do not overwrite added
+            touched = 'modified'
+        fnode = flog.add(text, meta, tr, linkrev, fparent1, fparent2)
+    # are just the flags changed during merge?
+    elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
+        touched = 'modified'
+        fnode = fparent1
+    else:
+        fnode = fparent1
+    return fnode, touched
--- a/mercurial/debugcommands.py	Tue Jul 21 01:04:19 2020 +0200
+++ b/mercurial/debugcommands.py	Wed Jul 22 22:09:38 2020 -0400
@@ -1668,8 +1668,8 @@
     fm.data(re2=bool(util._re2))
 
     # templates
-    p = templater.templatepaths()
-    fm.write(b'templatedirs', b'checking templates (%s)...\n', b' '.join(p))
+    p = templater.templatedir()
+    fm.write(b'templatedirs', b'checking templates (%s)...\n', p)
     fm.condwrite(not p, b'', _(b" no template directories found\n"))
     if p:
         m = templater.templatepath(b"map-cmdline.default")
--- a/mercurial/hgweb/hgwebdir_mod.py	Tue Jul 21 01:04:19 2020 +0200
+++ b/mercurial/hgweb/hgwebdir_mod.py	Wed Jul 22 22:09:38 2020 -0400
@@ -414,10 +414,8 @@
                     fname = req.qsparams[b'static']
                 static = self.ui.config(b"web", b"static", untrusted=False)
                 if not static:
-                    tp = self.templatepath or templater.templatepaths()
-                    if isinstance(tp, bytes):
-                        tp = [tp]
-                    static = [os.path.join(p, b'static') for p in tp]
+                    tp = self.templatepath or templater.templatedir()
+                    static = [os.path.join(tp, b'static')]
 
                 staticfile(static, fname, res)
                 return res.sendresponse()
--- a/mercurial/hgweb/webcommands.py	Tue Jul 21 01:04:19 2020 +0200
+++ b/mercurial/hgweb/webcommands.py	Wed Jul 22 22:09:38 2020 -0400
@@ -1319,10 +1319,8 @@
     # readable by the user running the CGI script
     static = web.config(b"web", b"static", untrusted=False)
     if not static:
-        tp = web.templatepath or templater.templatepaths()
-        if isinstance(tp, bytes):
-            tp = [tp]
-        static = [os.path.join(p, b'static') for p in tp]
+        tp = web.templatepath or templater.templatedir()
+        static = [os.path.join(tp, b'static')]
 
     staticfile(static, fname, web.res)
     return web.res.sendresponse()
--- a/mercurial/localrepo.py	Tue Jul 21 01:04:19 2020 +0200
+++ b/mercurial/localrepo.py	Wed Jul 22 22:09:38 2020 -0400
@@ -32,6 +32,7 @@
     bundle2,
     changegroup,
     color,
+    commit,
     context,
     dirstate,
     dirstateguard,
@@ -46,7 +47,6 @@
     match as matchmod,
     mergestate as mergestatemod,
     mergeutil,
-    metadata,
     namespaces,
     narrowspec,
     obsolete,
@@ -2771,140 +2771,6 @@
         """Returns the wlock if it's held, or None if it's not."""
         return self._currentlock(self._wlockref)
 
-    def _filecommit(
-        self,
-        fctx,
-        manifest1,
-        manifest2,
-        linkrev,
-        tr,
-        changelist,
-        includecopymeta,
-    ):
-        """
-        commit an individual file as part of a larger transaction
-
-        input:
-
-            fctx:       a file context with the content we are trying to commit
-            manifest1:  manifest of changeset first parent
-            manifest2:  manifest of changeset second parent
-            linkrev:    revision number of the changeset being created
-            tr:         current transation
-            changelist: list of file being changed (modified inplace)
-            individual: boolean, set to False to skip storing the copy data
-                        (only used by the Google specific feature of using
-                        changeset extra as copy source of truth).
-
-        output:
-
-            The resulting filenode
-        """
-
-        fname = fctx.path()
-        fparent1 = manifest1.get(fname, nullid)
-        fparent2 = manifest2.get(fname, nullid)
-        if isinstance(fctx, context.filectx):
-            node = fctx.filenode()
-            if node in [fparent1, fparent2]:
-                self.ui.debug(b'reusing %s filelog entry\n' % fname)
-                if (
-                    fparent1 != nullid
-                    and manifest1.flags(fname) != fctx.flags()
-                ) or (
-                    fparent2 != nullid
-                    and manifest2.flags(fname) != fctx.flags()
-                ):
-                    changelist.append(fname)
-                return node
-
-        flog = self.file(fname)
-        meta = {}
-        cfname = fctx.copysource()
-        if cfname and cfname != fname:
-            # Mark the new revision of this file as a copy of another
-            # file.  This copy data will effectively act as a parent
-            # of this new revision.  If this is a merge, the first
-            # parent will be the nullid (meaning "look up the copy data")
-            # and the second one will be the other parent.  For example:
-            #
-            # 0 --- 1 --- 3   rev1 changes file foo
-            #   \       /     rev2 renames foo to bar and changes it
-            #    \- 2 -/      rev3 should have bar with all changes and
-            #                      should record that bar descends from
-            #                      bar in rev2 and foo in rev1
-            #
-            # this allows this merge to succeed:
-            #
-            # 0 --- 1 --- 3   rev4 reverts the content change from rev2
-            #   \       /     merging rev3 and rev4 should use bar@rev2
-            #    \- 2 --- 4        as the merge base
-            #
-
-            cnode = manifest1.get(cfname)
-            newfparent = fparent2
-
-            if manifest2:  # branch merge
-                if fparent2 == nullid or cnode is None:  # copied on remote side
-                    if cfname in manifest2:
-                        cnode = manifest2[cfname]
-                        newfparent = fparent1
-
-            # Here, we used to search backwards through history to try to find
-            # where the file copy came from if the source of a copy was not in
-            # the parent directory. However, this doesn't actually make sense to
-            # do (what does a copy from something not in your working copy even
-            # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
-            # the user that copy information was dropped, so if they didn't
-            # expect this outcome it can be fixed, but this is the correct
-            # behavior in this circumstance.
-
-            if cnode:
-                self.ui.debug(
-                    b" %s: copy %s:%s\n" % (fname, cfname, hex(cnode))
-                )
-                if includecopymeta:
-                    meta[b"copy"] = cfname
-                    meta[b"copyrev"] = hex(cnode)
-                fparent1, fparent2 = nullid, newfparent
-            else:
-                self.ui.warn(
-                    _(
-                        b"warning: can't find ancestor for '%s' "
-                        b"copied from '%s'!\n"
-                    )
-                    % (fname, cfname)
-                )
-
-        elif fparent1 == nullid:
-            fparent1, fparent2 = fparent2, nullid
-        elif fparent2 != nullid:
-            # is one parent an ancestor of the other?
-            fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
-            if fparent1 in fparentancestors:
-                fparent1, fparent2 = fparent2, nullid
-            elif fparent2 in fparentancestors:
-                fparent2 = nullid
-            elif not fparentancestors:
-                # TODO: this whole if-else might be simplified much more
-                ms = mergestatemod.mergestate.read(self)
-                if (
-                    fname in ms
-                    and ms[fname] == mergestatemod.MERGE_RECORD_MERGED_OTHER
-                ):
-                    fparent1, fparent2 = fparent2, nullid
-
-        # is the file changed?
-        text = fctx.data()
-        if fparent2 != nullid or meta or flog.cmp(fparent1, text):
-            changelist.append(fname)
-            return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
-        # are just the flags changed during merge?
-        elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
-            changelist.append(fname)
-
-        return fparent1
-
     def checkcommitpatterns(self, wctx, match, status, fail):
         """check for commit arguments that aren't committable"""
         if match.isexact() or match.prefix():
@@ -3062,203 +2928,7 @@
 
     @unfilteredmethod
     def commitctx(self, ctx, error=False, origctx=None):
-        """Add a new revision to current repository.
-        Revision information is passed via the context argument.
-
-        ctx.files() should list all files involved in this commit, i.e.
-        modified/added/removed files. On merge, it may be wider than the
-        ctx.files() to be committed, since any file nodes derived directly
-        from p1 or p2 are excluded from the committed ctx.files().
-
-        origctx is for convert to work around the problem that bug
-        fixes to the files list in changesets change hashes. For
-        convert to be the identity, it can pass an origctx and this
-        function will use the same files list when it makes sense to
-        do so.
-        """
-
-        p1, p2 = ctx.p1(), ctx.p2()
-        user = ctx.user()
-
-        if self.filecopiesmode == b'changeset-sidedata':
-            writechangesetcopy = True
-            writefilecopymeta = True
-            writecopiesto = None
-        else:
-            writecopiesto = self.ui.config(b'experimental', b'copies.write-to')
-            writefilecopymeta = writecopiesto != b'changeset-only'
-            writechangesetcopy = writecopiesto in (
-                b'changeset-only',
-                b'compatibility',
-            )
-        p1copies, p2copies = None, None
-        if writechangesetcopy:
-            p1copies = ctx.p1copies()
-            p2copies = ctx.p2copies()
-        filesadded, filesremoved = None, None
-        with self.lock(), self.transaction(b"commit") as tr:
-            trp = weakref.proxy(tr)
-
-            if ctx.manifestnode():
-                # reuse an existing manifest revision
-                self.ui.debug(b'reusing known manifest\n')
-                mn = ctx.manifestnode()
-                files = ctx.files()
-                if writechangesetcopy:
-                    filesadded = ctx.filesadded()
-                    filesremoved = ctx.filesremoved()
-            elif ctx.files():
-                m1ctx = p1.manifestctx()
-                m2ctx = p2.manifestctx()
-                mctx = m1ctx.copy()
-
-                m = mctx.read()
-                m1 = m1ctx.read()
-                m2 = m2ctx.read()
-
-                # check in files
-                added = []
-                changed = []
-                removed = list(ctx.removed())
-                linkrev = len(self)
-                self.ui.note(_(b"committing files:\n"))
-                uipathfn = scmutil.getuipathfn(self)
-                for f in sorted(ctx.modified() + ctx.added()):
-                    self.ui.note(uipathfn(f) + b"\n")
-                    try:
-                        fctx = ctx[f]
-                        if fctx is None:
-                            removed.append(f)
-                        else:
-                            added.append(f)
-                            m[f] = self._filecommit(
-                                fctx,
-                                m1,
-                                m2,
-                                linkrev,
-                                trp,
-                                changed,
-                                writefilecopymeta,
-                            )
-                            m.setflag(f, fctx.flags())
-                    except OSError:
-                        self.ui.warn(
-                            _(b"trouble committing %s!\n") % uipathfn(f)
-                        )
-                        raise
-                    except IOError as inst:
-                        errcode = getattr(inst, 'errno', errno.ENOENT)
-                        if error or errcode and errcode != errno.ENOENT:
-                            self.ui.warn(
-                                _(b"trouble committing %s!\n") % uipathfn(f)
-                            )
-                        raise
-
-                # update manifest
-                removed = [f for f in removed if f in m1 or f in m2]
-                drop = sorted([f for f in removed if f in m])
-                for f in drop:
-                    del m[f]
-                if p2.rev() != nullrev:
-                    rf = metadata.get_removal_filter(ctx, (p1, p2, m1, m2))
-                    removed = [f for f in removed if not rf(f)]
-
-                files = changed + removed
-                md = None
-                if not files:
-                    # if no "files" actually changed in terms of the changelog,
-                    # try hard to detect unmodified manifest entry so that the
-                    # exact same commit can be reproduced later on convert.
-                    md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
-                if not files and md:
-                    self.ui.debug(
-                        b'not reusing manifest (no file change in '
-                        b'changelog, but manifest differs)\n'
-                    )
-                if files or md:
-                    self.ui.note(_(b"committing manifest\n"))
-                    # we're using narrowmatch here since it's already applied at
-                    # other stages (such as dirstate.walk), so we're already
-                    # ignoring things outside of narrowspec in most cases. The
-                    # one case where we might have files outside the narrowspec
-                    # at this point is merges, and we already error out in the
-                    # case where the merge has files outside of the narrowspec,
-                    # so this is safe.
-                    mn = mctx.write(
-                        trp,
-                        linkrev,
-                        p1.manifestnode(),
-                        p2.manifestnode(),
-                        added,
-                        drop,
-                        match=self.narrowmatch(),
-                    )
-
-                    if writechangesetcopy:
-                        filesadded = [
-                            f for f in changed if not (f in m1 or f in m2)
-                        ]
-                        filesremoved = removed
-                else:
-                    self.ui.debug(
-                        b'reusing manifest from p1 (listed files '
-                        b'actually unchanged)\n'
-                    )
-                    mn = p1.manifestnode()
-            else:
-                self.ui.debug(b'reusing manifest from p1 (no file change)\n')
-                mn = p1.manifestnode()
-                files = []
-
-            if writecopiesto == b'changeset-only':
-                # If writing only to changeset extras, use None to indicate that
-                # no entry should be written. If writing to both, write an empty
-                # entry to prevent the reader from falling back to reading
-                # filelogs.
-                p1copies = p1copies or None
-                p2copies = p2copies or None
-                filesadded = filesadded or None
-                filesremoved = filesremoved or None
-
-            if origctx and origctx.manifestnode() == mn:
-                files = origctx.files()
-
-            # update changelog
-            self.ui.note(_(b"committing changelog\n"))
-            self.changelog.delayupdate(tr)
-            n = self.changelog.add(
-                mn,
-                files,
-                ctx.description(),
-                trp,
-                p1.node(),
-                p2.node(),
-                user,
-                ctx.date(),
-                ctx.extra().copy(),
-                p1copies,
-                p2copies,
-                filesadded,
-                filesremoved,
-            )
-            xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
-            self.hook(
-                b'pretxncommit',
-                throw=True,
-                node=hex(n),
-                parent1=xp1,
-                parent2=xp2,
-            )
-            # set the new commit is proper phase
-            targetphase = subrepoutil.newcommitphase(self.ui, ctx)
-            if targetphase:
-                # retract boundary do not alter parent changeset.
-                # if a parent have higher the resulting phase will
-                # be compliant anyway
-                #
-                # if minimal phase was 0 we don't need to retract anything
-                phases.registernew(self, tr, targetphase, [n])
-            return n
+        return commit.commitctx(self, ctx, error=error, origctx=origctx)
 
     @unfilteredmethod
     def destroying(self):
--- a/mercurial/templater.py	Tue Jul 21 01:04:19 2020 +0200
+++ b/mercurial/templater.py	Wed Jul 22 22:09:38 2020 -0400
@@ -800,10 +800,10 @@
 
 
 def stylelist():
-    paths = templatepaths()
-    if not paths:
+    path = templatedir()
+    if not path:
         return _(b'no templates found, try `hg debuginstall` for more info')
-    dirlist = os.listdir(paths[0])
+    dirlist = os.listdir(path)
     stylelist = []
     for file in dirlist:
         split = file.split(b".")
@@ -823,7 +823,7 @@
         )
 
     base = os.path.dirname(mapfile)
-    conf = config.config(includepaths=templatepaths())
+    conf = config.config(includepaths=[templatedir()])
     conf.read(mapfile, remap={b'': b'templates'})
 
     cache = {}
@@ -837,15 +837,13 @@
 
         # fallback check in template paths
         if not os.path.exists(path):
-            for p in templatepaths():
-                p2 = util.normpath(os.path.join(p, val))
-                if os.path.isfile(p2):
-                    path = p2
-                    break
+            p2 = util.normpath(os.path.join(templatedir(), val))
+            if os.path.isfile(p2):
+                path = p2
+            else:
                 p3 = util.normpath(os.path.join(p2, b"map"))
                 if os.path.isfile(p3):
                     path = p3
-                    break
 
         cache, tmap, aliases = _readmapfile(path)
 
@@ -1045,26 +1043,21 @@
         return stream
 
 
-def templatepaths():
-    '''return locations used for template files.'''
-    pathsrel = [b'templates']
-    paths = [
-        os.path.normpath(os.path.join(resourceutil.datapath, f))
-        for f in pathsrel
-    ]
-    return [p for p in paths if os.path.isdir(p)]
+def templatedir():
+    '''return the directory used for template files, or None.'''
+    path = os.path.normpath(os.path.join(resourceutil.datapath, b'templates'))
+    return path if os.path.isdir(path) else None
 
 
 def templatepath(name):
     '''return location of template file. returns None if not found.'''
-    for p in templatepaths():
-        f = os.path.join(p, name)
-        if os.path.exists(f):
-            return f
+    f = os.path.join(templatedir(), name)
+    if f and os.path.exists(f):
+        return f
     return None
 
 
-def stylemap(styles, paths=None):
+def stylemap(styles, path=None):
     """Return path to mapfile for a given style.
 
     Searches mapfile in the following locations:
@@ -1073,10 +1066,8 @@
     3. templatepath/map
     """
 
-    if paths is None:
-        paths = templatepaths()
-    elif isinstance(paths, bytes):
-        paths = [paths]
+    if path is None:
+        path = templatedir()
 
     if isinstance(styles, bytes):
         styles = [styles]
@@ -1094,10 +1085,9 @@
         locations = [os.path.join(style, b'map'), b'map-' + style]
         locations.append(b'map')
 
-        for path in paths:
-            for location in locations:
-                mapfile = os.path.join(path, location)
-                if os.path.isfile(mapfile):
-                    return style, mapfile
+        for location in locations:
+            mapfile = os.path.join(path, location)
+            if os.path.isfile(mapfile):
+                return style, mapfile
 
-    raise RuntimeError(b"No hgweb templates found in %r" % paths)
+    raise RuntimeError(b"No hgweb templates found in %r" % path)
--- a/tests/test-annotate.t	Tue Jul 21 01:04:19 2020 +0200
+++ b/tests/test-annotate.t	Wed Jul 22 22:09:38 2020 -0400
@@ -479,26 +479,24 @@
 
   $ cat > ../legacyrepo.py <<EOF
   > from __future__ import absolute_import
-  > from mercurial import error, node
-  > def reposetup(ui, repo):
-  >     class legacyrepo(repo.__class__):
-  >         def _filecommit(self, fctx, manifest1, manifest2,
-  >                         linkrev, tr, changelist, includecopymeta):
-  >             fname = fctx.path()
-  >             text = fctx.data()
-  >             flog = self.file(fname)
-  >             fparent1 = manifest1.get(fname, node.nullid)
-  >             fparent2 = manifest2.get(fname, node.nullid)
-  >             meta = {}
-  >             copy = fctx.copysource()
-  >             if copy and copy != fname:
-  >                 raise error.Abort('copying is not supported')
-  >             if fparent2 != node.nullid:
-  >                 changelist.append(fname)
-  >                 return flog.add(text, meta, tr, linkrev,
-  >                                 fparent1, fparent2)
-  >             raise error.Abort('only merging is supported')
-  >     repo.__class__ = legacyrepo
+  > from mercurial import commit, error, extensions, node
+  > def _filecommit(orig, repo, fctx, manifest1, manifest2,
+  >                 linkrev, tr, includecopymeta):
+  >     fname = fctx.path()
+  >     text = fctx.data()
+  >     flog = repo.file(fname)
+  >     fparent1 = manifest1.get(fname, node.nullid)
+  >     fparent2 = manifest2.get(fname, node.nullid)
+  >     meta = {}
+  >     copy = fctx.copysource()
+  >     if copy and copy != fname:
+  >         raise error.Abort('copying is not supported')
+  >     if fparent2 != node.nullid:
+  >         return flog.add(text, meta, tr, linkrev,
+  >                         fparent1, fparent2), 'modified'
+  >     raise error.Abort('only merging is supported')
+  > def uisetup(ui):
+  >     extensions.wrapfunction(commit, '_filecommit', _filecommit)
   > EOF
 
   $ cat > baz <<EOF
--- a/tests/test-fastannotate-hg.t	Tue Jul 21 01:04:19 2020 +0200
+++ b/tests/test-fastannotate-hg.t	Wed Jul 22 22:09:38 2020 -0400
@@ -481,26 +481,25 @@
 and its ancestor by overriding "repo._filecommit".
 
   $ cat > ../legacyrepo.py <<EOF
-  > from mercurial import error, node
-  > def reposetup(ui, repo):
-  >     class legacyrepo(repo.__class__):
-  >         def _filecommit(self, fctx, manifest1, manifest2,
-  >                         linkrev, tr, changelist, includecopymeta):
-  >             fname = fctx.path()
-  >             text = fctx.data()
-  >             flog = self.file(fname)
-  >             fparent1 = manifest1.get(fname, node.nullid)
-  >             fparent2 = manifest2.get(fname, node.nullid)
-  >             meta = {}
-  >             copy = fctx.renamed()
-  >             if copy and copy[0] != fname:
-  >                 raise error.Abort('copying is not supported')
-  >             if fparent2 != node.nullid:
-  >                 changelist.append(fname)
-  >                 return flog.add(text, meta, tr, linkrev,
-  >                                 fparent1, fparent2)
-  >             raise error.Abort('only merging is supported')
-  >     repo.__class__ = legacyrepo
+  > from __future__ import absolute_import
+  > from mercurial import commit, error, extensions, node
+  > def _filecommit(orig, repo, fctx, manifest1, manifest2,
+  >                 linkrev, tr, includecopymeta):
+  >     fname = fctx.path()
+  >     text = fctx.data()
+  >     flog = repo.file(fname)
+  >     fparent1 = manifest1.get(fname, node.nullid)
+  >     fparent2 = manifest2.get(fname, node.nullid)
+  >     meta = {}
+  >     copy = fctx.copysource()
+  >     if copy and copy != fname:
+  >         raise error.Abort('copying is not supported')
+  >     if fparent2 != node.nullid:
+  >         return flog.add(text, meta, tr, linkrev,
+  >                         fparent1, fparent2), 'modified'
+  >     raise error.Abort('only merging is supported')
+  > def uisetup(ui):
+  >     extensions.wrapfunction(commit, '_filecommit', _filecommit)
   > EOF
 
   $ cat > baz <<EOF