Mercurial > evolve
changeset 5512:2df762b9c0c5 stable
dirstate: replace _uncommitdirstate() by core's movedirstate()
IIRC, when _uncommitdirstate() was upstreamed, it got cleaned up a
lot. Let's switch to the simpler upstream version.
This fixes the brokeness in `hg uncommit` demonstrated by the previous
patch. The problem was that _uncommitdirstate() had not been properly
updated to support uncommitting to another commit. If you replace
`oldctx.p1()` by `ctx` there, you would get this same diff as from
this patch (the `interactive` code path had been fixed this way
already).
author | Martin von Zweigbergk <martinvonz@google.com> |
---|---|
date | Wed, 26 Aug 2020 00:05:52 -0700 |
parents | 80b5d4b85a52 |
children | 13e589fb61f5 |
files | CHANGELOG hgext3rd/evolve/cmdrewrite.py hgext3rd/evolve/evolvecmd.py tests/test-uncommit.t |
diffstat | 4 files changed, 46 insertions(+), 169 deletions(-) [+] |
line wrap: on
line diff
--- a/CHANGELOG Thu Sep 03 09:21:58 2020 -0700 +++ b/CHANGELOG Wed Aug 26 00:05:52 2020 -0700 @@ -11,6 +11,7 @@ into more than one commit * revset: no longer changeset without topic when running `topic(REVSET)` + * uncommit: fix situation where added file would be left in a wrong state 10.0.1 -- 2020-07-31 --------------------
--- a/hgext3rd/evolve/cmdrewrite.py Thu Sep 03 09:21:58 2020 -0700 +++ b/hgext3rd/evolve/cmdrewrite.py Wed Aug 26 00:05:52 2020 -0700 @@ -341,100 +341,49 @@ newid = repo.commitctx(new) return newid -def _uncommitdirstate(repo, oldctx, match, interactive): - """Fix the dirstate after switching the working directory from - oldctx to a copy of oldctx not containing changed files matched by - match. +# TODO: call core's version once we've dropped support for hg <= 4.9 +def movedirstate(repo, newctx, match=None): + """Move the dirstate to newctx and adjust it as necessary. + + A matcher can be provided as an optimization. It is probably a bug to pass + a matcher that doesn't match all the differences between the parent of the + working copy and newctx. """ - ctx = repo[b'.'] + oldctx = repo[b'.'] ds = repo.dirstate - copies = dict(ds.copies()) - if interactive: - # In interactive cases, we will find the status between oldctx and ctx - # and considering only the files which are changed between oldctx and - # ctx, and the status of what changed between oldctx and ctx will help - # us in defining the exact behavior - st = repo.status(oldctx, ctx, match=match) - for f in st.modified: - # These are files which are modified between oldctx and ctx which - # contains two cases: 1) Were modified in oldctx and some - # modifications are uncommitted - # 2) Were added in oldctx but some part is uncommitted (this cannot - # contain the case when added files are uncommitted completely as - # that will result in status as removed not modified.) - # Also any modifications to a removed file will result the status as - # added, so we have only two cases. So in either of the cases, the - # resulting status can be modified or clean. - if ds[f] == b'r': - # But the file is removed in the working directory, leaving that - # as removed - continue + dscopies = dict(ds.copies()) + ds.setparents(newctx.node(), node.nullid) + s = newctx.status(oldctx, match=match) + for f in s.modified: + if ds[f] == b'r': + # modified + removed -> removed + continue + ds.normallookup(f) + + for f in s.added: + if ds[f] == b'r': + # added + removed -> unknown + ds.drop(f) + elif ds[f] != b'a': + ds.add(f) + + for f in s.removed: + if ds[f] == b'a': + # removed + added -> normal ds.normallookup(f) - - for f in st.added: - # These are the files which are added between oldctx and ctx(new - # one), which means the files which were removed in oldctx - # but uncommitted completely while making the ctx - # This file should be marked as removed if the working directory - # does not adds it back. If it's adds it back, we do a normallookup. - # The file can't be removed in working directory, because it was - # removed in oldctx - if ds[f] == b'a': - ds.normallookup(f) - continue + elif ds[f] != b'r': ds.remove(f) - for f in st.removed: - # These are files which are removed between oldctx and ctx, which - # means the files which were added in oldctx and were completely - # uncommitted in ctx. If a added file is partially uncommitted, that - # would have resulted in modified status, not removed. - # So a file added in a commit, and uncommitting that addition must - # result in file being stated as unknown. - if ds[f] == b'r': - # The working directory say it's removed, so lets make the file - # unknown - ds.drop(f) - continue - ds.add(f) - else: - st = repo.status(oldctx.p1(), oldctx, match=match) - for f in st.modified: - if ds[f] == b'r': - # modified + removed -> removed - continue - ds.normallookup(f) - - for f in st.added: - if ds[f] == b'r': - # added + removed -> unknown - ds.drop(f) - elif ds[f] != b'a': - ds.add(f) - - for f in st.removed: - if ds[f] == b'a': - # removed + added -> normal - ds.normallookup(f) - elif ds[f] != b'r': - ds.remove(f) - # Merge old parent and old working dir copies - oldcopies = {} - if interactive: - # Interactive had different meaning of the variables so restoring the - # original meaning to use them - st = repo.status(oldctx.p1(), oldctx, match=match) - for f in (st.modified + st.added): - src = oldctx[f].renamed() - if src: - oldcopies[f] = src[0] - oldcopies.update(copies) - copies = dict((dst, oldcopies.get(src, src)) - for dst, src in oldcopies.items()) + oldcopies = copies.pathcopies(newctx, oldctx, match) + oldcopies.update(dscopies) + newcopies = { + dst: oldcopies.get(src, src) + for dst, src in oldcopies.items() + } # Adjust the dirstate copies - for dst, src in copies.items(): - if (src not in ctx or dst in ctx or ds[dst] != b'a'): + for dst, src in newcopies.items(): + if src not in newctx or dst in newctx or ds[dst] != b'a': src = None ds.copy(src, dst) @@ -567,8 +516,7 @@ hg.updaterepo(repo, newid, True) else: with repo.dirstate.parentchange(), compat.parentchange(repo): - repo.dirstate.setparents(newid, node.nullid) - _uncommitdirstate(repo, old, match, interactive) + movedirstate(repo, repo[newid], match) if not repo[newid].files(): ui.warn(_(b"new changeset is empty\n")) ui.status(_(b"(use 'hg prune .' to remove it)\n"))
--- a/hgext3rd/evolve/evolvecmd.py Thu Sep 03 09:21:58 2020 -0700 +++ b/hgext3rd/evolve/evolvecmd.py Wed Aug 26 00:05:52 2020 -0700 @@ -642,8 +642,7 @@ otherdiv = repo[othernode] with repo.dirstate.parentchange(), compat.parentchange(repo): - repo.dirstate.setparents(publicnode, nodemod.nullid) - dirstatedance(repo, divergent, publicnode, None) + cmdrewrite.movedirstate(repo, repo[publicnode]) # check if node to be committed has changes same as public one s = publicdiv.status() if not (s.added or s.removed or s.deleted or s.modified): @@ -655,9 +654,7 @@ return (True, publicnode) with repo.dirstate.parentchange(), compat.parentchange(repo): - repo.dirstate.setparents(resparent, nodemod.nullid) - - dirstatedance(repo, divergent, resparent, None) + cmdrewrite.movedirstate(repo, repo[resparent]) # merge the branches mergebranches(repo, divergent, other, base) @@ -798,73 +795,6 @@ metadata=metadata, ui=repo.ui) repo.filteredrevcache.clear() -def dirstatedance(repo, oldparent, newparent, match): - """utility function to fix the dirstate when we change parents from - oldparent to newparent with a dirty working directory using - repo.dirstate.setparents() - - Lets refer oldparent as Pold - newparent as Pnew - - Now when we are on oldparent with a dirty working directory, there are three - types of files which we are concerned about. They are files having modified, - added and removed status. - - Lets refer modified files as Fm - added files as Fa - removed files as Fr - - Now, between Pold and Pnew, files can be modified, files can be added, files - can be removed. - - Lets refer modification of a file between Pold to Pnew as Cm - addition of a file between Pold to Pnew as Ca - removal of a file between Pold to Pnew as Cr - - Now let's play combinations and permutations: - - |---------------------------------------------------------------| - | Type of file | Changes between | End status with Pnew as | - | in wdir | Pold -> Pnew | wdir parent | - |--------------|------------------|-----------------------------| - | | | | - | Fm | Cm | Modified or clean | - |--------------|------------------|-----------------------------| - | Fm | Cr | Added | - |--------------|------------------|-----------------------------| - | Fm | Ca | Not possible (1) | - |--------------|------------------|-----------------------------| - | Fa | Ca | Modified or clean | - |--------------|------------------|-----------------------------| - | Fa | Cm | Not possible (2) | - |--------------|------------------|-----------------------------| - | Fa | Cr | Not possible (2) | - |--------------|------------------|-----------------------------| - | Fr | Cr | File should be untracked (3)| - |--------------|------------------|-----------------------------| - | Fr | Ca | Not possible (4) | - |--------------|------------------|-----------------------------| - | Fr | Cm | Removed | - |--------------|------------------|-----------------------------| - - (1): File is modified in wdir, it means file was present in Pold, so - addition of that file between Pold to Pnew is not possible - - (2): File was added in wdir, it means file was not present in Pold, so - deletion or modification of that file from Pold to Pnew is not possible - - (3): File should be dropped from the dirstate, Pnew has it removed, so no - need to mark that removed again - - (4): File was removed in wdir, it means file was present in Pold, so - addition of that file between Pold to Pnew is not possible - - """ - - # falling back to an existing function, in future we should have logic in - # this function only - cmdrewrite._uncommitdirstate(repo, oldparent, match, True) - def mergehook(repo, base, divergent, other): """function which extensions can wrap and merge data introduced by them while resolving content-divergence"""
--- a/tests/test-uncommit.t Thu Sep 03 09:21:58 2020 -0700 +++ b/tests/test-uncommit.t Wed Aug 26 00:05:52 2020 -0700 @@ -364,10 +364,9 @@ $ hg cat b --rev . b: no such file in rev 5b27f6b17da2 [1] -BROKEN: 'b' is no longer in the parent commit, so it should be marked 'A' $ hg status - M b A aa + A b Test uncommiting predecessors @@ -427,9 +426,9 @@ R m R n $ hg status + M b M d A aa - R b $ hg amend --extract j $ hg status --change . M o @@ -444,10 +443,10 @@ R m R n $ hg status + M b M d M j A aa - R b (with all) @@ -456,6 +455,7 @@ (use 'hg prune .' to remove it) $ hg status --change . $ hg status + M b M d M j M o @@ -465,7 +465,6 @@ A h A k A l - R b R c R f R g @@ -478,11 +477,10 @@ $ hg amend $ hg status - ? b $ hg diff -c . --stat aa | 1 + - b | 1 - + b | 1 + c | 1 - d | 1 + e | 1 + @@ -496,7 +494,7 @@ m | 1 - n | 1 - o | 1 + - 15 files changed, 9 insertions(+), 6 deletions(-) + 15 files changed, 10 insertions(+), 5 deletions(-) $ hg uncommit --revert --all new changeset is empty (use 'hg prune .' to remove it)