[PATCH 10 of 11] graft: enable rotated-DAG copy tracing (issue4028)

2016-10-05 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik 
# Date 1475512693 -7200
#  Mon Oct 03 18:38:13 2016 +0200
# Node ID 4b963243504357812d13fa5c824f60253e0ba448
# Parent  bbc77d871f41a85db381b57acfe6dc550bb40506
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 bbc77d871f41 -r 4b9632435043 mercurial/copies.py
--- a/mercurial/copies.py   Tue Oct 04 12:51:54 2016 +0200
+++ b/mercurial/copies.py   Mon 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 bbc77d871f41 -r 4b9632435043 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, 

RE: [PATCH 10 of 11] graft: enable rotated-DAG copy tracing (issue4028)

2016-10-05 Thread Gábor STEFANIK
>


--
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: Pierre-Yves David [mailto:pierre-yves.da...@ens-lyon.org]
> Sent: Tuesday, October 4, 2016 11:42 PM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>; mercurial-
> de...@mercurial-scm.org
> Subject: Re: [PATCH 10 of 11] graft: enable rotated-DAG copy tracing
> (issue4028)
>
>
>
> On 10/04/2016 04:48 PM, Gábor STEFANIK wrote:
> > -Original Message-
> >> From: Mercurial-devel
> >> [mailto:mercurial-devel-boun...@mercurial-scm.org]
> >> On Behalf Of Gábor Stefanik
> >> Sent: Tuesday, October 4, 2016 4:40 PM
> >> To: mercurial-devel@mercurial-scm.org
> >> Subject: [PATCH 10 of 11] graft: enable rotated-DAG copy tracing
> >> (issue4028)
> >>
> >> # HG changeset patch
> >> # User G?bor Stefanik <gabor.stefa...@nng.com> # Date 1475512693
> >> -7200
> >
> > Alright, that didn't work either, sorry for the spam...
>
> I did not looked in details, but this seems to work fine for me. So started to
> take some of your series.

I meant the patchbomb setup didn't work properly, as it mangled my name.

Problem is it sends the mail as Latin-2 (or UTF-8 if I specify HGENCODING), but 
hardcodedly says
"encoding=us-ascii" in the headers. So strictly-conforming mail readers will 
reject all 8-bit characters
in the mail.

>
> --
> Pierre-Yves David
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH 10 of 11] graft: enable rotated-DAG copy tracing (issue4028)

2016-10-04 Thread Pierre-Yves David



On 10/04/2016 04:48 PM, Gábor STEFANIK wrote:

-Original Message-

From: Mercurial-devel [mailto:mercurial-devel-boun...@mercurial-scm.org]
On Behalf Of Gábor Stefanik
Sent: Tuesday, October 4, 2016 4:40 PM
To: mercurial-devel@mercurial-scm.org
Subject: [PATCH 10 of 11] graft: enable rotated-DAG copy tracing (issue4028)

# HG changeset patch
# User G?bor Stefanik <gabor.stefa...@nng.com> # Date 1475512693 -7200


Alright, that didn't work either, sorry for the spam...


I did not looked in details, but this seems to work fine for me. So 
started to take some of your series.


--
Pierre-Yves David
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH 10 of 11] graft: enable rotated-DAG copy tracing (issue4028)

2016-10-04 Thread Gábor STEFANIK
>


--
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-boun...@mercurial-scm.org]
> On Behalf Of Gábor Stefanik
> Sent: Tuesday, October 4, 2016 4:40 PM
> To: mercurial-devel@mercurial-scm.org
> Subject: [PATCH 10 of 11] graft: enable rotated-DAG copy tracing (issue4028)
>
> # HG changeset patch
> # User G?bor Stefanik <gabor.stefa...@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:
> -   

[PATCH 10 of 11] graft: enable rotated-DAG copy tracing (issue4028)

2016-10-04 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik 
# Date 1475512693 -7200
#  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.py   Tue Oct 04 12:51:54 2016 +0200
+++ b/mercurial/copies.py   Mon 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,