[PATCH 10 of 11] graft: enable rotated-DAG copy tracing (issue4028)
Gábor STEFANIK
Gabor.STEFANIK at nng.com
Tue Oct 4 10:48:43 EDT 2016
>
--------------------------------------------------------------------------
This message, including its attachments, is confidential. For more information please read NNG's email policy here:
http://www.nng.com/emailpolicy/
By responding to this email you accept the email policy.
-----Original Message-----
> From: Mercurial-devel [mailto:mercurial-devel-bounces at mercurial-scm.org]
> On Behalf Of Gábor Stefanik
> Sent: Tuesday, October 4, 2016 4:40 PM
> To: mercurial-devel at mercurial-scm.org
> Subject: [PATCH 10 of 11] graft: enable rotated-DAG copy tracing (issue4028)
>
> # HG changeset patch
> # User G?bor Stefanik <gabor.stefanik at nng.com> # Date 1475512693 -7200
Alright, that didn't work either, sorry for the spam...
> # Mon Oct 03 18:38:13 2016 +0200
> # Node ID 9e59cd55604c5e30b38c66c502c8c982c01a4a01
> # Parent 5977d6569b2e22487134af7be281e5d60cc987f5
> graft: enable rotated-DAG copy tracing (issue4028)
>
> Graft performs a merge in a rotated DAG (with a false common ancestor),
> which must be taken into account when tracking copies. Find the real
> common ancestor in this case, and track copies between the real and false
> common ancestors in reverse.
>
> Using this change, when grafting a commit with a change to a file moved
> earlier on the graft's source branch, the change is merged as expected into
> the original
> (unmoved) file, rather than recreating it under its new name.
> It should also eventually make it possible to support cross-branch updates
> that preserve changes in a dirty working copy.
>
> diff -r 5977d6569b2e -r 9e59cd55604c mercurial/copies.py
> --- a/mercurial/copies.pyTue Oct 04 12:51:54 2016 +0200
> +++ b/mercurial/copies.pyMon Oct 03 18:38:13 2016 +0200
> @@ -321,7 +321,23 @@
> if repo.ui.configbool('experimental', 'disablecopytrace'):
> return {}, {}, {}, {}
>
> - dirtyc1 = False # dummy bool for later use
> + # In certain scenarios (e.g. graft, update or rebase), ca can be overridden
> + # We still need to know a real common ancestor in this case
> + # We can't just compute _c1.ancestor(_c2) and compare it to ca, because
> + # there can be multiple common ancestors, e.g. in case of bidmerge.
> + # Because our caller may not know if the revision passed in lieu of the CA
> + # is a genuine common ancestor or not without explicitly checking it, it's
> + # better to determine that here.
> + tca = ca
> + # ca.descendant(wc) and ca.descendant(ca) are False, work around that
> + _c1 = c1.p1() if c1.rev() is None else c1
> + _c2 = c2.p1() if c2.rev() is None else c2
> + dirtyc1 = not (ca == _c1 or ca.descendant(_c1))
> + dirtyc2 = not (ca == _c2 or ca.descendant(_c2))
> + graft = dirtyc1 or dirtyc2
> + if graft:
> + tca = _c1.ancestor(_c2)
> +
> limit = _findlimit(repo, c1.rev(), c2.rev())
> if limit is None:
> # no common ancestor, no copies @@ -331,6 +347,7 @@
> m1 = c1.manifest()
> m2 = c2.manifest()
> ma = ca.manifest()
> + mta = tca.manifest()
>
> # see _checkcopies documentation below for these dicts
> copy1, copy2 = {}, {}
> @@ -340,18 +357,26 @@
> diverge, incompletediverge = {}, {}
>
> # find interesting file sets from manifests
> + if graft:
> + repo.ui.debug(" computing unmatched files in rotated DAG\n")
> addedinm1 = m1.filesnotin(ma)
> addedinm2 = m2.filesnotin(ma)
> u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
> - u1u, u2u = u1r, u2r
> + if not graft:
> + u1u, u2u = u1r, u2r
> + else: # need to recompute this for directory move handling when grafting
> + repo.ui.debug(" computing unmatched files in unrotated DAG\n")
> + u1u, u2u = _computenonoverlap(repo, c1, c2, m1.filesnotin(mta),
> + m2.filesnotin(mta))
> +
> bothnew = sorted(addedinm1 & addedinm2)
>
> for f in u1u:
> - _checkcopies(c1, f, m1, m2, ca, ca, False, limit, diverge, copy1,
> + _checkcopies(c1, f, m1, m2, ca, tca, dirtyc1, limit, diverge,
> + copy1,
> fullcopy1, incomplete1, incompletediverge)
>
> for f in u2u:
> - _checkcopies(c2, f, m2, m1, ca, ca, False, limit, diverge, copy2,
> + _checkcopies(c2, f, m2, m1, ca, tca, dirtyc2, limit, diverge,
> + copy2,
> fullcopy2, incomplete2, incompletediverge)
>
> copy = dict(copy1.items() + copy2.items()) @@ -401,9 +426,9 @@
> # reset incomplete dicts for bothdiverge generation
> incomplete1, incomplete2, incompletediverge = {}, {}, {}
> for f in bothnew:
> - _checkcopies(c1, f, m1, m2, ca, ca, False, limit, bothdiverge, _copy,
> + _checkcopies(c1, f, m1, m2, ca, tca, dirtyc1, limit,
> + bothdiverge, _copy,
> _fullcopy, incomplete1, incompletediverge)
> - _checkcopies(c2, f, m2, m1, ca, ca, False, limit, bothdiverge, _copy,
> + _checkcopies(c2, f, m2, m1, ca, tca, dirtyc2, limit,
> + bothdiverge, _copy,
> _fullcopy, incomplete2, incompletediverge)
> if dirtyc1:
> assert incomplete2 == {}
> diff -r 5977d6569b2e -r 9e59cd55604c tests/test-graft.t
> --- a/tests/test-graft.tTue Oct 04 12:51:54 2016 +0200
> +++ b/tests/test-graft.tMon Oct 03 18:38:13 2016 +0200
> @@ -179,6 +179,13 @@
> committing changelog
> grafting 5:97f8bfe72746 "5"
> searching for copies back to rev 1
> + computing unmatched files in rotated DAG
> + computing unmatched files in unrotated DAG
> + unmatched files in other:
> + c
> + all copies found (* = to merge, ! = divergent, % = renamed and deleted):
> + src: 'c' -> dst: 'b' *
> + checking for directory renames
> resolving manifests
> branchmerge: True, force: True, partial: False
> ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746 @@ -
> 193,6 +200,13 @@
> scanning for duplicate grafts
> grafting 4:9c233e8e184d "4"
> searching for copies back to rev 1
> + computing unmatched files in rotated DAG
> + computing unmatched files in unrotated DAG
> + unmatched files in other:
> + c
> + all copies found (* = to merge, ! = divergent, % = renamed and deleted):
> + src: 'c' -> dst: 'b' *
> + checking for directory renames
> resolving manifests
> branchmerge: True, force: True, partial: False
> ancestor: 4c60f11aa304, local: 1905859650ec+, remote: 9c233e8e184d @@ -
> 842,3 +856,431 @@
> |/
> o 0
>
> +Graft from behind a move or rename
> +==================================
> +
> +NOTE: This is affected by issue5343, and will need updating when it's
> +fixed
> +
> +Possible cases during a regular graft (when ca is between cta and c2):
> +
> +name | c1<-cta | cta<->ca | ca->c2
> +A.0 | | |
> +A.1 | X | |
> +A.2 | | X |
> +A.3 | | | X
> +A.4 | X | X |
> +A.5 | X | | X
> +A.6 | | X | X
> +A.7 | X | X | X
> +
> +A.0 is trivial, and doesn't need copy tracking.
> +For A.1, a forward rename is recorded in the c1 pass, to be followed later.
> +In A.2, the rename is recorded in the c2 pass and followed backwards.
> +A.3 is recorded in the c2 pass as a forward rename to be duplicated on
> target.
> +In A.4, both passes of checkcopies record incomplete renames, which are
> +then joined in mergecopies to record a rename to be followed.
> +In A.5 and A.7, the c1 pass records an incomplete rename, while the c2
> +pass records an incomplete divergence. The incomplete rename is then
> +joined to the appropriate side of the incomplete divergence, and the
> +result is recorded as a divergence. The code doesn't distinguish at all
> +between these two cases, since the end result of them is the same: an
> +incomplete divergence joined with an incomplete rename into a
> divergence.
> +Finally, A.6 records a divergence entirely in the c2 pass.
> +
> +A.4 has a degenerate case a<-b<-a->a, where checkcopies isn't needed at
> all.
> +A.5 has a special case a<-b<-b->a, which is treated like a<-b->a in a merge.
> +A.6 has a special case a<-a<-b->a. Here, checkcopies will find a
> +spurious incomplete divergence, which is in fact complete. This is
> +handled later in mergecopies.
> +A.7 has 4 special cases: a<-b<-a->b (the "ping-pong" case), a<-b<-c->b,
> +a<-b<-a->c and a<-b<-c->a. Of these, only the "ping-pong" case is
> +interesting, the others are fairly trivial (a<-b<-c->b and a<-b<-a->c
> +proceed like the base case, a<-b<-c->a is treated the same as a<-b<-b->a).
> +
> +f5a therefore tests the "ping-pong" rename case, where a file is
> +renamed to the same name on both branches, then the rename is backed
> +out on one branch, and the backout is grafted to the other branch. This
> +creates a challenging rename sequence of a<-b<-a->b in the graft
> +target, topological CA, graft CA and graft source, respectively. Since
> +rename detection will run on the c1 side for such a sequence (as for
> +technical reasons, we split the c1 and c2 sides not at the graft CA,
> +but rather at the topological CA), it will pick up a false rename, and
> +cause a spurious merge conflict. This false rename is always exactly
> +the reverse of the true rename that would be detected on the c2 side, so
> we can correct for it by detecting this condition and reversing as necessary.
> +
> +First, set up the repository with commits to be grafted
> +
> + $ hg init ../graftmove
> + $ cd ../graftmove
> + $ echo c1a > f1a
> + $ echo c2a > f2a
> + $ echo c3a > f3a
> + $ echo c4a > f4a
> + $ echo c5a > f5a
> + $ hg ci -qAm a
> + $ hg mv f1a f1b
> + $ hg mv f3a f3b
> + $ hg mv f5a f5b
> + $ hg ci -qAm b
> + $ echo c1c > f1b
> + $ hg mv f2a f2c
> + $ hg mv f5b f5a
> + $ echo c5c > f5a
> + $ hg ci -qAm c
> + $ hg mv f3b f3d
> + $ echo c4d > f4a
> + $ hg ci -qAm d
> + $ hg log -G
> + @ changeset: 3:aa2584f6dee9
> + | tag: tip
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: d
> + |
> + o changeset: 2:c8d3926d7649
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: c
> + |
> + o changeset: 1:7e9aff31b586
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: b
> + |
> + o changeset: 0:3340a7726e9e
> + user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: a
> +
> +
> +Test the cases A.2 (f1x), A.3 (f2x) and a special case of A.6 (f5x)
> +where the two renames actually converge to the same name (thus no
> actual divergence).
> +
> + $ hg up -q 0 # commit "a"
> + $ hg graft -r 2
> + grafting 2:c8d3926d7649 "c"
> + merging f1a and f1b to f1a
> + merging f5a
> + warning: can't find ancestor for 'f5a' copied from 'f5b'!
> + $ hg status --change .
> + M f1a
> + M f5a
> + A f2c
> + R f2a
> + $ hg cat f1a
> + c1c
> + $ hg cat f1b
> + f1b: no such file in rev 68af396ea7bf [1]
> +
> +Test the cases A.0 (f4x) and A.6 (f3x)
> +
> + $ hg graft -r 3
> + grafting 3:aa2584f6dee9 "d"
> + note: possible conflict - f3b was renamed multiple times to:
> + f3d
> + f3a
> + warning: can't find ancestor for 'f3d' copied from 'f3b'!
> +
> +Set up the repository for some further tests
> +
> + $ hg up -q 0
> + $ hg mv f1a f1e
> + $ echo c2e > f2a
> + $ hg mv f3a f3e
> + $ hg mv f4a f4e
> + $ hg mv f5a f5b
> + $ hg ci -qAm e
> + $ hg log -G
> + @ changeset: 6:52db7f4dcf33
> + | tag: tip
> + | parent: 0:3340a7726e9e
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: e
> + |
> + | o changeset: 5:29f6ffdbca28
> + | | user: test
> + | | date: Thu Jan 01 00:00:00 1970 +0000
> + | | summary: d
> + | |
> + | o changeset: 4:68af396ea7bf
> + |/ parent: 0:3340a7726e9e
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: c
> + |
> + | o changeset: 3:aa2584f6dee9
> + | | user: test
> + | | date: Thu Jan 01 00:00:00 1970 +0000
> + | | summary: d
> + | |
> + | o changeset: 2:c8d3926d7649
> + | | user: test
> + | | date: Thu Jan 01 00:00:00 1970 +0000
> + | | summary: c
> + | |
> + | o changeset: 1:7e9aff31b586
> + |/ user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | summary: b
> + |
> + o changeset: 0:3340a7726e9e
> + user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: a
> +
> +
> +Test the cases A.4 (f1x), the "ping-pong" special case of A.7 (f5x),
> +and A.3 with a local content change to be preserved (f2x).
> +
> + $ hg graft -r 2
> + grafting 2:c8d3926d7649 "c"
> + merging f1e and f1b to f1e
> + merging f2a and f2c to f2c
> + merging f5b and f5a to f5a
> +
> +Test the cases A.1 (f4x) and A.7 (f3x).
> +
> + $ hg graft -r 3
> + grafting 3:aa2584f6dee9 "d"
> + note: possible conflict - f3b was renamed multiple times to:
> + f3e
> + f3d
> + merging f4e and f4a to f4e
> + warning: can't find ancestor for 'f3d' copied from 'f3b'!
> +
> +Check the results of the grafts tested
> +
> + $ hg log -CGv --patch --git
> + @ changeset: 8:4c243d7a2f50
> + | tag: tip
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | files: f3d f4e
> + | description:
> + | d
> + |
> + |
> + | diff --git a/f3d b/f3d
> + | new file mode 100644
> + | --- /dev/null
> + | +++ b/f3d
> + | @@ -0,0 +1,1 @@
> + | +c3a
> + | diff --git a/f4e b/f4e
> + | --- a/f4e
> + | +++ b/f4e
> + | @@ -1,1 +1,1 @@
> + | -c4a
> + | +c4d
> + |
> + o changeset: 7:9b7ee4960a74
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | files: f1e f2a f2c f5a f5b
> + | copies: f2c (f2a) f5a (f5b)
> + | description:
> + | c
> + |
> + |
> + | diff --git a/f1e b/f1e
> + | --- a/f1e
> + | +++ b/f1e
> + | @@ -1,1 +1,1 @@
> + | -c1a
> + | +c1c
> + | diff --git a/f2a b/f2c
> + | rename from f2a
> + | rename to f2c
> + | diff --git a/f5b b/f5a
> + | rename from f5b
> + | rename to f5a
> + | --- a/f5b
> + | +++ b/f5a
> + | @@ -1,1 +1,1 @@
> + | -c5a
> + | +c5c
> + |
> + o changeset: 6:52db7f4dcf33
> + | parent: 0:3340a7726e9e
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | files: f1a f1e f2a f3a f3e f4a f4e f5a f5b
> + | copies: f1e (f1a) f3e (f3a) f4e (f4a) f5b (f5a)
> + | description:
> + | e
> + |
> + |
> + | diff --git a/f1a b/f1e
> + | rename from f1a
> + | rename to f1e
> + | diff --git a/f2a b/f2a
> + | --- a/f2a
> + | +++ b/f2a
> + | @@ -1,1 +1,1 @@
> + | -c2a
> + | +c2e
> + | diff --git a/f3a b/f3e
> + | rename from f3a
> + | rename to f3e
> + | diff --git a/f4a b/f4e
> + | rename from f4a
> + | rename to f4e
> + | diff --git a/f5a b/f5b
> + | rename from f5a
> + | rename to f5b
> + |
> + | o changeset: 5:29f6ffdbca28
> + | | user: test
> + | | date: Thu Jan 01 00:00:00 1970 +0000
> + | | files: f3d f4a
> + | | description:
> + | | d
> + | |
> + | |
> + | | diff --git a/f3d b/f3d
> + | | new file mode 100644
> + | | --- /dev/null
> + | | +++ b/f3d
> + | | @@ -0,0 +1,1 @@
> + | | +c3a
> + | | diff --git a/f4a b/f4a
> + | | --- a/f4a
> + | | +++ b/f4a
> + | | @@ -1,1 +1,1 @@
> + | | -c4a
> + | | +c4d
> + | |
> + | o changeset: 4:68af396ea7bf
> + |/ parent: 0:3340a7726e9e
> + | user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | files: f1a f2a f2c f5a
> + | copies: f2c (f2a)
> + | description:
> + | c
> + |
> + |
> + | diff --git a/f1a b/f1a
> + | --- a/f1a
> + | +++ b/f1a
> + | @@ -1,1 +1,1 @@
> + | -c1a
> + | +c1c
> + | diff --git a/f2a b/f2c
> + | rename from f2a
> + | rename to f2c
> + | diff --git a/f5a b/f5a
> + | --- a/f5a
> + | +++ b/f5a
> + | @@ -1,1 +1,1 @@
> + | -c5a
> + | +c5c
> + |
> + | o changeset: 3:aa2584f6dee9
> + | | user: test
> + | | date: Thu Jan 01 00:00:00 1970 +0000
> + | | files: f3b f3d f4a
> + | | copies: f3d (f3b)
> + | | description:
> + | | d
> + | |
> + | |
> + | | diff --git a/f3b b/f3d
> + | | rename from f3b
> + | | rename to f3d
> + | | diff --git a/f4a b/f4a
> + | | --- a/f4a
> + | | +++ b/f4a
> + | | @@ -1,1 +1,1 @@
> + | | -c4a
> + | | +c4d
> + | |
> + | o changeset: 2:c8d3926d7649
> + | | user: test
> + | | date: Thu Jan 01 00:00:00 1970 +0000
> + | | files: f1b f2a f2c f5a f5b
> + | | copies: f2c (f2a) f5a (f5b)
> + | | description:
> + | | c
> + | |
> + | |
> + | | diff --git a/f1b b/f1b
> + | | --- a/f1b
> + | | +++ b/f1b
> + | | @@ -1,1 +1,1 @@
> + | | -c1a
> + | | +c1c
> + | | diff --git a/f2a b/f2c
> + | | rename from f2a
> + | | rename to f2c
> + | | diff --git a/f5b b/f5a
> + | | rename from f5b
> + | | rename to f5a
> + | | --- a/f5b
> + | | +++ b/f5a
> + | | @@ -1,1 +1,1 @@
> + | | -c5a
> + | | +c5c
> + | |
> + | o changeset: 1:7e9aff31b586
> + |/ user: test
> + | date: Thu Jan 01 00:00:00 1970 +0000
> + | files: f1a f1b f3a f3b f5a f5b
> + | copies: f1b (f1a) f3b (f3a) f5b (f5a)
> + | description:
> + | b
> + |
> + |
> + | diff --git a/f1a b/f1b
> + | rename from f1a
> + | rename to f1b
> + | diff --git a/f3a b/f3b
> + | rename from f3a
> + | rename to f3b
> + | diff --git a/f5a b/f5b
> + | rename from f5a
> + | rename to f5b
> + |
> + o changeset: 0:3340a7726e9e
> + user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + files: f1a f2a f3a f4a f5a
> + description:
> + a
> +
> +
> + diff --git a/f1a b/f1a
> + new file mode 100644
> + --- /dev/null
> + +++ b/f1a
> + @@ -0,0 +1,1 @@
> + +c1a
> + diff --git a/f2a b/f2a
> + new file mode 100644
> + --- /dev/null
> + +++ b/f2a
> + @@ -0,0 +1,1 @@
> + +c2a
> + diff --git a/f3a b/f3a
> + new file mode 100644
> + --- /dev/null
> + +++ b/f3a
> + @@ -0,0 +1,1 @@
> + +c3a
> + diff --git a/f4a b/f4a
> + new file mode 100644
> + --- /dev/null
> + +++ b/f4a
> + @@ -0,0 +1,1 @@
> + +c4a
> + diff --git a/f5a b/f5a
> + new file mode 100644
> + --- /dev/null
> + +++ b/f5a
> + @@ -0,0 +1,1 @@
> + +c5a
> +
> + $ hg cat f2c
> + c2e
> diff -r 5977d6569b2e -r 9e59cd55604c tests/test-rebase-conflicts.t
> --- a/tests/test-rebase-conflicts.tTue Oct 04 12:51:54 2016 +0200
> +++ b/tests/test-rebase-conflicts.tMon Oct 03 18:38:13 2016 +0200
> @@ -238,6 +238,10 @@
> merge against 9:e31216eec445
> detach base 8:8e4e2c1a07ae
> searching for copies back to rev 3
> + computing unmatched files in rotated DAG
> + computing unmatched files in unrotated DAG
> + unmatched files in other:
> + f2.txt
> resolving manifests
> branchmerge: True, force: True, partial: False
> ancestor: 8e4e2c1a07ae, local: 4bc80088dc6b+, remote: e31216eec445 @@
> -255,6 +259,10 @@
> merge against 10:2f2496ddf49d
> detach base 9:e31216eec445
> searching for copies back to rev 3
> + computing unmatched files in rotated DAG
> + computing unmatched files in unrotated DAG
> + unmatched files in other:
> + f2.txt
> resolving manifests
> branchmerge: True, force: True, partial: False
> ancestor: e31216eec445, local: 19c888675e13+, remote: 2f2496ddf49d
More information about the Mercurial-devel
mailing list