--- a/mercurial/commit.py Mon Jul 06 23:14:52 2020 +0200
+++ b/mercurial/commit.py Tue Jul 07 00:18:15 2020 +0200
@@ -11,10 +11,13 @@
from .i18n import _
from .node import (
hex,
+ nullid,
nullrev,
)
from . import (
+ context,
+ mergestate,
metadata,
phases,
scmutil,
@@ -98,8 +101,8 @@
removed.append(f)
else:
added.append(f)
- m[f], is_touched = repo._filecommit(
- fctx, m1, m2, linkrev, trp, writefilecopymeta,
+ m[f], is_touched = _filecommit(
+ repo, fctx, m1, m2, linkrev, trp, writefilecopymeta,
)
if is_touched:
touched.append(f)
@@ -213,3 +216,139 @@
# 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