Re: [PATCH] py3: make keys of keyword arguments strings

2016-12-07 Thread Mateusz Kwapich
Excerpts from Pulkit Goyal's message of 2016-12-07 10:43:24 +0530:
> Also after this patch, `hg version` now runs on Python 3.5. Hurray!

LGTM, having `hg version` running is really good news. Congrats!


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


Re: [PATCH] push: add a message when pushing phases but not changes

2016-12-06 Thread Mateusz Kwapich
Excerpts from Jeremy Wall (zaphar)'s message of 2016-12-02 13:12:20 -0600:
> diff -r 9e29d4e4e08b -r 9cb1540e417d tests/test-phases-exchange.t
> --- a/tests/test-phases-exchange.tTue Nov 29 04:11:05 2016 -0800
> +++ b/tests/test-phases-exchange.tWed Nov 30 15:55:42 2016 -0600
> @@ -384,7 +384,7 @@
>$ hg push ../alpha # from nu
>pushing to ../alpha
>searching for changes
> -  no changes found
> +  sending phase public for 145e75495359
>[1]

I suppose now, that we are addmitting that there was something to push
we should change the RC to 0. Question to others: would such change be
considered a BC-breakage or a fix?

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


Re: [PATCH 3 of 3] py3: make a bytes version of getopt.getopt()

2016-12-06 Thread Mateusz Kwapich
The whole series looks good to me.

Best,
Mateusz

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


[PATCH 5 of 8 evolve-ext, V2] metaedit: use faster setparents instead of full update

2016-12-06 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1479325623 0
#  Wed Nov 16 19:47:03 2016 +
# Branch stable
# Node ID 651b6258e993f6d45e4ef7b324303201db5639b2
# Parent  d4a8c386a14b3e455e60fffec7fb315f9629ff12
metaedit: use faster setparents instead of full update

The working copy is not changing so there is no need to extra status call.
This makes metaedit work on dirty wc.

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -3303,7 +3303,7 @@ def metaedit(ui, repo, *revs, **opts):
 if opts['fold']:
 ui.status('%i changesets folded\n' % len(revs))
 if newp1 is not None:
-hg.update(repo, newp1)
+repo.setparents(newp1)
 finally:
 lockmod.release(lock, wlock)
 
diff --git a/tests/test-evolve.t b/tests/test-evolve.t
--- a/tests/test-evolve.t
+++ b/tests/test-evolve.t
@@ -1489,7 +1489,6 @@ check that metaedit respects allowunstab
   abort: cannot fold chain not ending with a head or with branching
   [255]
   $ hg metaedit --user foobar
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
   42: test
   43: foobar
@@ -1497,7 +1496,6 @@ check that metaedit respects allowunstab
   43: foobar
 
   $ HGEDITOR="sed -i'' -e 's/safely/quickly/g'" hg metaedit '.^::.'
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ HGEDITOR=cat hg metaedit '.^::.' --fold
   HG: This is a fold of 2 changesets.
@@ -1519,7 +1517,6 @@ check that metaedit respects allowunstab
   HG: changed a
   HG: changed newfile
   2 changesets folded
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ glog -r .
   @  45:ca7a9e928b25@default(draft) amended
@@ -1553,7 +1550,6 @@ no new commit is created here because th
 
 TODO: don't create a new commit in this case
   $ hg metaedit --config defaults.metaedit=
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -r '.^::.' --template '{rev}: {desc|firstline}\n'
   36: add uu
   46: amended
@@ -1570,8 +1566,11 @@ TODO: don't create a new commit in this 
   47: foobar2
   $ hg diff -r 45 -r 46 --hidden
 
-'fold' one commit
+'fold' one commit with dirty wc
+  $ echo x > newfile
   $ hg metaedit 39 --fold --user foobar3
   1 changesets folded
   $ hg log -r 48 --template '{rev}: {author}\n'
   48: foobar3
+  $ hg st -amr
+  M newfile
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 6 of 8 evolve-ext, V2] metaedit: rewrite rewritemeta to reuse manifests

2016-12-06 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1479405307 28800
#  Thu Nov 17 09:55:07 2016 -0800
# Branch stable
# Node ID 02a29df6827d1dae26b885c9c6c9d56be33ecd00
# Parent  651b6258e993f6d45e4ef7b324303201db5639b2
metaedit: rewrite rewritemeta to reuse manifests

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -908,11 +908,47 @@ def rewrite(repo, old, updates, head, ne
 lockmod.release(tr, lock, wlock)
 
 def metarewrite(repo, old, newbases, commitopts):
-'''Like rewrite but affects only the changeset metadata.'''
-# TODO: reuse the manifest for speed
-newid, created = rewrite(repo, old, [old], old, newbases,
- commitopts=commitopts)
-return newid, created
+"""Return (nodeid, created) where nodeid is the identifier of the
+changeset generated by the rewrite process, and created is True if
+nodeid was actually created. If created is False, nodeid
+references a changeset existing before the rewrite call.
+"""
+wlock = lock = tr = None
+try:
+wlock = repo.wlock()
+lock = repo.lock()
+tr = repo.transaction('rewrite')
+updatebookmarks = _bookmarksupdater(repo, old.node(), tr)
+
+message = cmdutil.logmessage(repo.ui, commitopts)
+if not message:
+message = old.description()
+
+user = commitopts.get('user') or old.user()
+date = commitopts.get('date') or None # old.date()
+extra = dict(commitopts.get('extra', old.extra()))
+extra['branch'] = old.branch()
+
+new = context.memlightctx(repo,
+  old,
+  parents=newbases,
+  text=message,
+  user=user,
+  date=date,
+  extra=extra)
+
+if commitopts.get('edit'):
+new._text = cmdutil.commitforceeditor(repo, new, [])
+revcount = len(repo)
+newid = repo.commitctx(new)
+new = repo[newid]
+created = len(repo) != revcount
+updatebookmarks(newid)
+
+tr.close()
+return newid, created
+finally:
+lockmod.release(tr, lock, wlock)
 
 class MergeFailure(error.Abort):
 pass
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 8 of 8 evolve-ext, V2] metaedit: make metaedit work nice when new unstable commits are disallowed

2016-12-06 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1481041359 28800
#  Tue Dec 06 08:22:39 2016 -0800
# Branch stable
# Node ID 50ef3a1f97fb8faf1506d7751ba3f7ffe7e0e7db
# Parent  a7cc11231c424e435252e7388dd05b139e766af2
metaedit: make metaedit work nice when new unstable commits are disallowed

Now, when metaedit is using a lightweight commits (reusing manifest) we can
easily afford to actually rewrite all descendants if we attempt to metaedit a
commit in the middle of a stack.

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -929,7 +929,7 @@ def metarewrite(repo, old, newbases, com
 extra = dict(commitopts.get('extra', old.extra()))
 extra['branch'] = old.branch()
 
-new = context.memlightctx(repo,
+new = context.metadataonlyctx(repo,
   old,
   parents=newbases,
   text=message,
@@ -3310,9 +3310,14 @@ def metaedit(ui, repo, *revs, **opts):
 replacemap = {}
 # we need topological order
 allctx = sorted(allctx, key=lambda c: c.rev())
-for c in allctx:
-if commitopts['edit']:
-commitopts['message'] = \
+# all descendats that can be safely rewritten
+newunstable = _disallowednewunstable(repo, revs)
+newunstablectx = sorted([repo[r] for r in newunstable],
+ key=lambda c: c.rev())
+
+def _rewritesingle(c, _commitopts):
+if _commitopts.get('edit', False):
+_commitopts['message'] = \
 "HG: Commit message of changeset %s\n%s" %\
 (str(c), c.description())
 bases = [
@@ -3320,9 +3325,13 @@ def metaedit(ui, repo, *revs, **opts):
 replacemap.get(c.p2().node(), c.p2().node()),
 ]
 newid, created = metarewrite(repo, c, bases,
- commitopts=commitopts)
+ commitopts=_commitopts)
 if created:
 replacemap[c.node()] = newid
+for c in allctx:
+_rewritesingle(c, commitopts)
+for c in newunstablectx:
+_rewritesingle(c, {})
 
 if p1.node() in replacemap:
 newp1 = replacemap[p1.node()]
diff --git a/tests/test-evolve.t b/tests/test-evolve.t
--- a/tests/test-evolve.t
+++ b/tests/test-evolve.t
@@ -1574,3 +1574,54 @@ TODO: don't create a new commit in this 
   48: foobar3
   $ hg st -amr
   M newfile
+
+metaedit a commit in the middle of the stack:
+  $ glog -r '(.^)::'
+  o  48:2603fdb715ea@default(draft) will cause conflict at evolve
+  |
+  | o  47:f72774e0c084@default(draft) amended
+  | |
+  x |  38:f8e30e9317aa@default(draft) will be evolved safely
+  | |
+  x |  37:36030b147271@default(draft) will be amended
+  |/
+  @  36:43c3f5ef149f@default(draft) add uu
+  |
+  o  35:7a555adf2b4a@default(draft) _pp
+  |
+  ~
+  $ hg metaedit -m "add uu (with metaedit)" --config 
'experimental.evolution=createmarkers, allnewcommands'
+  abort: cannot edit commit information in the middle of a stack
+  (f72774e0c084 will be affected)
+  [255]
+  $ glog -r '(.^)::'
+  o  48:2603fdb715ea@default(draft) will cause conflict at evolve
+  |
+  | o  47:f72774e0c084@default(draft) amended
+  | |
+  x |  38:f8e30e9317aa@default(draft) will be evolved safely
+  | |
+  x |  37:36030b147271@default(draft) will be amended
+  |/
+  @  36:43c3f5ef149f@default(draft) add uu
+  |
+  o  35:7a555adf2b4a@default(draft) _pp
+  |
+  ~
+  $ hg metaedit -m "add uu (with metaedit)"
+  $ glog -r '(.^)::'
+  @  49:d56769f6b2a5@default(draft) add uu (with metaedit)
+  |
+  | o  48:2603fdb715ea@default(draft) will cause conflict at evolve
+  | |
+  | | o  47:f72774e0c084@default(draft) amended
+  | | |
+  | x |  38:f8e30e9317aa@default(draft) will be evolved safely
+  | | |
+  | x |  37:36030b147271@default(draft) will be amended
+  | |/
+  | x  36:43c3f5ef149f@default(draft) add uu
+  |/
+  o  35:7a555adf2b4a@default(draft) _pp
+  |
+  ~
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 7 of 8 evolve-ext, V2] evolve: make the disallowing new unstable more accurate

2016-12-06 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1481040901 28800
#  Tue Dec 06 08:15:01 2016 -0800
# Branch stable
# Node ID a7cc11231c424e435252e7388dd05b139e766af2
# Parent  02a29df6827d1dae26b885c9c6c9d56be33ecd00
evolve: make the disallowing new unstable more accurate

If the changesets are already unstable don't trigger disallownewunstable

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -3365,7 +3365,7 @@ def _disallowednewunstable(repo, revs):
 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
 if allowunstable:
 return revset.baseset()
-return repo.revs("(%ld::) - %ld", revs, revs)
+return repo.revs("(%ld::) - %ld - unstable() - obsolete()", revs, revs)
 
 @eh.wrapcommand('graft')
 def graftwrapper(orig, ui, repo, *revs, **kwargs):
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 3 of 8 evolve-ext, V2] metaedit: extend the functionality to support editing multiple commits

2016-12-06 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1481029159 28800
#  Tue Dec 06 04:59:19 2016 -0800
# Branch stable
# Node ID 2495e78f6db6bb8d885121c45c16b9ba9266d1a1
# Parent  b8cbdf6356d770b70df92b13095c11438b8ad1a1
metaedit: extend the functionality to support editing multiple commits

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -3282,17 +3282,29 @@ def metaedit(ui, repo, *revs, **opts):
 else:
 ui.status(_("nothing changed\n"))
 else:
-if commitopts['edit']:
-commitopts['message'] = head.description()
-newid, created = rewrite(repo, root, allctx, head,
- [root.p1().node(), root.p2().node()],
- commitopts=commitopts)
-if created:
-if p1.rev() in revs:
-newp1 = newid
-phases.retractboundary(repo, tr, targetphase, [newid])
-obsolete.createmarkers(repo, [(ctx, (repo[newid],))
-  for ctx in allctx])
+replacemap = {}
+# we need topological order
+allctx = sorted(allctx, key=lambda c: c.rev())
+for c in allctx:
+if commitopts['edit']:
+commitopts['message'] = \
+"HG: Commit message of changeset %s\n%s" %\
+(str(c), c.description())
+bases = [
+replacemap.get(c.p1().node(), c.p1().node()),
+replacemap.get(c.p2().node(), c.p2().node()),
+]
+newid, created = metarewrite(repo, c, bases,
+ commitopts=commitopts)
+if created:
+replacemap[c.node()] = newid
+
+if p1.node() in replacemap:
+newp1 = replacemap[p1.node()]
+if len(replacemap) > 0:
+obsolete.createmarkers(repo, [(repo[old], (repo[new],))
+for old, new in replacemap.iteritems()])
+# TODO: set poroper phase boundaries
 else:
 ui.status(_("nothing changed\n"))
 tr.close()
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 8 evolve-ext, V2] metaedit: add a helper function for just metadata rewrites

2016-12-06 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1481028829 28800
#  Tue Dec 06 04:53:49 2016 -0800
# Branch stable
# Node ID 78b75ed14103cee05ed13948025310919adde559
# Parent  727c7211c810d304ebf92b32db7ecf697ce46ac6
metaedit: add a helper function for just metadata rewrites

