# HG changeset patch # User Alexis S. L. Carvalho # Date 1215807183 25200 # Node ID fc13fef97a303e39dc1ad298758eec0f643fc1f4 # Parent e81d2bd669088aa22e3437c610d216500ffeb02c merge.py: don't ignore uncommitted copies (issue 1000) diff --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -363,7 +363,24 @@ if branchmerge: # We've done a branch merge, mark this file as merged # so that we properly record the merger later - repo.dirstate.merge(fd) + if repo.dirstate[fd] != 'a': + repo.dirstate.merge(fd) + else: + # This can only happen when fd was "a"dded and + # the user did a merge --force + copied = repo.dirstate.copied(fd) + if copied: + # fd was a copy from some other file. Make it + # look like a merge across a rename. The call + # to dirstate.copy is needed in the f == f2 + # case. In the f != f2 case we won't be able + # to store that fd is a copy of two files. + repo.dirstate.merge(fd) + repo.dirstate.copy(copied, fd) + else: + # No renames involved, file didn't exist in the + # local manifest. Mark it dirty. + repo.dirstate.normaldirty(fd) if f != f2: # copy/rename if move: repo.dirstate.remove(f) @@ -377,7 +394,11 @@ # of that file some time in the past. Thus our # merge will appear as a normal local file # modification. - repo.dirstate.normallookup(fd) + # If the file was an uncommitted copy, we don't + # change its state - we want it in state "a", and + # we don't want to lose the copy data. + if f == f2 or repo.dirstate[fd] != "a": + repo.dirstate.normallookup(fd) if move: repo.dirstate.forget(f) elif m == "d": # directory rename @@ -396,6 +417,15 @@ repo.dirstate.normal(fd) if f: repo.dirstate.forget(f) + + if repo.dirstate.parents()[1] == nullid: + # Make sure the source of every copy is still present in the dirstate + copies = repo.dirstate.copies() + for target, source in copies.items(): + if source not in repo.dirstate: + del copies[target] + repo.ui.debug(_("forgetting copy %s -> %s\n") + % (source, target)) def update(repo, node, branchmerge, force, partial): """ @@ -473,8 +503,8 @@ stats = applyupdates(repo, action, wc, p2) if not partial: + repo.dirstate.setparents(fp1, fp2) recordupdates(repo, action, branchmerge) - repo.dirstate.setparents(fp1, fp2) if not branchmerge and not fastforward: repo.dirstate.setbranch(p2.branch()) repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3]) diff --git a/tests/test-merge-force b/tests/test-merge-force --- a/tests/test-merge-force +++ b/tests/test-merge-force @@ -25,3 +25,36 @@ hg ci -m merge echo % manifest. should not have a: hg manifest + +echo '% local added some files' +echo foo > foo +echo bar > bar +hg ci -qAm 'add foo bar' + +echo quux > quux +hg mv foo foo1 +echo 1 >> foo1 +echo 1 >> bar +hg ci -qAm 'add quux; mv foo foo1' + +hg up -C -- -2 +echo 2 >> foo +hg mv bar bar1 +echo >> bar1 +hg ci -m 'change foo; mv bar bar1' + +hg mv foo foo1 +hg mv bar1 bar2 +echo quux2 > quux +hg add quux +hg merge +HGMERGE=internal:local hg merge --debug --force + +echo +echo '% quux should have size == -2 (i.e. dirty)' +echo '% foo1 and bar2 should be in state "m"' +echo '% foo1 is a copy from foo' +echo '% bar2 is a copy from either bar1 or bar' +echo '% (ideally from both, but that is not possible right now)' +hg debugstate | grep -v '^copy' | cut -b 1-16,37- +hg debugstate | grep copy diff --git a/tests/test-merge-force.out b/tests/test-merge-force.out --- a/tests/test-merge-force.out +++ b/tests/test-merge-force.out @@ -9,3 +9,41 @@ R a % manifest. should not have a: b +% local added some files +2 files updated, 0 files merged, 2 files removed, 0 files unresolved +created new head +abort: outstanding uncommitted changes +resolving manifests + overwrite False partial False + ancestor 075212827c76 local 02f30eb0b544+ remote 082f5c4c43e8 + searching for copies back to rev 5 + unmatched files in local: + bar2 + all copies found (* = to merge, ! = divergent): + bar2 -> bar1 * + checking for directory renames + quux: versions differ -> m + bar2: local moved to bar -> m + foo1: versions differ -> m +preserving bar2 for resolve of bar2 +preserving foo1 for resolve of foo1 +preserving quux for resolve of quux +picked tool 'internal:local' for bar2 (binary False symlink False) +picked tool 'internal:local' for foo1 (binary False symlink False) +picked tool 'internal:local' for quux (binary False symlink False) +0 files updated, 3 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) + +% quux should have size == -2 (i.e. dirty) +% foo1 and bar2 should be in state "m" +% foo1 is a copy from foo +% bar2 is a copy from either bar1 or bar +% (ideally from both, but that is not possible right now) +n 644 2 b +r 0 0 bar1 +m 644 5 bar2 +r 0 0 foo +m 644 6 foo1 +n 0 -2 quux +copy: bar -> bar2 +copy: foo -> foo1 diff --git a/tests/test-up-local-change b/tests/test-up-local-change --- a/tests/test-up-local-change +++ b/tests/test-up-local-change @@ -66,3 +66,25 @@ hg add a hg pull -u ../a hg st + +# test local copies +cd .. +hg init copy +cd copy +echo foo > foo +echo bar > bar +hg ci -qAm 'add foo bar' + +echo >> foo +hg rm bar +hg ci -m 'change foo; rm bar' + +hg up -qC 0 +hg mv foo foo2 +hg mv bar bar2 +hg st -C + +hg up +echo '% foo2 should still be a copy of foo; bar2 should not be a copy' +hg st -A +hg diff --git diff --git a/tests/test-up-local-change.out b/tests/test-up-local-change.out --- a/tests/test-up-local-change.out +++ b/tests/test-up-local-change.out @@ -151,3 +151,25 @@ adding file changes added 1 changesets with 1 changes to 1 files 1 files updated, 0 files merged, 0 files removed, 0 files unresolved +A bar2 + bar +A foo2 + foo +R bar +R foo +merging foo2 and foo to foo2 +0 files updated, 1 files merged, 0 files removed, 0 files unresolved +% foo2 should still be a copy of foo; bar2 should not be a copy +A bar2 +A foo2 + foo +R foo +diff --git a/bar2 b/bar2 +new file mode 100644 +--- /dev/null ++++ b/bar2 +@@ -0,0 +1,1 @@ ++bar +diff --git a/foo b/foo2 +rename from foo +rename to foo2