[PATCH 2 of 2 v2] copies: clean up _related logic

2018-04-06 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1522943470 -7200
#  Thu Apr 05 17:51:10 2018 +0200
# Node ID 9f344b1ec04c43870c4ea439451714dbf9b35739
# Parent  7d13af1b5cd6e5c95dceefc7d905429889583c8c
copies: clean up _related logic

The limit parameter was never actually used, since the only way the 4th case
could be reached was if f1r and f2r converged. The new code makes this clear,
and additionally reduces the conditional block to just 3 cases.

diff -r 7d13af1b5cd6 -r 9f344b1ec04c mercurial/copies.py
--- a/mercurial/copies.py   Thu Apr 05 17:52:31 2018 +0200
+++ b/mercurial/copies.py   Thu Apr 05 17:51:10 2018 +0200
@@ -723,7 +723,7 @@

 for candidate in movecandidates:
 f1 = c1.filectx(candidate)
-if _related(f1, f2, anc.rev()):
+if _related(f1, f2):
 # if there are a few related copies then we'll merge
 # changes into all of them. This matches the behaviour
 # of upstream copytracing
@@ -737,7 +737,7 @@
 except StopIteration:
 raise

-def _related(f1, f2, limit):
+def _related(f1, f2):
 """return True if f1 and f2 filectx have a common ancestor

 Walk back to common ancestor to see if the two files originate
@@ -764,10 +764,8 @@
 f1, g1 = _loosenext(g1)
 elif f2r > f1r:
 f2, g2 = _loosenext(g2)
-elif f1 == f2:
-return f1 # a match
-elif f1r == f2r or f1r < limit or f2r < limit:
-return False # copy no longer relevant
+else: # f1 and f2 point to files in the same linkrev
+return f1 == f2 # true if they point to the same file
 except StopIteration:
 return False

@@ -835,7 +833,7 @@
 c2 = getdstfctx(of, mdst[of])
 # c2 might be a plain new file on added on destination side that is
 # unrelated to the droids we are looking for.
-cr = _related(oc, c2, tca.rev())
+cr = _related(oc, c2)
 if cr and (of == f or of == c2.path()): # non-divergent
 if backwards:
 data['copy'][of] = f

 This message, including its attachments, is confidential and the property of 
NNG Llc. For more information please read NNG's email policy here:
https://www.nng.com/email-policy/
By responding to this email you accept the email policy.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH 2 of 2] copies: clean up _related logic

2018-04-05 Thread Gábor STEFANIK


> -Original Message-
> From: Yuya Nishihara [mailto:you...@gmail.com] On Behalf Of Yuya
> Nishihara
> Sent: Thursday, April 5, 2018 2:57 PM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> Cc: mercurial-devel@mercurial-scm.org
> Subject: Re: [PATCH 2 of 2] copies: clean up _related logic
>
> On Wed, 04 Apr 2018 15:35:34 +0200, Gábor Stefanik wrote:
> > # HG changeset patch
> > # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1522848882 -7200
> > #  Wed Apr 04 15:34:42 2018 +0200
> > # Node ID 084ee003f2f3cb4d51129c4f1bb63e1ff4db14d0
> > # Parent  d72ca973100a1f1a4451a7d1efdc3e43ebc2912e
> > copies: clean up _related logic
> >
> > The limit parameter was never actually used, since the only way the
> > 4th case could be reached was if f1r and f2r converged. The new code
> > makes this clear, and additionally reduces the conditional block to just 3
> cases.
>
> Yeah. I suspect the limit should be tested first, but doing that breaks some
> tests. So, perhaps we have to handle the case of f.linkrev() < anc.rev()
> anyway.

We do need to care for relatedness behind anc.rev(), since anc may well be a 
revision that doesn't modify f at all. In that case, the file revision 
contained in anc will have a linkrev before anc (since linkrev points to the 
changelog revision in which the file revision in question was introduced, not 
the last one where it's present). The limit logic really doesn't make sense 
here.

>
> > diff -r d72ca973100a -r 084ee003f2f3 mercurial/copies.py
> > --- a/mercurial/copies.py   Wed Apr 04 15:28:09 2018 +0200
> > +++ b/mercurial/copies.py   Wed Apr 04 15:34:42 2018 +0200
> > @@ -737,7 +737,7 @@
> >  except StopIteration:
> >  raise
> >
> > -def _related(f1, f2, limit):
> > +def _related(f1, f2):
>
> There's one more caller of _related().

Right, in heuristicscopytracing.

>
> > @@ -764,10 +764,8 @@
> >  f1, g1 = _loose_next(g1)
> >  elif f2r > f1r:
> >  f1, g1 = _loose_next(g1)
> > -elif f1 == f2:
> > -return f1 # a match
> > -elif f1r == f2r or f1r < limit or f2r < limit:
> > -return False # copy no longer relevant
> > +else: # f1 and f2 point to files in the same linkrev
> > +return f1 == f2 # true if they point to the same file

 This message, including its attachments, is confidential and the property of 
NNG Llc. For more information please read NNG's email policy here:
https://www.nng.com/email-policy/
By responding to this email you accept the email policy.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 2 v2] copies: create and use _loosenext function for _related

2018-04-05 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1522943551 -7200
#  Thu Apr 05 17:52:31 2018 +0200
# Node ID 7d13af1b5cd6e5c95dceefc7d905429889583c8c
# Parent  656ac240f39284eec4435d25c01d71056aa4e8a5
copies: create and use _loosenext function for _related

_loosenext is going to be a variant of the next function that tries to follow
grafts and similar relationship when the regular next raises StopIteration.
This is needed for bug 5834.

diff -r 656ac240f392 -r 7d13af1b5cd6 mercurial/copies.py
--- a/mercurial/copies.py   Sat Mar 24 01:30:50 2018 -0400
+++ b/mercurial/copies.py   Thu Apr 05 17:52:31 2018 +0200
@@ -731,6 +731,12 @@

 return copies, {}, {}, {}, {}

+def _loosenext(g):
+try:
+return next(g), g
+except StopIteration:
+raise
+
 def _related(f1, f2, limit):
 """return True if f1 and f2 filectx have a common ancestor

@@ -748,16 +754,16 @@
 f1r, f2r = f1.linkrev(), f2.linkrev()

 if f1r is None:
-f1 = next(g1)
+f1, g1 = _loosenext(g1)
 if f2r is None:
-f2 = next(g2)
+f2, g2 = _loosenext(g2)

 while True:
 f1r, f2r = f1.linkrev(), f2.linkrev()
 if f1r > f2r:
-f1 = next(g1)
+f1, g1 = _loosenext(g1)
 elif f2r > f1r:
-f2 = next(g2)
+f2, g2 = _loosenext(g2)
 elif f1 == f2:
 return f1 # a match
 elif f1r == f2r or f1r < limit or f2r < limit:

 This message, including its attachments, is confidential and the property of 
NNG Llc. For more information please read NNG's email policy here:
https://www.nng.com/email-policy/
By responding to this email you accept the email policy.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 2] copies: create and use _loose_next function for _related

2018-04-04 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1522848489 -7200
#  Wed Apr 04 15:28:09 2018 +0200
# Node ID d72ca973100a1f1a4451a7d1efdc3e43ebc2912e
# Parent  656ac240f39284eec4435d25c01d71056aa4e8a5
copies: create and use _loose_next function for _related

_loose_next is going to be a variant of the next function that tries to follow
grafts and similar relationship when the regular next raises StopIteration.
This is needed for bug 5834.

diff -r 656ac240f392 -r d72ca973100a mercurial/copies.py
--- a/mercurial/copies.py   Sat Mar 24 01:30:50 2018 -0400
+++ b/mercurial/copies.py   Wed Apr 04 15:28:09 2018 +0200
@@ -731,6 +731,12 @@

 return copies, {}, {}, {}, {}

+def _loose_next(g):
+try:
+return next(g), g
+except StopIteration:
+raise
+
 def _related(f1, f2, limit):
 """return True if f1 and f2 filectx have a common ancestor

@@ -748,16 +754,16 @@
 f1r, f2r = f1.linkrev(), f2.linkrev()

 if f1r is None:
-f1 = next(g1)
+f1, g1 = _loose_next(g1)
 if f2r is None:
-f2 = next(g2)
+f1, g1 = _loose_next(g1)

 while True:
 f1r, f2r = f1.linkrev(), f2.linkrev()
 if f1r > f2r:
-f1 = next(g1)
+f1, g1 = _loose_next(g1)
 elif f2r > f1r:
-f2 = next(g2)
+f1, g1 = _loose_next(g1)
 elif f1 == f2:
 return f1 # a match
 elif f1r == f2r or f1r < limit or f2r < limit:

 This message, including its attachments, is confidential and the property of 
NNG Llc. For more information please read NNG's email policy here:
https://www.nng.com/email-policy/
By responding to this email you accept the email policy.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 2] copies: clean up _related logic

2018-04-04 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1522848882 -7200
#  Wed Apr 04 15:34:42 2018 +0200
# Node ID 084ee003f2f3cb4d51129c4f1bb63e1ff4db14d0
# Parent  d72ca973100a1f1a4451a7d1efdc3e43ebc2912e
copies: clean up _related logic

The limit parameter was never actually used, since the only way the 4th case
could be reached was if f1r and f2r converged. The new code makes this clear,
and additionally reduces the conditional block to just 3 cases.

diff -r d72ca973100a -r 084ee003f2f3 mercurial/copies.py
--- a/mercurial/copies.py   Wed Apr 04 15:28:09 2018 +0200
+++ b/mercurial/copies.py   Wed Apr 04 15:34:42 2018 +0200
@@ -737,7 +737,7 @@
 except StopIteration:
 raise

-def _related(f1, f2, limit):
+def _related(f1, f2):
 """return True if f1 and f2 filectx have a common ancestor

 Walk back to common ancestor to see if the two files originate
@@ -764,10 +764,8 @@
 f1, g1 = _loose_next(g1)
 elif f2r > f1r:
 f1, g1 = _loose_next(g1)
-elif f1 == f2:
-return f1 # a match
-elif f1r == f2r or f1r < limit or f2r < limit:
-return False # copy no longer relevant
+else: # f1 and f2 point to files in the same linkrev
+return f1 == f2 # true if they point to the same file
 except StopIteration:
 return False

@@ -835,7 +833,7 @@
 c2 = getdstfctx(of, mdst[of])
 # c2 might be a plain new file on added on destination side that is
 # unrelated to the droids we are looking for.
-cr = _related(oc, c2, tca.rev())
+cr = _related(oc, c2)
 if cr and (of == f or of == c2.path()): # non-divergent
 if backwards:
 data['copy'][of] = f

 This message, including its attachments, is confidential and the property of 
NNG Llc. For more information please read NNG's email policy here:
https://www.nng.com/email-policy/
By responding to this email you accept the email policy.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH hglib v2] client: ignore close() on non-open clients (issue5751)

2018-02-14 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1518443649 -3600
#  Mon Feb 12 14:54:09 2018 +0100
# Node ID b5d05859a5d7956024b169dd7fe53a6ea45a4d67
# Parent  1085c904d8c04d51c6897027fe9c7bae0964b64b
client: ignore close() on non-open clients (issue5751)

Closing a client twice currently triggers a rather confusing exception.
Instead, follow the convention set by Python's file objects, and ignore close()
commands on non-open clients.

diff -r 1085c904d8c0 -r b5d05859a5d7 hglib/client.py
--- a/hglib/client.py   Thu Feb 01 15:10:02 2018 -0500
+++ b/hglib/client.py   Mon Feb 12 14:54:09 2018 +0100
@@ -291,6 +291,8 @@
 Attempting to call any function afterwards that needs to
 communicate with the server will raise a ValueError.
 """
+if not self.server:
+return 0
 return self._close()[0]

 def _close(self):

 This message, including its attachments, is confidential and the property of 
NNG Llc. For more information please read NNG's email policy here:
https://www.nng.com/email-policy/
By responding to this email you accept the email policy.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH hglib] [b] ignore close() on non-open clients (issue5751)

2018-02-12 Thread Gábor STEFANIK
> -Original Message-
> From: Yuya Nishihara [mailto:you...@gmail.com] On Behalf Of Yuya
> Nishihara
> Sent: Monday, February 12, 2018 3:50 PM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> Cc: mercurial-devel@mercurial-scm.org
> Subject: Re: [PATCH hglib] [b] ignore close() on non-open clients (issue5751)
>
> On Mon, 12 Feb 2018 14:55:57 +0100, Gábor Stefanik wrote:
> > # HG changeset patch
> > # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1518443649 -3600
> > #  Mon Feb 12 14:54:09 2018 +0100
> > # Node ID fe38aeeb1586464769caa6e9bb819078028fc858
> > # Parent  1085c904d8c04d51c6897027fe9c7bae0964b64b
> > [b] ignore close() on non-open clients (issue5751)
>   ^^^
>   what's [b]?

Ouch... our internal notation for a bugfix commit.

>
> Thanks, this looks good, except the patch is malformed because of a
> corporate mail server?
>
> > diff -r 1085c904d8c0 -r fe38aeeb1586 hglib/client.py
> > --- a/hglib/client.py   Thu Feb 01 15:10:02 2018 -0500
> > +++ b/hglib/client.py   Mon Feb 12 14:54:09 2018 +0100
> > @@ -294,6 +294,8 @@
> >  return self._close()[0]
> >
> >  def _close(self):
> > +if not self.server:
> > +return 0, ''
>
> I slight prefer moving this to close() so _close() stays as a low-level 
> helper,
> but that doesn't matter in practice.
>
> >  _sout, serr = self.server.communicate()
> >  ret = self.server.returncode
> >  self.server = None

 This message, including its attachments, is confidential and the property of 
NNG Llc. For more information please read NNG's email policy here:
https://www.nng.com/email-policy/
By responding to this email you accept the email policy.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH hglib] [b] ignore close() on non-open clients (issue5751)

2018-02-12 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1518443649 -3600
#  Mon Feb 12 14:54:09 2018 +0100
# Node ID fe38aeeb1586464769caa6e9bb819078028fc858
# Parent  1085c904d8c04d51c6897027fe9c7bae0964b64b
[b] ignore close() on non-open clients (issue5751)

Closing a client twice currently triggers a rather confusing exception.
Instead, follow the convention set by Python's file objects, and ignore close()
commands on non-open clients.

diff -r 1085c904d8c0 -r fe38aeeb1586 hglib/client.py
--- a/hglib/client.py   Thu Feb 01 15:10:02 2018 -0500
+++ b/hglib/client.py   Mon Feb 12 14:54:09 2018 +0100
@@ -294,6 +294,8 @@
 return self._close()[0]

 def _close(self):
+if not self.server:
+return 0, ''
 _sout, serr = self.server.communicate()
 ret = self.server.returncode
 self.server = None

 This message, including its attachments, is confidential and the property of 
NNG Llc. For more information please read NNG's email policy here:
https://www.nng.com/email-policy/
By responding to this email you accept the email policy.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH 1 of 4] debug: add a 'debugdownload' command

2018-01-03 Thread Gábor STEFANIK
> -Original Message-
> From: Mercurial-devel [mailto:mercurial-devel-boun...@mercurial-scm.org]
> On Behalf Of Boris Feld
> Sent: Tuesday, January 2, 2018 11:40 AM
> To: mercurial-devel@mercurial-scm.org
> Subject: [PATCH 1 of 4] debug: add a 'debugdownload' command
>
> # HG changeset patch
> # User Boris Feld  # Date 1513326616 -3600
> #  Fri Dec 15 09:30:16 2017 +0100
> # Node ID 2bd6c705949fae0b4477759479e9a0a905788ca4
> # Parent  2c47986505ff1f9c9c77117eca584347dbd1d89b
> # EXP-Topic largefile-url
> # Available At https://bitbucket.org/octobus/mercurial-devel/
> #  hg pull https://bitbucket.org/octobus/mercurial-devel/ -r
> 2bd6c705949f
> debug: add a 'debugdownload' command
>
> This command resolve and fetch and URL through the Mercurial logic.
> Mercurial logic add various headers (including authentication) while resolving
> an URL so the commands helps with building the same request Mercurial
> would be doing.
>
> A new test file is created because we'll add more logic regarding Mercurial
> download logic and it will grow to a reasonable size.
>
> diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
> --- a/mercurial/debugcommands.py
> +++ b/mercurial/debugcommands.py
> @@ -69,6 +69,7 @@ from . import (
>  templater,
>  treediscovery,
>  upgrade,
> +url as urlmod,
>  util,
>  vfs as vfsmod,
>  )
> @@ -786,6 +787,25 @@ def debugdiscovery(ui, repo, remoteurl="
>  localrevs = opts['rev']
>  doit(localrevs, remoterevs)
>
> +@command('debugdownload',
> +[
> +('o', 'output', '', _('URL')),
> +],
> +norepo=True)
> +def debugdownload(ui, url, output=None, **opts):
> +"""Download a ressource using Mercurial logic and config

typo: resource

> +"""
> +fh = urlmod.open(ui, url, output)
> +
> +dest = ui
> +if output:
> +dest = open(output, "wb", 4<<10)

Repeatedly appearing magic number, please name it instead.

> +
> +data = fh.read(4<<10)
> +while data:
> +dest.write(data)
> +data = fh.read(4<<10)
> +
>  @command('debugextensions', cmdutil.formatteropts, [], norepo=True)
> def debugextensions(ui, **opts):
>  '''show information about active extensions'''
> diff --git a/tests/test-completion.t b/tests/test-completion.t
> --- a/tests/test-completion.t
> +++ b/tests/test-completion.t
> @@ -85,6 +85,7 @@ Show debug commands if there are no othe
>debugdeltachain
>debugdirstate
>debugdiscovery
> +  debugdownload
>debugextensions
>debugfileset
>debugformat
> @@ -263,6 +264,7 @@ Show all commands + options
>debugdeltachain: changelog, manifest, dir, template
>debugdirstate: nodates, datesort
>debugdiscovery: old, nonheads, rev, ssh, remotecmd, insecure
> +  debugdownload: output
>debugextensions: template
>debugfileset: rev
>debugformat: template
> diff --git a/tests/test-help.t b/tests/test-help.t
> --- a/tests/test-help.t
> +++ b/tests/test-help.t
> @@ -919,6 +919,8 @@ Test list of internal help commands
>   show the contents of the current dirstate
> debugdiscovery
>   runs the changeset discovery protocol in isolation
> +   debugdownload
> + Download a ressource using Mercurial logic and config

typo: resource

> debugextensions
>   show information about active extensions
> debugfileset  parse and apply a fileset specification diff --git 
> a/tests/test-
> url-download.t b/tests/test-url-download.t new file mode 100644
> --- /dev/null
> +++ b/tests/test-url-download.t
> @@ -0,0 +1,36 @@
> +#require serve
> +
> +  $ hg init server
> +  $ hg serve -R server -p $HGPORT -d --pid-file=hg1.pid -E ../error.log
> + $ cat hg1.pid >> $DAEMON_PIDS
> +
> +Check basic fetching
> +
> +  $ hg debugdownload "http://localhost:$HGPORT/?cmd=lookup=tip;
> +  1 
> +  $ hg debugdownload  -o null.txt
> "http://localhost:$HGPORT/?cmd=lookup=null;
> +  $ cat null.txt
> +  1 
> +
> +Check the request is seens as coming from Mercurial (rev details, give

typo... what exactly was intended here? "is seen as coming from Mercurial"? 
"seems as if coming from Mercurial"?

> +different content if the request has a Mercurial user agent)
> +
> +  $ get-with-headers.py --headeronly "localhost:$HGPORT" "rev/tip"
> + content-type
> +  200 Script output follows
> +  content-type: text/html; charset=ascii  $ hg debugdownload
> + "http://localhost:$HGPORT/rev/tip;
> +
> +  # HG changeset patch
> +  # User
> +  # Date 0 0
> +  # Node ID 
> +
> +
> +
> +
> +
> +Check other kind of compatible url
> +
> +  $ hg debugdownload ./null.txt
> +  1 
> +
> ___
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> 

RE: [PATCH hglib] package: update package url

2017-08-30 Thread Gábor STEFANIK

> -Original Message-
> From: Mercurial-devel [mailto:mercurial-devel-boun...@mercurial-scm.org]
> On Behalf Of Boris Feld
> Sent: Wednesday, July 19, 2017 3:26 PM
> To: mercurial-devel@mercurial-scm.org
> Subject: [PATCH hglib] package: update package url
>
> # HG changeset patch
> # User Boris Feld  # Date 1500468520 -7200
> #  Wed Jul 19 14:48:40 2017 +0200
> # Node ID d06b480016620be412ee3eee45822fdd3062fc0f
> # Parent  8e959ad6a25c9fe6ab490e1a4cbd5f7445d9bcb1
> package: update package url
>
> Replace the package url which was pointing to the repository to the wiki page
> instead. This way pypi users will directly see the basic usage and can easily
> find the repository url on the wiki page.
>
> diff -r 8e959ad6a25c -r d06b48001662 setup.py
> --- a/setup.pyMon Apr 03 16:02:08 2017 -0500
> +++ b/setup.pyWed Jul 19 14:48:40 2017 +0200
> @@ -36,7 +36,7 @@
>  version=version,
>  author='Idan Kamara',
>  author_email='idank...@gmail.com',
> -url='http://selenic.com/repo/python-hglib',
> +url='https://www.mercurial-scm.org/wiki/PythonHglibs',

PythonHglibs? That page doesn't exist...

>  description='Mercurial Python library',
>  long_description=open(os.path.join(os.path.dirname(__file__),
> 'README')).read(),
> ___
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

 This message, including its attachments, is confidential and the property of 
NNG Llc. 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.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH 4 of 4] client: raise ResponseError in _readchannel()

2017-08-30 Thread Gábor STEFANIK
> -Original Message-
> From: Augie Fackler [mailto:r...@durin42.com]
> Sent: Monday, August 28, 2017 8:32 PM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> Cc: mercurial-devel@mercurial-scm.org
> Subject: Re: [PATCH 4 of 4] client: raise ResponseError in _readchannel()
>
> On Mon, Aug 21, 2017 at 06:06:23PM +0200, Gábor Stefanik wrote:
> > # HG changeset patch
> > # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1503328417 -7200
> > #  Mon Aug 21 17:13:37 2017 +0200
> > # Node ID 6f59b1f3360d599dfe66cf509bbde926d87ebe5e
> > # Parent  8b4d606b49655d44091c2689a3f35a3fff17a28d
> > client: raise ResponseError in _readchannel()
>
> Hmmm, I'm trying to apply these and I'm getting failed hunks in this first
> patch. Can you resend?
>
> (Sorry about the delay and the trouble.)

Probably CR characters in the patch. I can do nothing about them, our new SMTP 
server appears to do this on its own.

Try dos2unixing the patch before applying.

>
> >
> > Make it clearer that this is an unrecoverable communication error.
> > ServerError alone is not always unrecoverable, as CapabilityError is
> > considered a subtype of it.
> >
> > This way, a caller can check for ResponseError to identify errors that
> > require reopening the client to recover.
> >
> > diff -r 8b4d606b4965 -r 6f59b1f3360d hglib/client.py
> > --- a/hglib/client.py   Mon Aug 21 17:06:13 2017 +0200
> > +++ b/hglib/client.py   Mon Aug 21 17:13:37 2017 +0200
> > @@ -140,7 +140,7 @@
> >  data = self.server.stdout.read(hgclient.outputfmtsize)
> >  if not data:
> >  self.close()
> > -raise error.ServerError()
> > +raise error.ResponseError('no response received from
> > + server')
> >  channel, length = struct.unpack(hgclient.outputfmt, data)
> >  if channel in b('IL'):
> >  return channel, length
> > 
> >  This message, including its attachments, is confidential and the property 
> > of
> NNG Llc. 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.
> > ___
> > Mercurial-devel mailing list
> > Mercurial-devel@mercurial-scm.org
> > https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

 This message, including its attachments, is confidential and the property of 
NNG Llc. 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.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH] copies: fix misaligned lines

2017-08-22 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1503411399 -7200
#  Tue Aug 22 16:16:39 2017 +0200
# Node ID 3b8c9025f9b966a3e58334024c3f7b3136f500fe
# Parent  e02a08d63ad20097419f935fdd8a91c42ecd88e3
copies: fix misaligned lines

diff -r e02a08d63ad2 -r 3b8c9025f9b9 mercurial/copies.py
--- a/mercurial/copies.py   Tue Aug 22 16:08:31 2017 +0200
+++ b/mercurial/copies.py   Tue Aug 22 16:16:39 2017 +0200
@@ -379,7 +379,7 @@
 # if we have a dirty endpoint, we need to trigger graft logic, and also
 # keep track of which endpoint is dirty
 dirtyc1 = not (base == _c1 or base.descendant(_c1))
-dirtyc2 = not (base== _c2 or base.descendant(_c2))
+dirtyc2 = not (base == _c2 or base.descendant(_c2))
 graft = dirtyc1 or dirtyc2
 tca = base
 if graft:

 This message, including its attachments, is confidential and the property of 
NNG Llc. 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.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH] copies: fix typo in comment

2017-08-22 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1503410911 -7200
#  Tue Aug 22 16:08:31 2017 +0200
# Node ID e02a08d63ad20097419f935fdd8a91c42ecd88e3
# Parent  af20468eb0a499c094dbd6e27ffcacf54cf5a8e6
copies: fix typo in comment

"will not be limited" was meant to be "will not be visited". I missed this
when writing the original graft-through-rename patch series.

diff -r af20468eb0a4 -r e02a08d63ad2 mercurial/copies.py
--- a/mercurial/copies.py   Mon Aug 21 21:35:06 2017 -0700
+++ b/mercurial/copies.py   Tue Aug 22 16:08:31 2017 +0200
@@ -635,8 +635,8 @@
 limit = the rev number to not search beyond
 data = dictionary of dictionary to store copy data. (see mergecopies)

-note: limit is only an optimization, and there is no guarantee that
-irrelevant revisions will not be limited
+note: limit is only an optimization, and provides no guarantee that
+irrelevant revisions will not be visited
 there is no easy way to make this algorithm stop in a guaranteed way
 once it "goes behind a certain revision".
 """

 This message, including its attachments, is confidential and the property of 
NNG Llc. 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.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 3 of 4] client: don't swallow ResponseError inside open()

2017-08-21 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1503327973 -7200
#  Mon Aug 21 17:06:13 2017 +0200
# Node ID 8b4d606b49655d44091c2689a3f35a3fff17a28d
# Parent  e2b082707b44c5d9f630bf0ca722723bad8cefb1
client: don't swallow ResponseError inside open()

_readhello() can generate meaningful ResponseError exceptions. However,
open()'s exception handler swallows these, converting them into generic
ServerErrors. Allow the original ResponseErrors to pass through.

diff -r e2b082707b44 -r 8b4d606b4965 hglib/client.py
--- a/hglib/client.py   Mon Aug 21 17:02:14 2017 +0200
+++ b/hglib/client.py   Mon Aug 21 17:06:13 2017 +0200
@@ -259,6 +259,9 @@
 self.server = util.popen(self._args, self._env)
 try:
 self._readhello()
+except error.ResponseError:
+self.close()
+raise
 except error.ServerError:
 ret, serr = self._close()
 raise error.ServerError('server exited with status %d: %s'

 This message, including its attachments, is confidential and the property of 
NNG Llc. 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.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 4 of 4] client: raise ResponseError in _readchannel()

2017-08-21 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1503328417 -7200
#  Mon Aug 21 17:13:37 2017 +0200
# Node ID 6f59b1f3360d599dfe66cf509bbde926d87ebe5e
# Parent  8b4d606b49655d44091c2689a3f35a3fff17a28d
client: raise ResponseError in _readchannel()

Make it clearer that this is an unrecoverable communication error.
ServerError alone is not always unrecoverable, as CapabilityError is considered
a subtype of it.

This way, a caller can check for ResponseError to identify errors that require
reopening the client to recover.

diff -r 8b4d606b4965 -r 6f59b1f3360d hglib/client.py
--- a/hglib/client.py   Mon Aug 21 17:06:13 2017 +0200
+++ b/hglib/client.py   Mon Aug 21 17:13:37 2017 +0200
@@ -140,7 +140,7 @@
 data = self.server.stdout.read(hgclient.outputfmtsize)
 if not data:
 self.close()
-raise error.ServerError()
+raise error.ResponseError('no response received from server')
 channel, length = struct.unpack(hgclient.outputfmt, data)
 if channel in b('IL'):
 return channel, length

 This message, including its attachments, is confidential and the property of 
NNG Llc. 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.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 4] client: kill the server on unrecoverable communication errors (issue5516)

2017-08-21 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1503327734 -7200
#  Mon Aug 21 17:02:14 2017 +0200
# Node ID e2b082707b44c5d9f630bf0ca722723bad8cefb1
# Parent  242d719e85ce451e61ab262eec443668bb4792ce
client: kill the server on unrecoverable communication errors (issue5516)

Once an unrecoverable communication error occurs between the client and server,
it's no longer safe to send further commands to the same server.
On Windows, attempting to do so is known to cause lockups and memory leaks.

Close the client and kill the server when an such an error occurs. This way,
any further commands will fail gracefully with ValueError until the client
is reopened.

diff -r 242d719e85ce -r e2b082707b44 hglib/client.py
--- a/hglib/client.py   Mon Aug 21 16:39:17 2017 +0200
+++ b/hglib/client.py   Mon Aug 21 17:02:14 2017 +0200
@@ -139,6 +139,7 @@
 def _readchannel(self):
 data = self.server.stdout.read(hgclient.outputfmtsize)
 if not data:
+self.close()
 raise error.ServerError()
 channel, length = struct.unpack(hgclient.outputfmt, data)
 if channel in b('IL'):
@@ -190,6 +191,7 @@
 return struct.unpack(hgclient.retfmt, data)[0]
 # a channel that we don't know and can't ignore
 elif channel.isupper():
+self.close()
 raise error.ResponseError(
 "unexpected data on required channel '%s'" % channel)
 # optional channel

 This message, including its attachments, is confidential and the property of 
NNG Llc. 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.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 4] client: fail gracefully on unexpected prompts (issue5516)

2017-08-21 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1503326357 -7200
#  Mon Aug 21 16:39:17 2017 +0200
# Node ID 242d719e85ce451e61ab262eec443668bb4792ce
# Parent  8e959ad6a25c9fe6ab490e1a4cbd5f7445d9bcb1
client: fail gracefully on unexpected prompts (issue5516)

Right now, if hglib encounters an unexpected prompt, it fails with a rather
opaque "unexpected data on required channel 'L'" error message. Furthermore,
if subsequently another command is called on the same client instance, both
the client and server processes lock up hard (at least on Windows), and the
server process rapidly leaks ~2GB of memory.

Fix this by responding with an empty string to any unexpected prompt. This
will trigger an "abort: response expected" exception from the server, which
is easily handled as a CommandError, and subsequent commands sent from the
same client work as expected.

This doesn't completely resolve bug 5516, as unexpected requests on another
required channel (e.g. I) can still cause a lockup.
However, it does fix the most common case of an unexpected password prompt.

diff -r 8e959ad6a25c -r 242d719e85ce hglib/client.py
--- a/hglib/client.py   Mon Apr 03 16:02:08 2017 -0500
+++ b/hglib/client.py   Mon Aug 21 16:39:17 2017 +0200
@@ -235,6 +235,8 @@
 reply = prompt(size, out.getvalue())
 return reply
 inchannels[b('L')] = func
+else:
+inchannels[b('L')] = lambda _: ''
 if input is not None:
 inchannels[b('I')] = input


 This message, including its attachments, is confidential and the property of 
NNG Llc. 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.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 3 of 4] client: don't swallow ResponseError inside open()

2017-08-21 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1503327973 -7200
#  Mon Aug 21 17:06:13 2017 +0200
# Node ID 8b4d606b49655d44091c2689a3f35a3fff17a28d
# Parent  e2b082707b44c5d9f630bf0ca722723bad8cefb1
client: don't swallow ResponseError inside open()

_readhello() can generate meaningful ResponseError exceptions. However,
open()'s exception handler swallows these, converting them into generic
ServerErrors. Allow the original ResponseErrors to pass through.

diff -r e2b082707b44 -r 8b4d606b4965 hglib/client.py
--- a/hglib/client.py   Mon Aug 21 17:02:14 2017 +0200
+++ b/hglib/client.py   Mon Aug 21 17:06:13 2017 +0200
@@ -259,6 +259,9 @@
 self.server = util.popen(self._args, self._env)
 try:
 self._readhello()
+except error.ResponseError:
+self.close()
+raise
 except error.ServerError:
 ret, serr = self._close()
 raise error.ServerError('server exited with status %d: %s'

 This message, including its attachments, is confidential and the property of 
NNG Llc. 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.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 4] client: fail gracefully on unexpected prompts (issue5516)

2017-08-21 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1503326357 -7200
#  Mon Aug 21 16:39:17 2017 +0200
# Node ID 242d719e85ce451e61ab262eec443668bb4792ce
# Parent  8e959ad6a25c9fe6ab490e1a4cbd5f7445d9bcb1
client: fail gracefully on unexpected prompts (issue5516)

Right now, if hglib encounters an unexpected prompt, it fails with a rather
opaque "unexpected data on required channel 'L'" error message. Furthermore,
if subsequently another command is called on the same client instance, both
the client and server processes lock up hard (at least on Windows), and the
server process rapidly leaks ~2GB of memory.

Fix this by responding with an empty string to any unexpected prompt. This
will trigger an "abort: response expected" exception from the server, which
is easily handled as a CommandError, and subsequent commands sent from the
same client work as expected.

This doesn't completely resolve bug 5516, as unexpected requests on another
required channel (e.g. I) can still cause a lockup.
However, it does fix the most common case of an unexpected password prompt.

diff -r 8e959ad6a25c -r 242d719e85ce hglib/client.py
--- a/hglib/client.py   Mon Apr 03 16:02:08 2017 -0500
+++ b/hglib/client.py   Mon Aug 21 16:39:17 2017 +0200
@@ -235,6 +235,8 @@
 reply = prompt(size, out.getvalue())
 return reply
 inchannels[b('L')] = func
+else:
+inchannels[b('L')] = lambda _: ''
 if input is not None:
 inchannels[b('I')] = input


 This message, including its attachments, is confidential and the property of 
NNG Llc. 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.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 4 of 4] client: raise ResponseError in _readchannel()

2017-08-21 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1503328417 -7200
#  Mon Aug 21 17:13:37 2017 +0200
# Node ID 6f59b1f3360d599dfe66cf509bbde926d87ebe5e
# Parent  8b4d606b49655d44091c2689a3f35a3fff17a28d
client: raise ResponseError in _readchannel()

Make it clearer that this is an unrecoverable communication error.
ServerError alone is not always unrecoverable, as CapabilityError is considered
a subtype of it.

This way, a caller can check for ResponseError to identify errors that require
reopening the client to recover.

diff -r 8b4d606b4965 -r 6f59b1f3360d hglib/client.py
--- a/hglib/client.py   Mon Aug 21 17:06:13 2017 +0200
+++ b/hglib/client.py   Mon Aug 21 17:13:37 2017 +0200
@@ -140,7 +140,7 @@
 data = self.server.stdout.read(hgclient.outputfmtsize)
 if not data:
 self.close()
-raise error.ServerError()
+raise error.ResponseError('no response received from server')
 channel, length = struct.unpack(hgclient.outputfmt, data)
 if channel in b('IL'):
 return channel, length

 This message, including its attachments, is confidential and the property of 
NNG Llc. 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.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 4] client: kill the server on unrecoverable communication errors (issue5516)

2017-08-21 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1503327734 -7200
#  Mon Aug 21 17:02:14 2017 +0200
# Node ID e2b082707b44c5d9f630bf0ca722723bad8cefb1
# Parent  242d719e85ce451e61ab262eec443668bb4792ce
client: kill the server on unrecoverable communication errors (issue5516)

Once an unrecoverable communication error occurs between the client and server,
it's no longer safe to send further commands to the same server.
On Windows, attempting to do so is known to cause lockups and memory leaks.

Close the client and kill the server when an such an error occurs. This way,
any further commands will fail gracefully with ValueError until the client
is reopened.

diff -r 242d719e85ce -r e2b082707b44 hglib/client.py
--- a/hglib/client.py   Mon Aug 21 16:39:17 2017 +0200
+++ b/hglib/client.py   Mon Aug 21 17:02:14 2017 +0200
@@ -139,6 +139,7 @@
 def _readchannel(self):
 data = self.server.stdout.read(hgclient.outputfmtsize)
 if not data:
+self.close()
 raise error.ServerError()
 channel, length = struct.unpack(hgclient.outputfmt, data)
 if channel in b('IL'):
@@ -190,6 +191,7 @@
 return struct.unpack(hgclient.retfmt, data)[0]
 # a channel that we don't know and can't ignore
 elif channel.isupper():
+self.close()
 raise error.ResponseError(
 "unexpected data on required channel '%s'" % channel)
 # optional channel

 This message, including its attachments, is confidential and the property of 
NNG Llc. 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.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: D243: obsmarker: rename precnode into prednode

2017-08-14 Thread Gábor STEFANIK


> -Original Message-
> From: Mercurial-devel [mailto:mercurial-devel-boun...@mercurial-scm.org]
> On Behalf Of lothiraldan (Boris Feld)
> Sent: Wednesday, August 9, 2017 7:20 PM
> To: mercurial-devel@mercurial-scm.org
> Subject: D243: obsmarker: rename precnode into prednode
>
> lothiraldan updated this revision to Diff 705.
>
> REPOSITORY
>   rHG Mercurial
>
> CHANGES SINCE LAST UPDATE
>   https://phab.mercurial-scm.org/D243?vs=699=705
>
> REVISION DETAIL
>   https://phab.mercurial-scm.org/D243
>
> AFFECTED FILES
>   mercurial/cmdutil.py
>   mercurial/obsutil.py
>
> CHANGE DETAILS
>
> diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
> --- a/mercurial/obsutil.py
> +++ b/mercurial/obsutil.py
> @@ -9,6 +9,7 @@
>
>  from . import (
>  phases,
> +util
>  )
>
>  class marker(object):
> @@ -29,15 +30,21 @@
>  return self._data == other._data
>
>  def precnode(self):
> -"""Precursor changeset node identifier"""
> +msg = ("'marker.precnode' is deprecated, "
> +   "use 'marker.precnode'")

precnode is deprecated, use precnode instead? is that a typo?

> +util.nouideprecwarn(msg, '4.4')
> +return self.prednode()
> +
> +def prednode(self):
> +"""Predecessor changeset node identifier"""
>  return self._data[0]
>
>  def succnodes(self):
>  """List of successor changesets node identifiers"""
>  return self._data[1]
>
>  def parentnodes(self):
> -"""Parents of the precursors (None if not recorded)"""
> +"""Parents of the predecessors (None if not recorded)"""
>  return self._data[5]
>
>  def metadata(self):
> diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
> --- a/mercurial/cmdutil.py
> +++ b/mercurial/cmdutil.py
> @@ -1913,7 +1913,7 @@
>  To be used by debug function."""
>  if index is not None:
>  fm.write('index', '%i ', index)
> -fm.write('precnode', '%s ', hex(marker.precnode()))
> +fm.write('precnode', '%s ', hex(marker.prednode()))
>  succs = marker.succnodes()
>  fm.condwrite(succs, 'succnodes', '%s ',
>   fm.formatlist(map(hex, succs), name='node'))
>
>
>
> To: lothiraldan, #hg-reviewers, dsp
> Cc: mercurial-devel
> ___
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

 This message, including its attachments, is confidential and the property of 
NNG Llc. 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.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH STABLE] revlog: add an experimental option to mitigated delta issues (issue5480)

2017-06-29 Thread Gábor STEFANIK
> -Original Message-
> From: Pierre-Yves David [mailto:pierre-yves.da...@ens-lyon.org]
> Sent: Wednesday, June 28, 2017 10:48 PM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>; mercurial-
> de...@mercurial-scm.org
> Subject: Re: [PATCH STABLE] revlog: add an experimental option to mitigated
> delta issues (issue5480)
>
>
>
> On 06/28/2017 02:26 PM, Gábor STEFANIK wrote:
> >> -Original Message-
> >> From: Mercurial-devel [mailto:mercurial-devel-bounces@mercurial-
> scm.org]
> >> On Behalf Of Pierre-Yves David
> >> Sent: Tuesday, June 27, 2017 1:16 PM
> >> To: mercurial-devel@mercurial-scm.org
> >> Subject: [PATCH STABLE] revlog: add an experimental option to mitigated
> >> delta issues (issue5480)
> >>
> >> # HG changeset patch
> >> # User Pierre-Yves David <pierre-yves.da...@octobus.net>
> >> # Date 1498218574 -7200
> >> #  Fri Jun 23 13:49:34 2017 +0200
> >> # Branch stable
> >> # Node ID 33998dea4a10b09502bf458e458daca273a3f29a
> >> # Parent  231690dba9b4d31b5ad2c93284e454135f2763ca
> >> # EXP-Topic manifest
> >> # Available At https://www.mercurial-
> >> scm.org/repo/users/marmoute/mercurial/
> >> #  hg pull https://www.mercurial-
> >> scm.org/repo/users/marmoute/mercurial/ -r 33998dea4a10
> >> revlog: add an experimental option to mitigated delta issues (issue5480)
> >>
> >> The general delta heuristic to select a delta do not scale with the number
> of
> >> branch. The delta base is frequently too far away to be able to reuse a
> chain
> >> according to the "distance" criteria. This leads to insertion of larger 
> >> delta
> (or
> >> even full text) that themselves push the bases for the next delta further
> >> away
> >> leading to more large deltas and full texts. This full text and frequent
> >> recomputation throw Mercurial performance in disarray.
> >>
> >> For example of a slightly large repository
> >>
> >>280 000 files (2 150 000 versions)
> >>430 000 changesets (10 000 topological heads)
> >>
> >> Number below compares repository with and without the distance
> criteria:
> >>
> >> manifest size:
> >>  with:21.4 GB
> >>  without:  0.3 GB
> >>
> >> store size:
> >>  with:28.7 GB
> >>  without   7.4 GB
> >>
> >> bundle last 15 00 revisions:
> >>  with:800 seconds
> >>   971 MB
> >>  without:  50 seconds
> >>73 MB
> >>
> >> unbundle time (of the last 15K revisions):
> >>  with:1150 seconds (~19 minutes)
> >>  without:   35 seconds
> >>
> >> Similar issues has been observed in other repositories.
> >>
> >>
> >> Adding a new option or "feature" on stable is uncommon. However, given
> >> that this
> >> issues is making Mercurial practically unusable, I'm exceptionally 
> >> targeting
> >> this patch for stable.
> >>
> >> What is actually needed is a full rework of the delta building and reading
> >> logic. However, that will be a longer process and churn not suitable for
> stable.
> >>
> >> In the meantime, we introduces a quick and dirty mitigation of this in the
> >> 'experimental' config space. The new option introduces a way to set the
> >> maximum
> >> amount of memory usable to store a diff in memory. This extend the
> ability
> >> for
> >> Mercurial to create chains without removing all safe guard regarding
> memory
> >> access. The option should be phased out when core has a more proper
> >> solution
> >> available.
> >>
> >> Setting the limit to '0' remove all limits, setting it to '-1' use the 
> >> default
> >> limit (textsize x 4).
> >>
> >> diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
> >> --- a/mercurial/localrepo.py
> >> +++ b/mercurial/localrepo.py
> >> @@ -432,6 +432,9 @@ class localrepository(object):
> >>   'aggressivemergedeltas', False)
> >>   self.svfs.options['aggressivemergedeltas'] = 
> >> aggressivemergedeltas
> >>   self.svfs.options['lazydeltabase'] = not 
> >> scmutil.gddeltaconfig(self.ui)
> >> +chainspan = self.ui.configbytes('experimental', 
> >> 'maxdeltachainspan',
> -1)

RE: [PATCH STABLE] revlog: add an experimental option to mitigated delta issues (issue5480)

2017-06-28 Thread Gábor STEFANIK
> -Original Message-
> From: Mercurial-devel [mailto:mercurial-devel-boun...@mercurial-scm.org]
> On Behalf Of Pierre-Yves David
> Sent: Tuesday, June 27, 2017 1:16 PM
> To: mercurial-devel@mercurial-scm.org
> Subject: [PATCH STABLE] revlog: add an experimental option to mitigated
> delta issues (issue5480)
>
> # HG changeset patch
> # User Pierre-Yves David 
> # Date 1498218574 -7200
> #  Fri Jun 23 13:49:34 2017 +0200
> # Branch stable
> # Node ID 33998dea4a10b09502bf458e458daca273a3f29a
> # Parent  231690dba9b4d31b5ad2c93284e454135f2763ca
> # EXP-Topic manifest
> # Available At https://www.mercurial-
> scm.org/repo/users/marmoute/mercurial/
> #  hg pull https://www.mercurial-
> scm.org/repo/users/marmoute/mercurial/ -r 33998dea4a10
> revlog: add an experimental option to mitigated delta issues (issue5480)
>
> The general delta heuristic to select a delta do not scale with the number of
> branch. The delta base is frequently too far away to be able to reuse a chain
> according to the "distance" criteria. This leads to insertion of larger delta 
> (or
> even full text) that themselves push the bases for the next delta further
> away
> leading to more large deltas and full texts. This full text and frequent
> recomputation throw Mercurial performance in disarray.
>
> For example of a slightly large repository
>
>   280 000 files (2 150 000 versions)
>   430 000 changesets (10 000 topological heads)
>
> Number below compares repository with and without the distance criteria:
>
> manifest size:
> with:21.4 GB
> without:  0.3 GB
>
> store size:
> with:28.7 GB
> without   7.4 GB
>
> bundle last 15 00 revisions:
> with:800 seconds
>  971 MB
> without:  50 seconds
>   73 MB
>
> unbundle time (of the last 15K revisions):
> with:1150 seconds (~19 minutes)
> without:   35 seconds
>
> Similar issues has been observed in other repositories.
>
>
> Adding a new option or "feature" on stable is uncommon. However, given
> that this
> issues is making Mercurial practically unusable, I'm exceptionally targeting
> this patch for stable.
>
> What is actually needed is a full rework of the delta building and reading
> logic. However, that will be a longer process and churn not suitable for 
> stable.
>
> In the meantime, we introduces a quick and dirty mitigation of this in the
> 'experimental' config space. The new option introduces a way to set the
> maximum
> amount of memory usable to store a diff in memory. This extend the ability
> for
> Mercurial to create chains without removing all safe guard regarding memory
> access. The option should be phased out when core has a more proper
> solution
> available.
>
> Setting the limit to '0' remove all limits, setting it to '-1' use the default
> limit (textsize x 4).
>
> diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
> --- a/mercurial/localrepo.py
> +++ b/mercurial/localrepo.py
> @@ -432,6 +432,9 @@ class localrepository(object):
>  'aggressivemergedeltas', False)
>  self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
>  self.svfs.options['lazydeltabase'] = not 
> scmutil.gddeltaconfig(self.ui)
> +chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan', 
> -1)
> +if 0 <= chainspan:
> +self.svfs.options['maxdeltachainspan'] = chainspan
>
>  for r in self.requirements:
>  if r.startswith('exp-compression-'):
> diff --git a/mercurial/revlog.py b/mercurial/revlog.py
> --- a/mercurial/revlog.py
> +++ b/mercurial/revlog.py
> @@ -282,6 +282,7 @@ class revlog(object):
>  self._nodecache = {nullid: nullrev}
>  self._nodepos = None
>  self._compengine = 'zlib'
> +self._maxdeltachainspan = -1
>
>  v = REVLOG_DEFAULT_VERSION
>  opts = getattr(opener, 'options', None)
> @@ -300,6 +301,8 @@ class revlog(object):
>  self._lazydeltabase = bool(opts.get('lazydeltabase', False))
>  if 'compengine' in opts:
>  self._compengine = opts['compengine']
> +if 'maxdeltachainspan' in opts:
> +self._maxdeltachainspan = opts['maxdeltachainspan']
>
>  if self._chunkcachesize <= 0:
>  raise RevlogError(_('revlog chunk cache size %r is not greater '
> @@ -1596,7 +1599,13 @@ class revlog(object):
>  # - 'compresseddeltalen' is the sum of the total size of deltas we 
> need
>  #   to apply -- bounding it limits the amount of CPU we consume.
>  dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
> -if (dist > textlen * 4 or l > textlen or
> +
> +defaultmax = textlen * 4
> +maxdist = self._maxdeltachainspan
> +if not maxdist:
> +maxdist = dist # ensure the conditional pass
> +maxdist = max(maxdist, defaultmax)
> +if (dist > maxdist or l > 

[PATCH] util: fix human-readable printing of negative byte counts

2017-04-11 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1491840990 -7200
#  Mon Apr 10 18:16:30 2017 +0200
# Node ID c3d89ef348120ead9c2280c55d05c7ce435ea589
# Parent  e0dc40530c5aa514feb6a09cf79ab6a3aa2ec331
util: fix human-readable printing of negative byte counts

Apply the same human-readable printing rules to negative byte counts as to
positive ones. Fixes output of debugupgraderepo.

diff -r e0dc40530c5a -r c3d89ef34812 mercurial/util.py
--- a/mercurial/util.py Sat Apr 01 15:24:03 2017 -0700
+++ b/mercurial/util.py Mon Apr 10 18:16:30 2017 +0200
@@ -2160,7 +2160,7 @@
 
 def go(count):
 for multiplier, divisor, format in unittable:
-if count >= divisor * multiplier:
+if abs(count) >= divisor * multiplier:
 return format % (count / float(divisor))
 return unittable[-1][2] % count
 



--
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.


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


[PATCH] util: fix human-readable printing of negative byte counts

2017-04-11 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1491840990 -7200
#  Mon Apr 10 18:16:30 2017 +0200
# Node ID c3d89ef348120ead9c2280c55d05c7ce435ea589
# Parent  e0dc40530c5aa514feb6a09cf79ab6a3aa2ec331
util: fix human-readable printing of negative byte counts

Apply the same human-readable printing rules to negative byte counts as to
positive ones. Fixes output of debugupgraderepo.

diff -r e0dc40530c5a -r c3d89ef34812 mercurial/util.py
--- a/mercurial/util.py Sat Apr 01 15:24:03 2017 -0700
+++ b/mercurial/util.py Mon Apr 10 18:16:30 2017 +0200
@@ -2160,7 +2160,7 @@
 
 def go(count):
 for multiplier, divisor, format in unittable:
-if count >= divisor * multiplier:
+if abs(count) >= divisor * multiplier:
 return format % (count / float(divisor))
 return unittable[-1][2] % count
 



--
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.


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


[PATCH] util: fix human-readable printing of negative byte counts

2017-04-11 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1491840990 -7200
#  Mon Apr 10 18:16:30 2017 +0200
# Node ID c3d89ef348120ead9c2280c55d05c7ce435ea589
# Parent  e0dc40530c5aa514feb6a09cf79ab6a3aa2ec331
util: fix human-readable printing of negative byte counts

Apply the same human-readable printing rules to negative byte counts as to
positive ones. Fixes output of debugupgraderepo.

diff -r e0dc40530c5a -r c3d89ef34812 mercurial/util.py
--- a/mercurial/util.py Sat Apr 01 15:24:03 2017 -0700
+++ b/mercurial/util.py Mon Apr 10 18:16:30 2017 +0200
@@ -2160,7 +2160,7 @@
 
 def go(count):
 for multiplier, divisor, format in unittable:
-if count >= divisor * multiplier:
+if abs(count) >= divisor * multiplier:
 return format % (count / float(divisor))
 return unittable[-1][2] % count
 



--
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.


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


RE: [PATCH 3 of 4 V3] update: also suggest --merge when non-linear update is aborted

2017-02-20 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 Augie Fackler
> Sent: Wednesday, February 15, 2017 11:25 PM
> To: Martin von Zweigbergk 
> Cc: mercurial-devel@mercurial-scm.org
> Subject: Re: [PATCH 3 of 4 V3] update: also suggest --merge when non-linear
> update is aborted
> 
> On Wed, Feb 15, 2017 at 12:56:41PM -0800, Martin von Zweigbergk via
> Mercurial-devel wrote:
> > # HG changeset patch
> > # User Martin von Zweigbergk  # Date
> 1487140898
> > 28800
> > #  Tue Feb 14 22:41:38 2017 -0800
> > # Node ID c6cd58d272aee6633fbad5eacdad742e2f9909cd
> > # Parent  542a99ede6c3ac7cb4afccd3703fcc30e3d4c90d
> > update: also suggest --merge when non-linear update is aborted
> 
> This makes me a touch nervous, since the merge can leave the user in an
> state that's hard to recover from.

Basically "hg resolve -au; hg resolve -at:local; hg update --merge -r$(head 
-c40 .hg/merge/state) -t:local"; but we need to expose a less hacky way of 
doing so. 

"hg update --abort"?

> 
> Series LG otherwise, but I won't land it since I'm also enthusiastic about the
> feature added in patch 4.
> 
> >
> > diff -r 542a99ede6c3 -r c6cd58d272ae mercurial/merge.py
> > --- a/mercurial/merge.pyMon Feb 13 16:03:05 2017 -0800
> > +++ b/mercurial/merge.pyTue Feb 14 22:41:38 2017 -0800
> > @@ -1570,7 +1570,8 @@
> >  pass # allow updating to successors
> >  else:
> >  msg = _("uncommitted changes")
> > -hint = _("commit or update --clean to discard 
> > changes")
> > +hint = _("commit, or use --clean to discard 
> > changes, "
> > + "or use --merge to allow update")
> >  raise error.UpdateAbort(msg, hint=hint)
> >  else:
> >  # Allow jumping branches if clean and specific
> > rev given diff -r 542a99ede6c3 -r c6cd58d272ae tests/test-merge5.t
> > --- a/tests/test-merge5.t   Mon Feb 13 16:03:05 2017 -0800
> > +++ b/tests/test-merge5.t   Tue Feb 14 22:41:38 2017 -0800
> > @@ -26,7 +26,7 @@
> >
> >$ hg update 1
> >abort: uncommitted changes
> > -  (commit or update --clean to discard changes)
> > +  (commit, or use --clean to discard changes, or use --merge to allow
> > + update)
> >[255]
> >$ mv c a
> >
> > diff -r 542a99ede6c3 -r c6cd58d272ae tests/test-subrepo-svn.t
> > --- a/tests/test-subrepo-svn.t  Mon Feb 13 16:03:05 2017 -0800
> > +++ b/tests/test-subrepo-svn.t  Tue Feb 14 22:41:38 2017 -0800
> > @@ -472,7 +472,7 @@
> >$ echo "updating should (maybe) fail" > obstruct/other
> >$ hg co tip
> >abort: uncommitted changes
> > -  (commit or update --clean to discard changes)
> > +  (commit, or use --clean to discard changes, or use --merge to allow
> > + update)
> >[255]
> >
> >  Point to a Subversion branch which has since been deleted and
> > recreated diff -r 542a99ede6c3 -r c6cd58d272ae tests/test-update-
> branches.t
> > --- a/tests/test-update-branches.t  Mon Feb 13 16:03:05 2017 -0800
> > +++ b/tests/test-update-branches.t  Tue Feb 14 22:41:38 2017 -0800
> > @@ -123,19 +123,19 @@
> >
> >$ revtest 'none dirty same'   dirty 2 3
> >abort: uncommitted changes
> > -  (commit or update --clean to discard changes)
> > +  (commit, or use --clean to discard changes, or use --merge to allow
> > + update)
> >parent=2
> >M foo
> >
> >$ revtest 'none dirtysub same'   dirtysub 2 3
> >abort: uncommitted changes
> > -  (commit or update --clean to discard changes)
> > +  (commit, or use --clean to discard changes, or use --merge to allow
> > + update)
> >parent=2
> >M sub/suba
> >
> >$ revtest 'none dirty cross'  dirty 3 4
> >abort: uncommitted changes
> > -  (commit or update --clean to discard changes)
> > +  (commit, or use --clean to discard changes, or use --merge to allow
> > + update)
> >parent=3
> >M foo
> >
> > @@ -147,7 +147,7 @@
> >
> >$ revtest 'none dirtysub cross'  dirtysub 3 4
> >abort: uncommitted changes
> > -  (commit or update --clean to discard changes)
> > +  (commit, or use --clean to discard changes, or use --merge to allow
> > + update)
> >parent=3
> >M sub/suba
> >
> > @@ -258,7 +258,7 @@
> >
> >$ revtest 'dirty cross'  dirty 3 4
> >abort: uncommitted changes
> > -  (commit or update --clean to discard changes)
> > +  (commit, or use --clean to discard changes, or use --merge to allow
> > + update)
> >parent=3
> >M foo
> >
> > @@ -476,7 +476,7 @@
> >$ hg up --quiet 2
> >$ hg up 5
> >   

RE: [PATCH 5 of 6] update: learn --merge to allow merging across topological branches (issue5125)

2017-02-15 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: Martin von Zweigbergk [mailto:martinv...@google.com]
> Sent: Wednesday, February 15, 2017 7:41 PM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> Cc: Yuya Nishihara <y...@tcha.org>; Mercurial-devel  de...@mercurial-scm.org>
> Subject: Re: [PATCH 5 of 6] update: learn --merge to allow merging across
> topological branches (issue5125)
> 
> On Wed, Feb 15, 2017 at 10:35 AM, Gábor STEFANIK
> <gabor.stefa...@nng.com> wrote:
> >>
> >
> >
> > --
> >  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: Martin von Zweigbergk [mailto:martinv...@google.com]
> >> Sent: Wednesday, February 15, 2017 7:26 PM
> >> To: Yuya Nishihara <y...@tcha.org>
> >> Cc: Gábor STEFANIK <gabor.stefa...@nng.com>; Mercurial-devel
> >> <mercurial-devel@mercurial-scm.org>
> >> Subject: Re: [PATCH 5 of 6] update: learn --merge to allow merging
> >> across topological branches (issue5125)
> >>
> >> On Wed, Feb 15, 2017 at 6:22 AM, Yuya Nishihara <y...@tcha.org> wrote:
> >> > On Wed, 15 Feb 2017 13:29:30 +, Gábor STEFANIK wrote:
> >> >> > >> -def updatetotally(ui, repo, checkout, brev, clean=False,
> >> check=False):
> >> >> > >> +def updatetotally(ui, repo, checkout, brev, clean=False,
> >> >> > >> +  updatecheck='linear'):
> >> >> > >
> >> >> > > Please make this updatecheck=None, and set it to 'linear'
> >> >> > > inside
> >> >> > > merge.update() explicitly if None was passed to that function.
> >> >> >
> >> >> > Will do.
> >> >> >
> >> >> > >
> >> >> > > This way, in patch 6, you can red the config option inside
> >> >> > > merge.update(), and let other callers (primarily extensions)
> >> >> > > also
> >> >> > automatically follow the config option.
> >> >> >
> >> >> > I don't think it's a good idea for the low-level merge.update()
> >> >> > to read the config. That function is called e.g. by rebase and
> >> >> > graft and they should not have their behavior changed by this config.
> >> >
> >> > I tend to agree with Martin, low-level functions should have less
> >> > dependency on config. Instead, maybe we could add a function that
> >> > builds options from ui (like patch.diffopts), but I don't know if
> >> > that's
> >> appropriate here.
> >> >
> >> >> I forgot to add, I'm particularly concerned about the "guestrepo"
> >> >> extension, which we use extensively at NNG. IIRC in the "grupdate"
> >> >> command, it doesn't use commands.update, and so changing the
> >> >> config
> >> option causes "update"
> >> >> and "grupdate" to behave differently.
> >> >>
> >> >> Also, care must be taken about subrepositories. Not sure if our
> >> >> subrepo update code calls command.update - if not, we may even end
> >> >> up with a situation where "hg update" in a repository with
> >> >> subrepos with
> >> ui.updatecheck="merge"
> >> >> will do nonlinear updates with a dirty main repo, but not with a
> >> >> dirty
> >> subrepo.
> >> >
> >> > IIRC, updating including subrepos isn't a simple cascading operation.
> >> > Perhaps users would be asked how to process changes in subrepos?
> >> >
> >> >> In fact, if we introduce a config option, but only use it in
> >> >> commands.update(), we should make the lower functions error out or
> >> >> at least warn if called without explicit updatecheck, and without
> >> >> other options (e.g. branchmerge) that override linearity c

RE: [PATCH 5 of 6] update: learn --merge to allow merging across topological branches (issue5125)

2017-02-15 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: Martin von Zweigbergk [mailto:martinv...@google.com]
> Sent: Wednesday, February 15, 2017 7:26 PM
> To: Yuya Nishihara <y...@tcha.org>
> Cc: Gábor STEFANIK <gabor.stefa...@nng.com>; Mercurial-devel
> <mercurial-devel@mercurial-scm.org>
> Subject: Re: [PATCH 5 of 6] update: learn --merge to allow merging across
> topological branches (issue5125)
> 
> On Wed, Feb 15, 2017 at 6:22 AM, Yuya Nishihara <y...@tcha.org> wrote:
> > On Wed, 15 Feb 2017 13:29:30 +, Gábor STEFANIK wrote:
> >> > >> -def updatetotally(ui, repo, checkout, brev, clean=False,
> check=False):
> >> > >> +def updatetotally(ui, repo, checkout, brev, clean=False,
> >> > >> +  updatecheck='linear'):
> >> > >
> >> > > Please make this updatecheck=None, and set it to 'linear' inside
> >> > > merge.update() explicitly if None was passed to that function.
> >> >
> >> > Will do.
> >> >
> >> > >
> >> > > This way, in patch 6, you can red the config option inside
> >> > > merge.update(), and let other callers (primarily extensions) also
> >> > automatically follow the config option.
> >> >
> >> > I don't think it's a good idea for the low-level merge.update() to
> >> > read the config. That function is called e.g. by rebase and graft
> >> > and they should not have their behavior changed by this config.
> >
> > I tend to agree with Martin, low-level functions should have less
> > dependency on config. Instead, maybe we could add a function that
> > builds options from ui (like patch.diffopts), but I don't know if that's
> appropriate here.
> >
> >> I forgot to add, I'm particularly concerned about the "guestrepo"
> >> extension, which we use extensively at NNG. IIRC in the "grupdate"
> >> command, it doesn't use commands.update, and so changing the config
> option causes "update"
> >> and "grupdate" to behave differently.
> >>
> >> Also, care must be taken about subrepositories. Not sure if our
> >> subrepo update code calls command.update - if not, we may even end up
> >> with a situation where "hg update" in a repository with subrepos with
> ui.updatecheck="merge"
> >> will do nonlinear updates with a dirty main repo, but not with a dirty
> subrepo.
> >
> > IIRC, updating including subrepos isn't a simple cascading operation.
> > Perhaps users would be asked how to process changes in subrepos?
> >
> >> In fact, if we introduce a config option, but only use it in
> >> commands.update(), we should make the lower functions error out or at
> >> least warn if called without explicit updatecheck, and without other
> >> options (e.g. branchmerge) that override linearity checks. This way,
> >> we at least catch extensions that don't follow the config option, but try 
> >> to
> rely on merge.update()'s linearity checks.
> >
> > Good point.
> 
> Yeah, I agree with that last point. I tried it and there are several places 
> that
> fail. For example, clone, rebase, and mq all call hg.update[repo](). AFAICT,
> there should never be any changes where it's being called, so maybe that
> should be setting force=True to overwrite. Or maybe we should teach
> merge.update() to accept updatecheck='abort' and make these callers pass
> that value.
> Regardless, since what I'm adding is an experimental config, I'd prefer to
> make merge.update() default to 'linear' for now and I'll add a TODO to add
> the check you mention. I don't have time right now to clean up all the other
> callers, and I'd still like to be able to make progress on this topic.

These other callers IIRC call with options that  already disable linearity 
checks.

We should only fail if we are in a normal update scenario with linearity checks
enabled, but no check strategy was provided.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH 5 of 6] update: learn --merge to allow merging across topological branches (issue5125)

2017-02-15 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: Martin von Zweigbergk [mailto:martinv...@google.com]
> Sent: Tuesday, February 14, 2017 11:36 PM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> Cc: Mercurial-devel <mercurial-devel@mercurial-scm.org>
> Subject: Re: [PATCH 5 of 6] update: learn --merge to allow merging across
> topological branches (issue5125)
> 
> (+mercurial-devel)
> 
> On Tue, Feb 14, 2017 at 10:48 AM, Gábor STEFANIK
> <gabor.stefa...@nng.com> wrote:
> >>
> >
> >
> > --
> >  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 Martin von Zweigbergk via Mercurial-devel
> >> Sent: Tuesday, February 14, 2017 2:07 AM
> >> To: mercurial-devel@mercurial-scm.org
> >> Subject: [PATCH 5 of 6] update: learn --merge to allow merging across
> >> topological branches (issue5125)
> >>
> >> # HG changeset patch
> >> # User Martin von Zweigbergk <martinv...@google.com> # Date
> >> 1487019517 28800
> >> #  Mon Feb 13 12:58:37 2017 -0800
> >> # Node ID 75e5a492d7f69420a554fe498ae24060c755b09f
> >> # Parent  59e2d3da607cc09ebe133ab199fc4f343d74e1e6
> >> update: learn --merge to allow merging across topological branches
> >> (issue5125)
> >>
> >> diff -r 59e2d3da607c -r 75e5a492d7f6 mercurial/commands.py
> >> --- a/mercurial/commands.py   Mon Feb 13 15:04:46 2017 -0800
> >> +++ b/mercurial/commands.py   Mon Feb 13 12:58:37 2017 -0800
> >> @@ -6471,12 +6471,13 @@
> >>  @command('^update|up|checkout|co',
> >>  [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
> >>  ('c', 'check', None, _('require clean working directory')),
> >> +('m', 'merge', None, _('merge local changes')),
> >>  ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
> >>  ('r', 'rev', '', _('revision'), _('REV'))
> >>   ] + mergetoolopts,
> >> -_('[-C|-c] [-d DATE] [[-r] REV]'))
> >> +_('[-C|-c|-m] [-d DATE] [[-r] REV]'))
> >>  def update(ui, repo, node=None, rev=None, clean=False, date=None,
> >> check=False,
> >> -   tool=None):
> >> +   merge=None, tool=None):
> >>  """update working directory (or switch revisions)
> >>
> >>  Update the repository's working directory to the specified @@
> >> -6498,7 +6499,7 @@
> >>The following rules apply when the working directory contains
> >>uncommitted changes:
> >>
> >> -  1. If neither -c/--check nor -C/--clean is specified, and if
> >> +  1. If none of -c/--check, -C/--clean, or -m/--merge is
> >> + specified, and if
> >>   the requested changeset is an ancestor or descendant of
> >>   the working directory's parent, the uncommitted changes
> >>   are merged into the requested changeset and the merged @@
> >> -6507,10 +6508,14 @@
> >>   branch), the update is aborted and the uncommitted changes
> >>   are preserved.
> >>
> >> -  2. With the -c/--check option, the update is aborted and the
> >> +  2. With the -m/--merge option, the update is allowed even if the
> >> + requested changeset is not an ancestor or descendant of
> >> + the working directory's parent.
> >> +
> >> +  3. With the -c/--check option, the update is aborted and the
> >>   uncommitted changes are preserved.
> >>
> >> -  3. With the -C/--clean option, uncommitted changes are discarded and
> >> +  4. With the -C/--clean option, uncommitted changes are
> >> + discarded and
> >>   the working directory is updated to the requested changeset.
> >>
> >>  To cancel an uncommitted merge (and lose your changes), use @@
> >> -6535,8 +6540,15 @@
> >>  if d

RE: [PATCH] update: introduce config option ui.allowdirtyupdate for dirty nonlinear updates

2016-12-19 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: Augie Fackler [mailto:r...@durin42.com]
> Sent: Friday, December 16, 2016 3:13 AM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> Cc: Jun Wu <qu...@fb.com>; mercurial-devel <mercurial-devel@mercurial-
> scm.org>
> Subject: Re: [PATCH] update: introduce config option ui.allowdirtyupdate for
> dirty nonlinear updates
>
> This ended up long, because I’m riffing a bit on what it means to be
> experimental etc. If you don’t care about those details (most readers of this
> list probably don’t?), skip to the “TL;DR:” bit and let’s talk about footguns
> now that the bikeshed is painted...
>
> > On Dec 13, 2016, at 11:48 AM, Gábor STEFANIK
> <gabor.stefa...@nng.com> wrote:
> >
> > -Original Message-
> >> On Thu, Dec 8, 2016 at 9:13 AM, Gábor STEFANIK
> >> <gabor.stefa...@nng.com> wrote:
> >>>
> >>> -Original Message-
> >>>> From: Jun Wu [mailto:qu...@fb.com]
> >>>> Sent: Wednesday, December 7, 2016 6:44 PM
> >>>> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> >>>> Cc: mercurial-devel <mercurial-devel@mercurial-scm.org>
> >>>> Subject: Re: [PATCH] update: introduce config option
> >>>> ui.allowdirtyupdate for dirty nonlinear updates
> >>>>
> >>>> Excerpts from Gábor Stefanik's message of 2016-12-07 16:56:09 +0100:
> >>>>> # HG changeset patch
> >>>>> # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1481126137
> - 3600
> >>>>> #  Wed Dec 07 16:55:37 2016 +0100
> >>>>> # Node ID dabbe365b843fcf9b8a0de6c08e9db6100b391e9
> >>>>> # Parent  6472c33e16326b8c817a8bae0e75053b19badb2c
> >>>>> update: introduce config option ui.allowdirtyupdate for dirty
> >>>>> nonlinear updates
>
> [...]
>
> >>>
> >>> This is experimental for now, since we need to support "hg update
> >>> --abort" to make this safe for users, but eventually I hope to get
> >>> this de-experimentalized and maybe even enabled by default. It would
> >>> be better not to break backwards compatibility just because this
> >>> becomes no longer experimental.
> >>
> >> Until we're confident that this feature will live forever, it should
> >> be in experimental.
> >
> > I would argue that this will live forever, because:
> > a) If we eventually decide to make this the default
>
> (By this point it wouldn’t be experimental...)
>
> > , some users will want to
> > still have the old behavior - which they can get by explicitly setting
> > ui.allowdirtyupdate=False. I don't envision ever dropping the current
> > behavior completely.
>
> Yep! That’s very true. Note that if we were going to try and move the default
> (or even the recommended setting), I’d probably bias in the other direction:
> making something like --check the default, in the name of greater safety. The
> only way I can see that changing would be if we had some fast-but-reliable
> way to undo an in-progress update that hit merge conflicts.

"Fast-but-reliable way to undo an in-progress-update" is bug 4404. I've 
outlined the solution
on Bugzilla for that one, the only thing needed is a place to store the 
revision we're updating
from. We do save some revision in the merge state files, but I don't know if 
that's always
guaranteed to be the right revision, or if it's instead possibly an 
older/ancestral revision where
the files being merged were last modified.

The solution I outlined would look like this:
1. Find the revision "last()" which we have to update back to.
2. For files with saved mergestate, restore the "local" version.
3. Update all "clean" files to the file revisions in "last()".
(4. Leave "dirty" files with no mergestate unchanged - if there were any change 
to undo,
the original update would have resulted in the files gaining mergestate.)

Always defaulting to --check is another way to fix the UI inconsistency where 
some
updates just work, but in a potentially unsafe way; while others fail with an 
obscure
"not a linear update" error message (the user doesn't know or care what our code
considers "linear") - the problem is that it would be a BC break.

>
> > b) If we decide that 

RE: [PATCH 1 of 2] error: add ProgrammingError

2016-12-13 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, December 13, 2016 5:53 PM
> To: Jun Wu <qu...@fb.com>; mercurial-devel@mercurial-scm.org
> Subject: RE: [PATCH 1 of 2] error: add ProgrammingError
>
> >
>
>
> --
> 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 Jun Wu
> > Sent: Tuesday, December 6, 2016 6:14 PM
> > To: mercurial-devel@mercurial-scm.org
> > Subject: [PATCH 1 of 2] error: add ProgrammingError
> >
> > # HG changeset patch
> > # User Jun Wu <qu...@fb.com>
> > # Date 1481036267 0
> > #  Tue Dec 06 14:57:47 2016 +
> > # Node ID 67bcd43e64ca03f486a817fccf38e83020b06793
> > # Parent  2463b16bbd3710b619e0e948651db9948341f990
> > # Available At https://bitbucket.org/quark-zju/hg-draft
> > #  hg pull https://bitbucket.org/quark-zju/hg-draft -r 
> > 67bcd43e64ca
> > error: add ProgrammingError
> >
> > We have requirement to express "this is clearly an error caused by the
> > programmer". The code base uses RuntimeError for that in some places,
> > not ideal. So let's add a formal exception for that.
> >
> > diff --git a/mercurial/error.py b/mercurial/error.py
> > --- a/mercurial/error.py
> > +++ b/mercurial/error.py
> > @@ -169,4 +169,7 @@ class PushRaced(RuntimeError):
> >  """An exception raised during unbundling that indicate a push race"""
> >
> > +class ProgrammingError(RuntimeError):
>
> I would suggest InternalError, assuming it's not reserved by the Python
> language, or used for something else already in Hg.
> "ProgrammingError" could be interpreted as a programming error in Hg, but
> also a programming error in tracked code. It's more of a stretch than with
> "developer", but "internal" is still clearer than "programming".
>
> See also: GCC's ICEs (but "in-SCM exception" is clunky IMO)

Looks like I was wrong - ICE apparently stands for "internal compiler error", 
not "in-compiler exception".
So, InternalError is in agreement with GCC's nomenclature.

>
> > +"""Raised if a developer has made some mistake"""
> > +
> >  # bundle2 related errors
> >  class BundleValueError(ValueError):
> > ___
> > Mercurial-devel mailing list
> > Mercurial-devel@mercurial-scm.org
> > https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
> ___
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH 1 of 2] error: add ProgrammingError

2016-12-13 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 Jun Wu
> Sent: Tuesday, December 6, 2016 6:14 PM
> To: mercurial-devel@mercurial-scm.org
> Subject: [PATCH 1 of 2] error: add ProgrammingError
>
> # HG changeset patch
> # User Jun Wu 
> # Date 1481036267 0
> #  Tue Dec 06 14:57:47 2016 +
> # Node ID 67bcd43e64ca03f486a817fccf38e83020b06793
> # Parent  2463b16bbd3710b619e0e948651db9948341f990
> # Available At https://bitbucket.org/quark-zju/hg-draft
> #  hg pull https://bitbucket.org/quark-zju/hg-draft -r 
> 67bcd43e64ca
> error: add ProgrammingError
>
> We have requirement to express "this is clearly an error caused by the
> programmer". The code base uses RuntimeError for that in some places, not
> ideal. So let's add a formal exception for that.
>
> diff --git a/mercurial/error.py b/mercurial/error.py
> --- a/mercurial/error.py
> +++ b/mercurial/error.py
> @@ -169,4 +169,7 @@ class PushRaced(RuntimeError):
>  """An exception raised during unbundling that indicate a push race"""
>
> +class ProgrammingError(RuntimeError):

I would suggest InternalError, assuming it's not reserved by the Python
language, or used for something else already in Hg.
"ProgrammingError" could be interpreted as a programming error in Hg,
but also a programming error in tracked code. It's more of a stretch than
with "developer", but "internal" is still clearer than "programming".

See also: GCC's ICEs (but "in-SCM exception" is clunky IMO)

> +"""Raised if a developer has made some mistake"""
> +
>  # bundle2 related errors
>  class BundleValueError(ValueError):
> ___
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH] update: introduce config option ui.allowdirtyupdate for dirty nonlinear updates

2016-12-13 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: Augie Fackler [mailto:r...@durin42.com]
> Sent: Thursday, December 8, 2016 4:02 PM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> Cc: Jun Wu <qu...@fb.com>; mercurial-devel <mercurial-devel@mercurial-
> scm.org>
> Subject: Re: [PATCH] update: introduce config option ui.allowdirtyupdate for
> dirty nonlinear updates
>
> On Thu, Dec 8, 2016 at 9:13 AM, Gábor STEFANIK
> <gabor.stefa...@nng.com> wrote:
> >>
> >
> >
> > --
> >  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: Jun Wu [mailto:qu...@fb.com]
> >> Sent: Wednesday, December 7, 2016 6:44 PM
> >> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> >> Cc: mercurial-devel <mercurial-devel@mercurial-scm.org>
> >> Subject: Re: [PATCH] update: introduce config option
> >> ui.allowdirtyupdate for dirty nonlinear updates
> >>
> >> Excerpts from Gábor Stefanik's message of 2016-12-07 16:56:09 +0100:
> >> > # HG changeset patch
> >> > # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1481126137 -
> 3600
> >> > #  Wed Dec 07 16:55:37 2016 +0100
> >> > # Node ID dabbe365b843fcf9b8a0de6c08e9db6100b391e9
> >> > # Parent  6472c33e16326b8c817a8bae0e75053b19badb2c
> >> > update: introduce config option ui.allowdirtyupdate for dirty
> >> > nonlinear updates
> >> >
> >> > Make it easier to test codepaths common to graft and update without
> >> > having to mess around with obsolete markers to force a nonlinear
> update.
> >> > Named by analogy with ui.allowemptycommit.
> >> >
> >> > diff -r 6472c33e1632 -r dabbe365b843 mercurial/merge.py
> >> > --- a/mercurial/merge.pyMon Dec 05 17:40:01 2016 +0100
> >> > +++ b/mercurial/merge.pyWed Dec 07 16:55:37 2016 +0100
> >> > @@ -1536,7 +1536,10 @@
> >> >
> >> >  if pas not in ([p1], [p2]):  # nonlinear
> >> >  dirty = wc.dirty(missing=True)
> >> > -if dirty or onode is None:
> >> > +# experimental config: ui.allowdirtyupdate
> >> > +if repo.ui.configbool('ui', 'allowdirtyupdate', False):
> >>
> >> I think experimental config options are usually under the "experimental"
> >> section.
> >
> > Format.generaldelta was also experimental.
> >
> > Grepping through the repo for "experimental config", I'm under the
> > impression that "experimental" is to be used for options that can't be
> > cleanly categorized under any existing sections, and don't warrant
> introducing a new section.
>
> [experimental] is also for things that aren't sure to be supported forever.
>
> >
> > This is experimental for now, since we need to support "hg update
> > --abort" to make this safe for users, but eventually I hope to get
> > this de-experimentalized and maybe even enabled by default. It would
> > be better not to break backwards compatibility just because this becomes
> no longer experimental.
>
> Until we're confident that this feature will live forever, it should be in
> experimental.

I would argue that this will live forever, because:
a) If we eventually decide to make this the default, some users will want to
still have the old behavior - which they can get by explicitly setting
ui.allowdirtyupdate=False. I don't envision ever dropping the current behavior
completely.
b) If we decide that nonlinear merge updates shall never be supported using
the "update" command for whatever reason, we will still need it for the same
debugging uses that we have now.
This is as likely to live forever as merge.preferancestor, which is also marked
with "experimental config:", but not in [experimental].

In terms of backwards compatibility, I would also argue that we shouldn't use
[experimental] for options that _may_ live forever either, only for those
that are guaranteed to be temporary (e.g. experimental.bundle2-exp, w

RE: [PATCH] update: introduce config option ui.allowdirtyupdate for dirty nonlinear updates

2016-12-08 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: Jun Wu [mailto:qu...@fb.com]
> Sent: Wednesday, December 7, 2016 6:44 PM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> Cc: mercurial-devel <mercurial-devel@mercurial-scm.org>
> Subject: Re: [PATCH] update: introduce config option ui.allowdirtyupdate for
> dirty nonlinear updates
>
> Excerpts from Gábor Stefanik's message of 2016-12-07 16:56:09 +0100:
> > # HG changeset patch
> > # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1481126137 -3600
> > #  Wed Dec 07 16:55:37 2016 +0100
> > # Node ID dabbe365b843fcf9b8a0de6c08e9db6100b391e9
> > # Parent  6472c33e16326b8c817a8bae0e75053b19badb2c
> > update: introduce config option ui.allowdirtyupdate for dirty
> > nonlinear updates
> >
> > Make it easier to test codepaths common to graft and update without
> > having to mess around with obsolete markers to force a nonlinear update.
> > Named by analogy with ui.allowemptycommit.
> >
> > diff -r 6472c33e1632 -r dabbe365b843 mercurial/merge.py
> > --- a/mercurial/merge.pyMon Dec 05 17:40:01 2016 +0100
> > +++ b/mercurial/merge.pyWed Dec 07 16:55:37 2016 +0100
> > @@ -1536,7 +1536,10 @@
> >
> >  if pas not in ([p1], [p2]):  # nonlinear
> >  dirty = wc.dirty(missing=True)
> > -if dirty or onode is None:
> > +# experimental config: ui.allowdirtyupdate
> > +if repo.ui.configbool('ui', 'allowdirtyupdate', False):
>
> I think experimental config options are usually under the "experimental"
> section.

Format.generaldelta was also experimental.

Grepping through the repo for "experimental config", I'm under the impression 
that
"experimental" is to be used for options that can't be cleanly categorized 
under any
existing sections, and don't warrant introducing a new section.

This is experimental for now, since we need to support "hg update --abort" to 
make
this safe for users, but eventually I hope to get this de-experimentalized and 
maybe
even enabled by default. It would be better not to break backwards compatibility
just because this becomes no longer experimental.

>
> > +pas = [p1]
> > +elif dirty or onode is None:
> >  # Branching is a bit strange to ensure we do the 
> > minimal
> >  # amount of call to obsolete.background.
> >  foreground = obsolete.foreground(repo,
> > [p1.node()])
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH] update: introduce config option ui.allowdirtyupdate for dirty nonlinear updates

2016-12-07 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1481126137 -3600
#  Wed Dec 07 16:55:37 2016 +0100
# Node ID dabbe365b843fcf9b8a0de6c08e9db6100b391e9
# Parent  6472c33e16326b8c817a8bae0e75053b19badb2c
update: introduce config option ui.allowdirtyupdate for dirty nonlinear updates

Make it easier to test codepaths common to graft and update without having to
mess around with obsolete markers to force a nonlinear update.
Named by analogy with ui.allowemptycommit.

diff -r 6472c33e1632 -r dabbe365b843 mercurial/merge.py
--- a/mercurial/merge.pyMon Dec 05 17:40:01 2016 +0100
+++ b/mercurial/merge.pyWed Dec 07 16:55:37 2016 +0100
@@ -1536,7 +1536,10 @@
 
 if pas not in ([p1], [p2]):  # nonlinear
 dirty = wc.dirty(missing=True)
-if dirty or onode is None:
+# experimental config: ui.allowdirtyupdate
+if repo.ui.configbool('ui', 'allowdirtyupdate', False):
+pas = [p1]
+elif dirty or onode is None:
 # Branching is a bit strange to ensure we do the minimal
 # amount of call to obsolete.background.
 foreground = obsolete.foreground(repo, [p1.node()])
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH] graft: support grafting changes to new file in renamed directory (issue5436)

2016-12-05 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1480956001 -3600
#  Mon Dec 05 17:40:01 2016 +0100
# Node ID 5b31c62d10786bccaaebf33f4baa4cab8594363a
# Parent  cbeb54ec0481a4bf9723ba4b80a5861a813c8531
graft: support grafting changes to new file in renamed directory (issue5436)

diff -r cbeb54ec0481 -r 5b31c62d1078 mercurial/copies.py
--- a/mercurial/copies.py   Wed Nov 30 19:23:04 2016 +
+++ b/mercurial/copies.py   Mon Dec 05 17:40:01 2016 +0100
@@ -310,8 +310,8 @@
 Find moves and copies between context c1 and c2 that are relevant
 for merging. 'base' will be used as the merge base.
 
-Returns four dicts: "copy", "movewithdir", "diverge", and
-"renamedelete".
+Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
+"dirmove".
 
 "copy" is a mapping from destination name -> source name,
 where source is in c1 and destination is in c2 or vice-versa.
@@ -326,20 +326,24 @@
 
 "renamedelete" is a mapping of source name -> list of destination
 names for files deleted in c1 that were renamed in c2 or vice-versa.
+
+"dirmove" is a mapping of detected source dir -> destination dir renames.
+This is needed for handling changes to new files previously grafted into
+renamed directories.
 """
 # avoid silly behavior for update from empty dir
 if not c1 or not c2 or c1 == c2:
-return {}, {}, {}, {}
+return {}, {}, {}, {}, {}
 
 # avoid silly behavior for parent -> working dir
 if c2.node() is None and c1.node() == repo.dirstate.p1():
-return repo.dirstate.copies(), {}, {}, {}
+return repo.dirstate.copies(), {}, {}, {}, {}
 
 # Copy trace disabling is explicitly below the node == p1 logic above
 # because the logic above is required for a simple copy to be kept across a
 # rebase.
 if repo.ui.configbool('experimental', 'disablecopytrace'):
-return {}, {}, {}, {}
+return {}, {}, {}, {}, {}
 
 # In certain scenarios (e.g. graft, update or rebase), base can be
 # overridden We still need to know a real common ancestor in this case We
@@ -365,7 +369,7 @@
 limit = _findlimit(repo, c1.rev(), c2.rev())
 if limit is None:
 # no common ancestor, no copies
-return {}, {}, {}, {}
+return {}, {}, {}, {}, {}
 repo.ui.debug("  searching for copies back to rev %d\n" % limit)
 
 m1 = c1.manifest()
@@ -503,7 +507,7 @@
 del divergeset
 
 if not fullcopy:
-return copy, {}, diverge, renamedelete
+return copy, {}, diverge, renamedelete, {}
 
 repo.ui.debug("  checking for directory renames\n")
 
@@ -541,7 +545,7 @@
 del d1, d2, invalid
 
 if not dirmove:
-return copy, {}, diverge, renamedelete
+return copy, {}, diverge, renamedelete, {}
 
 for d in dirmove:
 repo.ui.debug("   discovered dir src: '%s' -> dst: '%s'\n" %
@@ -561,7 +565,7 @@
"dst: '%s'\n") % (f, df))
 break
 
-return copy, movewithdir, diverge, renamedelete
+return copy, movewithdir, diverge, renamedelete, dirmove
 
 def _related(f1, f2, limit):
 """return True if f1 and f2 filectx have a common ancestor
diff -r cbeb54ec0481 -r 5b31c62d1078 mercurial/merge.py
--- a/mercurial/merge.pyWed Nov 30 19:23:04 2016 +
+++ b/mercurial/merge.pyMon Dec 05 17:40:01 2016 +0100
@@ -794,7 +794,7 @@
 if matcher is not None and matcher.always():
 matcher = None
 
-copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
+copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
 
 # manifests fetched in order are going to be faster, so prime the caches
 [x.manifest() for x in
@@ -802,7 +802,7 @@
 
 if followcopies:
 ret = copies.mergecopies(repo, wctx, p2, pa)
-copy, movewithdir, diverge, renamedelete = ret
+copy, movewithdir, diverge, renamedelete, dirmove = ret
 
 repo.ui.note(_("resolving manifests\n"))
 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
@@ -870,7 +870,16 @@
   "local copied/moved from " + f2)
 elif f in ma: # clean, a different, no remote
 if n1 != ma[f]:
-if acceptremote:
+df = None
+for d in dirmove:
+if f.startswith(dirmove[d]):
+# new file added in a directory that was moved
+df = d + f[len(dirmove[d]):]
+break
+if df in m2:
+actions[f] = ('m', (f, df, df, False, pa.node()),
+  

RE: [PATCH STABLE] build: include a dummy $PATH in the custom environment used by build.py

2016-11-02 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 Pierre-Yves David
> Sent: Tuesday, November 1, 2016 4:56 PM
> To: Jun Wu 
> Cc: mercurial-devel@mercurial-scm.org
> Subject: Re: [PATCH STABLE] build: include a dummy $PATH in the custom
> environment used by build.py
>
>
>
> On 11/01/2016 04:39 PM, Jun Wu wrote:
> > Excerpts from Pierre-Yves David's message of 2016-10-29 02:18:56 +0200:
> >> My question is more "What is the meaning and effect of adding "." here?
> >> I'm fine with trying to fix pypiwin32 but we have to be aware of the
> >> actual consequence. A related quest is "What is happening is 'PATH'
> >> is already in the envirement? are we overwriting it?
> >
> > For Windows, PATH='.' is equivalent to PATH='' since Windows searches
> > for PWD by design.
> >
> > For *nix, PATH='' is better. PATH='.' has undesired side-effects.
> >
> > I checked the code, if PATH is already set, it will be dropped by our
> > code because we created a new "env" dict and pass it to "runhg" ->
> > "runcmd" -> "subprocess.Popen(..., env=env)" so the new process does
> > not inherit the old PATH.
> >
> > We rely on "sys.executable" to be correct to find the correct Python.
> >
> > I think having an empty value, or inherit it from os.environ are both
> > acceptable solutions. But it's pypiwin32 to blame anyway.
>
> It seem slike we should at least inherit a value if one exists. This patch 
> does
> not seems ready for inclusion. I'm dropping it from patchwork and we'll
> revisit post release.

I believe the point of using a custom environment here is precisely to avoid 
passing the real $PATH.
Inheriting from the system environment would defeat this.

The only reason for including a PATH entry here is so site.py can do 
os.environ['PATH'] instead of os.environ.get('PATH').
Unfortunately some python modules (like pypiwin32) assume that a $PATH 
environment variable must always exist.

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


RE: [PATCH STABLE] build: include a dummy $PATH in the custom environment used by build.py

2016-10-28 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: Friday, October 28, 2016 7:13 PM
> To: Pierre-Yves David <pierre-yves.da...@ens-lyon.org>; mercurial-
> de...@mercurial-scm.org
> Subject: RE: [PATCH STABLE] build: include a dummy $PATH in the custom
> environment used by build.py
>
> >
>
>
> --
> 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: Friday, October 28, 2016 7:03 PM
> > To: Gábor STEFANIK <gabor.stefa...@nng.com>; mercurial-
> > de...@mercurial-scm.org
> > Subject: Re: [PATCH STABLE] build: include a dummy $PATH in the custom
> > environment used by build.py
> >
> >
> >
> > On 10/28/2016 05:55 PM, Gábor Stefanik wrote:
> > > # HG changeset patch
> > > # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1477669468 -
> 7200
> > > #  Fri Oct 28 17:44:28 2016 +0200
> > > # Branch stable
> > > # Node ID 3cda0b069802af8b4dbdf9f5598965a522a566b3
> > > # Parent  3afde791dce192f38d8a228ed8e49397e353837e
> > > build: include a dummy $PATH in the custom environment used by
> > > build.py
> > >
> > > This is required for building with pypiwin32, the pip-installable
> > > replacement for pywin32.
> >
> > What does the '.' value means here?
>
> It is supposed to mean $(pwd). Maybe setting to an empty string would be
> safer?

The problem is that pypiwin32 does os.environ['PATH'] += ... in a .pth file 
(loaded by site.py),
which fails if 'PATH' is not in os.environ.

The original pywin32 doesn't have this issue, but it can only be obtained by 
downloading
a Windows .exe installer from SourceForge, a site known to have previously 
injected
malware into .exe installers it hosts.

>
> >
> > >
> > > diff -r 3afde791dce1 -r 3cda0b069802 setup.py
> > > --- a/setup.pyThu Oct 27 20:06:33 2016 +0200
> > > +++ b/setup.pyFri Oct 28 17:44:28 2016 +0200
> > > @@ -167,7 +167,8 @@
> > >  # to not use any hgrc files and do no localization.
> > >  env = {'HGMODULEPOLICY': 'py',
> > > 'HGRCPATH': '',
> > > -   'LANGUAGE': 'C'}
> > > +   'LANGUAGE': 'C',
> > > +   'PATH': '.'}
> > >  if 'LD_LIBRARY_PATH' in os.environ:
> > >  env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']  if
> > > 'SystemRoot' in os.environ:
> > > ___
> > > Mercurial-devel mailing list
> > > Mercurial-devel@mercurial-scm.org
> > > https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
> > >
> >
> > --
> > Pierre-Yves David
> ___
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH STABLE] build: include a dummy $PATH in the custom environment used by build.py

2016-10-28 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: Friday, October 28, 2016 5:55 PM
> To: mercurial-devel@mercurial-scm.org
> Subject: [PATCH STABLE] build: include a dummy $PATH in the custom
> environment used by build.py
>
> # HG changeset patch
> # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1477669468 -7200
> #  Fri Oct 28 17:44:28 2016 +0200
> # Branch stable
> # Node ID 3cda0b069802af8b4dbdf9f5598965a522a566b3
> # Parent  3afde791dce192f38d8a228ed8e49397e353837e
> build: include a dummy $PATH in the custom environment used by build.py
>
> This is required for building with pypiwin32, the pip-installable replacement
> for pywin32.

Just to clarify, without this, merely installing pypiwin32 completely breaks 
setup.py,
even for targets that don't try to use pywin32 for anything.

>
> diff -r 3afde791dce1 -r 3cda0b069802 setup.py
> --- a/setup.pyThu Oct 27 20:06:33 2016 +0200
> +++ b/setup.pyFri Oct 28 17:44:28 2016 +0200
> @@ -167,7 +167,8 @@
>  # to not use any hgrc files and do no localization.
>  env = {'HGMODULEPOLICY': 'py',
> 'HGRCPATH': '',
> -   'LANGUAGE': 'C'}
> +   'LANGUAGE': 'C',
> +   'PATH': '.'}
>  if 'LD_LIBRARY_PATH' in os.environ:
>  env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']  if
> 'SystemRoot' in os.environ:
> ___
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: Main and stable migrated

2016-10-26 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 Kevin Bullock
> Sent: Wednesday, October 26, 2016 7:26 PM
> To: mercurial-devel 
> Subject: Main and stable migrated
>
> Hi all,
>
> We've just finished migrating the main and stable repos from selenic.com to
> mercurial-scm.org. The new main repo is:
>
> https://www.mercurial-scm.org/repo/hg/
>
> Let me know if you run into any problems with the new location.

What about hg-push? Has it also been migrated? Is it being kept in sync with 
the new location, or the old one?

>
> pacem in terris / мир / शान्ति / ‎‫سَلاَم‬ / 平和
> Kevin R. Bullock
>
> ___
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH STABLE] merge: avoid superfluous filemerges when grafting through renames (issue5407)

2016-10-26 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: Wednesday, October 26, 2016 12:21 AM
> To: mercurial-devel@mercurial-scm.org
> Subject: [PATCH STABLE] merge: avoid superfluous filemerges when grafting
> through renames (issue5407)
>
> # HG changeset patch
> # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1477422113 -7200
> #  Tue Oct 25 21:01:53 2016 +0200
> # Branch stable
> # Node ID 1f40c1fe34442812291b000c5e1a8b22ad14f091
> # Parent  76c57e1fe79b0980b377b4f305635dea393d6315
> merge: avoid superfluous filemerges when grafting through renames
> (issue5407)
>
> This is a fix for a regression introduced by the patches for issue4028.
>
> The test changes are due to us doing fewer _checkcopies searches now,
> which makes some test outputs revert to the pre-issue4028 behavior. That
> issue itself remains fixed, we only skip copy tracing for files where it isn't
> relevant.
> As a nice side effect, this makes copy detection much faster when tracing
> backwards through lots of renames.
>
> diff --git a/mercurial/copies.py b/mercurial/copies.py
> --- a/mercurial/copies.py
> +++ b/mercurial/copies.py
> @@ -631,6 +631,10 @@
>  backwards = not remotebase and base != tca and f in mb
>  getfctx = _makegetfctx(ctx)
>
> +if m1[f] == mb.get(f) and not remotebase:

Note: this remotebase check is probably not needed, and removing it actually 
speeds
things up further. However, it also causes additional debug output changes in 
tests
(i.e. more output reverts to pre-4028 behavior), so I decided to keep it in for 
stable.

Let me know if it's OK to just remove it and adjust the tests.

After 4.0, if we remove the remotebase check, we can possibly get rid of or 
simplify
the "ping-pong" rename handling code.

> +# Nothing to merge
> +return
> +
>  of = None
>  seen = set([f])
>  for oc in getfctx(f, m1[f]).ancestors():
> diff --git a/tests/test-graft.t b/tests/test-graft.t
> --- a/tests/test-graft.t
> +++ b/tests/test-graft.t
> @@ -181,9 +181,6 @@
>  searching for copies back to rev 1
>  unmatched files in other (from topological common ancestor):
>   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 @@ -
> 200,9 +197,6 @@
>  searching for copies back to rev 1
>  unmatched files in other (from topological common ancestor):
>   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 @@ -
> 1280,3 +1274,15 @@
>
>$ hg cat f2c
>c2e
> +
> +Check superfluous filemerge of files renamed in the past but untouched
> +by graft
> +
> +  $ echo a > a
> +  $ hg ci -qAma
> +  $ hg mv a b
> +  $ echo b > b
> +  $ hg ci -qAmb
> +  $ echo c > c
> +  $ hg ci -qAmc
> +  $ hg up -q .~2
> +  $ hg graft tip -qt:fail
> diff --git a/tests/test-merge-local.t b/tests/test-merge-local.t
> --- a/tests/test-merge-local.t
> +++ b/tests/test-merge-local.t
> @@ -66,7 +66,7 @@
>merging zzz1_merge_ok
>merging zzz2_merge_bad
>warning: conflicts while merging zzz2_merge_bad! (edit, then use 'hg
> resolve --mark')
> -  2 files updated, 1 files merged, 2 files removed, 1 files unresolved
> +  2 files updated, 1 files merged, 3 files removed, 1 files unresolved
>use 'hg resolve' to retry unresolved file merges
>[1]
>
> @@ -104,7 +104,7 @@
>merging zzz1_merge_ok
>merging zzz2_merge_bad
>warning: conflicts while merging zzz2_merge_bad! (edit, then use 'hg
> resolve --mark')
> -  2 files updated, 1 files merged, 2 files removed, 1 files unresolved
> +  2 files updated, 1 files merged, 3 files removed, 1 files unresolved
>use 'hg resolve' to retry unresolved file merges
>[1]
>
> diff --git a/tests/test-up-local-change.t b/tests/test-up-local-change.t
> --- a/tests/test-up-local-change.t
> +++ b/tests/test-up-local-change.t
> @@ -242,4 +2

[PATCH STABLE] merge: avoid superfluous filemerges when grafting through renames (issue5407)

2016-10-25 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1477422113 -7200
#  Tue Oct 25 21:01:53 2016 +0200
# Branch stable
# Node ID 1f40c1fe34442812291b000c5e1a8b22ad14f091
# Parent  76c57e1fe79b0980b377b4f305635dea393d6315
merge: avoid superfluous filemerges when grafting through renames (issue5407)

This is a fix for a regression introduced by the patches for issue4028.

The test changes are due to us doing fewer _checkcopies searches now, which
makes some test outputs revert to the pre-issue4028 behavior. That issue itself
remains fixed, we only skip copy tracing for files where it isn't relevant.
As a nice side effect, this makes copy detection much faster when tracing
backwards through lots of renames.

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -631,6 +631,10 @@
 backwards = not remotebase and base != tca and f in mb
 getfctx = _makegetfctx(ctx)
 
+if m1[f] == mb.get(f) and not remotebase:
+# Nothing to merge
+return
+
 of = None
 seen = set([f])
 for oc in getfctx(f, m1[f]).ancestors():
diff --git a/tests/test-graft.t b/tests/test-graft.t
--- a/tests/test-graft.t
+++ b/tests/test-graft.t
@@ -181,9 +181,6 @@
 searching for copies back to rev 1
 unmatched files in other (from topological common ancestor):
  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
@@ -200,9 +197,6 @@
 searching for copies back to rev 1
 unmatched files in other (from topological common ancestor):
  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
@@ -1280,3 +1274,15 @@
   
   $ hg cat f2c
   c2e
+
+Check superfluous filemerge of files renamed in the past but untouched by graft
+
+  $ echo a > a
+  $ hg ci -qAma
+  $ hg mv a b
+  $ echo b > b
+  $ hg ci -qAmb
+  $ echo c > c
+  $ hg ci -qAmc
+  $ hg up -q .~2
+  $ hg graft tip -qt:fail
diff --git a/tests/test-merge-local.t b/tests/test-merge-local.t
--- a/tests/test-merge-local.t
+++ b/tests/test-merge-local.t
@@ -66,7 +66,7 @@
   merging zzz1_merge_ok
   merging zzz2_merge_bad
   warning: conflicts while merging zzz2_merge_bad! (edit, then use 'hg resolve 
--mark')
-  2 files updated, 1 files merged, 2 files removed, 1 files unresolved
+  2 files updated, 1 files merged, 3 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
   [1]
 
@@ -104,7 +104,7 @@
   merging zzz1_merge_ok
   merging zzz2_merge_bad
   warning: conflicts while merging zzz2_merge_bad! (edit, then use 'hg resolve 
--mark')
-  2 files updated, 1 files merged, 2 files removed, 1 files unresolved
+  2 files updated, 1 files merged, 3 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
   [1]
 
diff --git a/tests/test-up-local-change.t b/tests/test-up-local-change.t
--- a/tests/test-up-local-change.t
+++ b/tests/test-up-local-change.t
@@ -242,4 +242,11 @@
   -a
   +b
 
+test for superfluous filemerge of clean files renamed in the past
+
+  $ hg up -qC tip
+  $ echo c > c
+  $ hg add c
+  $ hg up -qt:fail 0
+
   $ cd ..
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH STABLE v2] sslutil: guard against broken certifi installations (issue5406)

2016-10-24 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476893174 -7200
#  Wed Oct 19 18:06:14 2016 +0200
# Branch stable
# Node ID c3fe0e56546a44a7961354d4840cdcb82cbecefc
# Parent  76c57e1fe79b0980b377b4f305635dea393d6315
sslutil: guard against broken certifi installations (issue5406)

Certifi is currently incompatible with py2exe; the Python code for certifi gets
included in library.zip, but not the cacert.pem file - and even if it were
included, SSLContext can't load a cacert.pem file from library.zip.
This currently makes it impossible to build a standalone Windows version of
Mercurial.

Guard against this, and possibly other situations where a module with the name
"certifi" exists, but is not usable.

diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -690,14 +690,15 @@
 We don't print a message when the Python is able to load default
 CA certs because this scenario is detected at socket connect time.
 """
-# The "certifi" Python package provides certificates. If it is installed,
-# assume the user intends it to be used and use it.
+# The "certifi" Python package provides certificates. If it is installed
+# and usable, assume the user intends it to be used and use it.
 try:
 import certifi
 certs = certifi.where()
-ui.debug('using ca certificates from certifi\n')
-return certs
-except ImportError:
+if os.path.exists(certs):
+ui.debug('using ca certificates from certifi\n')
+return certs
+except (ImportError, AttributeError):
 pass
 
 # On Windows, only the modern ssl module is capable of loading the system
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH STABLE] sslutil: guard against broken certifi installations (issue5406)

2016-10-19 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: Kevin Bullock [mailto:kbullock+mercur...@ringworld.org]
> Sent: Wednesday, October 19, 2016 7:46 PM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> Cc: mercurial-devel@mercurial-scm.org
> Subject: Re: [PATCH STABLE] sslutil: guard against broken certifi 
> installations
> (issue5406)
>
> > On Oct 19, 2016, at 12:07, Gábor STEFANIK <gabor.stefa...@nng.com>
> wrote:
> >
> > -Original Message-
> >> From: Kevin Bullock [mailto:kbullock+mercur...@ringworld.org]
> >> Sent: Wednesday, October 19, 2016 6:18 PM
> >> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> >> Cc: mercurial-devel@mercurial-scm.org
> >> Subject: Re: [PATCH STABLE] sslutil: guard against broken certifi
> >> installations
> >> (issue5406)
> >>
> >> You've gone from catching an ImportError to swallowing all exceptions.
> >
> > Intentional. ImportError is not the only thing that can be thrown
> > here; e.g. if "certifi" is actually some unrelated module with no "where()"
> method.
> >
> > No reason to let certifi crash Hg under any circumstances.
>
> I have a hard time imagining how another module named "certifi" without a
> where() method would show up on any sane system.
>
> As Greg said, bare `except:` is banned in Mercurial. Catch the exceptions you
> expect might happen, none others.

Would "except Exception:" be acceptable? that one doesn't catch 
KeyboardInterrupt and other problematic exceptions.

>
> pacem in terris / мир / शान्ति / ‎‫سَلاَم‬ / 平和
> Kevin R. Bullock

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


RE: [PATCH STABLE] sslutil: guard against broken certifi installations (issue5406)

2016-10-19 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: Kevin Bullock [mailto:kbullock+mercur...@ringworld.org]
> Sent: Wednesday, October 19, 2016 6:18 PM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> Cc: mercurial-devel@mercurial-scm.org
> Subject: Re: [PATCH STABLE] sslutil: guard against broken certifi 
> installations
> (issue5406)
>
> > On Oct 19, 2016, at 11:07, Gábor Stefanik <gabor.stefa...@nng.com>
> wrote:
> >
> > # HG changeset patch
> > # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1476893174 -7200
> > #  Wed Oct 19 18:06:14 2016 +0200
> > # Branch stable
> > # Node ID 77e20e2892a869717db636f56ab1b9664fc8b285
> > # Parent  e478f11e418288b8308457303d3ddf6a23f874f8
> > sslutil: guard against broken certifi installations (issue5406)
> >
> > Certifi is currently incompatible with py2exe; the Python code for
> > certifi gets included in library.zip, but not the cacert.pem file -
> > and even if it were included, SSLContext can't load a cacert.pem file from
> library.zip.
> > This currently makes it impossible to build a standalone Windows
> > version of Mercurial.
> >
> > Guard against this, and possibly other situations where a module with
> > the name "certifi" exists, but is not usable.
> >
> > diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
> > --- a/mercurial/sslutil.py
> > +++ b/mercurial/sslutil.py
> > @@ -695,9 +695,10 @@
> > try:
> > import certifi
> > certs = certifi.where()
> > -ui.debug('using ca certificates from certifi\n')
> > -return certs
> > -except ImportError:
> > +if os.path.exists(certs):
> > +ui.debug('using ca certificates from certifi\n')
> > +return certs
> > +except:
>
> You've gone from catching an ImportError to swallowing all exceptions.

Intentional. ImportError is not the only thing that can be thrown here;
e.g. if "certifi" is actually some unrelated module with no "where()" method.

No reason to let certifi crash Hg under any circumstances.

>
> pacem in terris / мир / शान्ति / ‎‫سَلاَم‬ / 平和
> Kevin R. Bullock

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


[PATCH STABLE] sslutil: guard against broken certifi installations (issue5406)

2016-10-19 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476893174 -7200
#  Wed Oct 19 18:06:14 2016 +0200
# Branch stable
# Node ID 77e20e2892a869717db636f56ab1b9664fc8b285
# Parent  e478f11e418288b8308457303d3ddf6a23f874f8
sslutil: guard against broken certifi installations (issue5406)

Certifi is currently incompatible with py2exe; the Python code for certifi gets
included in library.zip, but not the cacert.pem file - and even if it were
included, SSLContext can't load a cacert.pem file from library.zip.
This currently makes it impossible to build a standalone Windows version of
Mercurial.

Guard against this, and possibly other situations where a module with the name
"certifi" exists, but is not usable.

diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -695,9 +695,10 @@
 try:
 import certifi
 certs = certifi.where()
-ui.debug('using ca certificates from certifi\n')
-return certs
-except ImportError:
+if os.path.exists(certs):
+ui.debug('using ca certificates from certifi\n')
+return certs
+except:
 pass
 
 # On Windows, only the modern ssl module is capable of loading the system
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH 2 of 5 v4] mergecopies: add logic to process incomplete data

2016-10-17 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: Monday, October 17, 2016 11:40 PM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>; mercurial-
> de...@mercurial-scm.org
> Subject: Re: [PATCH 2 of 5 v4] mergecopies: add logic to process incomplete
> data
>
>
>
> On 10/17/2016 08:42 PM, Gábor Stefanik wrote:
> > # HG changeset patch
> > # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1475578314 -7200
> > #  Tue Oct 04 12:51:54 2016 +0200
> > # Node ID f5f046a680e6730485df1760f82a0e0084305873
> > # Parent  d8379d11021b681a06cda08a07da237eaca520b2
> > mergecopies: add logic to process incomplete data
> >
> > We first combine incomplete copies on the two sides of the topological
> > CA into complete copies.
> > Any leftover incomplete copies are then combined with the incomplete
> > divergences to reconstruct divergences spanning over the topological CA.
> > Finally we promote any divergences falsely flagged as incomplete to
> > full divergences.
> >
> > Right now, there is nothing generating incomplete copy/divergence
> > data, so this code does nothing. Changes to _checkcopies to populate
> > these dicts are coming later in this series.
> >
> > diff -r d8379d11021b -r f5f046a680e6 mercurial/copies.py
> > --- a/mercurial/copies.pyWed Oct 12 11:54:03 2016 +0200
> > +++ b/mercurial/copies.pyTue Oct 04 12:51:54 2016 +0200
> > @@ -289,6 +289,22 @@
> >  return fctx
> >  return util.lrucachefunc(makectx)
> >
> > +def _combinecopies(copyfrom, copyto, finalcopy, diverge,
> incompletediverge):
> > +"""combine partial copy paths"""
> > +remainder = {}
> > +for f in copyfrom:
> > +if f in copyto:
> > +finalcopy[copyto[f]] = copyfrom[f]
> > +del copyto[f]
> > +for f in incompletediverge:
> > +assert f not in diverge
> > +ic = incompletediverge[f]
> > +if ic[0] in copyto:
> > +diverge[f] = [copyto[ic[0]], ic[1]]
> > +else:
> > +remainder[f] = ic
> > +return remainder
> > +
> >  def mergecopies(repo, c1, c2, base):
> >  """
> >  Find moves and copies between context c1 and c2 that are relevant
> > @@ -360,14 +376,21 @@
> >  # - diverge = record all diverges in this dict
> >  # - copy = record all non-divergent copies in this dict
> >  # - fullcopy = record all copies in this dict
> > +# - incomplete = record non-divergent partial copies here
> > +# - incompletediverge = record divergent partial copies here
> >  diverge = {} # divergence data is shared
> > +incompletediverge  = {}
> >  data1 = {'copy': {},
> >   'fullcopy': {},
> > + 'incomplete': {},
> >   'diverge': diverge,
> > + 'incompletediverge': incompletediverge,
> >  }
> >  data2 = {'copy': {},
> >   'fullcopy': {},
> > + 'incomplete': {},
> >   'diverge': diverge,
> > + 'incompletediverge': incompletediverge,
> >  }
> >
> >  # find interesting file sets from manifests @@ -398,6 +421,13 @@
> >  copy = dict(data1['copy'].items() + data2['copy'].items())
> >  fullcopy = dict(data1['fullcopy'].items() +
> > data2['fullcopy'].items())
> >
> > +if dirtyc1:
> > +_combinecopies(data2['incomplete'], data1['incomplete'], copy,
> diverge,
> > +   incompletediverge)
> > +else:
> > +_combinecopies(data1['incomplete'], data2['incomplete'], copy,
> diverge,
> > +   incompletediverge)
> > +
> >  renamedelete = {}
> >  renamedeleteset = set()
> >  divergeset = set()
> > @@ -416,13 +446,36 @@
> >  repo.ui.debug("  unmatched files new in both:\n   %s\n"
> >% "\n   ".join(bothnew))
> >  bothdiverge = {}
> > -bothdata = {'copy': {},
> > -'fullcopy': {},
> > -'diverge': bothdiverge,
> > -   }
> > +bothincompletediverge = {}
> > +both1

[PATCH] copies: improve assertions during copy recombination

2016-10-17 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476749348 -7200
#  Tue Oct 18 02:09:08 2016 +0200
# Node ID 87a7c0d403ff29dcae2a41e0516c75bbd9f6a5a8
# Parent  abe7230025099bbeb9eaa0d3ca29c8f700ddb1e2
copies: improve assertions during copy recombination

- Make sure there is nothing to recombine in non-graftlike scenarios
- More pythonic assert syntax

diff -r abe723002509 -r 87a7c0d403ff mercurial/copies.py
--- a/mercurial/copies.py   Mon Oct 17 16:12:12 2016 -0700
+++ b/mercurial/copies.py   Tue Oct 18 02:09:08 2016 +0200
@@ -447,6 +447,7 @@
   % "\n   ".join(bothnew))
 bothdiverge = {}
 bothincompletediverge = {}
+remainder = {}
 both1 = {'copy': {},
  'fullcopy': {},
  'incomplete': {},
@@ -463,13 +464,19 @@
 _checkcopies(c1, f, m1, m2, base, tca, dirtyc1, limit, both1)
 _checkcopies(c2, f, m2, m1, base, tca, dirtyc2, limit, both2)
 if dirtyc1:
-assert both2['incomplete'] == {}
+# incomplete copies may only be found on the "dirty" side for bothnew
+assert not both2['incomplete']
 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
bothincompletediverge)
-else:
-assert both1['incomplete'] == {}
+elif dirtyc2:
+assert not both1['incomplete']
 remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
bothincompletediverge)
+else:
+# incomplete copies and divergences can't happen outside grafts
+assert not both1['incomplete']
+assert not both2['incomplete']
+assert not bothincompletediverge
 for f in remainder:
 assert f not in bothdiverge
 ic = remainder[f]
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH v3] update: enable copy tracing for backwards and non-linear updates

2016-10-17 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: Kevin Bullock [mailto:kbullock+mercur...@ringworld.org]
> Sent: Monday, October 17, 2016 8:37 PM
> To: Gábor STEFANIK <gabor.stefa...@nng.com>
> Cc: mercurial-devel@mercurial-scm.org
> Subject: Re: [PATCH v3] update: enable copy tracing for backwards and non-
> linear updates
>
> > On Oct 17, 2016, at 13:27, Gábor Stefanik <gabor.stefa...@nng.com>
> wrote:
> >
> > # HG changeset patch
> > # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1472155346 -7200
> > #  Thu Aug 25 22:02:26 2016 +0200
> > # Node ID a41abe53dbc2c28d3b656f2a138da26949cf91d3
> > # Parent  3fc51a50bec71bb34c11beaaa6686d521ac706b1
> > update: enable copy tracing for backwards and non-linear updates
>
> Urk. It's generally confusing to send just one updated patch to the end of an
> in-flight series (but it's good you at least flagged it v3).
>
> Since 1-7 of the v2 series are queued already, can you resend current
> versions of 8-12 as a v4?

Done.

It was Pierre-Yves's request to separate the last patch from the series, since 
it's not related to graft.
But if there's disagreement on this, I'm more than happy to have it treated as 
part of the series.

>
> pacem in terris / мир / शान्ति / ‎‫سَلاَم‬ / 平和
> Kevin R. Bullock

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


[PATCH 5 of 5 v4] update: enable copy tracing for backwards and non-linear updates

2016-10-17 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1472155346 -7200
#  Thu Aug 25 22:02:26 2016 +0200
# Node ID a41abe53dbc2c28d3b656f2a138da26949cf91d3
# Parent  3fc51a50bec71bb34c11beaaa6686d521ac706b1
update: enable copy tracing for backwards and non-linear updates

As a followup to the issue4028 series, this fixes a variant of the issue
that can occur when updating with uncommited local changes.

The duplicated .hgsub warning is coming from wc.dirty(). We would previously
skip this call because it's only relevant when we're going to perform copy
tracing, which we didn't do before.

The change to the update summary line is because we now treat the rename as a
proper rename (which counts as a change), rather than an add+delete pair
(which counts as a change and a delete).

diff -r 3fc51a50bec7 -r a41abe53dbc2 mercurial/merge.py
--- a/mercurial/merge.pyTue Oct 11 04:39:47 2016 +0200
+++ b/mercurial/merge.pyThu Aug 25 22:02:26 2016 +0200
@@ -1555,15 +1555,16 @@
 pas = [p1]
 
 # deprecated config: merge.followcopies
-followcopies = False
+followcopies = repo.ui.configbool('merge', 'followcopies', True)
 if overwrite:
 pas = [wc]
+followcopies = False
 elif pas == [p2]: # backwards
-pas = [wc.p1()]
-elif not branchmerge and not wc.dirty(missing=True):
-pass
-elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
-followcopies = True
+pas = [p1]
+elif not pas[0]:
+followcopies = False
+if not branchmerge and not wc.dirty(missing=True):
+followcopies = False
 
 ### calculate phase
 actionbyfile, diverge, renamedelete = calculateupdates(
diff -r 3fc51a50bec7 -r a41abe53dbc2 tests/test-merge-local.t
--- a/tests/test-merge-local.t  Tue Oct 11 04:39:47 2016 +0200
+++ b/tests/test-merge-local.t  Thu Aug 25 22:02:26 2016 +0200
@@ -66,7 +66,7 @@
   merging zzz1_merge_ok
   merging zzz2_merge_bad
   warning: conflicts while merging zzz2_merge_bad! (edit, then use 'hg resolve 
--mark')
-  2 files updated, 1 files merged, 3 files removed, 1 files unresolved
+  2 files updated, 1 files merged, 2 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
   [1]
 
@@ -104,7 +104,7 @@
   merging zzz1_merge_ok
   merging zzz2_merge_bad
   warning: conflicts while merging zzz2_merge_bad! (edit, then use 'hg resolve 
--mark')
-  2 files updated, 1 files merged, 3 files removed, 1 files unresolved
+  2 files updated, 1 files merged, 2 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
   [1]
 
diff -r 3fc51a50bec7 -r a41abe53dbc2 tests/test-mq-subrepo.t
--- a/tests/test-mq-subrepo.t   Tue Oct 11 04:39:47 2016 +0200
+++ b/tests/test-mq-subrepo.t   Thu Aug 25 22:02:26 2016 +0200
@@ -304,6 +304,7 @@
   record this change to '.hgsub'? [Ynesfdaq?] y
   
   warning: subrepo spec file '.hgsub' not found
+  warning: subrepo spec file '.hgsub' not found
   abort: uncommitted changes in subrepository 'sub'
   [255]
   % update substate when adding .hgsub w/clean updated subrepo
@@ -319,6 +320,7 @@
   record this change to '.hgsub'? [Ynesfdaq?] y
   
   warning: subrepo spec file '.hgsub' not found
+  warning: subrepo spec file '.hgsub' not found
   path sub
source   sub
revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
diff -r 3fc51a50bec7 -r a41abe53dbc2 tests/test-up-local-change.t
--- a/tests/test-up-local-change.t  Tue Oct 11 04:39:47 2016 +0200
+++ b/tests/test-up-local-change.t  Thu Aug 25 22:02:26 2016 +0200
@@ -67,6 +67,10 @@
   summary: 2
   
   $ hg --debug up 0
+  starting 4 threads for background file closing (?)
+searching for copies back to rev 0
+unmatched files in local (from topological common ancestor):
+ b
   resolving manifests
branchmerge: False, force: False, partial: False
ancestor: 1e71731e6fbb, local: 1e71731e6fbb+, remote: c19d34741b0a
@@ -222,4 +226,20 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg st
 
+test updating backwards through a rename
+
+  $ hg mv a b
+  $ hg ci -m b
+  $ echo b > b
+  $ hg up -q 0
+  $ hg st
+  M a
+  $ hg diff --nodates
+  diff -r cb9a9f314b8b a
+  --- a/a
+  +++ b/a
+  @@ -1,1 +1,1 @@
+  -a
+  +b
+
   $ cd ..
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 5 v4] mergecopies: add logic to process incomplete data

2016-10-17 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475578314 -7200
#  Tue Oct 04 12:51:54 2016 +0200
# Node ID f5f046a680e6730485df1760f82a0e0084305873
# Parent  d8379d11021b681a06cda08a07da237eaca520b2
mergecopies: add logic to process incomplete data

We first combine incomplete copies on the two sides of the topological CA
into complete copies.
Any leftover incomplete copies are then combined with the incomplete
divergences to reconstruct divergences spanning over the topological CA.
Finally we promote any divergences falsely flagged as incomplete to full
divergences.

Right now, there is nothing generating incomplete copy/divergence data,
so this code does nothing. Changes to _checkcopies to populate these
dicts are coming later in this series.

diff -r d8379d11021b -r f5f046a680e6 mercurial/copies.py
--- a/mercurial/copies.py   Wed Oct 12 11:54:03 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 04 12:51:54 2016 +0200
@@ -289,6 +289,22 @@
 return fctx
 return util.lrucachefunc(makectx)
 
+def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
+"""combine partial copy paths"""
+remainder = {}
+for f in copyfrom:
+if f in copyto:
+finalcopy[copyto[f]] = copyfrom[f]
+del copyto[f]
+for f in incompletediverge:
+assert f not in diverge
+ic = incompletediverge[f]
+if ic[0] in copyto:
+diverge[f] = [copyto[ic[0]], ic[1]]
+else:
+remainder[f] = ic
+return remainder
+
 def mergecopies(repo, c1, c2, base):
 """
 Find moves and copies between context c1 and c2 that are relevant
@@ -360,14 +376,21 @@
 # - diverge = record all diverges in this dict
 # - copy = record all non-divergent copies in this dict
 # - fullcopy = record all copies in this dict
+# - incomplete = record non-divergent partial copies here
+# - incompletediverge = record divergent partial copies here
 diverge = {} # divergence data is shared
+incompletediverge  = {}
 data1 = {'copy': {},
  'fullcopy': {},
+ 'incomplete': {},
  'diverge': diverge,
+ 'incompletediverge': incompletediverge,
 }
 data2 = {'copy': {},
  'fullcopy': {},
+ 'incomplete': {},
  'diverge': diverge,
+ 'incompletediverge': incompletediverge,
 }
 
 # find interesting file sets from manifests
@@ -398,6 +421,13 @@
 copy = dict(data1['copy'].items() + data2['copy'].items())
 fullcopy = dict(data1['fullcopy'].items() + data2['fullcopy'].items())
 
+if dirtyc1:
+_combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
+   incompletediverge)
+else:
+_combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
+   incompletediverge)
+
 renamedelete = {}
 renamedeleteset = set()
 divergeset = set()
@@ -416,13 +446,36 @@
 repo.ui.debug("  unmatched files new in both:\n   %s\n"
   % "\n   ".join(bothnew))
 bothdiverge = {}
-bothdata = {'copy': {},
-'fullcopy': {},
-'diverge': bothdiverge,
-   }
+bothincompletediverge = {}
+both1 = {'copy': {},
+ 'fullcopy': {},
+ 'incomplete': {},
+ 'diverge': bothdiverge,
+ 'incompletediverge': bothincompletediverge
+}
+both2 = {'copy': {},
+ 'fullcopy': {},
+ 'incomplete': {},
+ 'diverge': bothdiverge,
+ 'incompletediverge': bothincompletediverge
+}
 for f in bothnew:
-_checkcopies(c1, f, m1, m2, base, tca, limit, bothdata)
-_checkcopies(c2, f, m2, m1, base, tca, limit, bothdata)
+_checkcopies(c1, f, m1, m2, base, tca, limit, both1)
+_checkcopies(c2, f, m2, m1, base, tca, limit, both2)
+if dirtyc1:
+assert both2['incomplete'] == {}
+remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
+   bothincompletediverge)
+else:
+assert both1['incomplete'] == {}
+remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
+   bothincompletediverge)
+for f in remainder:
+assert f not in bothdiverge
+ic = remainder[f]
+if ic[0] in (m1 if dirtyc1 else m2):
+# backed-out rename on one side, but watch out for deleted files
+bothdiverge[f] = ic
 for of, fl in bothdiverge.items():
 if len(fl) == 2 and fl[0] == fl[1]:
 copy[fl[0]] = of # not actually divergent, just matching renames
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 3 of 5 v4] checkcopies: add logic to handle remotebase

2016-10-17 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476152759 -7200
#  Tue Oct 11 04:25:59 2016 +0200
# Node ID d1a0366565988c2a58a21f1788557064c9590c46
# Parent  f5f046a680e6730485df1760f82a0e0084305873
checkcopies: add logic to handle remotebase

As the two _checkcopies passes' ranges are separated by tca, not base,
only one of the two passes will actually encounter the base.
Pass "remotebase" to the other pass to let it know not to expect passing
over the base. This is required for handling a few unusual rename cases.

diff -r f5f046a680e6 -r d1a036656598 mercurial/copies.py
--- a/mercurial/copies.py   Tue Oct 04 12:51:54 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 11 04:25:59 2016 +0200
@@ -413,10 +413,10 @@
   baselabel='topological common ancestor')
 
 for f in u1u:
-_checkcopies(c1, f, m1, m2, base, tca, limit, data1)
+_checkcopies(c1, f, m1, m2, base, tca, dirtyc1, limit, data1)
 
 for f in u2u:
-_checkcopies(c2, f, m2, m1, base, tca, limit, data2)
+_checkcopies(c2, f, m2, m1, base, tca, dirtyc2, limit, data2)
 
 copy = dict(data1['copy'].items() + data2['copy'].items())
 fullcopy = dict(data1['fullcopy'].items() + data2['fullcopy'].items())
@@ -460,8 +460,8 @@
  'incompletediverge': bothincompletediverge
 }
 for f in bothnew:
-_checkcopies(c1, f, m1, m2, base, tca, limit, both1)
-_checkcopies(c2, f, m2, m1, base, tca, limit, both2)
+_checkcopies(c1, f, m1, m2, base, tca, dirtyc1, limit, both1)
+_checkcopies(c2, f, m2, m1, base, tca, dirtyc2, limit, both2)
 if dirtyc1:
 assert both2['incomplete'] == {}
 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
@@ -590,7 +590,7 @@
 except StopIteration:
 return False
 
-def _checkcopies(ctx, f, m1, m2, base, tca, limit, data):
+def _checkcopies(ctx, f, m1, m2, base, tca, remotebase, limit, data):
 """
 check possible copies of f from m1 to m2
 
@@ -600,6 +600,7 @@
 m2 = the destination manifest
 base = the changectx used as a merge base
 tca = topological common ancestor for graft-like scenarios
+remotebase = True if base is outside tca::ctx, False otherwise
 limit = the rev number to not search beyond
 data = dictionary of dictionary to store copy data. (see mergecopies)
 
@@ -619,7 +620,7 @@
 # In the case there is both backward and forward renames (before and after
 # the base) this is more complicated as we must detect a divergence.
 # We use 'backwards = False' in that case.
-backwards = base != tca and f in mb
+backwards = not remotebase and base != tca and f in mb
 getfctx = _makegetfctx(ctx)
 
 of = None
@@ -652,6 +653,10 @@
 data['copy'][of] = f
 elif of in mb:
 data['copy'][f] = of
+elif remotebase: # special case: a <- b <- a -> b "ping-pong" 
rename
+data['copy'][of] = f
+del data['fullcopy'][f]
+data['fullcopy'][of] = f
 else: # divergence w.r.t. graft CA on one side of topological CA
 for sf in seen:
 if sf in mb:
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 5 v4] checkcopies: handle divergences contained entirely in tca::ctx

2016-10-17 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476266043 -7200
#  Wed Oct 12 11:54:03 2016 +0200
# Node ID d8379d11021b681a06cda08a07da237eaca520b2
# Parent  475efa21684cbd7c7fffba34250267dc500a7e9e
checkcopies: handle divergences contained entirely in tca::ctx

During a graftlike merge, _checkcopies runs from ctx to tca, possibly
passing over the merge base. If there is a rename both before and after
the base, then we're actually dealing with divergent renames.
If there is no rename on the other side of tca, then the divergence is
contained entirely in the range of one _checkcopies invocation, and
should be detected "in the loop" without having to rely on the other
_checkcopies pass.

diff -r 475efa21684c -r d8379d11021b mercurial/copies.py
--- a/mercurial/copies.py   Mon Sep 26 10:47:37 2016 +0200
+++ b/mercurial/copies.py   Wed Oct 12 11:54:03 2016 +0200
@@ -564,9 +564,8 @@
 # traversed backwards.
 #
 # In the case there is both backward and forward renames (before and after
-# the base) this is more complicated as we must detect a divergence. This
-# is currently broken and hopefully some later code update will make that
-# work (we use 'backwards = False' in that case)
+# the base) this is more complicated as we must detect a divergence.
+# We use 'backwards = False' in that case.
 backwards = base != tca and f in mb
 getfctx = _makegetfctx(ctx)
 
@@ -600,6 +599,12 @@
 data['copy'][of] = f
 elif of in mb:
 data['copy'][f] = of
+else: # divergence w.r.t. graft CA on one side of topological CA
+for sf in seen:
+if sf in mb:
+assert sf not in data['diverge']
+data['diverge'][sf] = [f, of]
+break
 return
 
 if of in mb:
diff -r 475efa21684c -r d8379d11021b tests/test-graft.t
--- a/tests/test-graft.tMon Sep 26 10:47:37 2016 +0200
+++ b/tests/test-graft.tWed Oct 12 11:54:03 2016 +0200
@@ -982,6 +982,9 @@
 
   $ HGEDITOR="echo D1 >" hg graft -r 'desc("D0")' --edit
   grafting 3:b69f5839d2d9 "D0"
+  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
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 4 of 5 v4] copies: make _checkcopies handle copy sequences spanning the TCA (issue4028)

2016-10-17 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476153587 -7200
#  Tue Oct 11 04:39:47 2016 +0200
# Node ID 3fc51a50bec71bb34c11beaaa6686d521ac706b1
# Parent  d1a0366565988c2a58a21f1788557064c9590c46
copies: make _checkcopies handle copy sequences spanning the TCA (issue4028)

When working in a rotated DAG (for a graftlike merge), there can be files
that are renamed both between the base and the topological CA, and between
the TCA and the endpoint farther from the base. Such renames span the TCA
(and thus need both passes of _checkcopies to be fully detected), but may
not necessarily be divergent.

Make _checkcopies return "incomplete copies" and "incomplete divergences"
in this case, and let mergecopies recombine them once data from both passes
of _checkcopies is available.

With this patch, all known cases involving renames and grafts pass.

(Developed together with Pierre-Yves David)

diff -r d1a036656598 -r 3fc51a50bec7 mercurial/copies.py
--- a/mercurial/copies.py   Tue Oct 11 04:25:59 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 11 04:39:47 2016 +0200
@@ -611,6 +611,7 @@
 """
 
 mb = base.manifest()
+mta = tca.manifest()
 # Might be true if this call is about finding backward renames,
 # This happens in the case of grafts because the DAG is then rotated.
 # If the file exists in both the base and the source, we are not looking
@@ -665,8 +666,17 @@
 break
 return
 
-if of in mb:
-data['diverge'].setdefault(of, []).append(f)
+if of in mta:
+if backwards or remotebase:
+data['incomplete'][of] = f
+else:
+for sf in seen:
+if sf in mb:
+if tca == base:
+data['diverge'].setdefault(sf, []).append(f)
+else:
+data['incompletediverge'][sf] = [of, f]
+return
 
 def duplicatecopies(repo, rev, fromrev, skiprev=None):
 '''reproduce copies from fromrev to rev in the dirstate
diff -r d1a036656598 -r 3fc51a50bec7 tests/test-graft.t
--- a/tests/test-graft.tTue Oct 11 04:25:59 2016 +0200
+++ b/tests/test-graft.tTue Oct 11 04:39:47 2016 +0200
@@ -957,15 +957,6 @@
   grafting 2:f58c7e2b28fa "C0"
   merging f1a and f1b to f1a
   merging f5a
-  warning: conflicts while merging f5a! (edit, then use 'hg resolve --mark')
-  abort: unresolved conflicts, can't continue
-  (use 'hg resolve' and 'hg graft --continue')
-  [255]
-  $ hg resolve f5a -t ':other' # XXX work around failure
-  (no more unresolved files)
-  continue: hg graft --continue
-  $ hg graft --continue # XXX work around failure
-  grafting 2:f58c7e2b28fa "C0"
   warning: can't find ancestor for 'f5a' copied from 'f5b'!
   $ hg status --change .
   M f1a
@@ -975,7 +966,7 @@
   $ hg cat f1a
   c1c
   $ hg cat f1b
-  f1b: no such file in rev 43e4b415492d
+  f1b: no such file in rev c9763722f9bd
   [1]
 
 Test the cases A.0 (f4x) and A.6 (f3x)
@@ -997,23 +988,23 @@
   $ hg mv f5a f5b
   $ hg ci -qAm "E0"
   $ hg log -G
-  @  changeset:   6:ebba59d1fb02
+  @  changeset:   6:6bd1736cab86
   |  tag: tip
   |  parent:  0:11f7a1b56675
   |  user:test
   |  date:Thu Jan 01 00:00:00 1970 +
   |  summary: E0
   |
-  | o  changeset:   5:4f4ba7a6e606
+  | o  changeset:   5:560daee679da
   | |  user:test
   | |  date:Thu Jan 01 00:00:00 1970 +
   | |  summary: D1
   | |
-  | o  changeset:   4:43e4b415492d
+  | o  changeset:   4:c9763722f9bd
   |/   parent:  0:11f7a1b56675
   |user:test
   |date:Thu Jan 01 00:00:00 1970 +
-  |summary: C0
+  |summary: C1
   |
   | o  changeset:   3:b69f5839d2d9
   | |  user:test
@@ -1041,34 +1032,24 @@
 
   $ HGEDITOR="echo C2 >" hg graft -r 'desc("C0")' --edit
   grafting 2:f58c7e2b28fa "C0"
-  other [graft] changed f1b which local [local] deleted
-  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
+  merging f1e and f1b to f1e
   merging f2a and f2c to f2c
   merging f5b and f5a to f5a
-  abort: unresolved conflicts, can't continue
-  (use 'hg resolve' and 'hg graft --continue')
-  [255]
-  $ hg resolve f1b -t ':other' # XXX work around failure
-  (no more unresolved files)
-  continue: hg graft --continue
-  $ hg graft --continue # XXX work around failure
-  grafting 2:f58c7e2b28fa "C0"
-  grafting 4:43e4b415492d "C0"
-  merging f1e and f1a to f1e
-  merging f2c
-  warning: can't find ancestor for 'f2c' copied from 'f2a'!
 
 Test the cases A.1 (f4x) and A.7 (f3x).
 
   $ HGEDITOR="echo D2 >" hg graft -r 'desc("D0")' --edit
   grafting 3:b69f5839d2d9 "D0"
+  note: possible conflict - f3b was renamed multiple times to:
+   f3e
+   f3d
   merging f4e and f4a to f4e

RE: [PATCH 05 of 12 v2] copies: make _checkcopies handle simple renames in a rotated DAG

2016-10-16 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: Sunday, October 16, 2016 5:16 PM
> To: mercurial-devel@mercurial-scm.org
> Subject: [PATCH 05 of 12 v2] copies: make _checkcopies handle simple
> renames in a rotated DAG
>
> # HG changeset patch
> # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1476317034 -7200
> #  Thu Oct 13 02:03:54 2016 +0200
> # Node ID 48dba8d1f7fab137418bfbb833c3096969eec934
> # Parent  1f91343556e7cef7a71edf512b02f7e0f09d5e9b
> copies: make _checkcopies handle simple renames in a rotated DAG
>
> This introduces a distinction between "merge base" and "topological
> common ancestor". During a regular merge, these two are identical. Graft,
> however, performs a merge in a rotated DAG, where the merge common
> ancestor will not be a common ancestor at all in the original DAG.

That was meant to read "merge base will not be".

>
> To correctly find copies in case of a graft, we need to take both the merge
> base and the topological CA into account, and track any renames between
> them in reverse. Fortunately we can detect this in advance, see comment in
> the code about "backwards".
>
> This patch only supports finding non-divergent renames contained entirely
> between the merge base and the topological CA. Further patches are coming
> to support more complex cases.
>
> (Pierre-Yves David was involved in the cleanup of this patch.)
>
> diff -r 1f91343556e7 -r 48dba8d1f7fa mercurial/copies.py
> --- a/mercurial/copies.pyThu Oct 13 02:03:49 2016 +0200
> +++ b/mercurial/copies.pyThu Oct 13 02:03:54 2016 +0200
> @@ -374,10 +374,10 @@
>  bothnew = sorted(addedinm1 & addedinm2)
>
>  for f in u1u:
> -_checkcopies(c1, f, m1, m2, base, limit, data1)
> +_checkcopies(c1, f, m1, m2, base, tca, limit, data1)
>
>  for f in u2u:
> -_checkcopies(c2, f, m2, m1, base, limit, data2)
> +_checkcopies(c2, f, m2, m1, base, tca, limit, data2)
>
>  copy = dict(data1['copy'].items() + data2['copy'].items())
>  fullcopy = dict(data1['fullcopy'].items() + data2['fullcopy'].items()) 
> @@ -
> 405,8 +405,8 @@
>  'diverge': bothdiverge,
> }
>  for f in bothnew:
> -_checkcopies(c1, f, m1, m2, base, limit, bothdata)
> -_checkcopies(c2, f, m2, m1, base, limit, bothdata)
> +_checkcopies(c1, f, m1, m2, base, tca, limit, bothdata)
> +_checkcopies(c2, f, m2, m1, base, tca, limit, bothdata)
>  for of, fl in bothdiverge.items():
>  if len(fl) == 2 and fl[0] == fl[1]:
>  copy[fl[0]] = of # not actually divergent, just matching renames 
> @@ -
> 521,7 +521,7 @@
>  except StopIteration:
>  return False
>
> -def _checkcopies(ctx, f, m1, m2, base, limit, data):
> +def _checkcopies(ctx, f, m1, m2, base, tca, limit, data):
>  """
>  check possible copies of f from m1 to m2
>
> @@ -530,6 +530,7 @@
>  m1 = the source manifest
>  m2 = the destination manifest
>  base = the changectx used as a merge base
> +tca = topological common ancestor for graft-like scenarios
>  limit = the rev number to not search beyond
>  data = dictionary of dictionary to store copy data. (see mergecopies)
>
> @@ -540,6 +541,17 @@
>  """
>
>  mb = base.manifest()
> +# Might be true if this call is about finding backward renames,
> +# This happens in the case of grafts because the DAG is then rotated.
> +# If the file exists in both the base and the source, we are not looking
> +# for a rename on the source side, but on the part of the DAG that is
> +# traversed backwards.
> +#
> +# In the case there is both backward and forward renames (before and
> after
> +# the base) this is more complicated as we must detect a divergence. This
> +# is currently broken and hopefully some later code update will make that
> +# work (we use 'backwards = False' in that case)
> +backwards = base != tca and f in mb
>  getfctx = _makegetfctx(ctx)
>
>  of = None
> @@ -554,7 +566,11 @@
>  continue
>  seen.add(of)
>
> -data['fullcopy'][f] = of # remember for dir rename detection
> +# remember for dir rename detection
> +i

[PATCH 12 of 12 v2] update: enable copy tracing for backwards and non-linear updates

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1472155346 -7200
#  Thu Aug 25 22:02:26 2016 +0200
# Node ID 066dc1a5c8f4c9dfb582543bff5df8bbeba9cc31
# Parent  a3a36d782f4d4252adb9069eb3714f946881
update: enable copy tracing for backwards and non-linear updates

As a followup to the issue4028 series, this fixes a variant of the issue
that can occur when updating with uncommited local changes.

diff -r a3a36d782f4d -r 066dc1a5c8f4 mercurial/merge.py
--- a/mercurial/merge.pyTue Oct 11 04:39:47 2016 +0200
+++ b/mercurial/merge.pyThu Aug 25 22:02:26 2016 +0200
@@ -1537,15 +1537,16 @@
 pas = [p1]
 
 # deprecated config: merge.followcopies
-followcopies = False
+followcopies = repo.ui.configbool('merge', 'followcopies', True)
 if overwrite:
 pas = [wc]
+followcopies = False
 elif pas == [p2]: # backwards
-pas = [wc.p1()]
-elif not branchmerge and not wc.dirty(missing=True):
-pass
-elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
-followcopies = True
+pas = [p1]
+elif not pas[0]:
+followcopies = False
+if not branchmerge and not wc.dirty(missing=True):
+followcopies = False
 
 ### calculate phase
 actionbyfile, diverge, renamedelete = calculateupdates(
diff -r a3a36d782f4d -r 066dc1a5c8f4 tests/test-merge-local.t
--- a/tests/test-merge-local.t  Tue Oct 11 04:39:47 2016 +0200
+++ b/tests/test-merge-local.t  Thu Aug 25 22:02:26 2016 +0200
@@ -66,7 +66,7 @@
   merging zzz1_merge_ok
   merging zzz2_merge_bad
   warning: conflicts while merging zzz2_merge_bad! (edit, then use 'hg resolve 
--mark')
-  2 files updated, 1 files merged, 3 files removed, 1 files unresolved
+  2 files updated, 1 files merged, 2 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
   [1]
 
@@ -104,7 +104,7 @@
   merging zzz1_merge_ok
   merging zzz2_merge_bad
   warning: conflicts while merging zzz2_merge_bad! (edit, then use 'hg resolve 
--mark')
-  2 files updated, 1 files merged, 3 files removed, 1 files unresolved
+  2 files updated, 1 files merged, 2 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
   [1]
 
diff -r a3a36d782f4d -r 066dc1a5c8f4 tests/test-mq-subrepo.t
--- a/tests/test-mq-subrepo.t   Tue Oct 11 04:39:47 2016 +0200
+++ b/tests/test-mq-subrepo.t   Thu Aug 25 22:02:26 2016 +0200
@@ -304,6 +304,7 @@
   record this change to '.hgsub'? [Ynesfdaq?] y
   
   warning: subrepo spec file '.hgsub' not found
+  warning: subrepo spec file '.hgsub' not found
   abort: uncommitted changes in subrepository 'sub'
   [255]
   % update substate when adding .hgsub w/clean updated subrepo
@@ -319,6 +320,7 @@
   record this change to '.hgsub'? [Ynesfdaq?] y
   
   warning: subrepo spec file '.hgsub' not found
+  warning: subrepo spec file '.hgsub' not found
   path sub
source   sub
revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
diff -r a3a36d782f4d -r 066dc1a5c8f4 tests/test-up-local-change.t
--- a/tests/test-up-local-change.t  Tue Oct 11 04:39:47 2016 +0200
+++ b/tests/test-up-local-change.t  Thu Aug 25 22:02:26 2016 +0200
@@ -67,6 +67,10 @@
   summary: 2
   
   $ hg --debug up 0
+  starting 4 threads for background file closing (?)
+searching for copies back to rev 0
+unmatched files in local (from topological common ancestor):
+ b
   resolving manifests
branchmerge: False, force: False, partial: False
ancestor: 1e71731e6fbb, local: 1e71731e6fbb+, remote: c19d34741b0a
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 05 of 12 v2] copies: make _checkcopies handle simple renames in a rotated DAG

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476317034 -7200
#  Thu Oct 13 02:03:54 2016 +0200
# Node ID 48dba8d1f7fab137418bfbb833c3096969eec934
# Parent  1f91343556e7cef7a71edf512b02f7e0f09d5e9b
copies: make _checkcopies handle simple renames in a rotated DAG

This introduces a distinction between "merge base" and
"topological common ancestor". During a regular merge, these two are
identical. Graft, however, performs a merge in a rotated DAG, where the
merge common ancestor will not be a common ancestor at all in the
original DAG.

To correctly find copies in case of a graft, we need to take both the
merge base and the topological CA into account, and track any renames
between them in reverse. Fortunately we can detect this in advance,
see comment in the code about "backwards".

This patch only supports finding non-divergent renames contained entirely
between the merge base and the topological CA. Further patches are coming
to support more complex cases.

(Pierre-Yves David was involved in the cleanup of this patch.)

diff -r 1f91343556e7 -r 48dba8d1f7fa mercurial/copies.py
--- a/mercurial/copies.py   Thu Oct 13 02:03:49 2016 +0200
+++ b/mercurial/copies.py   Thu Oct 13 02:03:54 2016 +0200
@@ -374,10 +374,10 @@
 bothnew = sorted(addedinm1 & addedinm2)
 
 for f in u1u:
-_checkcopies(c1, f, m1, m2, base, limit, data1)
+_checkcopies(c1, f, m1, m2, base, tca, limit, data1)
 
 for f in u2u:
-_checkcopies(c2, f, m2, m1, base, limit, data2)
+_checkcopies(c2, f, m2, m1, base, tca, limit, data2)
 
 copy = dict(data1['copy'].items() + data2['copy'].items())
 fullcopy = dict(data1['fullcopy'].items() + data2['fullcopy'].items())
@@ -405,8 +405,8 @@
 'diverge': bothdiverge,
}
 for f in bothnew:
-_checkcopies(c1, f, m1, m2, base, limit, bothdata)
-_checkcopies(c2, f, m2, m1, base, limit, bothdata)
+_checkcopies(c1, f, m1, m2, base, tca, limit, bothdata)
+_checkcopies(c2, f, m2, m1, base, tca, limit, bothdata)
 for of, fl in bothdiverge.items():
 if len(fl) == 2 and fl[0] == fl[1]:
 copy[fl[0]] = of # not actually divergent, just matching renames
@@ -521,7 +521,7 @@
 except StopIteration:
 return False
 
-def _checkcopies(ctx, f, m1, m2, base, limit, data):
+def _checkcopies(ctx, f, m1, m2, base, tca, limit, data):
 """
 check possible copies of f from m1 to m2
 
@@ -530,6 +530,7 @@
 m1 = the source manifest
 m2 = the destination manifest
 base = the changectx used as a merge base
+tca = topological common ancestor for graft-like scenarios
 limit = the rev number to not search beyond
 data = dictionary of dictionary to store copy data. (see mergecopies)
 
@@ -540,6 +541,17 @@
 """
 
 mb = base.manifest()
+# Might be true if this call is about finding backward renames,
+# This happens in the case of grafts because the DAG is then rotated.
+# If the file exists in both the base and the source, we are not looking
+# for a rename on the source side, but on the part of the DAG that is
+# traversed backwards.
+#
+# In the case there is both backward and forward renames (before and after
+# the base) this is more complicated as we must detect a divergence. This
+# is currently broken and hopefully some later code update will make that
+# work (we use 'backwards = False' in that case)
+backwards = base != tca and f in mb
 getfctx = _makegetfctx(ctx)
 
 of = None
@@ -554,7 +566,11 @@
 continue
 seen.add(of)
 
-data['fullcopy'][f] = of # remember for dir rename detection
+# remember for dir rename detection
+if backwards:
+data['fullcopy'][of] = f # grafting backwards through renames
+else:
+data['fullcopy'][f] = of
 if of not in m2:
 continue # no match, keep looking
 if m2[of] == mb.get(of):
@@ -562,9 +578,11 @@
 c2 = getfctx(of, m2[of])
 # c2 might be a plain new file on added on destination side that is
 # unrelated to the droids we are looking for.
-cr = _related(oc, c2, base.rev())
+cr = _related(oc, c2, tca.rev())
 if cr and (of == f or of == c2.path()): # non-divergent
-if of in mb:
+if backwards:
+data['copy'][of] = f
+elif of in mb:
 data['copy'][f] = of
 return
 
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 11 of 12 v2] copies: make _checkcopies handle copy sequences spanning the TCA (issue4028)

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476153587 -7200
#  Tue Oct 11 04:39:47 2016 +0200
# Node ID a3a36d782f4d4252adb9069eb3714f946881
# Parent  2bf601f8e1d3569fdef9ab9f3fefa354acce8669
copies: make _checkcopies handle copy sequences spanning the TCA (issue4028)

When working in a rotated DAG (for a graftlike merge), there can be files
that are renamed both between the base and the topological CA, and between
the TCA and the endpoint farther from the base. Such renames span the TCA
(and thus need both passes of _checkcopies to be fully detected), but may
not necessarily be divergent.

Make _checkcopies return "incomplete copies" and "incomplete divergences"
in this case, and let mergecopies recombine them once data from both passes
of _checkcopies is available.

With this patch, all known cases involving renames and grafts pass.

(Developed together with Pierre-Yves David)

diff -r 2bf601f8e1d3 -r a3a36d782f4d mercurial/copies.py
--- a/mercurial/copies.py   Tue Oct 11 04:25:59 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 11 04:39:47 2016 +0200
@@ -611,6 +611,7 @@
 """
 
 mb = base.manifest()
+mta = tca.manifest()
 # Might be true if this call is about finding backward renames,
 # This happens in the case of grafts because the DAG is then rotated.
 # If the file exists in both the base and the source, we are not looking
@@ -665,8 +666,17 @@
 break
 return
 
-if of in mb:
-data['diverge'].setdefault(of, []).append(f)
+if of in mta:
+if backwards or remotebase:
+data['incomplete'][of] = f
+else:
+for sf in seen:
+if sf in mb:
+if tca == base:
+data['diverge'].setdefault(sf, []).append(f)
+else:
+data['incompletediverge'][sf] = [of, f]
+return
 
 def duplicatecopies(repo, rev, fromrev, skiprev=None):
 '''reproduce copies from fromrev to rev in the dirstate
diff -r 2bf601f8e1d3 -r a3a36d782f4d tests/test-graft.t
--- a/tests/test-graft.tTue Oct 11 04:25:59 2016 +0200
+++ b/tests/test-graft.tTue Oct 11 04:39:47 2016 +0200
@@ -957,15 +957,6 @@
   grafting 2:f58c7e2b28fa "C0"
   merging f1a and f1b to f1a
   merging f5a
-  warning: conflicts while merging f5a! (edit, then use 'hg resolve --mark')
-  abort: unresolved conflicts, can't continue
-  (use 'hg resolve' and 'hg graft --continue')
-  [255]
-  $ hg resolve f5a -t ':other' # XXX work around failure
-  (no more unresolved files)
-  continue: hg graft --continue
-  $ hg graft --continue # XXX work around failure
-  grafting 2:f58c7e2b28fa "C0"
   warning: can't find ancestor for 'f5a' copied from 'f5b'!
   $ hg status --change .
   M f1a
@@ -975,7 +966,7 @@
   $ hg cat f1a
   c1c
   $ hg cat f1b
-  f1b: no such file in rev 43e4b415492d
+  f1b: no such file in rev c9763722f9bd
   [1]
 
 Test the cases A.0 (f4x) and A.6 (f3x)
@@ -997,23 +988,23 @@
   $ hg mv f5a f5b
   $ hg ci -qAm "E0"
   $ hg log -G
-  @  changeset:   6:ebba59d1fb02
+  @  changeset:   6:6bd1736cab86
   |  tag: tip
   |  parent:  0:11f7a1b56675
   |  user:test
   |  date:Thu Jan 01 00:00:00 1970 +
   |  summary: E0
   |
-  | o  changeset:   5:4f4ba7a6e606
+  | o  changeset:   5:560daee679da
   | |  user:test
   | |  date:Thu Jan 01 00:00:00 1970 +
   | |  summary: D1
   | |
-  | o  changeset:   4:43e4b415492d
+  | o  changeset:   4:c9763722f9bd
   |/   parent:  0:11f7a1b56675
   |user:test
   |date:Thu Jan 01 00:00:00 1970 +
-  |summary: C0
+  |summary: C1
   |
   | o  changeset:   3:b69f5839d2d9
   | |  user:test
@@ -1041,34 +1032,24 @@
 
   $ HGEDITOR="echo C2 >" hg graft -r 'desc("C0")' --edit
   grafting 2:f58c7e2b28fa "C0"
-  other [graft] changed f1b which local [local] deleted
-  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
+  merging f1e and f1b to f1e
   merging f2a and f2c to f2c
   merging f5b and f5a to f5a
-  abort: unresolved conflicts, can't continue
-  (use 'hg resolve' and 'hg graft --continue')
-  [255]
-  $ hg resolve f1b -t ':other' # XXX work around failure
-  (no more unresolved files)
-  continue: hg graft --continue
-  $ hg graft --continue # XXX work around failure
-  grafting 2:f58c7e2b28fa "C0"
-  grafting 4:43e4b415492d "C0"
-  merging f1e and f1a to f1e
-  merging f2c
-  warning: can't find ancestor for 'f2c' copied from 'f2a'!
 
 Test the cases A.1 (f4x) and A.7 (f3x).
 
   $ HGEDITOR="echo D2 >" hg graft -r 'desc("D0")' --edit
   grafting 3:b69f5839d2d9 "D0"
+  note: possible conflict - f3b was renamed multiple times to:
+   f3e
+   f3d
   merging f4e and f4a to f4e

[PATCH 03 of 12 v2] copies: detect graft-like merges

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476316053 -7200
#  Thu Oct 13 01:47:33 2016 +0200
# Node ID 981a622717343796f65b5858f4d4b89f81cb437a
# Parent  7f9a6f30730074c869e5cf9b77c53929b06c4fb3
copies: detect graft-like merges

Right now, nothing changes as a result of this, but we want to handle
grafts differently from ordinary merges later.

(Series developed together with Pierre-Yves David)

diff -r 7f9a6f307300 -r 981a62271734 mercurial/copies.py
--- a/mercurial/copies.py   Wed Oct 12 12:41:28 2016 +0200
+++ b/mercurial/copies.py   Thu Oct 13 01:47:33 2016 +0200
@@ -321,6 +321,23 @@
 if repo.ui.configbool('experimental', 'disablecopytrace'):
 return {}, {}, {}, {}
 
+# In certain scenarios (e.g. graft, update or rebase), base 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.
+#
+# base.descendant(wc) and base.descendant(base) 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
+# an endpoint is "dirty" if it isn't a descendant of the merge base
+# if we have a dirty endpoint, we need to trigger graft logic, and also
+# keep track of which endpoint is dirty
+dirtyc1 = not (base == _c1 or base.descendant(_c1))
+dirtyc2 = not (base== _c2 or base.descendant(_c2))
+graft = dirtyc1 or dirtyc2
 limit = _findlimit(repo, c1.rev(), c2.rev())
 if limit is None:
 # no common ancestor, no copies
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 01 of 12 v2] checkcopies: add a sanity check against false-positive copies

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476300825 -7200
#  Wed Oct 12 21:33:45 2016 +0200
# Node ID e8e15a687df328f37b2d5e97e67f1a2071b35134
# Parent  bfb99ef3749757161bf439cc0ec678de46e24672
checkcopies: add a sanity check against false-positive copies

When grafting a copy backwards through a rename, a copy is wrongly detected,
which causes the graft to be applied inappropriately, in a destructive way.
Make sure that the old file name really exists in the common ancestor,
and bail out if it doesn't.

This fixes the aggravated case of bug 5343, although the basic issue
(failure to duplicate the copy information) still occurs.

diff -r bfb99ef37497 -r e8e15a687df3 mercurial/copies.py
--- a/mercurial/copies.py   Thu Oct 13 01:30:14 2016 +0200
+++ b/mercurial/copies.py   Wed Oct 12 21:33:45 2016 +0200
@@ -543,7 +543,8 @@
 # unrelated to the droids we are looking for.
 cr = _related(oc, c2, base.rev())
 if cr and (of == f or of == c2.path()): # non-divergent
-data['copy'][f] = of
+if of in mb:
+data['copy'][f] = of
 return
 
 if of in mb:
diff -r bfb99ef37497 -r e8e15a687df3 tests/test-graft.t
--- a/tests/test-graft.tThu Oct 13 01:30:14 2016 +0200
+++ b/tests/test-graft.tWed Oct 12 21:33:45 2016 +0200
@@ -427,8 +427,8 @@
   $ hg graft 3 --log -u foo
   grafting 3:4c60f11aa304 "3"
   warning: can't find ancestor for 'c' copied from 'b'!
-  $ hg log --template '{rev} {parents} {desc}\n' -r tip
-  14 1:5d205f8b35b6  3
+  $ hg log --template '{rev}:{node|short} {parents} {desc}\n' -r tip
+  14:0c921c65ef1e 1:5d205f8b35b6  3
   (grafted from 4c60f11aa304a54ae1c199feb94e7fc771e51ed8)
 
 Resolve conflicted graft
@@ -620,7 +620,7 @@
   date:Thu Jan 01 00:00:00 1970 +
   summary: 2
   
-  changeset:   14:f64defefacee
+  changeset:   14:0c921c65ef1e
   parent:  1:5d205f8b35b6
   user:foo
   date:Thu Jan 01 00:00:00 1970 +
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 02 of 12 v2] tests: introduce tests for grafting through renames

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 147626 -7200
#  Wed Oct 12 12:41:28 2016 +0200
# Node ID 7f9a6f30730074c869e5cf9b77c53929b06c4fb3
# Parent  e8e15a687df328f37b2d5e97e67f1a2071b35134
tests: introduce tests for grafting through renames

These cover all currently known cases of renames being grafted,
or changes being grafted through renames.

Right now, most of these cases are broken. Later patches in this series
will make them behave correctly.

The testcases heavily rely on each other, which would make it very difficult
to separate them and add them one-by-one for each case fixed by a patch.
Separating them should perhaps be a 4.1 task, if it doesn't slow down
the tests too much.

(Developed together with Pierre-Yves David)

diff -r e8e15a687df3 -r 7f9a6f307300 tests/test-graft.t
--- a/tests/test-graft.tWed Oct 12 21:33:45 2016 +0200
+++ b/tests/test-graft.tWed Oct 12 12:41:28 2016 +0200
@@ -842,3 +842,461 @@
   |/
   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 A0
+  $ hg mv f1a f1b
+  $ hg mv f3a f3b
+  $ hg mv f5a f5b
+  $ hg ci -qAm B0
+  $ echo c1c > f1b
+  $ hg mv f2a f2c
+  $ hg mv f5b f5a
+  $ echo c5c > f5a
+  $ hg ci -qAm C0
+  $ hg mv f3b f3d
+  $ echo c4d > f4a
+  $ hg ci -qAm D0
+  $ hg log -G
+  @  changeset:   3:b69f5839d2d9
+  |  tag: tip
+  |  user:test
+  |  date:Thu Jan 01 00:00:00 1970 +
+  |  summary: D0
+  |
+  o  changeset:   2:f58c7e2b28fa
+  |  user:test
+  |  date:Thu Jan 01 00:00:00 1970 +
+  |  summary: C0
+  |
+  o  changeset:   1:3d7bba921b5d
+  |  user:test
+  |  date:Thu Jan 01 00:00:00 1970 +
+  |  summary: B0
+  |
+  o  changeset:   0:11f7a1b56675
+ user:test
+ date:Thu Jan 01 00:0

[PATCH 10 of 12 v2] checkcopies: add logic to handle remotebase

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476152759 -7200
#  Tue Oct 11 04:25:59 2016 +0200
# Node ID 2bf601f8e1d3569fdef9ab9f3fefa354acce8669
# Parent  9fc636d9950feb76af774f881ea179037231e697
checkcopies: add logic to handle remotebase

As the two _checkcopies passes' ranges are separated by tca, not base,
only one of the two passes will actually encounter the base.
Pass "remotebase" to the other pass to let it know not to expect passing
over the base. This is required for handling a few unusual rename cases.

diff -r 9fc636d9950f -r 2bf601f8e1d3 mercurial/copies.py
--- a/mercurial/copies.py   Tue Oct 04 12:51:54 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 11 04:25:59 2016 +0200
@@ -413,10 +413,10 @@
   baselabel='topological common ancestor')
 
 for f in u1u:
-_checkcopies(c1, f, m1, m2, base, tca, limit, data1)
+_checkcopies(c1, f, m1, m2, base, tca, dirtyc1, limit, data1)
 
 for f in u2u:
-_checkcopies(c2, f, m2, m1, base, tca, limit, data2)
+_checkcopies(c2, f, m2, m1, base, tca, dirtyc2, limit, data2)
 
 copy = dict(data1['copy'].items() + data2['copy'].items())
 fullcopy = dict(data1['fullcopy'].items() + data2['fullcopy'].items())
@@ -460,8 +460,8 @@
  'incompletediverge': bothincompletediverge
 }
 for f in bothnew:
-_checkcopies(c1, f, m1, m2, base, tca, limit, both1)
-_checkcopies(c2, f, m2, m1, base, tca, limit, both2)
+_checkcopies(c1, f, m1, m2, base, tca, dirtyc1, limit, both1)
+_checkcopies(c2, f, m2, m1, base, tca, dirtyc2, limit, both2)
 if dirtyc1:
 assert both2['incomplete'] == {}
 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
@@ -590,7 +590,7 @@
 except StopIteration:
 return False
 
-def _checkcopies(ctx, f, m1, m2, base, tca, limit, data):
+def _checkcopies(ctx, f, m1, m2, base, tca, remotebase, limit, data):
 """
 check possible copies of f from m1 to m2
 
@@ -600,6 +600,7 @@
 m2 = the destination manifest
 base = the changectx used as a merge base
 tca = topological common ancestor for graft-like scenarios
+remotebase = True if base is outside tca::ctx, False otherwise
 limit = the rev number to not search beyond
 data = dictionary of dictionary to store copy data. (see mergecopies)
 
@@ -619,7 +620,7 @@
 # In the case there is both backward and forward renames (before and after
 # the base) this is more complicated as we must detect a divergence.
 # We use 'backwards = False' in that case.
-backwards = base != tca and f in mb
+backwards = not remotebase and base != tca and f in mb
 getfctx = _makegetfctx(ctx)
 
 of = None
@@ -652,6 +653,10 @@
 data['copy'][of] = f
 elif of in mb:
 data['copy'][f] = of
+elif remotebase: # special case: a <- b <- a -> b "ping-pong" 
rename
+data['copy'][of] = f
+del data['fullcopy'][f]
+data['fullcopy'][of] = f
 else: # divergence w.r.t. graft CA on one side of topological CA
 for sf in seen:
 if sf in mb:
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 07 of 12 v2] mergecopies: invoke _computenonoverlap for both base and tca during merges

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476317983 -7200
#  Thu Oct 13 02:19:43 2016 +0200
# Node ID e61355cb337ab5328e4cf5d0f153821600c53e72
# Parent  72b4f2514efd9decffb1115c0f7b8e8f004bdf44
mergecopies: invoke _computenonoverlap for both base and tca during merges

The algorithm of _checkcopies can only walk backwards in the DAG, never
forward. Because of this, the two _checkcopies patches need to run from
their respective endpoints to the TCA to cover the entire subgraph where
the merge is being performed. However, detection of files new in both
endpoints, as well as directory rename detection, need to run with respect
to the merge base, so we need lists of new files both from the TCA's and
the merge base's viewpoint to correctly detect renames in a graft-like
merge scenario.

(Series reworked by Pierre-Yves David)

diff -r 72b4f2514efd -r e61355cb337a mercurial/copies.py
--- a/mercurial/copies.py   Thu Oct 13 17:21:49 2016 +0200
+++ b/mercurial/copies.py   Thu Oct 13 02:19:43 2016 +0200
@@ -373,9 +373,21 @@
 # find interesting file sets from manifests
 addedinm1 = m1.filesnotin(mb)
 addedinm2 = m2.filesnotin(mb)
-u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
-u1u, u2u = u1r, u2r
 bothnew = sorted(addedinm1 & addedinm2)
+if tca == base:
+# unmatched file from base
+u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
+u1u, u2u = u1r, u2r
+else:
+# unmatched file from base (DAG rotation in the graft case)
+u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
+  baselabel='base')
+# unmatched file from topological common ancestors (no DAG rotation)
+# need to recompute this for directory move handling when grafting
+mta = tca.manifest()
+u1u, u2u = _computenonoverlap(repo, c1, c2, m1.filesnotin(mta),
+m2.filesnotin(mta),
+  baselabel='topological common ancestor')
 
 for f in u1u:
 _checkcopies(c1, f, m1, m2, base, tca, limit, data1)
diff -r 72b4f2514efd -r e61355cb337a tests/test-graft.t
--- a/tests/test-graft.tThu Oct 13 17:21:49 2016 +0200
+++ b/tests/test-graft.tThu Oct 13 02:19:43 2016 +0200
@@ -179,6 +179,11 @@
   committing changelog
   grafting 5:97f8bfe72746 "5"
 searching for copies back to rev 1
+unmatched files in other (from topological common ancestor):
+ 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 +198,11 @@
   scanning for duplicate grafts
   grafting 4:9c233e8e184d "4"
 searching for copies back to rev 1
+unmatched files in other (from topological common ancestor):
+ 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
@@ -945,6 +955,7 @@
   $ hg up -q 'desc("A0")'
   $ HGEDITOR="echo C1 >" hg graft -r 'desc("C0")' --edit
   grafting 2:f58c7e2b28fa "C0"
+  merging f1a and f1b to f1a
   merging f5a
   warning: conflicts while merging f5a! (edit, then use 'hg resolve --mark')
   abort: unresolved conflicts, can't continue
@@ -957,14 +968,15 @@
   grafting 2:f58c7e2b28fa "C0"
   warning: can't find ancestor for 'f5a' copied from 'f5b'!
   $ hg status --change .
+  M f1a
   M f5a
-  A f1b
   A f2c
   R f2a
   $ hg cat f1a
-  c1a
+  c1c
   $ hg cat f1b
-  c1c
+  f1b: no such file in rev 43e4b415492d
+  [1]
 
 Test the cases A.0 (f4x) and A.6 (f3x)
 
@@ -989,12 +1001,12 @@
   |  date:Thu Jan 01 00:00:00 1970 +
   |  summary: E0
   |
-  | o  changeset:   5:573bb6b4b56d
+  | o  changeset:   5:4f4ba7a6e606
   | |  user:test
   | |  date:Thu Jan 01 00:00:00 1970 +
   | |  summary: D1
   | |
-  | o  changeset:   4:af23416e619b
+  | o  changeset:   4:43e4b415492d
   |/   parent:  0:11f7a1b56675
   |user:test
   |date:Thu Jan 01 00:00:00 1970 +
@@ -1029,6 +1041,7 @@
   other [graft] changed f1b which local [local] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging f2a and f2c to f2c
+  merging f5b and f5a to f5a
   abort: unresolved conflicts, can't continue
   (use 'hg resolve' and 'hg graft --continue')
   [255]
@@ -1037,7 +1050,8 @@
   continue: hg graft --continue
   $ hg graft --continue # XXX work around failure
   grafting 2:f58c7e2b28fa "C0&

[PATCH 08 of 12 v2] checkcopies: handle divergences contained entirely in tca::ctx

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476266043 -7200
#  Wed Oct 12 11:54:03 2016 +0200
# Node ID 73b55839aca7019d88cc9885e43244fb1e6d68ab
# Parent  e61355cb337ab5328e4cf5d0f153821600c53e72
checkcopies: handle divergences contained entirely in tca::ctx

During a graftlike merge, _checkcopies runs from ctx to tca, possibly
passing over the merge base. If there is a rename both before and after
the base, then we're actually dealing with divergent renames.
If there is no rename on the other side of tca, then the divergence is
contained entirely in the range of one _checkcopies invocation, and
should be detected "in the loop" without having to rely on the other
_checkcopies pass.

diff -r e61355cb337a -r 73b55839aca7 mercurial/copies.py
--- a/mercurial/copies.py   Thu Oct 13 02:19:43 2016 +0200
+++ b/mercurial/copies.py   Wed Oct 12 11:54:03 2016 +0200
@@ -564,9 +564,8 @@
 # traversed backwards.
 #
 # In the case there is both backward and forward renames (before and after
-# the base) this is more complicated as we must detect a divergence. This
-# is currently broken and hopefully some later code update will make that
-# work (we use 'backwards = False' in that case)
+# the base) this is more complicated as we must detect a divergence.
+# We use 'backwards = False' in that case.
 backwards = base != tca and f in mb
 getfctx = _makegetfctx(ctx)
 
@@ -600,6 +599,12 @@
 data['copy'][of] = f
 elif of in mb:
 data['copy'][f] = of
+else: # divergence w.r.t. graft CA on one side of topological CA
+for sf in seen:
+if sf in mb:
+assert sf not in data['diverge']
+data['diverge'][sf] = [f, of]
+break
 return
 
 if of in mb:
diff -r e61355cb337a -r 73b55839aca7 tests/test-graft.t
--- a/tests/test-graft.tThu Oct 13 02:19:43 2016 +0200
+++ b/tests/test-graft.tWed Oct 12 11:54:03 2016 +0200
@@ -982,6 +982,9 @@
 
   $ HGEDITOR="echo D1 >" hg graft -r 'desc("D0")' --edit
   grafting 3:b69f5839d2d9 "D0"
+  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
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 09 of 12 v2] mergecopies: add logic to process incomplete data

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475578314 -7200
#  Tue Oct 04 12:51:54 2016 +0200
# Node ID 9fc636d9950feb76af774f881ea179037231e697
# Parent  73b55839aca7019d88cc9885e43244fb1e6d68ab
mergecopies: add logic to process incomplete data

We first combine incomplete copies on the two sides of the topological CA
into complete copies.
Any leftover incomplete copies are then combined with the incomplete
divergences to reconstruct divergences spanning over the topological CA.
Finally we promote any divergences falsely flagged as incomplete to full
divergences.

Right now, there is nothing generating incomplete copy/divergence data,
so this code does nothing. Changes to _checkcopies to populate these
dicts are coming later in this series.

diff -r 73b55839aca7 -r 9fc636d9950f mercurial/copies.py
--- a/mercurial/copies.py   Wed Oct 12 11:54:03 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 04 12:51:54 2016 +0200
@@ -289,6 +289,22 @@
 return fctx
 return util.lrucachefunc(makectx)
 
+def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
+"""combine partial copy paths"""
+remainder = {}
+for f in copyfrom:
+if f in copyto:
+finalcopy[copyto[f]] = copyfrom[f]
+del copyto[f]
+for f in incompletediverge:
+assert f not in diverge
+ic = incompletediverge[f]
+if ic[0] in copyto:
+diverge[f] = [copyto[ic[0]], ic[1]]
+else:
+remainder[f] = ic
+return remainder
+
 def mergecopies(repo, c1, c2, base):
 """
 Find moves and copies between context c1 and c2 that are relevant
@@ -360,14 +376,21 @@
 # - diverge = record all diverges in this dict
 # - copy = record all non-divergent copies in this dict
 # - fullcopy = record all copies in this dict
+# - incomplete = record non-divergent partial copies here
+# - incompletediverge = record divergent partial copies here
 diverge = {} # divergence data is shared
+incompletediverge  = {}
 data1 = {'copy': {},
  'fullcopy': {},
+ 'incomplete': {},
  'diverge': diverge,
+ 'incompletediverge': incompletediverge,
 }
 data2 = {'copy': {},
  'fullcopy': {},
+ 'incomplete': {},
  'diverge': diverge,
+ 'incompletediverge': incompletediverge,
 }
 
 # find interesting file sets from manifests
@@ -398,6 +421,13 @@
 copy = dict(data1['copy'].items() + data2['copy'].items())
 fullcopy = dict(data1['fullcopy'].items() + data2['fullcopy'].items())
 
+if dirtyc1:
+_combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
+   incompletediverge)
+else:
+_combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
+   incompletediverge)
+
 renamedelete = {}
 renamedeleteset = set()
 divergeset = set()
@@ -416,13 +446,36 @@
 repo.ui.debug("  unmatched files new in both:\n   %s\n"
   % "\n   ".join(bothnew))
 bothdiverge = {}
-bothdata = {'copy': {},
-'fullcopy': {},
-'diverge': bothdiverge,
-   }
+bothincompletediverge = {}
+both1 = {'copy': {},
+ 'fullcopy': {},
+ 'incomplete': {},
+ 'diverge': bothdiverge,
+ 'incompletediverge': bothincompletediverge
+}
+both2 = {'copy': {},
+ 'fullcopy': {},
+ 'incomplete': {},
+ 'diverge': bothdiverge,
+ 'incompletediverge': bothincompletediverge
+}
 for f in bothnew:
-_checkcopies(c1, f, m1, m2, base, tca, limit, bothdata)
-_checkcopies(c2, f, m2, m1, base, tca, limit, bothdata)
+_checkcopies(c1, f, m1, m2, base, tca, limit, both1)
+_checkcopies(c2, f, m2, m1, base, tca, limit, both2)
+if dirtyc1:
+assert both2['incomplete'] == {}
+remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
+   bothincompletediverge)
+else:
+assert both1['incomplete'] == {}
+remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
+   bothincompletediverge)
+for f in remainder:
+assert f not in bothdiverge
+ic = remainder[f]
+if ic[0] in (m1 if dirtyc1 else m2):
+# backed-out rename on one side, but watch out for deleted files
+bothdiverge[f] = ic
 for of, fl in bothdiverge.items():
 if len(fl) == 2 and fl[0] == fl[1]:
 copy[fl[0]] = of # not actually divergent, just matching renames
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 04 of 12 v2] copies: compute a suitable TCA if base turns out to be unsuitable

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476317029 -7200
#  Thu Oct 13 02:03:49 2016 +0200
# Node ID 1f91343556e7cef7a71edf512b02f7e0f09d5e9b
# Parent  981a622717343796f65b5858f4d4b89f81cb437a
copies: compute a suitable TCA if base turns out to be unsuitable


This will be used later in an update to _checkcopies.

(Pierre-Yves David was involved in the cleanup of this patch.)

diff -r 981a62271734 -r 1f91343556e7 mercurial/copies.py
--- a/mercurial/copies.py   Thu Oct 13 01:47:33 2016 +0200
+++ b/mercurial/copies.py   Thu Oct 13 02:03:49 2016 +0200
@@ -338,6 +338,10 @@
 dirtyc1 = not (base == _c1 or base.descendant(_c1))
 dirtyc2 = not (base== _c2 or base.descendant(_c2))
 graft = dirtyc1 or dirtyc2
+tca = base
+if graft:
+tca = _c1.ancestor(_c2)
+
 limit = _findlimit(repo, c1.rev(), c2.rev())
 if limit is None:
 # no common ancestor, no copies
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 06 of 12 v2] copies: make it possible to distinguish betwen _computenonoverlap invocations

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Pierre-Yves David 
# Date 1476372109 -7200
#  Thu Oct 13 17:21:49 2016 +0200
# Node ID 72b4f2514efd9decffb1115c0f7b8e8f004bdf44
# Parent  48dba8d1f7fab137418bfbb833c3096969eec934
copies: make it possible to distinguish betwen _computenonoverlap invocations

_computenonoverlap needs to be invoked twice during a graft, and debugging
messages should be distinguishable between the two invocations

diff -r 48dba8d1f7fa -r 72b4f2514efd mercurial/copies.py
--- a/mercurial/copies.py   Thu Oct 13 02:03:54 2016 +0200
+++ b/mercurial/copies.py   Thu Oct 13 17:21:49 2016 +0200
@@ -231,23 +231,27 @@
 return _chain(x, y, _backwardrenames(x, a),
   _forwardcopies(a, y, match=match))
 
-def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2):
+def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2, baselabel=''):
 """Computes, based on addedinm1 and addedinm2, the files exclusive to c1
 and c2. This is its own function so extensions can easily wrap this call
 to see what files mergecopies is about to process.
 
 Even though c1 and c2 are not used in this function, they are useful in
 other extensions for being able to read the file nodes of the changed 
files.
+
+"baselabel" can be passed to help distinguish the multiple computations
+done in the graft case.
 """
 u1 = sorted(addedinm1 - addedinm2)
 u2 = sorted(addedinm2 - addedinm1)
 
+header = "  unmatched files in %s"
+if baselabel:
+header += ' (from %s)' % baselabel
 if u1:
-repo.ui.debug("  unmatched files in local:\n   %s\n"
-  % "\n   ".join(u1))
+repo.ui.debug("%s:\n   %s\n" % (header % 'local', "\n   ".join(u1)))
 if u2:
-repo.ui.debug("  unmatched files in other:\n   %s\n"
-  % "\n   ".join(u2))
+repo.ui.debug("%s:\n   %s\n" % (header % 'other', "\n   ".join(u2)))
 return u1, u2
 
 def _makegetfctx(ctx):
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH 07 of 16] copies: detect graft-like merges penis

2016-10-16 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: Sunday, October 16, 2016 4:30 PM
> To: mercurial-devel@mercurial-scm.org
> Subject: [PATCH 07 of 16] copies: detect graft-like merges penis
>
> # HG changeset patch
> # User Gábor Stefanik <gabor.stefa...@nng.com> # Date 1476316053 -7200
> #  Thu Oct 13 01:47:33 2016 +0200
> # Node ID b3631f615bc2fff5cbce4cde7373dde052197a0e
> # Parent  7f9a6f30730074c869e5cf9b77c53929b06c4fb3
> copies: detect graft-like merges penis

Sorry! That was a random change to make histedit generate a new version. Please 
disregard,
new version coming soon.

>
> Right now, nothing changes as a result of this, but we want to handle grafts
> differently from ordinary merges later.
>
> (Series developed together with Pierre-Yves David)
>
> diff -r 7f9a6f307300 -r b3631f615bc2 mercurial/copies.py
> --- a/mercurial/copies.pyWed Oct 12 12:41:28 2016 +0200
> +++ b/mercurial/copies.pyThu Oct 13 01:47:33 2016 +0200
> @@ -321,6 +321,23 @@
>  if repo.ui.configbool('experimental', 'disablecopytrace'):
>  return {}, {}, {}, {}
>
> +# In certain scenarios (e.g. graft, update or rebase), base 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.
> +#
> +# base.descendant(wc) and base.descendant(base) 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
> +# an endpoint is "dirty" if it isn't a descendant of the merge base
> +# if we have a dirty endpoint, we need to trigger graft logic, and also
> +# keep track of which endpoint is dirty
> +dirtyc1 = not (base == _c1 or base.descendant(_c1))
> +dirtyc2 = not (base== _c2 or base.descendant(_c2))
> +graft = dirtyc1 or dirtyc2
>  limit = _findlimit(repo, c1.rev(), c2.rev())
>  if limit is None:
>  # no common ancestor, no copies
> ___
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 11 of 16] mergecopies: invoke _computenonoverlap for both base and tca during merges

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476317983 -7200
#  Thu Oct 13 02:19:43 2016 +0200
# Node ID e61355cb337ab5328e4cf5d0f153821600c53e72
# Parent  72b4f2514efd9decffb1115c0f7b8e8f004bdf44
mergecopies: invoke _computenonoverlap for both base and tca during merges

The algorithm of _checkcopies can only walk backwards in the DAG, never
forward. Because of this, the two _checkcopies patches need to run from
their respective endpoints to the TCA to cover the entire subgraph where
the merge is being performed. However, detection of files new in both
endpoints, as well as directory rename detection, need to run with respect
to the merge base, so we need lists of new files both from the TCA's and
the merge base's viewpoint to correctly detect renames in a graft-like
merge scenario.

(Series reworked by Pierre-Yves David)

diff -r 72b4f2514efd -r e61355cb337a mercurial/copies.py
--- a/mercurial/copies.py   Thu Oct 13 17:21:49 2016 +0200
+++ b/mercurial/copies.py   Thu Oct 13 02:19:43 2016 +0200
@@ -373,9 +373,21 @@
 # find interesting file sets from manifests
 addedinm1 = m1.filesnotin(mb)
 addedinm2 = m2.filesnotin(mb)
-u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
-u1u, u2u = u1r, u2r
 bothnew = sorted(addedinm1 & addedinm2)
+if tca == base:
+# unmatched file from base
+u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
+u1u, u2u = u1r, u2r
+else:
+# unmatched file from base (DAG rotation in the graft case)
+u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
+  baselabel='base')
+# unmatched file from topological common ancestors (no DAG rotation)
+# need to recompute this for directory move handling when grafting
+mta = tca.manifest()
+u1u, u2u = _computenonoverlap(repo, c1, c2, m1.filesnotin(mta),
+m2.filesnotin(mta),
+  baselabel='topological common ancestor')
 
 for f in u1u:
 _checkcopies(c1, f, m1, m2, base, tca, limit, data1)
diff -r 72b4f2514efd -r e61355cb337a tests/test-graft.t
--- a/tests/test-graft.tThu Oct 13 17:21:49 2016 +0200
+++ b/tests/test-graft.tThu Oct 13 02:19:43 2016 +0200
@@ -179,6 +179,11 @@
   committing changelog
   grafting 5:97f8bfe72746 "5"
 searching for copies back to rev 1
+unmatched files in other (from topological common ancestor):
+ 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 +198,11 @@
   scanning for duplicate grafts
   grafting 4:9c233e8e184d "4"
 searching for copies back to rev 1
+unmatched files in other (from topological common ancestor):
+ 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
@@ -945,6 +955,7 @@
   $ hg up -q 'desc("A0")'
   $ HGEDITOR="echo C1 >" hg graft -r 'desc("C0")' --edit
   grafting 2:f58c7e2b28fa "C0"
+  merging f1a and f1b to f1a
   merging f5a
   warning: conflicts while merging f5a! (edit, then use 'hg resolve --mark')
   abort: unresolved conflicts, can't continue
@@ -957,14 +968,15 @@
   grafting 2:f58c7e2b28fa "C0"
   warning: can't find ancestor for 'f5a' copied from 'f5b'!
   $ hg status --change .
+  M f1a
   M f5a
-  A f1b
   A f2c
   R f2a
   $ hg cat f1a
-  c1a
+  c1c
   $ hg cat f1b
-  c1c
+  f1b: no such file in rev 43e4b415492d
+  [1]
 
 Test the cases A.0 (f4x) and A.6 (f3x)
 
@@ -989,12 +1001,12 @@
   |  date:Thu Jan 01 00:00:00 1970 +
   |  summary: E0
   |
-  | o  changeset:   5:573bb6b4b56d
+  | o  changeset:   5:4f4ba7a6e606
   | |  user:test
   | |  date:Thu Jan 01 00:00:00 1970 +
   | |  summary: D1
   | |
-  | o  changeset:   4:af23416e619b
+  | o  changeset:   4:43e4b415492d
   |/   parent:  0:11f7a1b56675
   |user:test
   |date:Thu Jan 01 00:00:00 1970 +
@@ -1029,6 +1041,7 @@
   other [graft] changed f1b which local [local] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging f2a and f2c to f2c
+  merging f5b and f5a to f5a
   abort: unresolved conflicts, can't continue
   (use 'hg resolve' and 'hg graft --continue')
   [255]
@@ -1037,7 +1050,8 @@
   continue: hg graft --continue
   $ hg graft --continue # XXX work around failure
   grafting 2:f58c7e2b28fa "C0&

[PATCH 13 of 16] mergecopies: add logic to process incomplete data

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475578314 -7200
#  Tue Oct 04 12:51:54 2016 +0200
# Node ID 9fc636d9950feb76af774f881ea179037231e697
# Parent  73b55839aca7019d88cc9885e43244fb1e6d68ab
mergecopies: add logic to process incomplete data

We first combine incomplete copies on the two sides of the topological CA
into complete copies.
Any leftover incomplete copies are then combined with the incomplete
divergences to reconstruct divergences spanning over the topological CA.
Finally we promote any divergences falsely flagged as incomplete to full
divergences.

Right now, there is nothing generating incomplete copy/divergence data,
so this code does nothing. Changes to _checkcopies to populate these
dicts are coming later in this series.

diff -r 73b55839aca7 -r 9fc636d9950f mercurial/copies.py
--- a/mercurial/copies.py   Wed Oct 12 11:54:03 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 04 12:51:54 2016 +0200
@@ -289,6 +289,22 @@
 return fctx
 return util.lrucachefunc(makectx)
 
+def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
+"""combine partial copy paths"""
+remainder = {}
+for f in copyfrom:
+if f in copyto:
+finalcopy[copyto[f]] = copyfrom[f]
+del copyto[f]
+for f in incompletediverge:
+assert f not in diverge
+ic = incompletediverge[f]
+if ic[0] in copyto:
+diverge[f] = [copyto[ic[0]], ic[1]]
+else:
+remainder[f] = ic
+return remainder
+
 def mergecopies(repo, c1, c2, base):
 """
 Find moves and copies between context c1 and c2 that are relevant
@@ -360,14 +376,21 @@
 # - diverge = record all diverges in this dict
 # - copy = record all non-divergent copies in this dict
 # - fullcopy = record all copies in this dict
+# - incomplete = record non-divergent partial copies here
+# - incompletediverge = record divergent partial copies here
 diverge = {} # divergence data is shared
+incompletediverge  = {}
 data1 = {'copy': {},
  'fullcopy': {},
+ 'incomplete': {},
  'diverge': diverge,
+ 'incompletediverge': incompletediverge,
 }
 data2 = {'copy': {},
  'fullcopy': {},
+ 'incomplete': {},
  'diverge': diverge,
+ 'incompletediverge': incompletediverge,
 }
 
 # find interesting file sets from manifests
@@ -398,6 +421,13 @@
 copy = dict(data1['copy'].items() + data2['copy'].items())
 fullcopy = dict(data1['fullcopy'].items() + data2['fullcopy'].items())
 
+if dirtyc1:
+_combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
+   incompletediverge)
+else:
+_combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
+   incompletediverge)
+
 renamedelete = {}
 renamedeleteset = set()
 divergeset = set()
@@ -416,13 +446,36 @@
 repo.ui.debug("  unmatched files new in both:\n   %s\n"
   % "\n   ".join(bothnew))
 bothdiverge = {}
-bothdata = {'copy': {},
-'fullcopy': {},
-'diverge': bothdiverge,
-   }
+bothincompletediverge = {}
+both1 = {'copy': {},
+ 'fullcopy': {},
+ 'incomplete': {},
+ 'diverge': bothdiverge,
+ 'incompletediverge': bothincompletediverge
+}
+both2 = {'copy': {},
+ 'fullcopy': {},
+ 'incomplete': {},
+ 'diverge': bothdiverge,
+ 'incompletediverge': bothincompletediverge
+}
 for f in bothnew:
-_checkcopies(c1, f, m1, m2, base, tca, limit, bothdata)
-_checkcopies(c2, f, m2, m1, base, tca, limit, bothdata)
+_checkcopies(c1, f, m1, m2, base, tca, limit, both1)
+_checkcopies(c2, f, m2, m1, base, tca, limit, both2)
+if dirtyc1:
+assert both2['incomplete'] == {}
+remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
+   bothincompletediverge)
+else:
+assert both1['incomplete'] == {}
+remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
+   bothincompletediverge)
+for f in remainder:
+assert f not in bothdiverge
+ic = remainder[f]
+if ic[0] in (m1 if dirtyc1 else m2):
+# backed-out rename on one side, but watch out for deleted files
+bothdiverge[f] = ic
 for of, fl in bothdiverge.items():
 if len(fl) == 2 and fl[0] == fl[1]:
 copy[fl[0]] = of # not actually divergent, just matching renames
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 08 of 16] copies: compute a suitable TCA if base turns out to be unsuitable

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476317029 -7200
#  Thu Oct 13 02:03:49 2016 +0200
# Node ID 1f91343556e7cef7a71edf512b02f7e0f09d5e9b
# Parent  981a622717343796f65b5858f4d4b89f81cb437a
copies: compute a suitable TCA if base turns out to be unsuitable


This will be used later in an update to _checkcopies.

(Pierre-Yves David was involved in the cleanup of this patch.)

diff -r 981a62271734 -r 1f91343556e7 mercurial/copies.py
--- a/mercurial/copies.py   Thu Oct 13 01:47:33 2016 +0200
+++ b/mercurial/copies.py   Thu Oct 13 02:03:49 2016 +0200
@@ -338,6 +338,10 @@
 dirtyc1 = not (base == _c1 or base.descendant(_c1))
 dirtyc2 = not (base== _c2 or base.descendant(_c2))
 graft = dirtyc1 or dirtyc2
+tca = base
+if graft:
+tca = _c1.ancestor(_c2)
+
 limit = _findlimit(repo, c1.rev(), c2.rev())
 if limit is None:
 # no common ancestor, no copies
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 06 of 16] tests: introduce tests for grafting through renames

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 147626 -7200
#  Wed Oct 12 12:41:28 2016 +0200
# Node ID 7f9a6f30730074c869e5cf9b77c53929b06c4fb3
# Parent  e8e15a687df328f37b2d5e97e67f1a2071b35134
tests: introduce tests for grafting through renames

These cover all currently known cases of renames being grafted,
or changes being grafted through renames.

Right now, most of these cases are broken. Later patches in this series
will make them behave correctly.

The testcases heavily rely on each other, which would make it very difficult
to separate them and add them one-by-one for each case fixed by a patch.
Separating them should perhaps be a 4.1 task, if it doesn't slow down
the tests too much.

(Developed together with Pierre-Yves David)

diff -r e8e15a687df3 -r 7f9a6f307300 tests/test-graft.t
--- a/tests/test-graft.tWed Oct 12 21:33:45 2016 +0200
+++ b/tests/test-graft.tWed Oct 12 12:41:28 2016 +0200
@@ -842,3 +842,461 @@
   |/
   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 A0
+  $ hg mv f1a f1b
+  $ hg mv f3a f3b
+  $ hg mv f5a f5b
+  $ hg ci -qAm B0
+  $ echo c1c > f1b
+  $ hg mv f2a f2c
+  $ hg mv f5b f5a
+  $ echo c5c > f5a
+  $ hg ci -qAm C0
+  $ hg mv f3b f3d
+  $ echo c4d > f4a
+  $ hg ci -qAm D0
+  $ hg log -G
+  @  changeset:   3:b69f5839d2d9
+  |  tag: tip
+  |  user:test
+  |  date:Thu Jan 01 00:00:00 1970 +
+  |  summary: D0
+  |
+  o  changeset:   2:f58c7e2b28fa
+  |  user:test
+  |  date:Thu Jan 01 00:00:00 1970 +
+  |  summary: C0
+  |
+  o  changeset:   1:3d7bba921b5d
+  |  user:test
+  |  date:Thu Jan 01 00:00:00 1970 +
+  |  summary: B0
+  |
+  o  changeset:   0:11f7a1b56675
+ user:test
+ date:Thu Jan 01 00:0

[PATCH 02 of 16] checkcopies: pass data as a dictionary of dictionaries

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Pierre-Yves David 
# Date 1476145302 -7200
#  Tue Oct 11 02:21:42 2016 +0200
# Node ID 52fcb1d6cf58c18d63c6e63f3bc1d39194271707
# Parent  c2b7ab2954aecc3030a4c401390031513712d360
checkcopies: pass data as a dictionary of dictionaries

New dictionaries will be introduced in later parts of this patch series.
Make the prototype of _checkcopies more resilient to such changes.

diff -r c2b7ab2954ae -r 52fcb1d6cf58 mercurial/copies.py
--- a/mercurial/copies.py   Tue Oct 11 02:15:23 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 11 02:21:42 2016 +0200
@@ -332,9 +332,15 @@
 ma = ca.manifest()
 
 # see _checkcopies documentation below for these dicts
-copy1, copy2 = {}, {}
-fullcopy1, fullcopy2 = {}, {}
-diverge = {}
+diverge = {} # divergence data is shared
+data1 = {'copy': {},
+ 'fullcopy': {},
+ 'diverge': diverge,
+}
+data2 = {'copy': {},
+ 'fullcopy': {},
+ 'diverge': diverge,
+}
 
 # find interesting file sets from manifests
 addedinm1 = m1.filesnotin(ma)
@@ -344,13 +350,13 @@
 bothnew = sorted(addedinm1 & addedinm2)
 
 for f in u1u:
-_checkcopies(c1, f, m1, m2, ca, limit, diverge, copy1, fullcopy1)
+_checkcopies(c1, f, m1, m2, ca, limit, data1)
 
 for f in u2u:
-_checkcopies(c2, f, m2, m1, ca, limit, diverge, copy2, fullcopy2)
+_checkcopies(c2, f, m2, m1, ca, limit, data2)
 
-copy = dict(copy1.items() + copy2.items())
-fullcopy = dict(fullcopy1.items() + fullcopy2.items())
+copy = dict(data1['copy'].items() + data2['copy'].items())
+fullcopy = dict(data1['fullcopy'].items() + data2['fullcopy'].items())
 
 renamedelete = {}
 renamedeleteset = set()
@@ -369,10 +375,14 @@
 if bothnew:
 repo.ui.debug("  unmatched files new in both:\n   %s\n"
   % "\n   ".join(bothnew))
-bothdiverge, _copy, _fullcopy = {}, {}, {}
+bothdiverge = {}
+bothdata = {'copy': {},
+'fullcopy': {},
+'diverge': bothdiverge,
+   }
 for f in bothnew:
-_checkcopies(c1, f, m1, m2, ca, limit, bothdiverge, _copy, _fullcopy)
-_checkcopies(c2, f, m2, m1, ca, limit, bothdiverge, _copy, _fullcopy)
+_checkcopies(c1, f, m1, m2, ca, limit, bothdata)
+_checkcopies(c2, f, m2, m1, ca, limit, bothdata)
 for of, fl in bothdiverge.items():
 if len(fl) == 2 and fl[0] == fl[1]:
 copy[fl[0]] = of # not actually divergent, just matching renames
@@ -487,7 +497,7 @@
 except StopIteration:
 return False
 
-def _checkcopies(ctx, f, m1, m2, base, limit, diverge, copy, fullcopy):
+def _checkcopies(ctx, f, m1, m2, base, limit, data):
 """
 check possible copies of f from m1 to m2
 
@@ -497,9 +507,10 @@
 m2 = the destination manifest
 base = the changectx used as a merge base
 limit = the rev number to not search beyond
-diverge = record all diverges in this dict
-copy = record all non-divergent copies in this dict
-fullcopy = record all copies in this dict
+data = dictionary of dictionary to store copy data. The keys are:
+- diverge = record all diverges in this dict
+- copy = record all non-divergent copies in this dict
+- fullcopy = record all copies in this dict
 
 note: limit is only an optimization, and there is no guarantee that
 irrelevant revisions will not be limited
@@ -522,7 +533,7 @@
 continue
 seen.add(of)
 
-fullcopy[f] = of # remember for dir rename detection
+data['fullcopy'][f] = of # remember for dir rename detection
 if of not in m2:
 continue # no match, keep looking
 if m2[of] == mb.get(of):
@@ -532,11 +543,11 @@
 # unrelated to the droids we are looking for.
 cr = _related(oc, c2, base.rev())
 if cr and (of == f or of == c2.path()): # non-divergent
-copy[f] = of
+data['copy'][f] = of
 return
 
 if of in mb:
-diverge.setdefault(of, []).append(f)
+data['diverge'].setdefault(of, []).append(f)
 
 def duplicatecopies(repo, rev, fromrev, skiprev=None):
 '''reproduce copies from fromrev to rev in the dirstate
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 05 of 16] checkcopies: add a sanity check against false-positive copies

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476300825 -7200
#  Wed Oct 12 21:33:45 2016 +0200
# Node ID e8e15a687df328f37b2d5e97e67f1a2071b35134
# Parent  bfb99ef3749757161bf439cc0ec678de46e24672
checkcopies: add a sanity check against false-positive copies

When grafting a copy backwards through a rename, a copy is wrongly detected,
which causes the graft to be applied inappropriately, in a destructive way.
Make sure that the old file name really exists in the common ancestor,
and bail out if it doesn't.

This fixes the aggravated case of bug 5343, although the basic issue
(failure to duplicate the copy information) still occurs.

diff -r bfb99ef37497 -r e8e15a687df3 mercurial/copies.py
--- a/mercurial/copies.py   Thu Oct 13 01:30:14 2016 +0200
+++ b/mercurial/copies.py   Wed Oct 12 21:33:45 2016 +0200
@@ -543,7 +543,8 @@
 # unrelated to the droids we are looking for.
 cr = _related(oc, c2, base.rev())
 if cr and (of == f or of == c2.path()): # non-divergent
-data['copy'][f] = of
+if of in mb:
+data['copy'][f] = of
 return
 
 if of in mb:
diff -r bfb99ef37497 -r e8e15a687df3 tests/test-graft.t
--- a/tests/test-graft.tThu Oct 13 01:30:14 2016 +0200
+++ b/tests/test-graft.tWed Oct 12 21:33:45 2016 +0200
@@ -427,8 +427,8 @@
   $ hg graft 3 --log -u foo
   grafting 3:4c60f11aa304 "3"
   warning: can't find ancestor for 'c' copied from 'b'!
-  $ hg log --template '{rev} {parents} {desc}\n' -r tip
-  14 1:5d205f8b35b6  3
+  $ hg log --template '{rev}:{node|short} {parents} {desc}\n' -r tip
+  14:0c921c65ef1e 1:5d205f8b35b6  3
   (grafted from 4c60f11aa304a54ae1c199feb94e7fc771e51ed8)
 
 Resolve conflicted graft
@@ -620,7 +620,7 @@
   date:Thu Jan 01 00:00:00 1970 +
   summary: 2
   
-  changeset:   14:f64defefacee
+  changeset:   14:0c921c65ef1e
   parent:  1:5d205f8b35b6
   user:foo
   date:Thu Jan 01 00:00:00 1970 +
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 03 of 16] copies: move variable document from checkcopies to mergecopies

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Pierre-Yves David 
# Date 1476314793 -7200
#  Thu Oct 13 01:26:33 2016 +0200
# Node ID 92dfc856c6cc25366eb8a7fb9b59d992ad5efc36
# Parent  52fcb1d6cf58c18d63c6e63f3bc1d39194271707
copies: move variable document from checkcopies to mergecopies

It appears that 'mergecopies' is the function consuming these data so we move
the documentation there.

diff -r 52fcb1d6cf58 -r 92dfc856c6cc mercurial/copies.py
--- a/mercurial/copies.py   Tue Oct 11 02:21:42 2016 +0200
+++ b/mercurial/copies.py   Thu Oct 13 01:26:33 2016 +0200
@@ -331,7 +331,10 @@
 m2 = c2.manifest()
 ma = ca.manifest()
 
-# see _checkcopies documentation below for these dicts
+# gather data from _checkcopies:
+# - diverge = record all diverges in this dict
+# - copy = record all non-divergent copies in this dict
+# - fullcopy = record all copies in this dict
 diverge = {} # divergence data is shared
 data1 = {'copy': {},
  'fullcopy': {},
@@ -507,10 +510,7 @@
 m2 = the destination manifest
 base = the changectx used as a merge base
 limit = the rev number to not search beyond
-data = dictionary of dictionary to store copy data. The keys are:
-- diverge = record all diverges in this dict
-- copy = record all non-divergent copies in this dict
-- fullcopy = record all copies in this dict
+data = dictionary of dictionary to store copy data. (see mergecopies)
 
 note: limit is only an optimization, and there is no guarantee that
 irrelevant revisions will not be limited
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 16 of 16] update: enable copy tracing for backwards and non-linear updates

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1472155346 -7200
#  Thu Aug 25 22:02:26 2016 +0200
# Node ID 066dc1a5c8f4c9dfb582543bff5df8bbeba9cc31
# Parent  a3a36d782f4d4252adb9069eb3714f946881
update: enable copy tracing for backwards and non-linear updates

As a followup to the issue4028 series, this fixes a variant of the issue
that can occur when updating with uncommited local changes.

diff -r a3a36d782f4d -r 066dc1a5c8f4 mercurial/merge.py
--- a/mercurial/merge.pyTue Oct 11 04:39:47 2016 +0200
+++ b/mercurial/merge.pyThu Aug 25 22:02:26 2016 +0200
@@ -1537,15 +1537,16 @@
 pas = [p1]
 
 # deprecated config: merge.followcopies
-followcopies = False
+followcopies = repo.ui.configbool('merge', 'followcopies', True)
 if overwrite:
 pas = [wc]
+followcopies = False
 elif pas == [p2]: # backwards
-pas = [wc.p1()]
-elif not branchmerge and not wc.dirty(missing=True):
-pass
-elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
-followcopies = True
+pas = [p1]
+elif not pas[0]:
+followcopies = False
+if not branchmerge and not wc.dirty(missing=True):
+followcopies = False
 
 ### calculate phase
 actionbyfile, diverge, renamedelete = calculateupdates(
diff -r a3a36d782f4d -r 066dc1a5c8f4 tests/test-merge-local.t
--- a/tests/test-merge-local.t  Tue Oct 11 04:39:47 2016 +0200
+++ b/tests/test-merge-local.t  Thu Aug 25 22:02:26 2016 +0200
@@ -66,7 +66,7 @@
   merging zzz1_merge_ok
   merging zzz2_merge_bad
   warning: conflicts while merging zzz2_merge_bad! (edit, then use 'hg resolve 
--mark')
-  2 files updated, 1 files merged, 3 files removed, 1 files unresolved
+  2 files updated, 1 files merged, 2 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
   [1]
 
@@ -104,7 +104,7 @@
   merging zzz1_merge_ok
   merging zzz2_merge_bad
   warning: conflicts while merging zzz2_merge_bad! (edit, then use 'hg resolve 
--mark')
-  2 files updated, 1 files merged, 3 files removed, 1 files unresolved
+  2 files updated, 1 files merged, 2 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
   [1]
 
diff -r a3a36d782f4d -r 066dc1a5c8f4 tests/test-mq-subrepo.t
--- a/tests/test-mq-subrepo.t   Tue Oct 11 04:39:47 2016 +0200
+++ b/tests/test-mq-subrepo.t   Thu Aug 25 22:02:26 2016 +0200
@@ -304,6 +304,7 @@
   record this change to '.hgsub'? [Ynesfdaq?] y
   
   warning: subrepo spec file '.hgsub' not found
+  warning: subrepo spec file '.hgsub' not found
   abort: uncommitted changes in subrepository 'sub'
   [255]
   % update substate when adding .hgsub w/clean updated subrepo
@@ -319,6 +320,7 @@
   record this change to '.hgsub'? [Ynesfdaq?] y
   
   warning: subrepo spec file '.hgsub' not found
+  warning: subrepo spec file '.hgsub' not found
   path sub
source   sub
revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
diff -r a3a36d782f4d -r 066dc1a5c8f4 tests/test-up-local-change.t
--- a/tests/test-up-local-change.t  Tue Oct 11 04:39:47 2016 +0200
+++ b/tests/test-up-local-change.t  Thu Aug 25 22:02:26 2016 +0200
@@ -67,6 +67,10 @@
   summary: 2
   
   $ hg --debug up 0
+  starting 4 threads for background file closing (?)
+searching for copies back to rev 0
+unmatched files in local (from topological common ancestor):
+ b
   resolving manifests
branchmerge: False, force: False, partial: False
ancestor: 1e71731e6fbb, local: 1e71731e6fbb+, remote: c19d34741b0a
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 12 of 16] checkcopies: handle divergences contained entirely in tca::ctx

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476266043 -7200
#  Wed Oct 12 11:54:03 2016 +0200
# Node ID 73b55839aca7019d88cc9885e43244fb1e6d68ab
# Parent  e61355cb337ab5328e4cf5d0f153821600c53e72
checkcopies: handle divergences contained entirely in tca::ctx

During a graftlike merge, _checkcopies runs from ctx to tca, possibly
passing over the merge base. If there is a rename both before and after
the base, then we're actually dealing with divergent renames.
If there is no rename on the other side of tca, then the divergence is
contained entirely in the range of one _checkcopies invocation, and
should be detected "in the loop" without having to rely on the other
_checkcopies pass.

diff -r e61355cb337a -r 73b55839aca7 mercurial/copies.py
--- a/mercurial/copies.py   Thu Oct 13 02:19:43 2016 +0200
+++ b/mercurial/copies.py   Wed Oct 12 11:54:03 2016 +0200
@@ -564,9 +564,8 @@
 # traversed backwards.
 #
 # In the case there is both backward and forward renames (before and after
-# the base) this is more complicated as we must detect a divergence. This
-# is currently broken and hopefully some later code update will make that
-# work (we use 'backwards = False' in that case)
+# the base) this is more complicated as we must detect a divergence.
+# We use 'backwards = False' in that case.
 backwards = base != tca and f in mb
 getfctx = _makegetfctx(ctx)
 
@@ -600,6 +599,12 @@
 data['copy'][of] = f
 elif of in mb:
 data['copy'][f] = of
+else: # divergence w.r.t. graft CA on one side of topological CA
+for sf in seen:
+if sf in mb:
+assert sf not in data['diverge']
+data['diverge'][sf] = [f, of]
+break
 return
 
 if of in mb:
diff -r e61355cb337a -r 73b55839aca7 tests/test-graft.t
--- a/tests/test-graft.tThu Oct 13 02:19:43 2016 +0200
+++ b/tests/test-graft.tWed Oct 12 11:54:03 2016 +0200
@@ -982,6 +982,9 @@
 
   $ HGEDITOR="echo D1 >" hg graft -r 'desc("D0")' --edit
   grafting 3:b69f5839d2d9 "D0"
+  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
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 01 of 16] checkcopies: move 'movewithdir' initialisation right before its usage

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Pierre-Yves David 
# Date 1476144923 -7200
#  Tue Oct 11 02:15:23 2016 +0200
# Node ID c2b7ab2954aecc3030a4c401390031513712d360
# Parent  5cb830801855dbb63e98b948e355bc995d295bf3
checkcopies: move 'movewithdir' initialisation right before its usage

The 'movewithdir' had a lot of related logic all around the 'mergecopies'.
However it is actually never containing anything until the very last loop in
that function. We move the (simplified) variable definition there for clarity

diff -r 5cb830801855 -r c2b7ab2954ae mercurial/copies.py
--- a/mercurial/copies.py   Wed Oct 12 12:22:18 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 11 02:15:23 2016 +0200
@@ -333,7 +333,6 @@
 
 # see _checkcopies documentation below for these dicts
 copy1, copy2 = {}, {}
-movewithdir1, movewithdir2 = {}, {}
 fullcopy1, fullcopy2 = {}, {}
 diverge = {}
 
@@ -351,7 +350,6 @@
 _checkcopies(c2, f, m2, m1, ca, limit, diverge, copy2, fullcopy2)
 
 copy = dict(copy1.items() + copy2.items())
-movewithdir = dict(movewithdir1.items() + movewithdir2.items())
 fullcopy = dict(fullcopy1.items() + fullcopy2.items())
 
 renamedelete = {}
@@ -395,7 +393,7 @@
 del divergeset
 
 if not fullcopy:
-return copy, movewithdir, diverge, renamedelete
+return copy, {}, diverge, renamedelete
 
 repo.ui.debug("  checking for directory renames\n")
 
@@ -433,12 +431,13 @@
 del d1, d2, invalid
 
 if not dirmove:
-return copy, movewithdir, diverge, renamedelete
+return copy, {}, diverge, renamedelete
 
 for d in dirmove:
 repo.ui.debug("   discovered dir src: '%s' -> dst: '%s'\n" %
   (d, dirmove[d]))
 
+movewithdir = {}
 # check unaccounted nonoverlapping files against directory moves
 for f in u1r + u2r:
 if f not in fullcopy:
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 02 of 16] checkcopies: pass data as a dictionary of dictionaries

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Pierre-Yves David 
# Date 1476145302 -7200
#  Tue Oct 11 02:21:42 2016 +0200
# Node ID 52fcb1d6cf58c18d63c6e63f3bc1d39194271707
# Parent  c2b7ab2954aecc3030a4c401390031513712d360
checkcopies: pass data as a dictionary of dictionaries

New dictionaries will be introduced in later parts of this patch series.
Make the prototype of _checkcopies more resilient to such changes.

diff -r c2b7ab2954ae -r 52fcb1d6cf58 mercurial/copies.py
--- a/mercurial/copies.py   Tue Oct 11 02:15:23 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 11 02:21:42 2016 +0200
@@ -332,9 +332,15 @@
 ma = ca.manifest()
 
 # see _checkcopies documentation below for these dicts
-copy1, copy2 = {}, {}
-fullcopy1, fullcopy2 = {}, {}
-diverge = {}
+diverge = {} # divergence data is shared
+data1 = {'copy': {},
+ 'fullcopy': {},
+ 'diverge': diverge,
+}
+data2 = {'copy': {},
+ 'fullcopy': {},
+ 'diverge': diverge,
+}
 
 # find interesting file sets from manifests
 addedinm1 = m1.filesnotin(ma)
@@ -344,13 +350,13 @@
 bothnew = sorted(addedinm1 & addedinm2)
 
 for f in u1u:
-_checkcopies(c1, f, m1, m2, ca, limit, diverge, copy1, fullcopy1)
+_checkcopies(c1, f, m1, m2, ca, limit, data1)
 
 for f in u2u:
-_checkcopies(c2, f, m2, m1, ca, limit, diverge, copy2, fullcopy2)
+_checkcopies(c2, f, m2, m1, ca, limit, data2)
 
-copy = dict(copy1.items() + copy2.items())
-fullcopy = dict(fullcopy1.items() + fullcopy2.items())
+copy = dict(data1['copy'].items() + data2['copy'].items())
+fullcopy = dict(data1['fullcopy'].items() + data2['fullcopy'].items())
 
 renamedelete = {}
 renamedeleteset = set()
@@ -369,10 +375,14 @@
 if bothnew:
 repo.ui.debug("  unmatched files new in both:\n   %s\n"
   % "\n   ".join(bothnew))
-bothdiverge, _copy, _fullcopy = {}, {}, {}
+bothdiverge = {}
+bothdata = {'copy': {},
+'fullcopy': {},
+'diverge': bothdiverge,
+   }
 for f in bothnew:
-_checkcopies(c1, f, m1, m2, ca, limit, bothdiverge, _copy, _fullcopy)
-_checkcopies(c2, f, m2, m1, ca, limit, bothdiverge, _copy, _fullcopy)
+_checkcopies(c1, f, m1, m2, ca, limit, bothdata)
+_checkcopies(c2, f, m2, m1, ca, limit, bothdata)
 for of, fl in bothdiverge.items():
 if len(fl) == 2 and fl[0] == fl[1]:
 copy[fl[0]] = of # not actually divergent, just matching renames
@@ -487,7 +497,7 @@
 except StopIteration:
 return False
 
-def _checkcopies(ctx, f, m1, m2, base, limit, diverge, copy, fullcopy):
+def _checkcopies(ctx, f, m1, m2, base, limit, data):
 """
 check possible copies of f from m1 to m2
 
@@ -497,9 +507,10 @@
 m2 = the destination manifest
 base = the changectx used as a merge base
 limit = the rev number to not search beyond
-diverge = record all diverges in this dict
-copy = record all non-divergent copies in this dict
-fullcopy = record all copies in this dict
+data = dictionary of dictionary to store copy data. The keys are:
+- diverge = record all diverges in this dict
+- copy = record all non-divergent copies in this dict
+- fullcopy = record all copies in this dict
 
 note: limit is only an optimization, and there is no guarantee that
 irrelevant revisions will not be limited
@@ -522,7 +533,7 @@
 continue
 seen.add(of)
 
-fullcopy[f] = of # remember for dir rename detection
+data['fullcopy'][f] = of # remember for dir rename detection
 if of not in m2:
 continue # no match, keep looking
 if m2[of] == mb.get(of):
@@ -532,11 +543,11 @@
 # unrelated to the droids we are looking for.
 cr = _related(oc, c2, base.rev())
 if cr and (of == f or of == c2.path()): # non-divergent
-copy[f] = of
+data['copy'][f] = of
 return
 
 if of in mb:
-diverge.setdefault(of, []).append(f)
+data['diverge'].setdefault(of, []).append(f)
 
 def duplicatecopies(repo, rev, fromrev, skiprev=None):
 '''reproduce copies from fromrev to rev in the dirstate
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 10 of 16] copies: make it possible to distinguish betwen _computenonoverlap invocations

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Pierre-Yves David 
# Date 1476372109 -7200
#  Thu Oct 13 17:21:49 2016 +0200
# Node ID 4bb6c6ac3db22c3c4f947dd69496d895ad083efd
# Parent  1420cffe78a3ef2d0a2d3f125ce780754745ead8
copies: make it possible to distinguish betwen _computenonoverlap invocations

_computenonoverlap needs to be invoked twice during a graft, and debugging
messages should be distinguishable between the two invocations

diff -r 1420cffe78a3 -r 4bb6c6ac3db2 mercurial/copies.py
--- a/mercurial/copies.py   Thu Oct 13 02:03:54 2016 +0200
+++ b/mercurial/copies.py   Thu Oct 13 17:21:49 2016 +0200
@@ -231,23 +231,27 @@
 return _chain(x, y, _backwardrenames(x, a),
   _forwardcopies(a, y, match=match))
 
-def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2):
+def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2, baselabel=''):
 """Computes, based on addedinm1 and addedinm2, the files exclusive to c1
 and c2. This is its own function so extensions can easily wrap this call
 to see what files mergecopies is about to process.
 
 Even though c1 and c2 are not used in this function, they are useful in
 other extensions for being able to read the file nodes of the changed 
files.
+
+"baselabel" can be passed to help distinguish the multiple computations
+done in the graft case.
 """
 u1 = sorted(addedinm1 - addedinm2)
 u2 = sorted(addedinm2 - addedinm1)
 
+header = "  unmatched files in %s"
+if baselabel:
+header += ' (from %s)' % baselabel
 if u1:
-repo.ui.debug("  unmatched files in local:\n   %s\n"
-  % "\n   ".join(u1))
+repo.ui.debug("%s:\n   %s\n" % (header % 'local', "\n   ".join(u1)))
 if u2:
-repo.ui.debug("  unmatched files in other:\n   %s\n"
-  % "\n   ".join(u2))
+repo.ui.debug("%s:\n   %s\n" % (header % 'other', "\n   ".join(u2)))
 return u1, u2
 
 def _makegetfctx(ctx):
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 13 of 16] mergecopies: add logic to process incomplete data

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475578314 -7200
#  Tue Oct 04 12:51:54 2016 +0200
# Node ID a1c80b2f6896c7c77c660d15344f2c50845aee84
# Parent  905ceb1509480f912ece7c5622e151f80581782f
mergecopies: add logic to process incomplete data

We first combine incomplete copies on the two sides of the topological CA
into complete copies.
Any leftover incomplete copies are then combined with the incomplete
divergences to reconstruct divergences spanning over the topological CA.
Finally we promote any divergences falsely flagged as incomplete to full
divergences.

Right now, there is nothing generating incomplete copy/divergence data,
so this code does nothing. Changes to _checkcopies to populate these
dicts are coming later in this series.

diff -r 905ceb150948 -r a1c80b2f6896 mercurial/copies.py
--- a/mercurial/copies.py   Wed Oct 12 11:54:03 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 04 12:51:54 2016 +0200
@@ -289,6 +289,22 @@
 return fctx
 return util.lrucachefunc(makectx)
 
+def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
+"""combine partial copy paths"""
+remainder = {}
+for f in copyfrom:
+if f in copyto:
+finalcopy[copyto[f]] = copyfrom[f]
+del copyto[f]
+for f in incompletediverge:
+assert f not in diverge
+ic = incompletediverge[f]
+if ic[0] in copyto:
+diverge[f] = [copyto[ic[0]], ic[1]]
+else:
+remainder[f] = ic
+return remainder
+
 def mergecopies(repo, c1, c2, base):
 """
 Find moves and copies between context c1 and c2 that are relevant
@@ -360,14 +376,21 @@
 # - diverge = record all diverges in this dict
 # - copy = record all non-divergent copies in this dict
 # - fullcopy = record all copies in this dict
+# - incomplete = record non-divergent partial copies here
+# - incompletediverge = record divergent partial copies here
 diverge = {} # divergence data is shared
+incompletediverge  = {}
 data1 = {'copy': {},
  'fullcopy': {},
+ 'incomplete': {},
  'diverge': diverge,
+ 'incompletediverge': incompletediverge,
 }
 data2 = {'copy': {},
  'fullcopy': {},
+ 'incomplete': {},
  'diverge': diverge,
+ 'incompletediverge': incompletediverge,
 }
 
 # find interesting file sets from manifests
@@ -398,6 +421,13 @@
 copy = dict(data1['copy'].items() + data2['copy'].items())
 fullcopy = dict(data1['fullcopy'].items() + data2['fullcopy'].items())
 
+if dirtyc1:
+_combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
+   incompletediverge)
+else:
+_combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
+   incompletediverge)
+
 renamedelete = {}
 renamedeleteset = set()
 divergeset = set()
@@ -416,13 +446,36 @@
 repo.ui.debug("  unmatched files new in both:\n   %s\n"
   % "\n   ".join(bothnew))
 bothdiverge = {}
-bothdata = {'copy': {},
-'fullcopy': {},
-'diverge': bothdiverge,
-   }
+bothincompletediverge = {}
+both1 = {'copy': {},
+ 'fullcopy': {},
+ 'incomplete': {},
+ 'diverge': bothdiverge,
+ 'incompletediverge': bothincompletediverge
+}
+both2 = {'copy': {},
+ 'fullcopy': {},
+ 'incomplete': {},
+ 'diverge': bothdiverge,
+ 'incompletediverge': bothincompletediverge
+}
 for f in bothnew:
-_checkcopies(c1, f, m1, m2, base, tca, limit, bothdata)
-_checkcopies(c2, f, m2, m1, base, tca, limit, bothdata)
+_checkcopies(c1, f, m1, m2, base, tca, limit, both1)
+_checkcopies(c2, f, m2, m1, base, tca, limit, both2)
+if dirtyc1:
+assert both2['incomplete'] == {}
+remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
+   bothincompletediverge)
+else:
+assert both1['incomplete'] == {}
+remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
+   bothincompletediverge)
+for f in remainder:
+assert f not in bothdiverge
+ic = remainder[f]
+if ic[0] in (m1 if dirtyc1 else m2):
+# backed-out rename on one side, but watch out for deleted files
+bothdiverge[f] = ic
 for of, fl in bothdiverge.items():
 if len(fl) == 2 and fl[0] == fl[1]:
 copy[fl[0]] = of # not actually divergent, just matching renames
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 09 of 16] copies: make _checkcopies handle simple renames in a rotated DAG

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476317034 -7200
#  Thu Oct 13 02:03:54 2016 +0200
# Node ID 1420cffe78a3ef2d0a2d3f125ce780754745ead8
# Parent  2284ef0bd7df9b83f10f91f79f74f5b44b6ffde0
copies: make _checkcopies handle simple renames in a rotated DAG

This introduces a distinction between "merge base" and
"topological common ancestor". During a regular merge, these two are
identical. Graft, however, performs a merge in a rotated DAG, where the
merge common ancestor will not be a common ancestor at all in the
original DAG.

To correctly find copies in case of a graft, we need to take both the
merge base and the topological CA into account, and track any renames
between them in reverse. Fortunately we can detect this in advance,
see comment in the code about "backwards".

This patch only supports finding non-divergent renames contained entirely
between the merge base and the topological CA. Further patches are coming
to support more complex cases.

(Pierre-Yves David was involved in the cleanup of this patch.)

diff -r 2284ef0bd7df -r 1420cffe78a3 mercurial/copies.py
--- a/mercurial/copies.py   Thu Oct 13 02:03:49 2016 +0200
+++ b/mercurial/copies.py   Thu Oct 13 02:03:54 2016 +0200
@@ -374,10 +374,10 @@
 bothnew = sorted(addedinm1 & addedinm2)
 
 for f in u1u:
-_checkcopies(c1, f, m1, m2, base, limit, data1)
+_checkcopies(c1, f, m1, m2, base, tca, limit, data1)
 
 for f in u2u:
-_checkcopies(c2, f, m2, m1, base, limit, data2)
+_checkcopies(c2, f, m2, m1, base, tca, limit, data2)
 
 copy = dict(data1['copy'].items() + data2['copy'].items())
 fullcopy = dict(data1['fullcopy'].items() + data2['fullcopy'].items())
@@ -405,8 +405,8 @@
 'diverge': bothdiverge,
}
 for f in bothnew:
-_checkcopies(c1, f, m1, m2, base, limit, bothdata)
-_checkcopies(c2, f, m2, m1, base, limit, bothdata)
+_checkcopies(c1, f, m1, m2, base, tca, limit, bothdata)
+_checkcopies(c2, f, m2, m1, base, tca, limit, bothdata)
 for of, fl in bothdiverge.items():
 if len(fl) == 2 and fl[0] == fl[1]:
 copy[fl[0]] = of # not actually divergent, just matching renames
@@ -521,7 +521,7 @@
 except StopIteration:
 return False
 
-def _checkcopies(ctx, f, m1, m2, base, limit, data):
+def _checkcopies(ctx, f, m1, m2, base, tca, limit, data):
 """
 check possible copies of f from m1 to m2
 
@@ -530,6 +530,7 @@
 m1 = the source manifest
 m2 = the destination manifest
 base = the changectx used as a merge base
+tca = topological common ancestor for graft-like scenarios
 limit = the rev number to not search beyond
 data = dictionary of dictionary to store copy data. (see mergecopies)
 
@@ -540,6 +541,17 @@
 """
 
 mb = base.manifest()
+# Might be true if this call is about finding backward renames,
+# This happens in the case of grafts because the DAG is then rotated.
+# If the file exists in both the base and the source, we are not looking
+# for a rename on the source side, but on the part of the DAG that is
+# traversed backwards.
+#
+# In the case there is both backward and forward renames (before and after
+# the base) this is more complicated as we must detect a divergence. This
+# is currently broken and hopefully some later code update will make that
+# work (we use 'backwards = False' in that case)
+backwards = base != tca and f in mb
 getfctx = _makegetfctx(ctx)
 
 of = None
@@ -554,7 +566,11 @@
 continue
 seen.add(of)
 
-data['fullcopy'][f] = of # remember for dir rename detection
+# remember for dir rename detection
+if backwards:
+data['fullcopy'][of] = f # grafting backwards through renames
+else:
+data['fullcopy'][f] = of
 if of not in m2:
 continue # no match, keep looking
 if m2[of] == mb.get(of):
@@ -562,9 +578,11 @@
 c2 = getfctx(of, m2[of])
 # c2 might be a plain new file on added on destination side that is
 # unrelated to the droids we are looking for.
-cr = _related(oc, c2, base.rev())
+cr = _related(oc, c2, tca.rev())
 if cr and (of == f or of == c2.path()): # non-divergent
-if of in mb:
+if backwards:
+data['copy'][of] = f
+elif of in mb:
 data['copy'][f] = of
 return
 
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 08 of 16] copies: compute a suitable TCA if base turns out to be unsuitable

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476317029 -7200
#  Thu Oct 13 02:03:49 2016 +0200
# Node ID 2284ef0bd7df9b83f10f91f79f74f5b44b6ffde0
# Parent  b3631f615bc2fff5cbce4cde7373dde052197a0e
copies: compute a suitable TCA if base turns out to be unsuitable


This will be used later in an update to _checkcopies.

(Pierre-Yves David was involved in the cleanup of this patch.)

diff -r b3631f615bc2 -r 2284ef0bd7df mercurial/copies.py
--- a/mercurial/copies.py   Thu Oct 13 01:47:33 2016 +0200
+++ b/mercurial/copies.py   Thu Oct 13 02:03:49 2016 +0200
@@ -338,6 +338,10 @@
 dirtyc1 = not (base == _c1 or base.descendant(_c1))
 dirtyc2 = not (base== _c2 or base.descendant(_c2))
 graft = dirtyc1 or dirtyc2
+tca = base
+if graft:
+tca = _c1.ancestor(_c2)
+
 limit = _findlimit(repo, c1.rev(), c2.rev())
 if limit is None:
 # no common ancestor, no copies
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 07 of 16] copies: detect graft-like merges penis

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1476316053 -7200
#  Thu Oct 13 01:47:33 2016 +0200
# Node ID b3631f615bc2fff5cbce4cde7373dde052197a0e
# Parent  7f9a6f30730074c869e5cf9b77c53929b06c4fb3
copies: detect graft-like merges penis

Right now, nothing changes as a result of this, but we want to handle
grafts differently from ordinary merges later.

(Series developed together with Pierre-Yves David)

diff -r 7f9a6f307300 -r b3631f615bc2 mercurial/copies.py
--- a/mercurial/copies.py   Wed Oct 12 12:41:28 2016 +0200
+++ b/mercurial/copies.py   Thu Oct 13 01:47:33 2016 +0200
@@ -321,6 +321,23 @@
 if repo.ui.configbool('experimental', 'disablecopytrace'):
 return {}, {}, {}, {}
 
+# In certain scenarios (e.g. graft, update or rebase), base 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.
+#
+# base.descendant(wc) and base.descendant(base) 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
+# an endpoint is "dirty" if it isn't a descendant of the merge base
+# if we have a dirty endpoint, we need to trigger graft logic, and also
+# keep track of which endpoint is dirty
+dirtyc1 = not (base == _c1 or base.descendant(_c1))
+dirtyc2 = not (base== _c2 or base.descendant(_c2))
+graft = dirtyc1 or dirtyc2
 limit = _findlimit(repo, c1.rev(), c2.rev())
 if limit is None:
 # no common ancestor, no copies
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 01 of 16] checkcopies: move 'movewithdir' initialisation right before its usage

2016-10-16 Thread Gábor Stefanik
# HG changeset patch
# User Pierre-Yves David 
# Date 1476144923 -7200
#  Tue Oct 11 02:15:23 2016 +0200
# Node ID c2b7ab2954aecc3030a4c401390031513712d360
# Parent  5cb830801855dbb63e98b948e355bc995d295bf3
checkcopies: move 'movewithdir' initialisation right before its usage

The 'movewithdir' had a lot of related logic all around the 'mergecopies'.
However it is actually never containing anything until the very last loop in
that function. We move the (simplified) variable definition there for clarity

diff -r 5cb830801855 -r c2b7ab2954ae mercurial/copies.py
--- a/mercurial/copies.py   Wed Oct 12 12:22:18 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 11 02:15:23 2016 +0200
@@ -333,7 +333,6 @@
 
 # see _checkcopies documentation below for these dicts
 copy1, copy2 = {}, {}
-movewithdir1, movewithdir2 = {}, {}
 fullcopy1, fullcopy2 = {}, {}
 diverge = {}
 
@@ -351,7 +350,6 @@
 _checkcopies(c2, f, m2, m1, ca, limit, diverge, copy2, fullcopy2)
 
 copy = dict(copy1.items() + copy2.items())
-movewithdir = dict(movewithdir1.items() + movewithdir2.items())
 fullcopy = dict(fullcopy1.items() + fullcopy2.items())
 
 renamedelete = {}
@@ -395,7 +393,7 @@
 del divergeset
 
 if not fullcopy:
-return copy, movewithdir, diverge, renamedelete
+return copy, {}, diverge, renamedelete
 
 repo.ui.debug("  checking for directory renames\n")
 
@@ -433,12 +431,13 @@
 del d1, d2, invalid
 
 if not dirmove:
-return copy, movewithdir, diverge, renamedelete
+return copy, {}, diverge, renamedelete
 
 for d in dirmove:
 repo.ui.debug("   discovered dir src: '%s' -> dst: '%s'\n" %
   (d, dirmove[d]))
 
+movewithdir = {}
 # check unaccounted nonoverlapping files against directory moves
 for f in u1r + u2r:
 if f not in fullcopy:
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH v3] mail: take --encoding and HGENCODING into account

2016-10-08 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475667922 -7200
#  Wed Oct 05 13:45:22 2016 +0200
# Node ID d7125caa00dcc6036d3d5aee3e4d524211b95e02
# Parent  dbcef8918bbdd8a64d9f79a37bcfa284a26f3a39
mail: take --encoding and HGENCODING into account

Fall back to our encoding strategy for sending MIME text
that's neither ASCII nor UTF-8.

diff --git a/mercurial/mail.py b/mercurial/mail.py
--- a/mercurial/mail.py
+++ b/mercurial/mail.py
@@ -8,6 +8,7 @@
 from __future__ import absolute_import, print_function
 
 import email
+import email.charset
 import os
 import quopri
 import smtplib
@@ -203,24 +204,33 @@
 raise error.Abort(_('%r specified as email transport, '
'but not in PATH') % method)
 
+def codec2iana(cs):
+''
+cs = email.charset.Charset(cs).input_charset.lower()
+
+# "latin1" normalizes to "iso8859-1", standard calls for "iso-8859-1"
+if cs.startswith("iso") and not cs.startswith("iso-"):
+return "iso-" + cs[3:]
+return cs
+
 def mimetextpatch(s, subtype='plain', display=False):
 '''Return MIME message suitable for a patch.
-Charset will be detected as utf-8 or (possibly fake) us-ascii.
+Charset will be detected by first trying to decode as us-ascii, then utf-8,
+and finally the global encodings. If all those fail, fall back to
+ISO-8859-1, an encoding with that allows all byte sequences.
 Transfer encodings will be used if necessary.'''
 
-cs = 'us-ascii'
-if not display:
+cs = ['us-ascii', 'utf-8', encoding.encoding, encoding.fallbackencoding]
+if display:
+return mimetextqp(s, subtype, 'us-ascii')
+for charset in cs:
 try:
-s.decode('us-ascii')
+s.decode(charset)
+return mimetextqp(s, subtype, codec2iana(charset))
 except UnicodeDecodeError:
-try:
-s.decode('utf-8')
-cs = 'utf-8'
-except UnicodeDecodeError:
-# We'll go with us-ascii as a fallback.
-pass
+pass
 
-return mimetextqp(s, subtype, cs)
+return mimetextqp(s, subtype, "iso-8859-1")
 
 def mimetextqp(body, subtype, charset):
 '''Return MIME message.
diff --git a/tests/test-patchbomb.t b/tests/test-patchbomb.t
--- a/tests/test-patchbomb.t
+++ b/tests/test-patchbomb.t
@@ -632,7 +632,7 @@
   $ hg commit -A -d '5 0' -m 'isolatin 8-bit encoding'
   adding isolatin
 
-fake ascii mbox:
+iso-8859-1 mbox:
   $ hg email --date '1970-1-1 0:5' -f quux -t foo -c bar -r tip -m mbox
   this patch series consists of 1 patches.
   
@@ -640,9 +640,9 @@
   sending [PATCH] isolatin 8-bit encoding ...
   $ cat mbox
   From quux ... ... .. ..:..:..  (re)
-  Content-Type: text/plain; charset="us-ascii"
+  Content-Type: text/plain; charset="iso-8859-1"
   MIME-Version: 1.0
-  Content-Transfer-Encoding: 8bit
+  Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH] isolatin 8-bit encoding
   X-Mercurial-Node: 240fb913fc1b7ff15ddb9f33e73d82bf5277c720
   X-Mercurial-Series-Index: 1
@@ -667,7 +667,7 @@
   --- /dev/nullThu Jan 01 00:00:00 1970 +
   +++ b/isolatin   Thu Jan 01 00:00:05 1970 +
   @@ -0,0 +1,1 @@
-  +h\xf6mma! (esc)
+  +h=F6mma!
   
   
 
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH v2] mail: take --encoding and HGENCODING into account

2016-10-08 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475667922 -7200
#  Wed Oct 05 13:45:22 2016 +0200
# Node ID c03ad1ac8772ffe8cd626c70ea0f544298fdcf9c
# Parent  91a3c58ecf938ed675f5364b88f0d663f12b0047
mail: take --encoding and HGENCODING into account

Fall back to our encoding strategy for sending MIME text
that's neither ASCII nor UTF-8.

diff --git a/mercurial/mail.py b/mercurial/mail.py
--- a/mercurial/mail.py
+++ b/mercurial/mail.py
@@ -8,6 +8,7 @@
 from __future__ import absolute_import, print_function
 
 import email
+import email.charset
 import os
 import quopri
 import smtplib
@@ -203,24 +204,33 @@
 raise error.Abort(_('%r specified as email transport, '
'but not in PATH') % method)
 
+def codec2iana(cs):
+''
+cs = email.charset.Charset(cs).input_charset.lower()
+
+# "latin1" normalizes to "iso8859-1", standard calls for "iso-8859-1"
+if cs.startswith("iso") and not cs.startswith("iso-"):
+return "iso-" + cs[3:]
+return cs
+
 def mimetextpatch(s, subtype='plain', display=False):
 '''Return MIME message suitable for a patch.
-Charset will be detected as utf-8 or (possibly fake) us-ascii.
+Charset will be detected by first trying to decode as us-ascii, then utf-8,
+and finally the global encodings. If all those fail, fall back to
+ISO-8859-1, an encoding with that allows all byte sequences.
 Transfer encodings will be used if necessary.'''
-
-cs = 'us-ascii'
-if not display:
+
+cs = ['us-ascii', 'utf-8', encoding.encoding, encoding.fallbackencoding]
+if display:
+return mimetextqp(s, subtype, None)
+for charset in cs:
 try:
-s.decode('us-ascii')
+s.decode(charset)
+return mimetextqp(s, subtype, codec2iana(charset))
 except UnicodeDecodeError:
-try:
-s.decode('utf-8')
-cs = 'utf-8'
-except UnicodeDecodeError:
-# We'll go with us-ascii as a fallback.
-pass
+pass
 
-return mimetextqp(s, subtype, cs)
+return mimetextqp(s, subtype, "iso-8859-1")
 
 def mimetextqp(body, subtype, charset):
 '''Return MIME message.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH] mail: take --encoding and HGENCODING into account

2016-10-07 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475667922 -7200
#  Wed Oct 05 13:45:22 2016 +0200
# Node ID 31350841be0c6af1c335fb02b28b8fd1f79089b9
# Parent  91a3c58ecf938ed675f5364b88f0d663f12b0047
mail: take --encoding and HGENCODING into account

Fall back to our encoding strategy for sending MIME text
that's neither ASCII nor UTF-8.

diff --git a/mercurial/mail.py b/mercurial/mail.py
--- a/mercurial/mail.py
+++ b/mercurial/mail.py
@@ -205,22 +205,40 @@
 
 def mimetextpatch(s, subtype='plain', display=False):
 '''Return MIME message suitable for a patch.
-Charset will be detected as utf-8 or (possibly fake) us-ascii.
+Charset will be detected by first trying to decode as us-ascii, then utf-8,
+and finally the global encodings. If all those fail, fall back to
+ISO-8859-1, an encoding with that allows all byte sequences.
 Transfer encodings will be used if necessary.'''
 
-cs = 'us-ascii'
+def codec2iana(encoding):
+encoding = email.charset.Charset(encoding).input_charset.lower()
+
+if encoding.startswith("iso") and not encoding.startswith("iso-"):
+return "iso-" + encoding[3:]
+return encoding
+
+cs = "iso-8859-1" # a "safe" encoding with no invalid byte sequences
 if not display:
 try:
 s.decode('us-ascii')
+cs = 'us-ascii'
 except UnicodeDecodeError:
 try:
 s.decode('utf-8')
 cs = 'utf-8'
 except UnicodeDecodeError:
-# We'll go with us-ascii as a fallback.
-pass
+try:
+s.decode(encoding.encoding)
+cs = encoding.encoding
+except UnicodeDecodeError:
+try: 
+s.decode(encoding.fallbackencoding)
+cs = encoding.fallbackencoding
+except UnicodeDecodeError
+# fall back to ISO-8859-1
+pass
 
-return mimetextqp(s, subtype, cs)
+return mimetextqp(s, subtype, codec2iana(cs))
 
 def mimetextqp(body, subtype, charset):
 '''Return MIME message.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 5 of 7] mergecopies: process rotated-DAG divergences from _checkcopies

2016-10-07 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475578314 -7200
#  Tue Oct 04 12:51:54 2016 +0200
# Node ID 90c51e2c2df282184f2a991fe690dceac4c79aa8
# Parent  76b744060475aa20751310e9e2eab76ab78b9cc6
mergecopies: process rotated-DAG divergences from _checkcopies

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -321,6 +321,7 @@
 if repo.ui.configbool('experimental', 'disablecopytrace'):
 return {}, {}, {}, {}
 
+dirtyc1 = False # dummy bool for later use
 limit = _findlimit(repo, c1.rev(), c2.rev())
 if limit is None:
 # no common ancestor, no copies
@@ -357,6 +358,27 @@
 movewithdir = dict(movewithdir1.items() + movewithdir2.items())
 fullcopy = dict(fullcopy1.items() + fullcopy2.items())
 
+# combine partial copy paths discovered in the previous step
+def _combinecopies(copyfrom, copyto, diverge):
+remainder = {}
+for f in copyfrom:
+if f in copyto:
+copy[copyto[f]] = copyfrom[f]
+del copyto[f]
+for f in incompletediverge:
+assert f not in diverge
+ic = incompletediverge[f]
+if ic[0] in copyto:
+diverge[f] = [copyto[ic[0]], ic[1]]
+else:
+remainder[f] = ic
+return remainder
+
+if dirtyc1:
+_combinecopies(incomplete2, incomplete1, diverge)
+else:
+_combinecopies(incomplete1, incomplete2, diverge)
+
 renamedelete = {}
 renamedeleteset = set()
 divergeset = set()
@@ -383,6 +405,18 @@
  _fullcopy, incomplete1, incompletediverge)
 _checkcopies(c2, f, m2, m1, ca, ca, False, limit, bothdiverge, _copy,
  _fullcopy, incomplete2, incompletediverge)
+if dirtyc1:
+assert incomplete2 == {}
+remainder = _combinecopies({}, incomplete1, bothdiverge)
+else:
+assert incomplete1 == {}
+remainder = _combinecopies({}, incomplete2, bothdiverge)
+for f in remainder:
+assert f not in bothdiverge
+ic = remainder[f]
+if ic[0] in (m1 if dirtyc1 else m2):
+# backed-out rename on one side, but watch out for deleted files
+bothdiverge[f] = ic
 for of, fl in bothdiverge.items():
 if len(fl) == 2 and fl[0] == fl[1]:
 copy[fl[0]] = of # not actually divergent, just matching renames
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 6 of 7] graft: enable rotated-DAG copy tracing (issue4028)

2016-10-07 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475512693 -7200
#  Mon Oct 03 18:38:13 2016 +0200
# Node ID ed401af1b0503335816a3edc509dbc644eef0a11
# Parent  90c51e2c2df282184f2a991fe690dceac4c79aa8
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 --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -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 --git a/tests/test-graft.t b/tests/test-graft.t
--- a/tests/test-graft.t
+++ b/tests/test-graft.t
@@ -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 @@
   

[PATCH 4 of 7] copies: make _checkcopies handle divergences in rotated DAG

2016-10-07 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475588323 -7200
#  Tue Oct 04 15:38:43 2016 +0200
# Node ID 76b744060475aa20751310e9e2eab76ab78b9cc6
# Parent  887cbdaad15d5cc1b86c7dc419a83640d467ec5a
copies: make _checkcopies handle divergences in rotated DAG

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -347,11 +347,11 @@
 
 for f in u1u:
 _checkcopies(c1, f, m1, m2, ca, ca, False, limit, diverge, copy1,
- fullcopy1)
+ fullcopy1, incomplete1, incompletediverge)
 
 for f in u2u:
 _checkcopies(c2, f, m2, m1, ca, ca, False, limit, diverge, copy2,
- fullcopy2)
+ fullcopy2, incomplete2, incompletediverge)
 
 copy = dict(copy1.items() + copy2.items())
 movewithdir = dict(movewithdir1.items() + movewithdir2.items())
@@ -380,9 +380,9 @@
 incomplete1, incomplete2, incompletediverge = {}, {}, {}
 for f in bothnew:
 _checkcopies(c1, f, m1, m2, ca, ca, False, limit, bothdiverge, _copy,
- _fullcopy)
+ _fullcopy, incomplete1, incompletediverge)
 _checkcopies(c2, f, m2, m1, ca, ca, False, limit, bothdiverge, _copy,
- _fullcopy)
+ _fullcopy, incomplete2, incompletediverge)
 for of, fl in bothdiverge.items():
 if len(fl) == 2 and fl[0] == fl[1]:
 copy[fl[0]] = of # not actually divergent, just matching renames
@@ -463,7 +463,7 @@
 return copy, movewithdir, diverge, renamedelete
 
 def _checkcopies(ctx, f, m1, m2, ca, tca, remoteca, limit, diverge, copy,
- fullcopy):
+ fullcopy, incomplete, incompletediverge):
 """
 check possible copies of f from m1 to m2
 
@@ -478,6 +478,8 @@
 diverge = record all diverges in this dict
 copy = record all non-divergent copies in this dict
 fullcopy = record all copies in this dict
+incomplete = record non-divergent partial copies here
+incompletediverge = record divergent partial copies here
 
 note: limit is only an optimization, and there is no guarantee that
 irrelevant revisions will not be limited
@@ -553,10 +555,25 @@
 copy[of] = f
 del fullcopy[f]
 fullcopy[of] = f
+else: # divergence w.r.t. graft CA on one side of topological CA
+for sf in seen:
+if sf in ma:
+assert sf not in diverge
+diverge[sf] = [f, of]
+break
 return
 
-if of in ma:
-diverge.setdefault(of, []).append(f)
+if of in mta:
+if backwards or remoteca:
+incomplete[of] = f
+else:
+for sf in seen:
+if sf in ma:
+if tca == ca:
+diverge.setdefault(sf, []).append(f)
+else:
+incompletediverge[sf] = [of, f]
+return
 
 def duplicatecopies(repo, rev, fromrev, skiprev=None):
 '''reproduce copies from fromrev to rev in the dirstate
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 7] copies: update _checkcopies to work in a rotated DAG

2016-10-07 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475588213 -7200
#  Tue Oct 04 15:36:53 2016 +0200
# Node ID 08f3260e3764bcfbe13945aa62bf079807df02fc
# Parent  5b7cd65a9be6c82b3ad14096b41db44c198a6769
copies: update _checkcopies to work in a rotated DAG

This introduces a distinction between "merge common ancestor" and
"topological common ancestor". During a regular merge, these two are
identical. Graft, however, performs a merge in a rotated DAG, where the
merge common ancestor will not be a common ancestor at all in the
original DAG.

To correctly find copies in case of a graft, we need to take both the
merge CA and the topological CA into account, and track any renames
between them in reverse.

Special care must be taken about the case where the merge CA lies not
between the topological CA and the source, but outside it (typically
between the topological CA and the destination).
This is handled using the remoteca parameter; it would be possible to
have _checkcopies itself detect this, but it's quite expensive and also
guaranteed not to change between multiple _checkcopies invocations in the
same mergecopies call, so it's better to check it there, and pass to
_checkcopies as a parameter.

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -345,10 +345,12 @@
 bothnew = sorted(addedinm1 & addedinm2)
 
 for f in u1u:
-_checkcopies(c1, f, m1, m2, ca, limit, diverge, copy1, fullcopy1)
+_checkcopies(c1, f, m1, m2, ca, ca, False, limit, diverge, copy1,
+ fullcopy1)
 
 for f in u2u:
-_checkcopies(c2, f, m2, m1, ca, limit, diverge, copy2, fullcopy2)
+_checkcopies(c2, f, m2, m1, ca, ca, False, limit, diverge, copy2,
+ fullcopy2)
 
 copy = dict(copy1.items() + copy2.items())
 movewithdir = dict(movewithdir1.items() + movewithdir2.items())
@@ -373,8 +375,10 @@
   % "\n   ".join(bothnew))
 bothdiverge, _copy, _fullcopy = {}, {}, {}
 for f in bothnew:
-_checkcopies(c1, f, m1, m2, ca, limit, bothdiverge, _copy, _fullcopy)
-_checkcopies(c2, f, m2, m1, ca, limit, bothdiverge, _copy, _fullcopy)
+_checkcopies(c1, f, m1, m2, ca, ca, False, limit, bothdiverge, _copy,
+ _fullcopy)
+_checkcopies(c2, f, m2, m1, ca, ca, False, limit, bothdiverge, _copy,
+ _fullcopy)
 for of, fl in bothdiverge.items():
 if len(fl) == 2 and fl[0] == fl[1]:
 copy[fl[0]] = of # not actually divergent, just matching renames
@@ -454,7 +458,8 @@
 
 return copy, movewithdir, diverge, renamedelete
 
-def _checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy):
+def _checkcopies(ctx, f, m1, m2, ca, tca, remoteca, limit, diverge, copy,
+ fullcopy):
 """
 check possible copies of f from m1 to m2
 
@@ -462,7 +467,9 @@
 f = the filename to check
 m1 = the source manifest
 m2 = the destination manifest
-ca = the changectx of the common ancestor
+ca = the changectx of the common ancestor, overridden on graft
+tca = topological common ancestor for graft-like scenarios
+remoteca = True if ca is outside tca::ctx, False otherwise
 limit = the rev number to not search beyond
 diverge = record all diverges in this dict
 copy = record all non-divergent copies in this dict
@@ -475,6 +482,8 @@
 """
 
 ma = ca.manifest()
+mta = tca.manifest()
+backwards = ca != tca and not remoteca and f in ma
 getfctx = _makegetfctx(ctx)
 
 def _related(f1, f2, limit):
@@ -520,15 +529,26 @@
 continue
 seen.add(of)
 
-fullcopy[f] = of # remember for dir rename detection
+# remember for dir rename detection
+if backwards:
+fullcopy[of] = f # grafting backwards through renames
+else:
+fullcopy[f] = of
 if of not in m2:
 continue # no match, keep looking
 if m2[of] == ma.get(of):
 return # no merge needed, quit early
 c2 = getfctx(of, m2[of])
-cr = _related(oc, c2, ca.rev())
+cr = _related(oc, c2, tca.rev())
 if cr and (of == f or of == c2.path()): # non-divergent
-copy[f] = of
+if backwards:
+copy[of] = f
+elif of in ma:
+copy[f] = of
+elif remoteca: # special case: a <- b <- a -> b "ping-pong" rename
+copy[of] = f
+del fullcopy[f]
+fullcopy[of] = f
 return
 
 if of in ma:
diff --git a/tests/test-graft.t b/tests/test-graft.t
--- a/tests/test-graft.t
+++ b/tests/test-graft.t
@@ -427,8 +427,8 @@
   $ hg graft 3 --log -u foo
   grafting 3:4c60f11aa304 "3"
   warning: can't find ancestor for 'c' copied fro

[PATCH 1 of 7] copies: don't record divergence for files needing no merge

2016-10-07 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475494199 -7200
#  Mon Oct 03 13:29:59 2016 +0200
# Node ID 5b7cd65a9be6c82b3ad14096b41db44c198a6769
# Parent  91a3c58ecf938ed675f5364b88f0d663f12b0047
copies: don't record divergence for files needing no merge

This is left over from when _checkcopies was factored out from mergecopies.

The 2nd break has "of = None" before it, so it's a functionally equivalent
change. The 1st one, however, causes a divergence to be recorded when
a file has been renamed, but there is nothing to be merged to it.

This is currently harmless, since the extra divergence is simply ignored
later. However, the new _checkcopies introduced in the rest of this series
does more than just record a divergence after completing the main loop,
and it's important that the "post-processing" stage is really skipped
for no-merge-needed renames.

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -524,13 +524,12 @@
 if of not in m2:
 continue # no match, keep looking
 if m2[of] == ma.get(of):
-break # no merge needed, quit early
+return # no merge needed, quit early
 c2 = getfctx(of, m2[of])
 cr = _related(oc, c2, ca.rev())
 if cr and (of == f or of == c2.path()): # non-divergent
 copy[f] = of
-of = None
-break
+return
 
 if of in ma:
 diverge.setdefault(of, []).append(f)
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


RE: [PATCH 01 of 11] copies: style fixes and add comment (issue4028)

2016-10-07 Thread Gábor STEFANIK
From: timeless.b...@gmail.com [mailto:timeless.b...@gmail.com] On Behalf Of 
timeless
Sent: Wednesday, October 5, 2016 10:57 PM
To: Gábor STEFANIK <gabor.stefa...@nng.com>
Cc: mercurial-devel <mercurial-devel@mercurial-scm.org>
Subject: Re: [PATCH 01 of 11] copies: style fixes and add comment (issue4028)

>> @@ -331,7 +331,8 @@
>>  m2 = c2.manifest()
>>  ma = ca.manifest()
>>
>> -copy1, copy2, = {}, {}
>> +# see checkcopies documentation below for these dicts
>> +copy1, copy2 = {}, {}
>I've been meaning to ask:
>Do we know why this is a -1+2 instead of a -0+1 diff?

Because I've removed a superfluous comma from the 2nd line.



--
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.


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


[PATCH 09 of 11] mergecopies: process rotated-DAG divergences from _checkcopies (issue4028)

2016-10-05 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475578314 -7200
#  Tue Oct 04 12:51:54 2016 +0200
# Node ID bbc77d871f41a85db381b57acfe6dc550bb40506
# Parent  8ae0f32d74ea2fe2a4b47b9b36624459b3c1531f
mergecopies: process rotated-DAG divergences from _checkcopies (issue4028)

diff -r 8ae0f32d74ea -r bbc77d871f41 mercurial/copies.py
--- a/mercurial/copies.py   Tue Oct 04 15:38:43 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 04 12:51:54 2016 +0200
@@ -321,6 +321,7 @@
 if repo.ui.configbool('experimental', 'disablecopytrace'):
 return {}, {}, {}, {}
 
+dirtyc1 = False # dummy bool for later use
 limit = _findlimit(repo, c1.rev(), c2.rev())
 if limit is None:
 # no common ancestor, no copies
@@ -357,6 +358,27 @@
 movewithdir = dict(movewithdir1.items() + movewithdir2.items())
 fullcopy = dict(fullcopy1.items() + fullcopy2.items())
 
+# combine partial copy paths discovered in the previous step
+def _combinecopies(copyfrom, copyto, diverge):
+remainder = {}
+for f in copyfrom:
+if f in copyto:
+copy[copyto[f]] = copyfrom[f]
+del copyto[f]
+for f in incompletediverge:
+assert f not in diverge
+ic = incompletediverge[f]
+if ic[0] in copyto:
+diverge[f] = [copyto[ic[0]], ic[1]]
+else:
+remainder[f] = ic
+return remainder
+
+if dirtyc1:
+_combinecopies(incomplete2, incomplete1, diverge)
+else:
+_combinecopies(incomplete1, incomplete2, diverge)
+
 renamedelete = {}
 renamedeleteset = set()
 divergeset = set()
@@ -383,6 +405,18 @@
  _fullcopy, incomplete1, incompletediverge)
 _checkcopies(c2, f, m2, m1, ca, ca, False, limit, bothdiverge, _copy,
  _fullcopy, incomplete2, incompletediverge)
+if dirtyc1:
+assert incomplete2 == {}
+remainder = _combinecopies({}, incomplete1, bothdiverge)
+else:
+assert incomplete1 == {}
+remainder = _combinecopies({}, incomplete2, bothdiverge)
+for f in remainder:
+assert f not in bothdiverge
+ic = remainder[f]
+if ic[0] in (m1 if dirtyc1 else m2):
+# backed-out rename on one side, but watch out for deleted files
+bothdiverge[f] = ic
 for of, fl in bothdiverge.items():
 if len(fl) == 2 and fl[0] == fl[1]:
 copy[fl[0]] = of # not actually divergent, just matching renames
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[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 <gabor.stefa...@nng.com>
# 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 m

[PATCH 11 of 11] update: enable copy tracing for backwards and non-linear updates (issue4028)

2016-10-05 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1472155346 -7200
#  Thu Aug 25 22:02:26 2016 +0200
# Node ID 548610449759c452d307dbcc92a9bf8fbba46140
# Parent  4b963243504357812d13fa5c824f60253e0ba448
update: enable copy tracing for backwards and non-linear updates (issue4028)

diff -r 4b9632435043 -r 548610449759 mercurial/merge.py
--- a/mercurial/merge.pyMon Oct 03 18:38:13 2016 +0200
+++ b/mercurial/merge.pyThu Aug 25 22:02:26 2016 +0200
@@ -1535,15 +1535,16 @@
 pas = [p1]
 
 # deprecated config: merge.followcopies
-followcopies = False
+followcopies = repo.ui.configbool('merge', 'followcopies', True)
 if overwrite:
 pas = [wc]
+followcopies = False
 elif pas == [p2]: # backwards
-pas = [wc.p1()]
-elif not branchmerge and not wc.dirty(missing=True):
-pass
-elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
-followcopies = True
+pas = [p1]
+elif not pas[0]:
+followcopies = False
+if not branchmerge and not wc.dirty(missing=True):
+followcopies = False
 
 ### calculate phase
 actionbyfile, diverge, renamedelete = calculateupdates(
diff -r 4b9632435043 -r 548610449759 tests/test-merge-local.t
--- a/tests/test-merge-local.t  Mon Oct 03 18:38:13 2016 +0200
+++ b/tests/test-merge-local.t  Thu Aug 25 22:02:26 2016 +0200
@@ -66,7 +66,7 @@
   merging zzz1_merge_ok
   merging zzz2_merge_bad
   warning: conflicts while merging zzz2_merge_bad! (edit, then use 'hg resolve 
--mark')
-  2 files updated, 1 files merged, 3 files removed, 1 files unresolved
+  2 files updated, 1 files merged, 2 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
   [1]
 
@@ -104,7 +104,7 @@
   merging zzz1_merge_ok
   merging zzz2_merge_bad
   warning: conflicts while merging zzz2_merge_bad! (edit, then use 'hg resolve 
--mark')
-  2 files updated, 1 files merged, 3 files removed, 1 files unresolved
+  2 files updated, 1 files merged, 2 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
   [1]
 
diff -r 4b9632435043 -r 548610449759 tests/test-mq-subrepo.t
--- a/tests/test-mq-subrepo.t   Mon Oct 03 18:38:13 2016 +0200
+++ b/tests/test-mq-subrepo.t   Thu Aug 25 22:02:26 2016 +0200
@@ -304,6 +304,7 @@
   record this change to '.hgsub'? [Ynesfdaq?] y
   
   warning: subrepo spec file '.hgsub' not found
+  warning: subrepo spec file '.hgsub' not found
   abort: uncommitted changes in subrepository 'sub'
   [255]
   % update substate when adding .hgsub w/clean updated subrepo
@@ -319,6 +320,7 @@
   record this change to '.hgsub'? [Ynesfdaq?] y
   
   warning: subrepo spec file '.hgsub' not found
+  warning: subrepo spec file '.hgsub' not found
   path sub
source   sub
revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
diff -r 4b9632435043 -r 548610449759 tests/test-up-local-change.t
--- a/tests/test-up-local-change.t  Mon Oct 03 18:38:13 2016 +0200
+++ b/tests/test-up-local-change.t  Thu Aug 25 22:02:26 2016 +0200
@@ -67,13 +67,18 @@
   summary: 2
   
   $ hg --debug up 0
+  starting 4 threads for background file closing (?)
+searching for copies back to rev 0
+computing unmatched files in rotated DAG
+computing unmatched files in unrotated DAG
+unmatched files in local:
+ b
   resolving manifests
branchmerge: False, force: False, partial: False
ancestor: 1e71731e6fbb, local: 1e71731e6fbb+, remote: c19d34741b0a
preserving a for resolve of a
b: other deleted -> r
   removing b
-  starting 4 threads for background file closing (?)
a: versions differ -> m (premerge)
   picked tool 'true' for a (binary False symlink False changedelete False)
   merging a
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 08 of 11] copies: make _checkcopies handle divergences in rotated DAG (issue4028)

2016-10-05 Thread Gábor Stefanik
# HG changeset patch
# User Gábor Stefanik <gabor.stefa...@nng.com>
# Date 1475588323 -7200
#  Tue Oct 04 15:38:43 2016 +0200
# Node ID 8ae0f32d74ea2fe2a4b47b9b36624459b3c1531f
# Parent  08f08a606c0444a5033bd8fa28da4482713a4ea7
copies: make _checkcopies handle divergences in rotated DAG (issue4028)

diff -r 08f08a606c04 -r 8ae0f32d74ea mercurial/copies.py
--- a/mercurial/copies.py   Mon Oct 03 13:38:56 2016 +0200
+++ b/mercurial/copies.py   Tue Oct 04 15:38:43 2016 +0200
@@ -347,11 +347,11 @@
 
 for f in u1u:
 _checkcopies(c1, f, m1, m2, ca, ca, False, limit, diverge, copy1,
- fullcopy1)
+ fullcopy1, incomplete1, incompletediverge)
 
 for f in u2u:
 _checkcopies(c2, f, m2, m1, ca, ca, False, limit, diverge, copy2,
- fullcopy2)
+ fullcopy2, incomplete2, incompletediverge)
 
 copy = dict(copy1.items() + copy2.items())
 movewithdir = dict(movewithdir1.items() + movewithdir2.items())
@@ -380,9 +380,9 @@
 incomplete1, incomplete2, incompletediverge = {}, {}, {}
 for f in bothnew:
 _checkcopies(c1, f, m1, m2, ca, ca, False, limit, bothdiverge, _copy,
- _fullcopy)
+ _fullcopy, incomplete1, incompletediverge)
 _checkcopies(c2, f, m2, m1, ca, ca, False, limit, bothdiverge, _copy,
- _fullcopy)
+ _fullcopy, incomplete2, incompletediverge)
 for of, fl in bothdiverge.items():
 if len(fl) == 2 and fl[0] == fl[1]:
 copy[fl[0]] = of # not actually divergent, just matching renames
@@ -463,7 +463,7 @@
 return copy, movewithdir, diverge, renamedelete
 
 def _checkcopies(ctx, f, m1, m2, ca, tca, remoteca, limit, diverge, copy,
- fullcopy):
+ fullcopy, incomplete, incompletediverge):
 """
 check possible copies of f from m1 to m2
 
@@ -478,6 +478,8 @@
 diverge = record all diverges in this dict
 copy = record all non-divergent copies in this dict
 fullcopy = record all copies in this dict
+incomplete = record non-divergent partial copies here
+incompletediverge = record divergent partial copies here
 
 note: limit is only an optimization, and there is no guarantee that
 irrelevant revisions will not be limited
@@ -553,10 +555,25 @@
 copy[of] = f
 del fullcopy[f]
 fullcopy[of] = f
+else: # divergence w.r.t. graft CA on one side of topological CA
+for sf in seen:
+if sf in ma:
+assert sf not in diverge
+diverge[sf] = [f, of]
+break
 return
 
-if of in ma:
-diverge.setdefault(of, []).append(f)
+if of in mta:
+if backwards or remoteca:
+incomplete[of] = f
+else:
+for sf in seen:
+if sf in ma:
+if tca == ca:
+diverge.setdefault(sf, []).append(f)
+else:
+incompletediverge[sf] = [of, f]
+return
 
 def duplicatecopies(repo, rev, fromrev, skiprev=None):
 '''reproduce copies from fromrev to rev in the dirstate
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


  1   2   >