It will be used by metaedit.

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -907,6 +907,13 @@ def rewrite(repo, old, updates, head, ne
 finally:
 lockmod.release(tr, lock, wlock)
 
+def metarewrite(repo, old, newbases, commitopts):
+'''Like rewrite but affects only the changeset metadata.'''
+# TODO: reuse the manifest for speed
+newid, created = rewrite(repo, old, [old], old, newbases,
+ commitopts=commitopts)
+return newid, created
+
 class MergeFailure(error.Abort):
 pass
 
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 4 of 8 evolve-ext, V2] metaedit: remove the code gating the new metaedit feature and test it

2016-12-06 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1481038866 28800
#  Tue Dec 06 07:41:06 2016 -0800
# Branch stable
# Node ID d4a8c386a14b3e455e60fffec7fb315f9629ff12
# Parent  2495e78f6db6bb8d885121c45c16b9ba9266d1a1
metaedit: remove the code gating the new metaedit feature and test it

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -3220,17 +3220,6 @@ def metaedit(ui, repo, *revs, **opts):
 lock = repo.lock()
 
 revs = scmutil.revrange(repo, revs)
-if not opts['fold'] and len(revs) > 1:
-# TODO: handle multiple revisions. This is somewhat tricky because
-# if we want to edit a series of commits:
-#
-#   a  b  c
-#
-# we need to rewrite a first, then directly rewrite b on top of the
-# new a, then rewrite c on top of the new b. So we need to handle
-# revisions in topological order.
-raise error.Abort(_('editing multiple revisions without --fold is '
-'not currently supported'))
 
 if opts['fold']:
 root, head = _foldcheck(repo, revs)
diff --git a/tests/test-evolve.t b/tests/test-evolve.t
--- a/tests/test-evolve.t
+++ b/tests/test-evolve.t
@@ -1496,10 +1496,8 @@ check that metaedit respects allowunstab
   $ hg log --template '{rev}: {author}\n' -r .
   43: foobar
 
-TODO: support this
-  $ hg metaedit '.^::.'
-  abort: editing multiple revisions without --fold is not currently supported
-  [255]
+  $ HGEDITOR="sed -i'' -e 's/safely/quickly/g'" hg metaedit '.^::.'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ HGEDITOR=cat hg metaedit '.^::.' --fold
   HG: This is a fold of 2 changesets.
@@ -1507,9 +1505,9 @@ TODO: support this
   
   amended
   
-  HG: Commit message of changeset 43.
+  HG: Commit message of changeset 44.
   
-  will be evolved safely
+  will be evolved quickly
   
   
   
@@ -1524,16 +1522,17 @@ TODO: support this
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ glog -r .
-  @  44:41bf1183869c@default(draft) amended
+  @  45:ca7a9e928b25@default(draft) amended
   |
   ~
 
 no new commit is created here because the date is the same
   $ HGEDITOR=cat hg metaedit
+  HG: Commit message of changeset ca7a9e928b25
   amended
   
   
-  will be evolved safely
+  will be evolved quickly
   
   
   HG: Enter commit message.  Lines beginning with 'HG:' are removed.
@@ -1546,7 +1545,7 @@ no new commit is created here because th
   nothing changed
 
   $ glog -r '.^::.'
-  @  44:41bf1183869c@default(draft) amended
+  @  45:ca7a9e928b25@default(draft) amended
   |
   o  36:43c3f5ef149f@default(draft) add uu
   |
@@ -1557,21 +1556,22 @@ TODO: don't create a new commit in this 
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -r '.^::.' --template '{rev}: {desc|firstline}\n'
   36: add uu
-  45: amended
+  46: amended
 
   $ hg up .^
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg metaedit --user foobar2 45
+  $ hg metaedit --user foobar2 46
   $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
   42: test
   43: foobar
-  44: test
+  44: foobar
   45: test
-  46: foobar2
+  46: test
+  47: foobar2
   $ hg diff -r 45 -r 46 --hidden
 
 'fold' one commit
   $ hg metaedit 39 --fold --user foobar3
   1 changesets folded
-  $ hg log -r 47 --template '{rev}: {author}\n'
-  47: foobar3
+  $ hg log -r 48 --template '{rev}: {author}\n'
+  48: foobar3
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 8 evolve-ext, V2] metaedit: separate the fold and no-fold logic

2016-12-06 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1481029031 28800
#  Tue Dec 06 04:57:11 2016 -0800
# Branch stable
# Node ID b8cbdf6356d770b70df92b13095c11438b8ad1a1
# Parent  78b75ed14103cee05ed13948025310919adde559
metaedit: separate the fold and no-fold logic

As I want to implement the metaedit for multiple changesets I need to separate
the logic doing a metaedit with fold from the logic that is doing metaedit
without fold.

This change intentionally copies code so it's easier to follow when its
modified in the next changeset in that series.

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -3257,29 +3257,44 @@ def metaedit(ui, repo, *revs, **opts):
 if commitopts.get('message') or commitopts.get('logfile'):
 commitopts['edit'] = False
 else:
-if opts['fold']:
-msgs = ["HG: This is a fold of %d changesets." % 
len(allctx)]
-msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
- (c.rev(), c.description()) for c in allctx]
-else:
-msgs = [head.description()]
-commitopts['message'] =  "\n".join(msgs)
 commitopts['edit'] = True
 
 # TODO: if the author and message are the same, don't create a new
 # hash. Right now we create a new hash because the date can be
 # different.
-newid, created = rewrite(repo, root, allctx, head,
- [root.p1().node(), root.p2().node()],
- commitopts=commitopts)
-if created:
-if p1.rev() in revs:
-newp1 = newid
-phases.retractboundary(repo, tr, targetphase, [newid])
-obsolete.createmarkers(repo, [(ctx, (repo[newid],))
-  for ctx in allctx])
+if opts['fold']:
+if commitopts['edit']:
+msgs = ["HG: This is a fold of %d changesets." %
+len(allctx)]
+msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
+ (c.rev(), c.description()) for c in allctx]
+commitopts['message'] = "\n".join(msgs)
+
+newid, created = rewrite(repo, root, allctx, head,
+ [root.p1().node(), root.p2().node()],
+ commitopts=commitopts)
+if created:
+if p1.rev() in revs:
+newp1 = newid
+phases.retractboundary(repo, tr, targetphase, [newid])
+obsolete.createmarkers(repo, [(ctx, (repo[newid],))
+  for ctx in allctx])
+else:
+ui.status(_("nothing changed\n"))
 else:
-ui.status(_("nothing changed\n"))
+if commitopts['edit']:
+commitopts['message'] = head.description()
+newid, created = rewrite(repo, root, allctx, head,
+ [root.p1().node(), root.p2().node()],
+ commitopts=commitopts)
+if created:
+if p1.rev() in revs:
+newp1 = newid
+phases.retractboundary(repo, tr, targetphase, [newid])
+obsolete.createmarkers(repo, [(ctx, (repo[newid],))
+  for ctx in allctx])
+else:
+ui.status(_("nothing changed\n"))
 tr.close()
 finally:
 tr.release()
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH 2 of 4 evolve-ext] metaedit: extend the functionality to support editing multiple commits

2016-12-05 Thread Mateusz Kwapich
Excerpts from Ryan McElroy's message of 2016-12-05 13:14:58 +:
> 
> On 11/16/16 7:56 PM, Mateusz Kwapich wrote:
> > # HG changeset patch
> > # User Mateusz Kwapich <mitran...@fb.com>
> > # Date 1479324135 0
> > #  Wed Nov 16 19:22:15 2016 +
> > # Branch stable
> > # Node ID b2bde478bfebc390dba8f1ee314b7bdd062ab191
> > # Parent  744c6acd84edf73ffdf505b9673b0383db727a0e
> > metaedit: extend the functionality to support editing multiple commits
> >
> > diff --git a/hgext/evolve.py b/hgext/evolve.py
> > --- a/hgext/evolve.py
> > +++ b/hgext/evolve.py
> > @@ -3257,29 +3257,57 @@ def metaedit(ui, repo, *revs, **opts):
> >   if commitopts.get('message') or commitopts.get('logfile'):
> >   commitopts['edit'] = False
> >   else:
> > -if opts['fold']:
> > -msgs = ["HG: This is a fold of %d changesets." % 
> > len(allctx)]
> > -msgs += ["HG: Commit message of changeset %s.\n\n%s\n" 
> > %
> > - (c.rev(), c.description()) for c in allctx]
> > -else:
> > -msgs = [head.description()]
> > -commitopts['message'] =  "\n".join(msgs)
> 
> Can you put this logic move into a separate commit? The idea that this 
> logic can move down the file without affecting behavior is non-obvious.

I'll try to split it in the next version of this series.
> 
> >   commitopts['edit'] = True
> 
> Is this line now duplicated above and below the else? Why not remove the 
> if/else entirely then?
> >   
> >   # TODO: if the author and message are the same, don't create 
> > a new
> >   # hash. Right now we create a new hash because the date can be
> >   # different.
> > -newid, created = rewrite(repo, root, allctx, head,
> > - [root.p1().node(), root.p2().node()],
> > - commitopts=commitopts)
> > -if created:
> > -if p1.rev() in revs:
> > -newp1 = newid
> > -phases.retractboundary(repo, tr, targetphase, [newid])
> > -obsolete.createmarkers(repo, [(ctx, (repo[newid],))
> > -  for ctx in allctx])
> > +if opts['fold']:
> > +if commitopts['edit']:
> > +msgs = ["HG: This is a fold of %d changesets." %
> > +len(allctx)]
> > +msgs += ["HG: Commit message of changeset %s.\n\n%s\n" 
> > %
> > + (c.rev(), c.description()) for c in allctx]
> > +commitopts['message'] = "\n".join(msgs)
> > +
> > +newid, created = rewrite(repo, root, allctx, head,
> > + [root.p1().node(), 
> > root.p2().node()],
> > + commitopts=commitopts)
> > +if created:
> > +if p1.rev() in revs:
> > +newp1 = newid
> > +phases.retractboundary(repo, tr, targetphase, [newid])
> > +obsolete.createmarkers(repo, [(ctx, (repo[newid],))
> > +  for ctx in allctx])
> > +else:
> > +ui.status(_("nothing changed\n"))
> >   else:
> > -ui.status(_("nothing changed\n"))
> > +replacemap = {}
> > +# we need topological order
> > +allctx = sorted(allctx, key=lambda c: c.rev())
> > +for c in allctx:
> > +if commitopts['edit']:
> > +commitopts['message'] = \
> > +"HG: Commit message of changeset %s\n%s" %\
> > +(str(c), c.description())
> > +bases = [
> > +replacemap.get(c.p1().node(), c.p1().node()),
> > +replacemap.get(c.p2().node(), c.p2().node()),
> > +]
> > +newid, created = metarewrite(repo, c, bases,
> > + commitopts=commitopts)
> > +if created:
> > +replacemap[c.n

Re: [PATCH 1 of 4 evolve-ext] metaedit: add a helper function for just metadata rewrites

2016-12-05 Thread Mateusz Kwapich
Excerpts from Ryan McElroy's message of 2016-12-05 13:14:28 +:
> 
> On 11/16/16 7:56 PM, Mateusz Kwapich wrote:
> > # HG changeset patch
> > # User Mateusz Kwapich <mitran...@fb.com>
> > # Date 1479324110 0
> > #  Wed Nov 16 19:21:50 2016 +
> > # Branch stable
> > # Node ID 744c6acd84edf73ffdf505b9673b0383db727a0e
> > # Parent  727c7211c810d304ebf92b32db7ecf697ce46ac6
> > metaedit: add a helper function for just metadata rewrites
> >
> > It will be used by metaedit.
> >
> > diff --git a/hgext/evolve.py b/hgext/evolve.py
> > --- a/hgext/evolve.py
> > +++ b/hgext/evolve.py
> > @@ -907,6 +907,13 @@ def rewrite(repo, old, updates, head, ne
> >   finally:
> >   lockmod.release(tr, lock, wlock)
> >   
> > +def metarewrite(repo, old, newbases, commitopts):
> > +'''Like rewrite but affects only the changeset metadata.'''
> > +# TODO: reuse the manifest for speed
> 
> Your series that Augie just pushed will allow this, right?
Yes.

> 
> > +newid, created = rewrite(repo, old, [old], old, newbases,
> > + commitopts=commitopts)
> > +return newid, created
> > +
> >   class MergeFailure(error.Abort):
> >   pass
> >   
> > 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] posix: give the cached symlink a real target

2016-12-01 Thread Mateusz Kwapich
What Jun said. All the rest looks good to me.

Excerpts from Martijn Pieters's message of 2016-11-30 16:51:41 +:
> # HG changeset patch
> # User Martijn Pieters 
> # Date 1480523976 0
> #  Wed Nov 30 16:39:36 2016 +
> # Node ID 4053f60f77657f8f54afc72d00bf629b75d0b4b9
> # Parent  9e29d4e4e08b5996adda49cdd0b497d89e2b16ee
> posix: give the cached symlink a real target
> 
> The NamedTemporaryFile file is cleared up so checklink ends up as a dangling
> symlink, causing cp -r in tests to complain on both Solaris and OS X. Use
> a permanent file instead when there is a .hg/cache directory.
> 
> diff --git a/mercurial/posix.py b/mercurial/posix.py
> --- a/mercurial/posix.py
> +++ b/mercurial/posix.py
> @@ -231,10 +231,18 @@
>  cachedir = None
>  name = tempfile.mktemp(dir=checkdir, prefix='checklink-')
>  try:
> -fd = tempfile.NamedTemporaryFile(dir=checkdir,
> - prefix='hg-checklink-')
> +fd = None
> +if cachedir is None:
> +fd = tempfile.NamedTemporaryFile(dir=checkdir,
> + prefix='hg-checklink-')
> +target = os.path.basename(fd.name)
> +else:
> +# create a fixed file to link to; doesn't matter if it
> +# already exists.
> +target = 'checklink-target'
> +open(os.path.join(cachedir, target), 'w')
>  try:
> -os.symlink(os.path.basename(fd.name), name)
> +os.symlink(target, name)
>  if cachedir is None:
>  os.unlink(name)
>  else:
> @@ -249,7 +257,8 @@
>  continue
>  raise
>  finally:
> -fd.close()
> +if fd is not None:
> +fd.close()
>  except AttributeError:
>  return False
>  except OSError as inst:
> diff --git a/tests/test-clone.t b/tests/test-clone.t
> --- a/tests/test-clone.t
> +++ b/tests/test-clone.t
> @@ -33,6 +33,7 @@
>branch2-served
>checkisexec
>checklink
> +  checklink-target
>checknoexec
>rbc-names-v1
>rbc-revs-v1
> @@ -50,6 +51,7 @@
>branch2-served
>checkisexec
>checklink
> +  checklink-target
>  
>$ cat a
>a
> diff --git a/tests/test-hardlinks.t b/tests/test-hardlinks.t
> --- a/tests/test-hardlinks.t
> +++ b/tests/test-hardlinks.t
> @@ -212,6 +212,8 @@
>2 r4/.hg/branch
>2 r4/.hg/cache/branch2-served
>2 r4/.hg/cache/checkisexec
> +  3 r4/.hg/cache/checklink (?)
> +  ? r4/.hg/cache/checklink-target (glob)
>2 r4/.hg/cache/checknoexec
>2 r4/.hg/cache/rbc-names-v1
>2 r4/.hg/cache/rbc-revs-v1
> @@ -250,6 +252,7 @@
>1 r4/.hg/branch
>2 r4/.hg/cache/branch2-served
>2 r4/.hg/cache/checkisexec
> +  2 r4/.hg/cache/checklink-target
>2 r4/.hg/cache/checknoexec
>2 r4/.hg/cache/rbc-names-v1
>2 r4/.hg/cache/rbc-revs-v1
> diff --git a/tests/test-tags.t b/tests/test-tags.t
> --- a/tests/test-tags.t
> +++ b/tests/test-tags.t
> @@ -674,6 +674,7 @@
>branch2-served
>checkisexec
>checklink
> +  checklink-target
>hgtagsfnodes1
>rbc-names-v1
>rbc-revs-v1
> @@ -700,6 +701,7 @@
>branch2-served
>checkisexec
>checklink
> +  checklink-target
>hgtagsfnodes1
>rbc-names-v1
>rbc-revs-v1

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


Re: [PATCH] fsmonitor: be robust in the face of bad state

2016-11-25 Thread Mateusz Kwapich
The logic looks correct. It definitely makes the fsmonitor more robust.

Excerpts from Simon Farnsworth's message of 2016-11-25 07:30:53 -0800:
> # HG changeset patch
> # User Simon Farnsworth 
> # Date 1480087846 28800
> #  Fri Nov 25 07:30:46 2016 -0800
> # Node ID 0ca34f1b83da754246ee33e01c4f7d6652061f5d
> # Parent  a3163433647108b7bec8fc45896db1c20b18ab21
> fsmonitor: be robust in the face of bad state
> 
> fsmonitor could write out bad state if interrupted part way through, and
> would then crash when it tried to read it back in.
> 
> Make both sides of the operation more robust - reading state should fail
> cleanly, and we can use atomictemp to write out cleanly as the file is
> small. Between the two, we shouldn't crash with an IndexError any more.
> 
> diff --git a/hgext/fsmonitor/state.py b/hgext/fsmonitor/state.py
> --- a/hgext/fsmonitor/state.py
> +++ b/hgext/fsmonitor/state.py
> @@ -59,6 +59,12 @@
>  state = file.read().split('\0')
>  # state = hostname\0clock\0ignorehash\0 + list of files, each
>  # followed by a \0
> +if len(state) < 3:
> +self._ui.log(
> +'fsmonitor', 'fsmonitor: state file truncated (expected '
> +'3 chunks, found %d), nuking state\n' % len(state))
> +self.invalidate()
> +return None, None, None
>  diskhostname = state[0]
>  hostname = socket.gethostname()
>  if diskhostname != hostname:
> @@ -85,12 +91,12 @@
>  return
>  
>  try:
> -file = self._opener('fsmonitor.state', 'wb')
> +file = self._opener('fsmonitor.state', 'wb', atomictemp=True)
>  except (IOError, OSError):
>  self._ui.warn(_("warning: unable to write out fsmonitor 
> state\n"))
>  return
>  
> -try:
> +with file:
>  file.write(struct.pack(_versionformat, _version))
>  file.write(socket.gethostname() + '\0')
>  file.write(clock + '\0')
> @@ -98,8 +104,6 @@
>  if notefiles:
>  file.write('\0'.join(notefiles))
>  file.write('\0')
> -finally:
> -file.close()
>  
>  def invalidate(self):
>  try:

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


Re: [PATCH] test-rebase-base: add test cases about multiple branches with merges

2016-11-25 Thread Mateusz Kwapich

Please, forgive my email client (or my misuse of it). The proper
contents of my last reply are following:

LGTM

Excerpts from Jun Wu's message of 2016-11-25 12:49:01 +:
> # HG changeset patch
> # User Jun Wu 
> # Date 1480077830 0
> #  Fri Nov 25 12:43:50 2016 +
> # Node ID c0a9c4c2c6ae2a779c060dc2424942099d7c984d
> # Parent  fd4175ec0f4e9bd68f4bfdcd601e11d77499d486
> # Available At https://bitbucket.org/quark-zju/hg-draft 
> #  hg pull https://bitbucket.org/quark-zju/hg-draft  -r 
> c0a9c4c2c6ae
> test-rebase-base: add test cases about multiple branches with merges
> 
> This helps clarify the current behavior. When a merge changeset is selected
> in --base directly, only one path will be chosen. The behavior remains the
> same before and after "rebase: calculate ancestors for --base separately
> (issue5420)".
> 
> diff --git a/tests/test-rebase-base.t b/tests/test-rebase-base.t
> --- a/tests/test-rebase-base.t
> +++ b/tests/test-rebase-base.t
> @@ -93,2 +93,169 @@ Mixed rebasable and non-rebasable bases 
>nothing to rebase
>[1]
> +
> +  $ cd ..
> +
> +Multiple branches with merges:
> +
> +  $ hg init b
> +  $ cd b
> +
> +  $ hg debugdrawdag < +  > h   g
> +  > |  /|
> +  > | f |
> +  > |/ /
> +  > | e
> +  > |/  d
> +  > |  /|
> +  > | c |
> +  > |/ /
> +  > | b
> +  > |/
> +  > a
> +  > EOS
> +
> +  $ hg rebase -b b+f -d h
> +  rebasing 1:488e1b7e7341 "b" (b)
> +  rebasing 6:0c088b72e768 "d" (d)
> +  rebasing 4:0e9bbb7dd767 "f" (f)
> +  rebasing 7:9bdc802fd225 "g" (g tip)
> +  saved backup bundle to 
> $TESTTMP/b/.hg/strip-backup/0e9bbb7dd767-ff8b132b-backup.hg (glob)
> +  $ hg tglog
> +  o7: g
> +  |\
> +  | o  6: f
> +  | |
> +  | | o5: d
> +  | | |\
> +  | +---o  4: b
> +  | | |
> +  | o |  3: h
> +  | | |
> +  o | |  2: e
> +  |/ /
> +  | o  1: c
> +  |/
> +  o  0: a
> +  
> +  $ cd ..
> +
> +Multiple branches with multiple merges:
> +
> +  $ hg init c
> +  $ cd c
> +
> +  $ hg debugdrawdag <<'EOS'
> +  > j i
> +  > | |
> +  > | h
> +  > |/|
> +  > |   g | k
> +  > |  /| |/
> +  > | f | |
> +  > |/ /  |
> +  > | e  /
> +  > |/  d
> +  > |  /|
> +  > | c |
> +  > |/ /
> +  > | b
> +  > |/
> +  > a
> +  > EOS
> +  $ hg rebase -b b+f -d j
> +  rebasing 1:488e1b7e7341 "b" (b)
> +  rebasing 4:0e9bbb7dd767 "f" (f)
> +  rebasing 6:0c088b72e768 "d" (d)
> +  rebasing 9:91358dadd39b "k" (k)
> +  rebasing 7:9bdc802fd225 "g" (g)
> +  rebasing 8:91a34cc1f2a7 "h" (h)
> +  rebasing 10:f0d5af4cc88e "i" (i tip)
> +  saved backup bundle to 
> $TESTTMP/c/.hg/strip-backup/0e9bbb7dd767-6f921be4-backup.hg (glob)
> +  $ hg tglog
> +  o  10: i
> +  |
> +  o9: h
> +  |\
> +  | o8: g
> +  | |\
> +  +-o  7: k
> +  | | |
> +  o | |6: d
> +  |\ \ \
> +  | | | o  5: f
> +  | | | |
> +  | o---+  4: b
> +  |  / /
> +  | | o  3: j
> +  | | |
> +  | o |  2: e
> +  | |/
> +  o /  1: c
> +  |/
> +  o  0: a
> +  
> +
> +  $ cd ..
> +
> +Pick merge changesets in -b, only one of the two parents is selected:
> +
> +  $ hg init d
> +  $ cd d
> +
> +  $ hg debugdrawdag <<'EOS'
> +  >   dest  n
> +  > |   |\
> +  > m   |   g d
> +  > |\  |  /|
> +  > | l | f |
> +  >  \ \|/ /
> +  >   k | e
> +  > i  \|/  d
> +  > |\  |  /|
> +  > | h | c |
> +  >  \ \|/ /
> +  >   j | b
> +  >\|/
> +  > a
> +  > EOS
> +
> +  $ hg rebase -b n+i -d dest
> +  rebasing 6:e22eece29d69 "h" (h)
> +  rebasing 12:edd90b885ce0 "i" (i)
> +  rebasing 1:488e1b7e7341 "b" (b)
> +  rebasing 10:0c088b72e768 "d" (d)
> +  rebasing 14:f188fc87af23 "n" (n tip)
> +  saved backup bundle to 
> $TESTTMP/d/.hg/strip-backup/e22eece29d69-0eeca84a-backup.hg (glob)
> +  $ hg tglog
> +  o14: n
> +  |\
> +  | o13: d
> +  | |\
> +  | | o  12: b
> +  | | |
> +  | | | o11: i
> +  | | | |\
> +  | | +---o  10: h
> +  | | | |
> +  | | | | o9: m
> +  | | | | |\
> +  o \ \ \ \ \8: g
> +  |\ \ \ \ \ \
> +  | | | | | | o  7: l
> +  | | | | | | |
> +  | | | | | o |  6: k
> +  | | | | | |/
> +  | | | | o /  5: j
> +  | | | | |/
> +  | o-+  4: f
> +  |  / / /
> +  o-+  3: e
> +   / / /
> +  | o /  2: dest
> +  | |/
> +  o /  1: c
> +  |/
> +  o  0: a
> +  
> +  $ cd ..

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


Re: [PATCH] test-rebase-base: add test cases about multiple branches with merges

2016-11-25 Thread Mateusz Kwapich


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


Re: [PATCH V2] evolve: improve error message if unstable changes are disallowed

2016-11-25 Thread Mateusz Kwapich
LGTM, I suppose that timeless may also want to review it before it's
queued.

Excerpts from Pulkit Goyal's message of 2016-11-24 23:13:48 +0530:
> # HG changeset patch
> # User Pulkit Goyal <7895pul...@gmail.com>
> # Date 1479915042 -19800
> #  Wed Nov 23 21:00:42 2016 +0530
> # Node ID 920d5946d13339d9cf4828f678fb55063cd8
> # Parent  cb2bac3253fbd52894ffcb4719a148fe6a3da38b
> evolve: improve error message if unstable changes are disallowed
> 
> I saw a question on stackoverflow why evolve reports something like cannot
> fold chain not ending with head. Even I was confused the first time about the
> behavior. The error message can be improved to avoid confusion to people who
> are unaware about the config in future.
> 
> diff -r cb2bac3253fb -r 920d5946d133 hgext/evolve.py
> --- a/hgext/evolve.pyWed Nov 02 18:56:44 2016 +0100
> +++ b/hgext/evolve.pyWed Nov 23 21:00:42 2016 +0530
> @@ -2514,7 +2514,8 @@
>  raise error.Abort('nothing to prune')
>  
>  if _disallowednewunstable(repo, revs):
> -raise error.Abort(_("cannot prune in the middle of a stack"))
> +raise error.Abort(_("cannot prune in the middle of a stack"),
> +hint = _("new unstable changesets are not allowed"))
>  
>  # defines successors changesets
>  sucs = scmutil.revrange(repo, succs)
> @@ -3234,8 +3235,9 @@
>  newunstable = _disallowednewunstable(repo, revs)
>  if newunstable:
>  raise error.Abort(
> -_('cannot edit commit information in the middle of a 
> stack'),
> -hint=_('%s will be affected') % 
> repo[newunstable.first()])
> +_('cannot edit commit information in the middle of a '\
> +'stack'), hint=_('%s will become unstable and new 
> unstable'\
> +' changes are not allowed') % repo[newunstable.first()])
>  root = head = repo[revs.first()]
>  
>  wctx = repo[None]
> @@ -3299,7 +3301,8 @@
>  head = repo[heads.first()]
>  if _disallowednewunstable(repo, revs):
>  raise error.Abort(_("cannot fold chain not ending with a head "\
> -"or with branching"))
> +"or with branching"), hint = _("new unstable"\
> +" changesets are not allowed"))
>  return root, head
>  
>  def _disallowednewunstable(repo, revs):
> diff -r cb2bac3253fb -r 920d5946d133 tests/test-evolve.t
> --- a/tests/test-evolve.tWed Nov 02 18:56:44 2016 +0100
> +++ b/tests/test-evolve.tWed Nov 23 21:00:42 2016 +0530
> @@ -1301,9 +1301,11 @@
>created new head
>$ hg prune '26 + 27'
>abort: cannot prune in the middle of a stack
> +  (new unstable changesets are not allowed)
>[255]
>$ hg prune '19::28'
>abort: cannot prune in the middle of a stack
> +  (new unstable changesets are not allowed)
>[255]
>$ hg prune '26::'
>3 changesets pruned
> @@ -1338,9 +1340,11 @@
>  
>$ hg fold --exact "19 + 18"
>abort: cannot fold chain not ending with a head or with branching
> +  (new unstable changesets are not allowed)
>[255]
>$ hg fold --exact "18::29"
>abort: cannot fold chain not ending with a head or with branching
> +  (new unstable changesets are not allowed)
>[255]
>$ hg fold --exact "19::"
>2 changesets folded
> @@ -1483,10 +1487,11 @@
>  check that metaedit respects allowunstable
>$ hg metaedit '.^' --config 'experimental.evolution=createmarkers, 
> allnewcommands'
>abort: cannot edit commit information in the middle of a stack
> -  (c904da5245b0 will be affected)
> +  (c904da5245b0 will become unstable and new unstable changes are not 
> allowed)
>[255]
>$ hg metaedit '18::20' --fold --config 
> 'experimental.evolution=createmarkers, allnewcommands'
>abort: cannot fold chain not ending with a head or with branching
> +  (new unstable changesets are not allowed)
>[255]
>$ hg metaedit --user foobar
>0 files updated, 0 files merged, 0 files removed, 0 files unresolved

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


Re: [PATCH 3 of 3 V3] memctx: allow the memlightctx thats reusing the manifest node

2016-11-25 Thread Mateusz Kwapich
I like the relocate idea. It would be an useful abstractions - we have a
lot of things that are rewriting one commit at a time.

Excerpts from Jun Wu's message of 2016-11-22 22:18:59 +:
> Excerpts from Augie Fackler's message of 2016-11-21 18:26:16 -0500:
> > Also, could histedit be updated to use this for 'mess' actions
> > perhaps? Probably not easy, but if it is I'd love to see an in-tree
> > client of this class. Can you take a look?
> 
> It does not work if "mess" happens after other csets creating new manifests.
> 
> Instead of making it a special for histedit. I'm always more interested in
> a general purpose lower-level function doing some kind of "rebase" in core,
> that smartly deals with this case and handles bookmark and dirstate parent
> movements (and obsmarker creation) automatically.
> 
> There are currently multiple places reinventing the logic over and over:
>   - absorb / collate
>   - rebase
>   - histedit
>   - evolve
> 
> And once we have this API in core, we can migrate the above commands to use
> it.
> 
> I'm thinking about something like:
> 
>   relocate(origctx, newparents, fileoverrides, metaoverrides, obsoleted,
>transaction)
>   # origctx: context
>   # newparents: [node]
>   # fileoverrides: abstract dict, keys: path, value: (content, mode, renamed)
>   # metaoverrides: dict with defined set of keys. to override metadata like
>   #commit message etc. 
>   # obsoleted: bool, could also be a non-bool to provide more metadata
> 
> Then relocate will choose what *ctx to use internally. For absorb,
> metaoverrides is empty, for metaedit, fileoverrides is empty. For rebase,
> both are empty. For histedit, depends.
> 
> Two things I'd like to make sure they are considered while developing the
> API:
>   
>   1. Giant files friendly - if we need to commit two giant files, we don't
>  need to keep both of them in memory. "fileoverrides" being an abstract
>  dict should solve this.
>   2. Commit hooks friendly - if any commit hook is requiring a working copy,
>  fallback to the slow path that writs the disk. (this could be the most
>  complex part of the implementation, but I guess crecord-alike already
>  has similar things). We also need to have ways to mark a commit hook as
>  "do not require on-disk working copy" - could be a config option, or
>  checking some magic string in the hook itself.
> 
> If we decide to go this way, I could help start some more formal discussion
> (a plan page or so) - hopefully we can collect enough corner cases that
> need support, have a final decision about the API soon.
> 
> CC smf because it could be related to your memctx work.

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


Re: [PATCH shelve-ext v2] shelve: make --keep option survive user intevention (issue5431)

2016-11-24 Thread Mateusz Kwapich
LGTM

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


Re: [PATCH shelve-ext] shelve: make --keep option survive user intevention (issue5431)

2016-11-23 Thread Mateusz Kwapich
One less footgun in unshelve. Nice!

Excerpts from Kostia Balytskyi's message of 2016-11-23 10:51:47 -0800:
> diff --git a/hgext/shelve.py b/hgext/shelve.py
> --- a/hgext/shelve.py
> +++ b/hgext/shelve.py
> @@ -782,6 +787,7 @@ def _dounshelve(ui, repo, *shelved, **op
>  
>  try:
>  state = shelvedstate.load(repo)
> +opts['keep'] = opts.get('keep') or state.keep
>  except IOError as err:
>  if err.errno != errno.ENOENT:
>  raise
Respecting the "hg unshelve --continue --keep" while ignoring the
"hg unshelve --continue --no-keep" seems confusing to me. Either
we should always respoect the options provided to continue or we
should make them invalid.

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


Re: [PATCH evolve-ext] evolve: improve error message if unstable changes are disallowed

2016-11-23 Thread Mateusz Kwapich
Excerpts from Pulkit Goyal's message of 2016-11-23 21:06:29 +0530:
> # HG changeset patch
> # User Pulkit Goyal <7895pul...@gmail.com>
> # Date 1479915042 -19800
> #  Wed Nov 23 21:00:42 2016 +0530
> # Node ID 32083f1f0c67341e5b4c22e880b70ccc4e0fc088
> # Parent  cb2bac3253fbd52894ffcb4719a148fe6a3da38b
> evolve: improve error message if unstable changes are disallowed
> 
> I saw a question on stackoverflow why evolve reports something like cannot
> fold chain not ending with head. Even I was confused the first time about the
> behavior. The error message can be improved to avoid confusion to people who
> are unaware about the config in future.

That sounds like a very useful information. It sucks that the errors
have newlines in them. I think we can avoid it - see my inline comments.

> 
> diff -r cb2bac3253fb -r 32083f1f0c67 hgext/evolve.py
> --- a/hgext/evolve.pyWed Nov 02 18:56:44 2016 +0100
> +++ b/hgext/evolve.pyWed Nov 23 21:00:42 2016 +0530
> @@ -2514,7 +2514,8 @@
>  raise error.Abort('nothing to prune')
>  
>  if _disallowednewunstable(repo, revs):
> -raise error.Abort(_("cannot prune in the middle of a stack"))
> +raise error.Abort(_("cannot prune in the middle of a stack\n"\
> +"new unstable changesets are not allowed"))
This error doesn't have hint - maybe we could use it for that purpose.
>  
>  # defines successors changesets
>  sucs = scmutil.revrange(repo, succs)
> @@ -3234,7 +3235,8 @@
>  newunstable = _disallowednewunstable(repo, revs)
>  if newunstable:
>  raise error.Abort(
> -_('cannot edit commit information in the middle of a 
> stack'),
> +_('cannot edit commit information in the middle of a 
> stack\n'\
> +'new unstable changesets are not allowed'),
>  hint=_('%s will be affected') % 
> repo[newunstable.first()])
I would change the hint to say something along:
'%s would become unstable but new unstable commits are not allowed'
>  root = head = repo[revs.first()]
>  
> @@ -3299,7 +3301,8 @@
>  head = repo[heads.first()]
>  if _disallowednewunstable(repo, revs):
>  raise error.Abort(_("cannot fold chain not ending with a head "\
> -"or with branching"))
> +"or with branching\nnew unstable changesets are"\
> +" not allowed"))
I would move this to hint param.
>  return root, head
>  
>  def _disallowednewunstable(repo, revs):
> diff -r cb2bac3253fb -r 32083f1f0c67 tests/test-evolve.t
> --- a/tests/test-evolve.tWed Nov 02 18:56:44 2016 +0100
> +++ b/tests/test-evolve.tWed Nov 23 21:00:42 2016 +0530
> @@ -1301,9 +1301,11 @@
>created new head
>$ hg prune '26 + 27'
>abort: cannot prune in the middle of a stack
> +  new unstable changesets are not allowed
>[255]
>$ hg prune '19::28'
>abort: cannot prune in the middle of a stack
> +  new unstable changesets are not allowed
>[255]
>$ hg prune '26::'
>3 changesets pruned
> @@ -1338,9 +1340,11 @@
>  
>$ hg fold --exact "19 + 18"
>abort: cannot fold chain not ending with a head or with branching
> +  new unstable changesets are not allowed
>[255]
>$ hg fold --exact "18::29"
>abort: cannot fold chain not ending with a head or with branching
> +  new unstable changesets are not allowed
>[255]
>$ hg fold --exact "19::"
>2 changesets folded
> @@ -1483,10 +1487,12 @@
>  check that metaedit respects allowunstable
>$ hg metaedit '.^' --config 'experimental.evolution=createmarkers, 
> allnewcommands'
>abort: cannot edit commit information in the middle of a stack
> +  new unstable changesets are not allowed
>(c904da5245b0 will be affected)
>[255]
>$ hg metaedit '18::20' --fold --config 
> 'experimental.evolution=createmarkers, allnewcommands'
>abort: cannot fold chain not ending with a head or with branching
> +  new unstable changesets are not allowed
>[255]
>$ hg metaedit --user foobar
>0 files updated, 0 files merged, 0 files removed, 0 files unresolved

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


Re: [PATCH 3 of 3 V3] memctx: allow the memlightctx thats reusing the manifest node

2016-11-22 Thread Mateusz Kwapich
Excerpts from Augie Fackler's message of 2016-11-21 18:26:16 -0500:
> On Mon, Nov 21, 2016 at 08:13:32AM -0800, Mateusz Kwapich wrote:
> > # HG changeset patch
> > # User Mateusz Kwapich <mitran...@fb.com>
> > # Date 1479744581 28800
> > #  Mon Nov 21 08:09:41 2016 -0800
> > # Node ID 4af70f21264ac8e52d9b218080bbc96ee5505606
> > # Parent  4a0824bead3ba5980bd8528937fba5f7bb31ba9f
> > memctx: allow the memlightctx thats reusing the manifest node
> >
> > When we have a lot of files writing a new manifest revision can be 
> > expensive.
> > This commit adds a possibility for memctx to reuse a manifest from a 
> > different
> > commit. This can be beneficial for commands that are creating metadata 
> > changes
> > without any actual files changed like "hg metaedit" in evolve extension.
> >
> > I will send the change for evolve that leverages this once this is accepted.
> >
> > diff --git a/mercurial/context.py b/mercurial/context.py
> > --- a/mercurial/context.py
> > +++ b/mercurial/context.py
> > @@ -1975,3 +1975,101 @@ class memfilectx(committablefilectx):
> >  def write(self, data, flags):
> >  """wraps repo.wwrite"""
> >  self._data = data
> > +
> > +class memlightctx(committablectx):
> 
> Could this instead be called "metadataonlyctx"? No need to do a
> resend, if you're happy with my bikeshed color I can fix it in flight.
> 
> Also, could histedit be updated to use this for 'mess' actions
> perhaps? Probably not easy, but if it is I'd love to see an in-tree
> client of this class. Can you take a look?

sure, feel free to rename it.

I can look into hooking this up into the histedit.  It won't be helpful
in general case of message editing but it can be used when the previous
actions were all "pick" or "mess" and it will be much faster in that
case.

> 
> > +"""Like memctx but it's reusing the manifest of different commit.
> > +Intended to be used by lightweight operations that are creating
> > +metadata-only changes.
> > +
> > +Revision information is supplied at initialization time.  'repo' is the
> > +current localrepo, 'ctx' is original revision which manifest we're 
> > reuisng
> > +'parents' is a sequence of two parent revisions identifiers (pass None 
> > for
> > +every missing parent), 'text' is the commit.
> > +
> > +user receives the committer name and defaults to current repository
> > +username, date is the commit date in any format supported by
> > +util.parsedate() and defaults to current date, extra is a dictionary of
> > +metadata or is left empty.
> > +"""
> > +def __new__(cls, repo, path, *args, **kwargs):
> > +return super(memlightctx, cls).__new__(cls, repo)
> > +
> > +def __init__(self, repo, originalctx, parents, text, user=None, 
> > date=None,
> > + extra=None, editor=False):
> > +super(memlightctx, self).__init__(repo, text, user, date, extra)
> > +self._rev = None
> > +self._node = None
> > +self._originalctx = originalctx
> > +self._manifestnode = originalctx.manifestnode()
> > +parents = [(p or nullid) for p in parents]
> > +p1, p2 = self._parents = [changectx(self._repo, p) for p in 
> > parents]
> > +
> > +# sanity check to ensure that the reused manifest parents are
> > +# manifests of our commit parents
> > +mp1, mp2 = self.manifestctx().parents
> > +if p1 != nullid and p1.manifestctx().node() != mp1:
> > +raise RuntimeError('can\'t reuse the manifest: '
> > +   'its p1 doesn\'t match the new ctx p1')
> > +if p2 != nullid and p2.manifestctx().node() != mp2:
> > +raise RuntimeError('can\'t reuse the manifest: '
> > +   'its p2 doesn\'t match the new ctx p2')
> > +
> > +self._files = originalctx.files()
> > +self.substate = {}
> > +
> > +if extra:
> > +self._extra = extra.copy()
> > +else:
> > +self._extra = {}
> > +
> > +if self._extra.get('branch', '') == '':
> > +self._extra['branch'] = 'default'
> > +
> > +if editor:
> > +self._text = editor(self._repo, self, [])
> > +self._repo.savecommitmessage(self._text)
> > +
> > +def manifestnode(self):
&g

[PATCH 2 of 3 V3] localrepo: make it possible to reuse manifest when commiting context

2016-11-21 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1479409155 28800
#  Thu Nov 17 10:59:15 2016 -0800
# Node ID 4a0824bead3ba5980bd8528937fba5f7bb31ba9f
# Parent  7dfd4c184ee087f2c05e1bdae8a10ccefbff7a92
localrepo: make it possible to reuse manifest when commiting context

This makes the commit function understand the context that's reusing manifest.

diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1695,7 +1695,11 @@ class localrepository(object):
 tr = self.transaction("commit")
 trp = weakref.proxy(tr)
 
-if ctx.files():
+if ctx.manifestnode():
+# reuse an existing manifest revision
+mn = ctx.manifestnode()
+files = ctx.files()
+elif ctx.files():
 m1ctx = p1.manifestctx()
 m2ctx = p2.manifestctx()
 mctx = m1ctx.copy()
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 3 V3] manifest: expose the parents() method

2016-11-21 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1479409155 28800
#  Thu Nov 17 10:59:15 2016 -0800
# Node ID 7dfd4c184ee087f2c05e1bdae8a10ccefbff7a92
# Parent  96f2f50d923f94c23999df198ff16409e7539af8
manifest: expose the parents() method

diff --git a/mercurial/manifest.py b/mercurial/manifest.py
--- a/mercurial/manifest.py
+++ b/mercurial/manifest.py
@@ -1381,6 +1381,10 @@ class manifestctx(object):
 memmf._manifestdict = self.read().copy()
 return memmf
 
+@propertycache
+def parents(self):
+return self._revlog().parents(self._node)
+
 def read(self):
 if not self._data:
 if self._node == revlog.nullid:
@@ -1515,6 +1519,10 @@ class treemanifestctx(object):
 memmf._treemanifest = self.read().copy()
 return memmf
 
+@propertycache
+def parents(self):
+return self._revlog().parents(self._node)
+
 def readdelta(self, shallow=False):
 '''Returns a manifest containing just the entries that are present
 in this manifest, but not in its p1 manifest. This is efficient to read
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 3 of 3 V3] memctx: allow the memlightctx thats reusing the manifest node

2016-11-21 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1479744581 28800
#  Mon Nov 21 08:09:41 2016 -0800
# Node ID 4af70f21264ac8e52d9b218080bbc96ee5505606
# Parent  4a0824bead3ba5980bd8528937fba5f7bb31ba9f
memctx: allow the memlightctx thats reusing the manifest node

When we have a lot of files writing a new manifest revision can be expensive.
This commit adds a possibility for memctx to reuse a manifest from a different
commit. This can be beneficial for commands that are creating metadata changes
without any actual files changed like "hg metaedit" in evolve extension.

I will send the change for evolve that leverages this once this is accepted.

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1975,3 +1975,101 @@ class memfilectx(committablefilectx):
 def write(self, data, flags):
 """wraps repo.wwrite"""
 self._data = data
+
+class memlightctx(committablectx):
+"""Like memctx but it's reusing the manifest of different commit.
+Intended to be used by lightweight operations that are creating
+metadata-only changes.
+
+Revision information is supplied at initialization time.  'repo' is the
+current localrepo, 'ctx' is original revision which manifest we're reuisng
+'parents' is a sequence of two parent revisions identifiers (pass None for
+every missing parent), 'text' is the commit.
+
+user receives the committer name and defaults to current repository
+username, date is the commit date in any format supported by
+util.parsedate() and defaults to current date, extra is a dictionary of
+metadata or is left empty.
+"""
+def __new__(cls, repo, path, *args, **kwargs):
+return super(memlightctx, cls).__new__(cls, repo)
+
+def __init__(self, repo, originalctx, parents, text, user=None, date=None,
+ extra=None, editor=False):
+super(memlightctx, self).__init__(repo, text, user, date, extra)
+self._rev = None
+self._node = None
+self._originalctx = originalctx
+self._manifestnode = originalctx.manifestnode()
+parents = [(p or nullid) for p in parents]
+p1, p2 = self._parents = [changectx(self._repo, p) for p in parents]
+
+# sanity check to ensure that the reused manifest parents are
+# manifests of our commit parents
+mp1, mp2 = self.manifestctx().parents
+if p1 != nullid and p1.manifestctx().node() != mp1:
+raise RuntimeError('can\'t reuse the manifest: '
+   'its p1 doesn\'t match the new ctx p1')
+if p2 != nullid and p2.manifestctx().node() != mp2:
+raise RuntimeError('can\'t reuse the manifest: '
+   'its p2 doesn\'t match the new ctx p2')
+
+self._files = originalctx.files()
+self.substate = {}
+
+if extra:
+self._extra = extra.copy()
+else:
+self._extra = {}
+
+if self._extra.get('branch', '') == '':
+self._extra['branch'] = 'default'
+
+if editor:
+self._text = editor(self._repo, self, [])
+self._repo.savecommitmessage(self._text)
+
+def manifestnode(self):
+return self._manifestnode
+
+@propertycache
+def _manifestctx(self):
+return self._repo.manifestlog[self._manifestnode]
+
+def filectx(self, path, filelog=None):
+return self._originalctx.filectx(path, filelog=filelog)
+
+def commit(self):
+"""commit context to the repo"""
+return self._repo.commitctx(self)
+
+@property
+def _manifest(self):
+return self._originalctx.manifest()
+
+@propertycache
+def _status(self):
+"""Calculate exact status from ``files`` specified in the ``origctx``
+and parents manifests.
+"""
+man1 = self.p1().manifest()
+p2 = self._parents[1]
+# "1 < len(self._parents)" can't be used for checking
+# existence of the 2nd parent, because "memlightctx._parents" is
+# explicitly initialized by the list, of which length is 2.
+if p2.node() != nullid:
+man2 = p2.manifest()
+managing = lambda f: f in man1 or f in man2
+else:
+managing = lambda f: f in man1
+
+modified, added, removed = [], [], []
+for f in self._files:
+if not managing(f):
+added.append(f)
+elif self[f]:
+modified.append(f)
+else:
+removed.append(f)
+
+return scmutil.status(modified, added, removed, [], [], [], [])
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 3 of 3 V2] memctx: allow the memlightctx thats reusing the manifest node

2016-11-17 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1479410909 28800
#  Thu Nov 17 11:28:29 2016 -0800
# Node ID 0d41689f79cf22d8761dd6af9cb5da86008afe94
# Parent  4a0824bead3ba5980bd8528937fba5f7bb31ba9f
memctx: allow the memlightctx thats reusing the manifest node

When we have a lot of files writing a new manifest revision can be expensive.
This commit adds a possibility for memctx to reuse a manifest from a different
commit. This can be beneficial for commands that are creating metadata changes
without any actual files changed like "hg metaedit" in evolve extension.

I will send the change for evolve that leverages this once this is accepted.

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1975,3 +1975,99 @@ class memfilectx(committablefilectx):
 def write(self, data, flags):
 """wraps repo.wwrite"""
 self._data = data
+
+class memlightctx(committablectx):
+"""Like memctx but it's reusing the manifest of different commit.
+Intended to be used by lightweight operations that are creating
+metadata-only changes.
+
+Revision information is supplied at initialization time.  'repo' is the
+current localrepo, 'ctx' is original revision which manifest we're reuisng
+'parents' is a sequence of two parent revisions identifiers (pass None for
+every missing parent), 'text' is the commit.
+
+user receives the committer name and defaults to current repository
+username, date is the commit date in any format supported by
+util.parsedate() and defaults to current date, extra is a dictionary of
+metadata or is left empty.
+"""
+def __new__(cls, repo, path, *args, **kwargs):
+return super(memlightctx, cls).__new__(cls, repo)
+
+def __init__(self, repo, originalctx, parents, text, user=None, date=None,
+ extra=None, editor=False):
+super(memlightctx, self).__init__(repo, text, user, date, extra)
+self._rev = None
+self._node = None
+self._originalctx = originalctx
+self._manifestnode = originalctx.manifestnode()
+parents = [(p or nullid) for p in parents]
+p1, p2 = self._parents = [changectx(self._repo, p) for p in parents]
+
+# sanity check to ensure that the reused manifest parents are
+# manifests of our commit parents
+mp1, mp2 = self.manifestctx().parents
+if p1 != nullid and p1.manifestctx().node() != mp1:
+raise
+if p2 != nullid and p2.manifestctx().node() != mp2:
+raise
+
+self._files = originalctx.files()
+self.substate = {}
+
+if extra:
+self._extra = extra.copy()
+else:
+self._extra = {}
+
+if self._extra.get('branch', '') == '':
+self._extra['branch'] = 'default'
+
+if editor:
+self._text = editor(self._repo, self, [])
+self._repo.savecommitmessage(self._text)
+
+def manifestnode(self):
+return self._manifestnode
+
+@propertycache
+def _manifestctx(self):
+return self._repo.manifestlog[self._manifestnode]
+
+def filectx(self, path, filelog=None):
+return self._originalctx.filectx(path, filelog=filelog)
+
+def commit(self):
+"""commit context to the repo"""
+return self._repo.commitctx(self)
+
+@property
+def _manifest(self):
+return self._originalctx.manifest()
+
+@propertycache
+def _status(self):
+"""Calculate exact status from ``files`` specified in the ``origctx``
+and parents manifests.
+"""
+man1 = self.p1().manifest()
+p2 = self._parents[1]
+# "1 < len(self._parents)" can't be used for checking
+# existence of the 2nd parent, because "memlightctx._parents" is
+# explicitly initialized by the list, of which length is 2.
+if p2.node() != nullid:
+man2 = p2.manifest()
+managing = lambda f: f in man1 or f in man2
+else:
+managing = lambda f: f in man1
+
+modified, added, removed = [], [], []
+for f in self._files:
+if not managing(f):
+added.append(f)
+elif self[f]:
+modified.append(f)
+else:
+removed.append(f)
+
+return scmutil.status(modified, added, removed, [], [], [], [])
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 3 V2] manifest: expose the parents() method

2016-11-17 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1479409155 28800
#  Thu Nov 17 10:59:15 2016 -0800
# Node ID 7dfd4c184ee087f2c05e1bdae8a10ccefbff7a92
# Parent  96f2f50d923f94c23999df198ff16409e7539af8
manifest: expose the parents() method

diff --git a/mercurial/manifest.py b/mercurial/manifest.py
--- a/mercurial/manifest.py
+++ b/mercurial/manifest.py
@@ -1381,6 +1381,10 @@ class manifestctx(object):
 memmf._manifestdict = self.read().copy()
 return memmf
 
+@propertycache
+def parents(self):
+return self._revlog().parents(self._node)
+
 def read(self):
 if not self._data:
 if self._node == revlog.nullid:
@@ -1515,6 +1519,10 @@ class treemanifestctx(object):
 memmf._treemanifest = self.read().copy()
 return memmf
 
+@propertycache
+def parents(self):
+return self._revlog().parents(self._node)
+
 def readdelta(self, shallow=False):
 '''Returns a manifest containing just the entries that are present
 in this manifest, but not in its p1 manifest. This is efficient to read
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH] memctx: allow the memctx to reuse the manifest node

2016-11-17 Thread Mateusz Kwapich
Hi,
I’ll try write some tests for it – but it’s hard since we don’t have any 
feature that could
directly benefit from it in core.

Best,
Mateusz

On 11/16/16, 10:02 PM, "Jun Wu" <qu...@fb.com> wrote:

I like the direction!

I'm a bit concerned about how other existing "memctx" methods (ex.
"_status", "__contains__") would work. I'd like to make sure they still have
reasonable behavior with this change. See inline comments.

Excerpts from Mateusz Kwapich's message of 2016-11-16 20:15:28 +:
    > # HG changeset patch
> # User Mateusz Kwapich <mitran...@fb.com>
> # Date 1479327311 0
> #  Wed Nov 16 20:15:11 2016 +
> # Node ID 0fd8175aa4e8a3a0cd6f637b34bfa25a103c454e
> # Parent  c27614f2dec1405db606d1ef871dfabf72cc0737
> memctx: allow the memctx to reuse the manifest node
> 
> When we have a lot of files writing a new manifest revision can be 
expensive.
> This commit adds a possibility for memctx to reuse a manifest from a 
different
> commit. This can be beneficial for commands that are creating metadata 
changes
> without any actual files changed like "hg metaedit" in evolve extension.
> 
> I will send the change for evolve that leverages this once this is 
accepted.
> 
> diff --git a/mercurial/context.py b/mercurial/context.py
> --- a/mercurial/context.py
> +++ b/mercurial/context.py
> @@ -1160,6 +1160,7 @@
>   changes=None):
>  self._repo = repo
>  self._rev = None
> +self._manifestnode = None
>  self._node = None
>  self._text = text
>  if date:
> @@ -1268,7 +1269,8 @@
>  return None
>  
>  def manifestnode(self):
> -return None
> +return self._manifestnode
> +
>  def user(self):
>  return self._user or self._repo.ui.username()
>  def date(self):
> @@ -1833,11 +1835,12 @@
>  # this field to determine what to do in filectxfn.
>  _returnnoneformissingfiles = True
>  
> -def __init__(self, repo, parents, text, files, filectxfn, user=None,
> - date=None, extra=None, editor=False):
> +def __init__(self, repo, parents, text, files, filectxfn=None, 
user=None,
> + date=None, extra=None, editor=False, manifestnode=None):

IIUC, "manifestnode" conflicts with "files" / "filectxfn" here. If that's
true, I think we want:

  - memctx constructor change:
- "files" / "filectxfn" are optional, and conflict with "manifestnode"
- probably worthwhile to update the class docstring
  - if "manifestnode" is provided:
- change "memctx._manifest" to read "self._manifestnode" directly
- change "memctx._files" to get the files changed lazily from manifests

Then "memctx"'s other methods like "status", "filenode", "__contains__"
would probably work as expected and feel consistent.

If the new implementation diverges significantly, I think it may be a good
idea to do it in a different class, like "memlightctx" which must reuse a
manifest node.

By providing the “files” we can commit the ctx without reading the manifest 
which
gives us a serious perf boost if the manifest is large.

>  super(memctx, self).__init__(repo, text, user, date, extra)
>  self._rev = None
>  self._node = None
> +self._manifestnode = manifestnode
>  parents = [(p or nullid) for p in parents]
>  p1, p2 = parents
>  self._parents = [changectx(self._repo, p) for p in (p1, p2)]
> diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
> --- a/mercurial/localrepo.py
> +++ b/mercurial/localrepo.py
> @@ -1695,7 +1695,11 @@
>  tr = self.transaction("commit")
>  trp = weakref.proxy(tr)
>  
> -if ctx.files():
> +if ctx.manifestnode():

I think we want a sanity check here to prevent buggy linkrevs that can
give us trouble in the future. If ctx.parents' manifestnodes are differnet
from ctx.manifestnode's parents or ctx.manifestnode (for the empty commit
case), abort.

I’m fine with doing sanity checks. Thanks for  suggestion.

> +# reuse an existing manifest revision
> +mn = ctx.manifestnode()
> +files = ctx.files()
> +elif ctx.files():
>  m1ctx = p1.manifestctx()
>  m2ctx = p2.manifestctx()
>  mctx = m1ctx.copy()



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


[PATCH] memctx: allow the memctx to reuse the manifest node

2016-11-16 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1479327311 0
#  Wed Nov 16 20:15:11 2016 +
# Node ID 0fd8175aa4e8a3a0cd6f637b34bfa25a103c454e
# Parent  c27614f2dec1405db606d1ef871dfabf72cc0737
memctx: allow the memctx to reuse the manifest node

When we have a lot of files writing a new manifest revision can be expensive.
This commit adds a possibility for memctx to reuse a manifest from a different
commit. This can be beneficial for commands that are creating metadata changes
without any actual files changed like "hg metaedit" in evolve extension.

I will send the change for evolve that leverages this once this is accepted.

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1160,6 +1160,7 @@
  changes=None):
 self._repo = repo
 self._rev = None
+self._manifestnode = None
 self._node = None
 self._text = text
 if date:
@@ -1268,7 +1269,8 @@
 return None
 
 def manifestnode(self):
-return None
+return self._manifestnode
+
 def user(self):
 return self._user or self._repo.ui.username()
 def date(self):
@@ -1833,11 +1835,12 @@
 # this field to determine what to do in filectxfn.
 _returnnoneformissingfiles = True
 
-def __init__(self, repo, parents, text, files, filectxfn, user=None,
- date=None, extra=None, editor=False):
+def __init__(self, repo, parents, text, files, filectxfn=None, user=None,
+ date=None, extra=None, editor=False, manifestnode=None):
 super(memctx, self).__init__(repo, text, user, date, extra)
 self._rev = None
 self._node = None
+self._manifestnode = manifestnode
 parents = [(p or nullid) for p in parents]
 p1, p2 = parents
 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1695,7 +1695,11 @@
 tr = self.transaction("commit")
 trp = weakref.proxy(tr)
 
-if ctx.files():
+if ctx.manifestnode():
+# reuse an existing manifest revision
+mn = ctx.manifestnode()
+files = ctx.files()
+elif ctx.files():
 m1ctx = p1.manifestctx()
 m2ctx = p2.manifestctx()
 mctx = m1ctx.copy()
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 4 evolve-ext] metaedit: extend the functionality to support editing multiple commits

2016-11-16 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1479324135 0
#  Wed Nov 16 19:22:15 2016 +
# Branch stable
# Node ID b2bde478bfebc390dba8f1ee314b7bdd062ab191
# Parent  744c6acd84edf73ffdf505b9673b0383db727a0e
metaedit: extend the functionality to support editing multiple commits

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -3257,29 +3257,57 @@ def metaedit(ui, repo, *revs, **opts):
 if commitopts.get('message') or commitopts.get('logfile'):
 commitopts['edit'] = False
 else:
-if opts['fold']:
-msgs = ["HG: This is a fold of %d changesets." % 
len(allctx)]
-msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
- (c.rev(), c.description()) for c in allctx]
-else:
-msgs = [head.description()]
-commitopts['message'] =  "\n".join(msgs)
 commitopts['edit'] = True
 
 # TODO: if the author and message are the same, don't create a new
 # hash. Right now we create a new hash because the date can be
 # different.
-newid, created = rewrite(repo, root, allctx, head,
- [root.p1().node(), root.p2().node()],
- commitopts=commitopts)
-if created:
-if p1.rev() in revs:
-newp1 = newid
-phases.retractboundary(repo, tr, targetphase, [newid])
-obsolete.createmarkers(repo, [(ctx, (repo[newid],))
-  for ctx in allctx])
+if opts['fold']:
+if commitopts['edit']:
+msgs = ["HG: This is a fold of %d changesets." %
+len(allctx)]
+msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
+ (c.rev(), c.description()) for c in allctx]
+commitopts['message'] = "\n".join(msgs)
+
+newid, created = rewrite(repo, root, allctx, head,
+ [root.p1().node(), root.p2().node()],
+ commitopts=commitopts)
+if created:
+if p1.rev() in revs:
+newp1 = newid
+phases.retractboundary(repo, tr, targetphase, [newid])
+obsolete.createmarkers(repo, [(ctx, (repo[newid],))
+  for ctx in allctx])
+else:
+ui.status(_("nothing changed\n"))
 else:
-ui.status(_("nothing changed\n"))
+replacemap = {}
+# we need topological order
+allctx = sorted(allctx, key=lambda c: c.rev())
+for c in allctx:
+if commitopts['edit']:
+commitopts['message'] = \
+"HG: Commit message of changeset %s\n%s" %\
+(str(c), c.description())
+bases = [
+replacemap.get(c.p1().node(), c.p1().node()),
+replacemap.get(c.p2().node(), c.p2().node()),
+]
+newid, created = metarewrite(repo, c, bases,
+ commitopts=commitopts)
+if created:
+replacemap[c.node()] = newid
+
+if p1.node() in replacemap:
+newp1 = replacemap[p1.node()]
+if len(replacemap) > 0:
+obsolete.createmarkers(repo, [(repo[old], (repo[new],))
+for old, new in replacemap.iteritems()])
+# TODO: set poroper phase boundaries
+else:
+ui.status(_("nothing changed\n"))
+
 tr.close()
 finally:
 tr.release()
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 4 of 4 evolve-ext] metaedit: use faster setparents instead of full update

2016-11-16 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1479325623 0
#  Wed Nov 16 19:47:03 2016 +
# Branch stable
# Node ID 539a0ff6a3d6664c2dd1fede40ba8e3e2efa9986
# Parent  a10be4e9e682615db89200fbfb9583eaf5e05021
metaedit: use faster setparents instead of full update

The working copy is not changing so there is no need to extra status call.
This makes metaedit work on dirty wc.

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -3304,7 +3304,7 @@ def metaedit(ui, repo, *revs, **opts):
 if opts['fold']:
 ui.status('%i changesets folded\n' % len(revs))
 if newp1 is not None:
-hg.update(repo, newp1)
+repo.setparents(newp1)
 finally:
 lockmod.release(lock, wlock)
 
diff --git a/tests/test-evolve.t b/tests/test-evolve.t
--- a/tests/test-evolve.t
+++ b/tests/test-evolve.t
@@ -1489,7 +1489,6 @@ check that metaedit respects allowunstab
   abort: cannot fold chain not ending with a head or with branching
   [255]
   $ hg metaedit --user foobar
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
   42: test
   43: foobar
@@ -1497,7 +1496,6 @@ check that metaedit respects allowunstab
   43: foobar
 
   $ HGEDITOR="sed -i'' -e 's/safely/quickly/g'" hg metaedit '.^::.'
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ HGEDITOR=cat hg metaedit '.^::.' --fold
   HG: This is a fold of 2 changesets.
@@ -1519,7 +1517,6 @@ check that metaedit respects allowunstab
   HG: changed a
   HG: changed newfile
   2 changesets folded
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ glog -r .
   @  45:ca7a9e928b25@default(draft) amended
@@ -1553,7 +1550,6 @@ no new commit is created here because th
 
 TODO: don't create a new commit in this case
   $ hg metaedit --config defaults.metaedit=
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -r '.^::.' --template '{rev}: {desc|firstline}\n'
   36: add uu
   46: amended
@@ -1570,8 +1566,11 @@ TODO: don't create a new commit in this 
   47: foobar2
   $ hg diff -r 45 -r 46 --hidden
 
-'fold' one commit
+'fold' one commit with dirty wc
+  $ echo x > newfile
   $ hg metaedit 39 --fold --user foobar3
   1 changesets folded
   $ hg log -r 47 --template '{rev}: {author}\n'
   47: foobar2
+  $ hg st -amr
+  M newfile
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 3 of 4 evolve-ext] metaedit: remove the code gating the new metaedit feature and test it

2016-11-16 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1479325155 0
#  Wed Nov 16 19:39:15 2016 +
# Branch stable
# Node ID a10be4e9e682615db89200fbfb9583eaf5e05021
# Parent  b2bde478bfebc390dba8f1ee314b7bdd062ab191
metaedit: remove the code gating the new metaedit feature and test it

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -3220,17 +3220,6 @@ def metaedit(ui, repo, *revs, **opts):
 lock = repo.lock()
 
 revs = scmutil.revrange(repo, revs)
-if not opts['fold'] and len(revs) > 1:
-# TODO: handle multiple revisions. This is somewhat tricky because
-# if we want to edit a series of commits:
-#
-#   a  b  c
-#
-# we need to rewrite a first, then directly rewrite b on top of the
-# new a, then rewrite c on top of the new b. So we need to handle
-# revisions in topological order.
-raise error.Abort(_('editing multiple revisions without --fold is '
-'not currently supported'))
 
 if opts['fold']:
 root, head = _foldcheck(repo, revs)
diff --git a/tests/test-evolve.t b/tests/test-evolve.t
--- a/tests/test-evolve.t
+++ b/tests/test-evolve.t
@@ -1496,10 +1496,8 @@ check that metaedit respects allowunstab
   $ hg log --template '{rev}: {author}\n' -r .
   43: foobar
 
-TODO: support this
-  $ hg metaedit '.^::.'
-  abort: editing multiple revisions without --fold is not currently supported
-  [255]
+  $ HGEDITOR="sed -i'' -e 's/safely/quickly/g'" hg metaedit '.^::.'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ HGEDITOR=cat hg metaedit '.^::.' --fold
   HG: This is a fold of 2 changesets.
@@ -1507,9 +1505,9 @@ TODO: support this
   
   amended
   
-  HG: Commit message of changeset 43.
+  HG: Commit message of changeset 44.
   
-  will be evolved safely
+  will be evolved quickly
   
   
   
@@ -1524,16 +1522,17 @@ TODO: support this
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ glog -r .
-  @  44:41bf1183869c@default(draft) amended
+  @  45:ca7a9e928b25@default(draft) amended
   |
   ~
 
 no new commit is created here because the date is the same
   $ HGEDITOR=cat hg metaedit
+  HG: Commit message of changeset ca7a9e928b25
   amended
   
   
-  will be evolved safely
+  will be evolved quickly
   
   
   HG: Enter commit message.  Lines beginning with 'HG:' are removed.
@@ -1546,7 +1545,7 @@ no new commit is created here because th
   nothing changed
 
   $ glog -r '.^::.'
-  @  44:41bf1183869c@default(draft) amended
+  @  45:ca7a9e928b25@default(draft) amended
   |
   o  36:43c3f5ef149f@default(draft) add uu
   |
@@ -1557,21 +1556,22 @@ TODO: don't create a new commit in this 
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -r '.^::.' --template '{rev}: {desc|firstline}\n'
   36: add uu
-  45: amended
+  46: amended
 
   $ hg up .^
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg metaedit --user foobar2 45
+  $ hg metaedit --user foobar2 46
   $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
   42: test
   43: foobar
-  44: test
+  44: foobar
   45: test
-  46: foobar2
+  46: test
+  47: foobar2
   $ hg diff -r 45 -r 46 --hidden
 
 'fold' one commit
   $ hg metaedit 39 --fold --user foobar3
   1 changesets folded
   $ hg log -r 47 --template '{rev}: {author}\n'
-  47: foobar3
+  47: foobar2
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 4 evolve-ext] metaedit: add a helper function for just metadata rewrites

2016-11-16 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1479324110 0
#  Wed Nov 16 19:21:50 2016 +
# Branch stable
# Node ID 744c6acd84edf73ffdf505b9673b0383db727a0e
# Parent  727c7211c810d304ebf92b32db7ecf697ce46ac6
metaedit: add a helper function for just metadata rewrites

It will be used by metaedit.

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -907,6 +907,13 @@ def rewrite(repo, old, updates, head, ne
 finally:
 lockmod.release(tr, lock, wlock)
 
+def metarewrite(repo, old, newbases, commitopts):
+'''Like rewrite but affects only the changeset metadata.'''
+# TODO: reuse the manifest for speed
+newid, created = rewrite(repo, old, [old], old, newbases,
+ commitopts=commitopts)
+return newid, created
+
 class MergeFailure(error.Abort):
 pass
 
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 2] py3: make encodefun in store.py compatible with py3k

2016-10-08 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1475942045 25200
#  Sat Oct 08 08:54:05 2016 -0700
# Node ID 086b25d1866e33fb7ebbe6c51522e6b573e281e2
# Parent  225efa4bf7f497e55f0ba57f64a33dce39eaeb29
py3: make encodefun in store.py compatible with py3k

This ensures that the filename encoding functions always map bytestrings
to bytestrings regardless of python version.

diff --git a/mercurial/store.py b/mercurial/store.py
--- a/mercurial/store.py
+++ b/mercurial/store.py
@@ -16,6 +16,7 @@ from .i18n import _
 from . import (
 error,
 parsers,
+pycompat,
 scmutil,
 util,
 )
@@ -98,11 +99,20 @@ def _buildencodefun():
 'the\\x07quick\\xadshot'
 '''
 e = '_'
-cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
+if pycompat.ispy3:
+xchr = lambda x: bytes([x])
+asciistr = bytes(xrange(127))
+else:
+xchr = chr
+asciistr = map(chr, xrange(127))
+capitals = list(range(ord("A"), ord("Z") + 1))
+
+cmap = {x:x for x in asciistr}
 for x in _reserved():
-cmap[chr(x)] = "~%02x" % x
-for x in list(range(ord("A"), ord("Z") + 1)) + [ord(e)]:
-cmap[chr(x)] = e + chr(x).lower()
+cmap[xchr(x)] = "~%02x" % x
+for x in capitals + [ord(e)]:
+cmap[xchr(x)] = e + xchr(x).lower()
+
 dmap = {}
 for k, v in cmap.iteritems():
 dmap[v] = k
diff --git a/tests/test-check-py3-compat.t b/tests/test-check-py3-compat.t
--- a/tests/test-check-py3-compat.t
+++ b/tests/test-check-py3-compat.t
@@ -134,7 +134,6 @@
   mercurial/sshserver.py: error importing:  module 
'mercurial.util' has no attribute 'stringio' (error at patch.py:*)
   mercurial/statichttprepo.py: error importing:  module 
'mercurial.util' has no attribute 'urlerr' (error at byterange.py:*)
   mercurial/store.py: error importing module:  name 'xrange' is not 
defined (line *)
-  mercurial/streamclone.py: error importing:  can't concat bytes to 
str (error at store.py:*)
   mercurial/subrepo.py: error importing:  module 
'mercurial.util' has no attribute 'stringio' (error at patch.py:*)
   mercurial/templatefilters.py: error importing:  module 
'mercurial.util' has no attribute 'stringio' (error at patch.py:*)
   mercurial/templatekw.py: error importing:  module 
'mercurial.util' has no attribute 'stringio' (error at patch.py:*)
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH] dirstate: rebuild should update dirstate properly

2016-09-29 Thread Mateusz Kwapich
Yeah, the V2 is good to go.

On 9/29/16, 4:25 PM, "Yuya Nishihara" <you...@gmail.com on behalf of 
y...@tcha.org> wrote:

On Tue, 27 Sep 2016 14:06:24 +, Mateusz Kwapich wrote:
> On rebuild all the files are set to match the files in the revision but 
the revision doesn’t provide
> us the correct mtimes to set. I’d argue it’s better to set the mtimes to 
value explicitly meaning
> “unknown” than to a prepared arbitrary value.
> Same for file mode: why do we even try to guess the file mode on disk?

No idea why. I've checked b304c2496f52, which introduced the test, and
a8f7791e3680, which introduced dirstate.rebuild(). What I can guess from
them is we would set file modes because it was easy at that time.

So I lean toward taking this patch. Is V2 the latest one?


https://urldefense.proofpoint.com/v2/url?u=https-3A__patchwork.mercurial-2Dscm.org_patch_16496_=DQIDaQ=5VD0RTtNlTh3ycd41b3MUw=dK7q_6fOymlfdGMBe3wUaA=IsQ5Wde9tUWKzdvJxsr1Q4dsToyR34vkGXTiJ5Y6MAA=jPbJFKknCiMqPmtuWIpcn9-jehbUzpuFhTGdZQ6c33g=
 


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


Re: [PATCH] dirstate: rebuild should update dirstate properly

2016-09-27 Thread Mateusz Kwapich
Hey,

On rebuild all the files are set to match the files in the revision but the 
revision doesn’t provide
us the correct mtimes to set. I’d argue it’s better to set the mtimes to value 
explicitly meaning
“unknown” than to a prepared arbitrary value.
Same for file mode: why do we even try to guess the file mode on disk?

Does anybody use “hg debugrebuilddirstate” as a method of dirstate population? 
I’ve only seen it used
as a method of the whole dirstate invalidation.

Best,
Mateusz

On 8/31/16, 2:55 PM, "Yuya Nishihara" <you...@gmail.com on behalf of 
y...@tcha.org> wrote:

On Tue, 30 Aug 2016 21:34:01 +, Mateusz Kwapich wrote:
> On 8/28/16, 3:01 PM, "Yuya Nishihara" <you...@gmail.com on behalf of 
y...@tcha.org> wrote:
> > Updating dirstate by simply adding and dropping files from 
self._map doesn't
> > keep the other maps updated (think: _dirs, _copymap, _foldmap, 
_nonormalset)
> > thus introducing cache inconsistency.
> 
> I think that's why rebuild() calls self.clear(). IIRC, I've pointed 
out that it
> would be wrong to skip the whole clear() step when changedfiles is 
not None.
> 
> Clear doesn’t invalidate all the caches – for example it doesn’t touch 
filefoldmap or
> Dirfoldmap. Also: why would we clear those caches instead of updating 
them?

Perhaps that's a bug introduced when file/dirfoldmap were added.

> > --- a/tests/test-rebuildstate.t
> > +++ b/tests/test-rebuildstate.t
> > @@ -48,8 +48,8 @@ basic test for hg debugrebuildstate
> >  state dump after
> >  
> >$ hg debugstate --nodates | sort
> > -  n 644 -1 set bar
> > -  n 644 -1 set foo
> > +  n   0 -1 unset   bar
> > +  n   0 -1 unset   foo
> 
> This seems wrong. debugrebuilddirstate is documented as "dirstate 
will be
> set to the files of the given revision. The actual working directory 
content
> [...] is not considered."
>   
> Yeah. It still doesn’t consider the working directory contents, still 
contains the same files
> and all entries will still be checked at the next status call. Only 
things that are different in
> this output are file permissions and mtime which are not stored in the 
commit itself.

My concern is there might be a reason to fully populate a dirstate by
"hg debugrebuilddirstate" because the result is explicitly tested by
"hg debugstate". I hope my concern isn't a thing, but I'm not sure.


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


[PATCH V2] dirstate: rebuild should update dirstate properly

2016-08-30 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1472595388 25200
#  Tue Aug 30 15:16:28 2016 -0700
# Node ID aff2b9911d78a3d427e3ba18a565e76215971948
# Parent  12f8bef59bfa2739d0c5d8425ab494fd2fe38a81
dirstate: rebuild should update dirstate properly

Updating dirstate by simply adding and dropping files from self._map doesn't
keep the other maps updated (think: _dirs, _copymap, _foldmap, _nonormalset)
thus introducing cache inconsistency.

This is also affecting the debugstate tests since now we don't even try to set
correct mode and mtime for the files because they are marked dirty anyway and
will be checked during next status call.

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -680,21 +680,15 @@ class dirstate(object):
 self.clear()
 self._lastnormaltime = lastnormaltime
 
-for f in changedfiles:
-mode = 0o666
-if f in allfiles and 'x' in allfiles.flags(f):
-mode = 0o777
-
-if f in allfiles:
-self._map[f] = dirstatetuple('n', mode, -1, 0)
-else:
-self._map.pop(f, None)
-if f in self._nonnormalset:
-self._nonnormalset.remove(f)
-
 if self._origpl is None:
 self._origpl = self._pl
 self._pl = (parent, nullid)
+for f in changedfiles:
+if f in allfiles:
+self.normallookup(f)
+else:
+self.drop(f)
+
 self._dirty = True
 
 def write(self, tr):
diff --git a/tests/test-rebuildstate.t b/tests/test-rebuildstate.t
--- a/tests/test-rebuildstate.t
+++ b/tests/test-rebuildstate.t
@@ -48,8 +48,8 @@ basic test for hg debugrebuildstate
 state dump after
 
   $ hg debugstate --nodates | sort
-  n 644 -1 set bar
-  n 644 -1 set foo
+  n   0 -1 unset   bar
+  n   0 -1 unset   foo
 
   $ hg debugadddrop --normal-lookup file1 file2
   $ hg debugadddrop --drop bar
@@ -57,7 +57,7 @@ state dump after
   $ hg debugstate --nodates
   n   0 -1 unset   file1
   n   0 -1 unset   file2
-  n 644 -1 set foo
+  n   0 -1 unset   foo
   $ hg debugrebuildstate
 
 status
@@ -115,7 +115,7 @@ dirstate
   $ hg debugrebuilddirstate --minimal
   $ hg debugdirstate --nodates
   r   0  0 * bar (glob)
-  n 644 -1 * foo (glob)
+  n   0 -1 * foo (glob)
   a   0 -1 * qux (glob)
   $ hg status -A
   A qux
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH] localrepo: actually invalidate dirstate on invalidatedirstate()

2016-08-26 Thread Mateusz Kwapich
So invalidate is by design not-always invalidating and it’s relying on 
filecache to actually invalidate dirstate
when needed? Why do we even need the invalidate in this case if filecache can 
always properly invalidate dirstate?

Best,
Mateusz

On 8/26/16, 5:02 PM, "Yuya Nishihara" <you...@gmail.com on behalf of 
y...@tcha.org> wrote:

On Fri, 26 Aug 2016 08:31:09 -0700, Mateusz Kwapich wrote:
> # HG changeset patch
    > # User Mateusz Kwapich <mitran...@fb.com>
> # Date 1472225341 25200
> #  Fri Aug 26 08:29:01 2016 -0700
> # Node ID 430e8c2cc229e0fbed231370c36cc2a215ecb30e
> # Parent  318e2b600b80e4ed3c6f37df46ec7544f60d4c0b
> localrepo: actually invalidate dirstate on invalidatedirstate()
> 
> The old dirstate was still present in the filecache. It was invalidated 
only
> because filecache did stat the file on the next access. This can lead to 
errors
> in the case when file stat didn't change but the dirstate contents did 
change
> (and we know about it and thats why we sometimes call 
dirstateinvalidate() to
> refresh it).

(+CC foozy since he's working on fixing timestamp issues)

IIRC, that's by design. invalidate() just forces to compare timestamps to 
see
if in-memory cache is valid.



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


[PATCH] localrepo: actually invalidate dirstate on invalidatedirstate()

2016-08-26 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1472225341 25200
#  Fri Aug 26 08:29:01 2016 -0700
# Node ID 430e8c2cc229e0fbed231370c36cc2a215ecb30e
# Parent  318e2b600b80e4ed3c6f37df46ec7544f60d4c0b
localrepo: actually invalidate dirstate on invalidatedirstate()

The old dirstate was still present in the filecache. It was invalidated only
because filecache did stat the file on the next access. This can lead to errors
in the case when file stat didn't change but the dirstate contents did change
(and we know about it and thats why we sometimes call dirstateinvalidate() to
refresh it).

After this patch we will also invalidate the filecache on dirstateinvalidate.

diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1243,6 +1243,7 @@ class localrepository(object):
 delattr(self.dirstate, k)
 except AttributeError:
 pass
+self._filecache.pop('dirstate', None)
 delattr(self.unfiltered(), 'dirstate')
 
 def invalidate(self, clearfilecache=False):
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 2 V3] dirstate: add callback to notify extensions about wd parent change

2016-08-11 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1470927641 25200
#  Thu Aug 11 08:00:41 2016 -0700
# Node ID ab4af8f1ddcae13159edf3eeea9fa4358c93babf
# Parent  5e2365698d448c2a1d75f6a58e11ec65f66a0266
dirstate: add callback to notify extensions about wd parent change

The journal extension had to touch the dirstate internals to be notified about
wd parent change. To make that detection cleaner and reusable let's move it 
core.
Now the extension can register to be notified about parent changes.

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -101,6 +101,8 @@ class dirstate(object):
 self._parentwriters = 0
 self._filename = 'dirstate'
 self._pendingfilename = '%s.pending' % self._filename
+self._plchangecallbacks = {}
+self._origpl = None
 
 # for consistent view between _pl() and _read() invocations
 self._pendingmode = None
@@ -347,6 +349,8 @@ class dirstate(object):
 
 self._dirty = self._dirtypl = True
 oldp2 = self._pl[1]
+if self._origpl is None:
+self._origpl = self._pl
 self._pl = p1, p2
 copies = {}
 if oldp2 != nullid and p2 == nullid:
@@ -442,6 +446,7 @@ class dirstate(object):
 self._lastnormaltime = 0
 self._dirty = False
 self._parentwriters = 0
+self._origpl = None
 
 def copy(self, source, dest):
 """Mark dest as a copy of source. Unmark dest if source is None."""
@@ -687,6 +692,8 @@ class dirstate(object):
 if f in self._nonnormalset:
 self._nonnormalset.remove(f)
 
+if self._origpl is None:
+self._origpl = self._pl
 self._pl = (parent, nullid)
 self._dirty = True
 
@@ -721,7 +728,23 @@ class dirstate(object):
 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
 self._writedirstate(st)
 
+def addparentchangecallback(self, category, callback):
+"""add a callback to be called when the wd parents are changed
+
+Callback will be called with the following arguments:
+dirstate, (oldp1, oldp2), (newp1, newp2)
+
+Category is a unique identifier to allow overwriting an old callback
+with a newer callback.
+"""
+self._plchangecallbacks[category] = callback
+
 def _writedirstate(self, st):
+# notify callbacks about parents change
+if self._origpl is not None and self._origpl != self._pl:
+for c, callback in sorted(self._plchangecallbacks.iteritems()):
+callback(self, self._origpl, self._pl)
+self._origpl = None
 # use the modification time of the newly created temporary file as the
 # filesystem's notion of 'now'
 now = util.fstat(st).st_mtime & _rangemask
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 2 V3] journal: use the dirstate parentchange callbacks

2016-08-11 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1470759346 25200
#  Tue Aug 09 09:15:46 2016 -0700
# Node ID cd05624a1a1c133a0086e85a142dc65641a4e208
# Parent  ab4af8f1ddcae13159edf3eeea9fa4358c93babf
journal: use the dirstate parentchange callbacks

Instead of hacking into dirstate internals let's use the callbacks
to be notified about wd parent change.

diff --git a/hgext/journal.py b/hgext/journal.py
--- a/hgext/journal.py
+++ b/hgext/journal.py
@@ -24,7 +24,6 @@ from mercurial import (
 bookmarks,
 cmdutil,
 commands,
-dirstate,
 dispatch,
 error,
 extensions,
@@ -63,8 +62,6 @@ def extsetup(ui):
 extensions.wrapfunction(dispatch, 'runcommand', runcommand)
 extensions.wrapfunction(bookmarks.bmstore, '_write', recordbookmarks)
 extensions.wrapfunction(
-dirstate.dirstate, '_writedirstate', recorddirstateparents)
-extensions.wrapfunction(
 localrepo.localrepository.dirstate, 'func', wrapdirstate)
 extensions.wrapfunction(hg, 'postshare', wrappostshare)
 extensions.wrapfunction(hg, 'copystore', unsharejournal)
@@ -84,34 +81,19 @@ def wrapdirstate(orig, repo):
 dirstate = orig(repo)
 if util.safehasattr(repo, 'journal'):
 dirstate.journalstorage = repo.journal
+dirstate.addparentchangecallback('journal', recorddirstateparents)
 return dirstate
 
-def recorddirstateparents(orig, dirstate, dirstatefp):
+def recorddirstateparents(dirstate, old, new):
 """Records all dirstate parent changes in the journal."""
+old = list(old)
+new = list(new)
 if util.safehasattr(dirstate, 'journalstorage'):
-old = [node.nullid, node.nullid]
-nodesize = len(node.nullid)
-try:
-# The only source for the old state is in the dirstate file still
-# on disk; the in-memory dirstate object only contains the new
-# state. dirstate._opendirstatefile() switches beteen .hg/dirstate
-# and .hg/dirstate.pending depending on the transaction state.
-with dirstate._opendirstatefile() as fp:
-state = fp.read(2 * nodesize)
-if len(state) == 2 * nodesize:
-old = [state[:nodesize], state[nodesize:]]
-except IOError:
-pass
-
-new = dirstate.parents()
-if old != new:
-# only record two hashes if there was a merge
-oldhashes = old[:1] if old[1] == node.nullid else old
-newhashes = new[:1] if new[1] == node.nullid else new
-dirstate.journalstorage.record(
-wdirparenttype, '.', oldhashes, newhashes)
-
-return orig(dirstate, dirstatefp)
+# only record two hashes if there was a merge
+oldhashes = old[:1] if old[1] == node.nullid else old
+newhashes = new[:1] if new[1] == node.nullid else new
+dirstate.journalstorage.record(
+wdirparenttype, '.', oldhashes, newhashes)
 
 # hooks to record bookmark changes (both local and remote)
 def recordbookmarks(orig, store, fp):
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 2 V2] dirstate: add callback to notify extensions about wd parent change

2016-08-09 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1470759366 25200
#  Tue Aug 09 09:16:06 2016 -0700
# Node ID 2abe7cd83b7db665c8a8869ecb3caa10eccd7cc3
# Parent  5e2365698d448c2a1d75f6a58e11ec65f66a0266
dirstate: add callback to notify extensions about wd parent change

The journal extension had to touch the dirstate internals to be notified about
wd parent change. To make that detection cleaner and reusable let's move it 
core.
Now the extension can register to be notified about parent changes.

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -101,6 +101,8 @@ class dirstate(object):
 self._parentwriters = 0
 self._filename = 'dirstate'
 self._pendingfilename = '%s.pending' % self._filename
+self._plchangecallbacks = {}
+self._origpl = None
 
 # for consistent view between _pl() and _read() invocations
 self._pendingmode = None
@@ -347,6 +349,8 @@ class dirstate(object):
 
 self._dirty = self._dirtypl = True
 oldp2 = self._pl[1]
+if self._origpl is None:
+self._origpl = self._pl
 self._pl = p1, p2
 copies = {}
 if oldp2 != nullid and p2 == nullid:
@@ -442,6 +446,7 @@ class dirstate(object):
 self._lastnormaltime = 0
 self._dirty = False
 self._parentwriters = 0
+self._origpl = None
 
 def copy(self, source, dest):
 """Mark dest as a copy of source. Unmark dest if source is None."""
@@ -721,7 +726,24 @@ class dirstate(object):
 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
 self._writedirstate(st)
 
+def addparentchangecallback(self, category, callback):
+"""add a callback to be called when the wd parents are changed
+
+Callback will be called with the following arguments:
+dirstate, (oldp1, oldp2), (newp1, newp2)
+
+Category is a unique identifier to allow overwriting an old callback
+with a newer callback.
+"""
+self._plchangecallbacks[category] = callback
+
 def _writedirstate(self, st):
+# notify callbacks about parents change
+if self._origpl is not None:
+if self._origpl != self._pl:
+for c, callback in sorted(self._plchangecallbacks.iteritems()):
+callback(self, self._origpl, self._pl)
+self._origpl = None
 # use the modification time of the newly created temporary file as the
 # filesystem's notion of 'now'
 now = util.fstat(st).st_mtime & _rangemask
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 2 V2] journal: use the dirstate parentchange callbacks

2016-08-09 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1470759346 25200
#  Tue Aug 09 09:15:46 2016 -0700
# Node ID 027a1548685a1e6815d20e39aa55ed8c2c68b5e3
# Parent  2abe7cd83b7db665c8a8869ecb3caa10eccd7cc3
journal: use the dirstate parentchange callbacks

Instead of hacking into dirstate internals let's use the callbacks
to be notified about wd parent change.

diff --git a/hgext/journal.py b/hgext/journal.py
--- a/hgext/journal.py
+++ b/hgext/journal.py
@@ -24,7 +24,6 @@ from mercurial import (
 bookmarks,
 cmdutil,
 commands,
-dirstate,
 dispatch,
 error,
 extensions,
@@ -63,8 +62,6 @@ def extsetup(ui):
 extensions.wrapfunction(dispatch, 'runcommand', runcommand)
 extensions.wrapfunction(bookmarks.bmstore, '_write', recordbookmarks)
 extensions.wrapfunction(
-dirstate.dirstate, '_writedirstate', recorddirstateparents)
-extensions.wrapfunction(
 localrepo.localrepository.dirstate, 'func', wrapdirstate)
 extensions.wrapfunction(hg, 'postshare', wrappostshare)
 extensions.wrapfunction(hg, 'copystore', unsharejournal)
@@ -84,34 +81,19 @@ def wrapdirstate(orig, repo):
 dirstate = orig(repo)
 if util.safehasattr(repo, 'journal'):
 dirstate.journalstorage = repo.journal
+dirstate.addparentchangecallback('journal', recorddirstateparents)
 return dirstate
 
-def recorddirstateparents(orig, dirstate, dirstatefp):
+def recorddirstateparents(dirstate, old, new):
 """Records all dirstate parent changes in the journal."""
+old = list(old)
+new = list(new)
 if util.safehasattr(dirstate, 'journalstorage'):
-old = [node.nullid, node.nullid]
-nodesize = len(node.nullid)
-try:
-# The only source for the old state is in the dirstate file still
-# on disk; the in-memory dirstate object only contains the new
-# state. dirstate._opendirstatefile() switches beteen .hg/dirstate
-# and .hg/dirstate.pending depending on the transaction state.
-with dirstate._opendirstatefile() as fp:
-state = fp.read(2 * nodesize)
-if len(state) == 2 * nodesize:
-old = [state[:nodesize], state[nodesize:]]
-except IOError:
-pass
-
-new = dirstate.parents()
-if old != new:
-# only record two hashes if there was a merge
-oldhashes = old[:1] if old[1] == node.nullid else old
-newhashes = new[:1] if new[1] == node.nullid else new
-dirstate.journalstorage.record(
-wdirparenttype, '.', oldhashes, newhashes)
-
-return orig(dirstate, dirstatefp)
+# only record two hashes if there was a merge
+oldhashes = old[:1] if old[1] == node.nullid else old
+newhashes = new[:1] if new[1] == node.nullid else new
+dirstate.journalstorage.record(
+wdirparenttype, '.', oldhashes, newhashes)
 
 # hooks to record bookmark changes (both local and remote)
 def recordbookmarks(orig, store, fp):
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 2] journal: use the dirstate parentchange callbacks

2016-08-08 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1470681797 25200
#  Mon Aug 08 11:43:17 2016 -0700
# Node ID cee42d2bce7f34cae842d0f868e829ff3c7ecd04
# Parent  e7d2b86e2ee35fcb7b4e6ef6a4d0c6f696331f42
journal: use the dirstate parentchange callbacks

Instead of hacking into dirstate internals let's use the callbacks
to be notified about wd parent change.

diff --git a/hgext/journal.py b/hgext/journal.py
--- a/hgext/journal.py
+++ b/hgext/journal.py
@@ -24,7 +24,6 @@ from mercurial import (
 bookmarks,
 cmdutil,
 commands,
-dirstate,
 dispatch,
 error,
 extensions,
@@ -63,8 +62,6 @@ def extsetup(ui):
 extensions.wrapfunction(dispatch, 'runcommand', runcommand)
 extensions.wrapfunction(bookmarks.bmstore, '_write', recordbookmarks)
 extensions.wrapfunction(
-dirstate.dirstate, '_writedirstate', recorddirstateparents)
-extensions.wrapfunction(
 localrepo.localrepository.dirstate, 'func', wrapdirstate)
 extensions.wrapfunction(hg, 'postshare', wrappostshare)
 extensions.wrapfunction(hg, 'copystore', unsharejournal)
@@ -84,34 +81,19 @@ def wrapdirstate(orig, repo):
 dirstate = orig(repo)
 if util.safehasattr(repo, 'journal'):
 dirstate.journalstorage = repo.journal
+dirstate.addparentchangecallback(recorddirstateparents)
 return dirstate
 
-def recorddirstateparents(orig, dirstate, dirstatefp):
+def recorddirstateparents(dirstate, old, new):
 """Records all dirstate parent changes in the journal."""
+old = list(old)
+new = list(new)
 if util.safehasattr(dirstate, 'journalstorage'):
-old = [node.nullid, node.nullid]
-nodesize = len(node.nullid)
-try:
-# The only source for the old state is in the dirstate file still
-# on disk; the in-memory dirstate object only contains the new
-# state. dirstate._opendirstatefile() switches beteen .hg/dirstate
-# and .hg/dirstate.pending depending on the transaction state.
-with dirstate._opendirstatefile() as fp:
-state = fp.read(2 * nodesize)
-if len(state) == 2 * nodesize:
-old = [state[:nodesize], state[nodesize:]]
-except IOError:
-pass
-
-new = dirstate.parents()
-if old != new:
-# only record two hashes if there was a merge
-oldhashes = old[:1] if old[1] == node.nullid else old
-newhashes = new[:1] if new[1] == node.nullid else new
-dirstate.journalstorage.record(
-wdirparenttype, '.', oldhashes, newhashes)
-
-return orig(dirstate, dirstatefp)
+# only record two hashes if there was a merge
+oldhashes = old[:1] if old[1] == node.nullid else old
+newhashes = new[:1] if new[1] == node.nullid else new
+dirstate.journalstorage.record(
+wdirparenttype, '.', oldhashes, newhashes)
 
 # hooks to record bookmark changes (both local and remote)
 def recordbookmarks(orig, store, fp):
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 2] dirstate: add callback to notify extensions about wd parent change

2016-08-08 Thread Mateusz Kwapich
# HG changeset patch
# User Mateusz Kwapich <mitran...@fb.com>
# Date 1470681797 25200
#  Mon Aug 08 11:43:17 2016 -0700
# Node ID e7d2b86e2ee35fcb7b4e6ef6a4d0c6f696331f42
# Parent  5e2365698d448c2a1d75f6a58e11ec65f66a0266
dirstate: add callback to notify extensions about wd parent change

The journal extension had to touch the dirstate internals to be notified about
wd parent change. To make that detection cleaner and reusable let's move it 
core.
Now the extension can register to be notified about parent changes.

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -101,6 +101,8 @@ class dirstate(object):
 self._parentwriters = 0
 self._filename = 'dirstate'
 self._pendingfilename = '%s.pending' % self._filename
+self._parentchangecallbacks = []
+self._origpl = None
 
 # for consistent view between _pl() and _read() invocations
 self._pendingmode = None
@@ -347,6 +349,8 @@ class dirstate(object):
 
 self._dirty = self._dirtypl = True
 oldp2 = self._pl[1]
+if self._origpl is None:
+self._origpl = self._pl
 self._pl = p1, p2
 copies = {}
 if oldp2 != nullid and p2 == nullid:
@@ -442,6 +446,7 @@ class dirstate(object):
 self._lastnormaltime = 0
 self._dirty = False
 self._parentwriters = 0
+self._origpl = None
 
 def copy(self, source, dest):
 """Mark dest as a copy of source. Unmark dest if source is None."""
@@ -721,7 +726,16 @@ class dirstate(object):
 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
 self._writedirstate(st)
 
+def addparentchangecallback(self, callback):
+self._parentchangecallbacks.append(callback)
+
 def _writedirstate(self, st):
+# notify callbacks about parents change
+if self._origpl is not None:
+if self._origpl != self._pl:
+for callback in self._parentchangecallbacks:
+callback(self, self._origpl, self._pl)
+self._origpl = None
 # use the modification time of the newly created temporary file as the
 # filesystem's notion of 'now'
 now = util.fstat(st).st_mtime & _rangemask
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel