Commit 4a09e95d authored by Martin von Zweigbergk's avatar Martin von Zweigbergk
Browse files

rewriteutil: fix broken touch of merge commits (issue6416)

`rewriteutil.rewrite()` is for rewriting a linear chain of commits
into a single commit, i.e. what `hg fold` does. Many of the callers
passed in a single commit because they wanted to rewrite just a single
commit (e.g. `hg touch`). Before this patch, the code worked by going
through the commits to fold and adding up all the modified files in
them, then building a `memctx` based on that. As reported in
issue6416, that can lose changes in merge commits. We could probably
fix that without changing the existing code too much, but it seems the
easiest way is to rewrite the code so it instead creates the new
commit by effectively checking out the base and then revert to the
head of the chain, so that's what this patch does. It does so by using
in-memory merge.
parent bf48247af25a
......@@ -6,6 +6,11 @@
* next: remove duplicated targets when updating from an unstable changeset
* evolve: use "served" repo filter to guess what the server will publish
* touch/fold/metaedit/rewind: no longer lose changes from merge commits
(issue6416). As a consequence (for technical reasons), when run with
Mercurial 5.5 and earlier, these commands now require there to be no
unresolved conflicts.
topic (0.22.2)
......
......@@ -19,6 +19,7 @@
error,
hg,
lock as lockmod,
mergeutil,
node,
obsolete,
obsutil,
......@@ -205,9 +206,17 @@
revs = sorted(revs)
return repomarks, revs
try:
from mercural import mergestate
mergestate.memmergestate
hasmemmergestate = True
except (ImportError, AttributeError):
# hg <= 5.5 (19590b126764)
hasmemmergestate = False
def rewrite(repo, old, updates, head, newbases, commitopts):
"""Return (nodeid, created) where nodeid is the identifier of the
changeset generated by the rewrite process, and created is True if
nodeid was actually created. If created is False, nodeid
references a changeset existing before the rewrite call.
"""
......@@ -208,9 +217,17 @@
def rewrite(repo, old, updates, head, newbases, commitopts):
"""Return (nodeid, created) where nodeid is the identifier of the
changeset generated by the rewrite process, and created is True if
nodeid was actually created. If created is False, nodeid
references a changeset existing before the rewrite call.
"""
# Until there was memmergestate, in-memory would clear the on-disk
# mergestate and use that. We don't want that to happen, so we'll require
# users of old Mercurial versions to run `hg touch` etc without
# mergestate.
if not hasmemmergestate:
ms = compat.mergestate.read(repo)
mergeutil.checkunresolved(ms)
wlock = lock = tr = None
try:
wlock = repo.wlock()
......@@ -219,39 +236,6 @@
base = old.p1()
updatebookmarks = bookmarksupdater(repo, old.node(), tr)
# commit a new version of the old changeset, including the update
# collect all files which might be affected
files = set(old.files())
for u in updates:
files.update(u.files())
# Recompute copies (avoid recording a -> b -> a)
copied = copies.pathcopies(base, head)
# prune files which were reverted by the updates
def samefile(f):
if f in head.manifest():
a = head.filectx(f)
if f in base.manifest():
b = base.filectx(f)
return (a.data() == b.data()
and a.flags() == b.flags())
else:
return False
else:
return f not in base.manifest()
files = [f for f in files if not samefile(f)]
# commit version of these files as defined by head
headmf = head.manifest()
def filectxfn(repo, ctx, path):
if path in headmf:
fctx = head[path]
flags = fctx.flags()
mctx = compat.memfilectx(repo, ctx, fctx, flags, copied, path)
return mctx
return None
message = cmdutil.logmessage(repo.ui, commitopts)
if not message:
message = old.description()
......@@ -264,16 +248,23 @@
extra = dict(commitopts.get(b'extra', old.extra()))
extra[b'branch'] = head.branch()
new = context.memctx(repo,
parents=newbases,
text=message,
files=files,
filectxfn=filectxfn,
user=user,
date=date,
extra=extra)
wctx = context.overlayworkingctx(repo)
wctx.setbase(base)
compat._update(repo,
head.node(),
branchmerge=False,
force=True,
updatedirstate=False,
wc=wctx)
for pctx in head.parents():
for dst, src in copies.pathcopies(pctx, head).items():
wctx[dst].markcopied(src)
new = wctx.tomemctx(text=message,
parents=newbases,
date=date,
extra=extra,
user=user)
if commitopts.get(b'edit'):
new._text = cmdutil.commitforceeditor(repo, new, [])
revcount = len(repo)
newid = repo.commitctx(new)
......@@ -276,8 +267,7 @@
if commitopts.get(b'edit'):
new._text = cmdutil.commitforceeditor(repo, new, [])
revcount = len(repo)
newid = repo.commitctx(new)
new = repo[newid]
created = len(repo) != revcount
updatebookmarks(newid)
......
......@@ -256,5 +256,4 @@
$ hg status --hidden --change 'min(desc("merge"))'
A right1
A right2
BROKEN: should be the same as "5"
$ hg status --hidden --change 'max(desc("merge"))'
......@@ -260,2 +259,3 @@
$ hg status --hidden --change 'max(desc("merge"))'
A right1
A right2
......@@ -261,3 +261,2 @@
A right2
BROKEN: There should be no difference
$ hg status --hidden --rev 'min(desc("merge"))' --rev 'max(desc("merge"))'
......@@ -263,5 +262,4 @@
$ hg status --hidden --rev 'min(desc("merge"))' --rev 'max(desc("merge"))'
R right1
$ cd ..
Check that touching a merge commit doesn't lose copies
......@@ -315,6 +313,6 @@
left -> merge-copy-left
$ hg debugpathcopies 'min(desc("right"))' 'min(desc("merge"))'
base -> copy-on-left
right -> merge-copy-right (missing-correct-output !)
right -> merge-copy-right
$ hg debugpathcopies 'min(desc("right"))' 'max(desc("merge"))'
base -> copy-on-left
......@@ -319,6 +317,6 @@
$ hg debugpathcopies 'min(desc("right"))' 'max(desc("merge"))'
base -> copy-on-left
right -> merge-copy-right (missing-correct-output !)
right -> merge-copy-right
$ cd ..
Make sure touch doesn't fail to warn about divergence (issue6107)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment