D7805: graft: don't remove from a list in a loop

2020-01-06 Thread martinvonz (Martin von Zweigbergk)
martinvonz created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This addresses a TODO added in a1381eea7c7d 
 
(graft: do not use
  `.remove` on a smart set (regression), 2014-04-28). I couldn't measure
  any speedup.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7805

AFFECTED FILES
  mercurial/commands.py

CHANGE DETAILS

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -3080,14 +3080,13 @@
 crev = repo[b'.'].rev()
 ancestors = repo.changelog.ancestors([crev], inclusive=True)
 # XXX make this lazy in the future
-# don't mutate while iterating, create a copy
-for rev in list(revs):
+for rev in revs:
 if rev in ancestors:
 ui.warn(
 _(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev])
 )
-# XXX remove on list is slow
-revs.remove(rev)
+revs = [r for r in revs if r not in ancestors]
+
 if not revs:
 return -1
 



To: martinvonz, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7806: graft: use revset for intersecting with ancestor set

2020-01-06 Thread martinvonz (Martin von Zweigbergk)
martinvonz created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This addresses a TODO added in a1381eea7c7d 
 
(graft: do not use
  `.remove` on a smart set (regression), 2014-04-28).

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7806

AFFECTED FILES
  mercurial/commands.py

CHANGE DETAILS

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -3077,14 +3077,10 @@
 # already, they'd have been in the graftstate.
 if not (cont or opts.get(b'force')) and basectx is None:
 # check for ancestors of dest branch
-crev = repo[b'.'].rev()
-ancestors = repo.changelog.ancestors([crev], inclusive=True)
-# XXX make this lazy in the future
-for rev in revs:
-if rev in ancestors:
-ui.warn(
-_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev])
-)
+ancestors = repo.revs(b'%ld & (::.)', revs)
+for rev in ancestors:
+ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, 
repo[rev]))
+
 revs = [r for r in revs if r not in ancestors]
 
 if not revs:
@@ -3103,7 +3099,7 @@
 
 # The only changesets we can be sure doesn't contain grafts of any
 # revs, are the ones that are common ancestors of *all* revs:
-for rev in repo.revs(b'only(%d,ancestor(%ld))', crev, revs):
+for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), 
revs):
 ctx = repo[rev]
 n = ctx.extra().get(b'source')
 if n in ids:



To: martinvonz, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7803: tests: split out another ~1/2 of test-graft.t

2020-01-06 Thread martinvonz (Martin von Zweigbergk)
martinvonz created this revision.
Herald added subscribers: mercurial-devel, mjpieters.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  The tests involving renames were also quite independent from the rest.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7803

AFFECTED FILES
  tests/test-graft-rename.t
  tests/test-graft.t

CHANGE DETAILS

diff --git a/tests/test-graft.t b/tests/test-graft.t
--- a/tests/test-graft.t
+++ b/tests/test-graft.t
@@ -922,735 +922,3 @@
   |/
   o  0
   
-Graft from behind a move or rename
-==
-
-NOTE: This is affected by issue5343, and will need updating when it's fixed
-
-Consider this topology for a regular graft:
-
-o c1
-|
-| o c2
-| |
-| o ca # stands for "common ancestor"
-|/
-o cta # stands for "common topological ancestor"
-
-Note that in issue5343, ca==cta.
-
-The following table shows the possible cases. Here, "x->y" and, equivalently,
-"y<-x", where x is an ancestor of y, means that some copy happened from x to y.
-
-name | c1<-cta | cta<->ca | ca->c2
-A.0  | |  |
-A.1  |X|  |
-A.2  | | X|
-A.3  | |  |   X
-A.4  |X| X|
-A.5  |X|  |   X
-A.6  | | X|   X
-A.7  |X| X|   X
-
-A.0 is trivial, and doesn't need copy tracking.
-For A.1, a forward rename is recorded in the c1 pass, to be followed later.
-In A.2, the rename is recorded in the c2 pass and followed backwards.
-A.3 is recorded in the c2 pass as a forward rename to be duplicated on target.
-In A.4, both passes of checkcopies record incomplete renames, which are
-then joined in mergecopies to record a rename to be followed.
-In A.5 and A.7, the c1 pass records an incomplete rename, while the c2 pass
-records an incomplete divergence. The incomplete rename is then joined to the
-appropriate side of the incomplete divergence, and the result is recorded as a
-divergence. The code doesn't distinguish at all between these two cases, since
-the end result of them is the same: an incomplete divergence joined with an
-incomplete rename into a divergence.
-Finally, A.6 records a divergence entirely in the c2 pass.
-
-A.4 has a degenerate case a<-b<-a->a, where checkcopies isn't needed at all.
-A.5 has a special case a<-b<-b->a, which is treated like a<-b->a in a merge.
-A.5 has issue5343 as a special case.
-A.6 has a special case a<-a<-b->a. Here, checkcopies will find a spurious
-incomplete divergence, which is in fact complete. This is handled later in
-mergecopies.
-A.7 has 4 special cases: a<-b<-a->b (the "ping-pong" case), a<-b<-c->b,
-a<-b<-a->c and a<-b<-c->a. Of these, only the "ping-pong" case is interesting,
-the others are fairly trivial (a<-b<-c->b and a<-b<-a->c proceed like the base
-case, a<-b<-c->a is treated the same as a<-b<-b->a).
-
-f5a therefore tests the "ping-pong" rename case, where a file is renamed to the
-same name on both branches, then the rename is backed out on one branch, and
-the backout is grafted to the other branch. This creates a challenging rename
-sequence of a<-b<-a->b in the graft target, topological CA, graft CA and graft
-source, respectively. Since rename detection will run on the c1 side for such a
-sequence (as for technical reasons, we split the c1 and c2 sides not at the
-graft CA, but rather at the topological CA), it will pick up a false rename,
-and cause a spurious merge conflict. This false rename is always exactly the
-reverse of the true rename that would be detected on the c2 side, so we can
-correct for it by detecting this condition and reversing as necessary.
-
-First, set up the repository with commits to be grafted
-
-  $ hg init ../graftmove
-  $ cd ../graftmove
-  $ echo c1a > f1a
-  $ echo c2a > f2a
-  $ echo c3a > f3a
-  $ echo c4a > f4a
-  $ echo c5a > f5a
-  $ hg ci -qAm A0
-  $ hg mv f1a f1b
-  $ hg mv f3a f3b
-  $ hg mv f5a f5b
-  $ hg ci -qAm B0
-  $ echo c1c > f1b
-  $ hg mv f2a f2c
-  $ hg mv f5b f5a
-  $ echo c5c > f5a
-  $ hg ci -qAm C0
-  $ hg mv f3b f3d
-  $ echo c4d > f4a
-  $ hg ci -qAm D0
-  $ hg log -G
-  @  changeset:   3:b69f5839d2d9
-  |  tag: tip
-  |  user:test
-  |  date:Thu Jan 01 00:00:00 1970 +
-  |  summary: D0
-  |
-  o  changeset:   2:f58c7e2b28fa
-  |  user:test
-  |  date:Thu Jan 01 00:00:00 1970 +
-  |  summary: C0
-  |
-  o  changeset:   1:3d7bba921b5d
-  |  user:test
-  |  date:Thu Jan 01 00:00:00 1970 +
-  |  summary: B0
-  |
-  o  changeset:   0:11f7a1b56675
- user:test
- date:Thu Jan 01 00:00:00 1970 +
- summary: A0
-  
-
-Test the cases A.2 (f1x), A.3 (f2x) and a special case of A.6 (f5x) where the
-two renames actually converge to the same name (thus no actual divergence).
-
-  $ hg up -q 'desc("A0")'
-  $ HGEDITOR="echo C1 >" hg graft -r 'desc("C0")' --edit
-  grafting 2:f58c7e2b28fa "C0"
-  

D7802: tests: split out ~1/3 of test-graft.t

2020-01-06 Thread martinvonz (Martin von Zweigbergk)
martinvonz created this revision.
Herald added subscribers: mercurial-devel, mjpieters.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  test-graft.t is ~2400 lines and takes 34s to run. This patch moves the
  last ~1/3 of it to a separate file. The parts now run in 22s + 13s. On
  top of that, we can remove the #testcases from the old file, so it's
  only 22s + 2*13s instead of the 2*34s it was before.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7802

AFFECTED FILES
  tests/test-graft-interrupted.t
  tests/test-graft.t

CHANGE DETAILS

diff --git a/tests/test-graft.t b/tests/test-graft.t
--- a/tests/test-graft.t
+++ b/tests/test-graft.t
@@ -1,18 +1,9 @@
-#testcases abortcommand abortflag
-
   $ cat >> $HGRCPATH < [extdiff]
   > # for portability:
   > pdiff = sh "$RUNTESTDIR/pdiff"
   > EOF
 
-#if abortflag
-  $ cat >> $HGRCPATH < [alias]
-  > abort = graft --abort
-  > EOF
-#endif
-
 Create a repo with some stuff in it:
 
   $ hg init a
@@ -1663,765 +1654,3 @@
   [255]
 
   $ cd ../..
-
-Testing the reading of old format graftstate file with newer mercurial
-
-  $ hg init oldgraft
-  $ cd oldgraft
-  $ for ch in a b c; do echo foo > $ch; hg add $ch; hg ci -Aqm "added "$ch; 
done;
-  $ hg log -GT "{rev}:{node|short} {desc}\n"
-  @  2:8be98ac1a569 added c
-  |
-  o  1:80e6d2c47cfe added b
-  |
-  o  0:f7ad41964313 added a
-  
-  $ hg up 0
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ echo bar > b
-  $ hg add b
-  $ hg ci -m "bar to b"
-  created new head
-  $ hg graft -r 1 -r 2
-  grafting 1:80e6d2c47cfe "added b"
-  merging b
-  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
-  abort: unresolved conflicts, can't continue
-  (use 'hg resolve' and 'hg graft --continue')
-  [255]
-
-Writing the nodes in old format to graftstate
-
-  $ hg log -r 1 -r 2 -T '{node}\n' > .hg/graftstate
-  $ echo foo > b
-  $ hg resolve -m
-  (no more unresolved files)
-  continue: hg graft --continue
-  $ hg graft --continue
-  grafting 1:80e6d2c47cfe "added b"
-  grafting 2:8be98ac1a569 "added c"
-
-Testing that --user is preserved during conflicts and value is reused while
-running `hg graft --continue`
-
-  $ hg log -G
-  @  changeset:   5:711e9fa999f1
-  |  tag: tip
-  |  user:test
-  |  date:Thu Jan 01 00:00:00 1970 +
-  |  summary: added c
-  |
-  o  changeset:   4:e5ad7353b408
-  |  user:test
-  |  date:Thu Jan 01 00:00:00 1970 +
-  |  summary: added b
-  |
-  o  changeset:   3:9e887f7a939c
-  |  parent:  0:f7ad41964313
-  |  user:test
-  |  date:Thu Jan 01 00:00:00 1970 +
-  |  summary: bar to b
-  |
-  | o  changeset:   2:8be98ac1a569
-  | |  user:test
-  | |  date:Thu Jan 01 00:00:00 1970 +
-  | |  summary: added c
-  | |
-  | o  changeset:   1:80e6d2c47cfe
-  |/   user:test
-  |date:Thu Jan 01 00:00:00 1970 +
-  |summary: added b
-  |
-  o  changeset:   0:f7ad41964313
- user:test
- date:Thu Jan 01 00:00:00 1970 +
- summary: added a
-  
-
-  $ hg up '.^^'
-  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
-
-  $ hg graft -r 1 -r 2 --user batman
-  grafting 1:80e6d2c47cfe "added b"
-  merging b
-  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
-  abort: unresolved conflicts, can't continue
-  (use 'hg resolve' and 'hg graft --continue')
-  [255]
-
-  $ echo wat > b
-  $ hg resolve -m
-  (no more unresolved files)
-  continue: hg graft --continue
-
-  $ hg graft --continue
-  grafting 1:80e6d2c47cfe "added b"
-  grafting 2:8be98ac1a569 "added c"
-
-  $ hg log -Gr 3::
-  @  changeset:   7:11a36ffaacf2
-  |  tag: tip
-  |  user:batman
-  |  date:Thu Jan 01 00:00:00 1970 +
-  |  summary: added c
-  |
-  o  changeset:   6:76803afc6511
-  |  parent:  3:9e887f7a939c
-  |  user:batman
-  |  date:Thu Jan 01 00:00:00 1970 +
-  |  summary: added b
-  |
-  | o  changeset:   5:711e9fa999f1
-  | |  user:test
-  | |  date:Thu Jan 01 00:00:00 1970 +
-  | |  summary: added c
-  | |
-  | o  changeset:   4:e5ad7353b408
-  |/   user:test
-  |date:Thu Jan 01 00:00:00 1970 +
-  |summary: added b
-  |
-  o  changeset:   3:9e887f7a939c
-  |  parent:  0:f7ad41964313
-  ~  user:test
- date:Thu Jan 01 00:00:00 1970 +
- summary: bar to b
-  
-Test that --date is preserved and reused in `hg graft --continue`
-
-  $ hg up '.^^'
-  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg graft -r 1 -r 2 --date '123456 120'
-  grafting 1:80e6d2c47cfe "added b"
-  merging b
-  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
-  abort: unresolved conflicts, can't continue
-  (use 'hg resolve' and 'hg graft --continue')

D7804: tests: avoid grafting the same change over and over

2020-01-06 Thread martinvonz (Martin von Zweigbergk)
martinvonz created this revision.
Herald added subscribers: mercurial-devel, mjpieters.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  The test case added in a1381eea7c7d 
 
(graft: do not use `.remove` on a
  smart set (regression), 2014-04-28) added a test case that grafted the
  same change (renaming 'a' to 'b') three times over. It had description
  "graft works on complex revset", but AFACT, all that it cared about
  was that some ancestor of the working copy was in the set of revisions
  to graft. So this patch changes the test to do that instead.
  
  (I plan to later make it so that grafting these renames on top of each
  won't create the empty commits they currently create.)

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7804

AFFECTED FILES
  tests/test-graft.t

CHANGE DETAILS

diff --git a/tests/test-graft.t b/tests/test-graft.t
--- a/tests/test-graft.t
+++ b/tests/test-graft.t
@@ -797,19 +797,12 @@
   summary: 2
   
 
-graft works on complex revset
+graft skips ancestors
 
-  $ hg graft 'origin(13) or destination(origin(13))'
+  $ hg graft 21 3
   skipping ancestor revision 21:7e61b508e709
-  skipping ancestor revision 22:3a4e92d81b97
-  skipping revision 2:5c095ad7e90f (already grafted to 22:3a4e92d81b97)
-  grafting 7:ef0ef43d49e7 "2"
-  warning: can't find ancestor for 'b' copied from 'a'!
-  grafting 13:7a4785234d87 "2"
-  warning: can't find ancestor for 'b' copied from 'a'!
-  grafting 19:9627f653b421 "2"
-  merging b
-  warning: can't find ancestor for 'b' copied from 'a'!
+  grafting 3:4c60f11aa304 "3"
+  merging b and c to c
 
 graft with --force (still doesn't graft merges)
 
@@ -828,15 +821,15 @@
 graft --force after backout
 
   $ echo abc > a
-  $ hg ci -m 28
-  $ hg backout 28
+  $ hg ci -m 26
+  $ hg backout 26
   reverting a
-  changeset 29:9d95e865b00c backs out changeset 28:cc20d29aec8d
-  $ hg graft 28
-  skipping ancestor revision 28:cc20d29aec8d
+  changeset 27:e25e17192dc4 backs out changeset 26:44f862488a35
+  $ hg graft 26
+  skipping ancestor revision 26:44f862488a35
   [255]
-  $ hg graft 28 --force
-  grafting 28:cc20d29aec8d "28"
+  $ hg graft 26 --force
+  grafting 26:44f862488a35 "26"
   merging a
   $ cat a
   abc
@@ -844,9 +837,9 @@
 graft --continue after --force
 
   $ echo def > a
-  $ hg ci -m 31
-  $ hg graft 28 --force --tool internal:fail
-  grafting 28:cc20d29aec8d "28"
+  $ hg ci -m 29
+  $ hg graft 26 --force --tool internal:fail
+  grafting 26:44f862488a35 "26"
   abort: unresolved conflicts, can't continue
   (use 'hg resolve' and 'hg graft --continue')
   [255]
@@ -859,7 +852,7 @@
   (no more unresolved files)
   continue: hg graft --continue
   $ hg graft -c
-  grafting 28:cc20d29aec8d "28"
+  grafting 26:44f862488a35 "26"
   $ cat a
   abc
 
@@ -876,12 +869,12 @@
 
 Empty graft
 
-  $ hg up -qr 26
+  $ hg up -qr 24
   $ hg tag -f something
-  $ hg graft -qr 27
-  $ hg graft -f 27
-  grafting 27:17d42b8f5d50 "28"
-  note: graft of 27:17d42b8f5d50 created no changes to commit
+  $ hg graft -qr 25
+  $ hg graft -f 25
+  grafting 25:bd0c98709948 "26"
+  note: graft of 25:bd0c98709948 created no changes to commit
 
   $ cd ..
 



To: martinvonz, #hg-reviewers
Cc: mjpieters, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7801: overlwayworkingctx: remove doubly bad reference to wrapped ctx for copies

2020-01-06 Thread martinvonz (Martin von Zweigbergk)
martinvonz created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  `_wrappedctx` lives on overlwayworkingctx, not on the repo object, so
  we should access it as `._wrappedctx`, not `._repo._wrappedctx`. More
  importantly, the overlayworkingctx is relative to its base, not
  including it, so the copies returned should not include copies made in
  the base.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7801

AFFECTED FILES
  mercurial/context.py

CHANGE DETAILS

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -2218,7 +2218,7 @@
 ]
 
 def p1copies(self):
-copies = self._repo._wrappedctx.p1copies().copy()
+copies = {}
 narrowmatch = self._repo.narrowmatch()
 for f in self._cache.keys():
 if not narrowmatch(f):
@@ -2230,7 +2230,7 @@
 return copies
 
 def p2copies(self):
-copies = self._repo._wrappedctx.p2copies().copy()
+copies = {}
 narrowmatch = self._repo.narrowmatch()
 for f in self._cache.keys():
 if not narrowmatch(f):



To: martinvonz, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7800: movedirstate: get copies from dirstate before setting parents

2020-01-06 Thread martinvonz (Martin von Zweigbergk)
martinvonz created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Setting dirstate parents can modify the copies recorded in the
  dirstate when there are two dirstate parents. I don't think we ever
  call movedirstate() when there is more than one parent, but it seems
  clearer to get the copies from the dirstate first anyway.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7800

AFFECTED FILES
  mercurial/scmutil.py

CHANGE DETAILS

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -1426,8 +1426,8 @@
 """
 oldctx = repo[b'.']
 ds = repo.dirstate
+copies = dict(ds.copies())
 ds.setparents(newctx.node(), nullid)
-copies = dict(ds.copies())
 s = newctx.status(oldctx, match=match)
 for f in s.modified:
 if ds[f] == b'r':



To: martinvonz, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7799: merge: remove unused keepparent argument for graft()

2020-01-06 Thread martinvonz (Martin von Zweigbergk)
martinvonz created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  The option was added in d6859d86a5d5 
 
(merge.graft: add option to keep
  second parent, 2015-12-03) in order to help fix
  https://bz.mercurial-scm.org/show_bug.cgi?id=4389, but no code was
  ever added to either core or evolve to pass the option. Evolve handles
  at least some kinds of orphan merges these days, so I suspect this
  turned out to not be needed.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7799

AFFECTED FILES
  mercurial/merge.py

CHANGE DETAILS

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -2581,21 +2581,17 @@
 return stats
 
 
-def graft(
-repo, ctx, base, labels=None, keepparent=False, keepconflictparent=False
-):
+def graft(repo, ctx, base, labels=None, keepconflictparent=False):
 """Do a graft-like merge.
 
 This is a merge where the merge ancestor is chosen such that one
 or more changesets are grafted onto the current changeset. In
 addition to the merge, this fixes up the dirstate to include only
-a single parent (if keepparent is False) and tries to duplicate any
-renames/copies appropriately.
+a single parent and tries to duplicate any renames/copies appropriately.
 
 ctx - changeset to rebase
 base - merge base, usually ctx.p1()
 labels - merge labels eg ['local', 'graft']
-keepparent - keep second parent if any
 keepconflictparent - if unresolved, keep parent used for the merge
 
 """
@@ -2622,10 +2618,6 @@
 pother = ctx.node()
 else:
 pother = nullid
-parents = ctx.parents()
-if keepparent and len(parents) == 2 and base in parents:
-parents.remove(base)
-pother = parents[0].node()
 # Never set both parents equal to each other
 if pother == pctx.node():
 pother = nullid



To: martinvonz, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7705: phases: make the working directory consistently a draft

2020-01-06 Thread rdamazio (Rodrigo Damazio Bovendorp)
rdamazio added a comment.


  In D7705#114157 , @marmoute 
wrote:
  
  > In D7705#114045 , @rdamazio 
wrote:
  >
  >> I don't understand what you're asking me to do here, can you clarify?
  >
  > In short we should have a test matching
  >
  >   $ hg log -r 'wdir() and secret()' -T '{phase}\n' --config 
phases.new-commit=secret
  >   secret
  
  Done.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7705/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7705

To: rdamazio, #hg-reviewers, marmoute
Cc: marmoute, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7705: phases: make the working directory consistently a draft

2020-01-06 Thread rdamazio (Rodrigo Damazio Bovendorp)
rdamazio updated this revision to Diff 19050.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7705?vs=18921=19050

BRANCH
  default

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7705/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7705

AFFECTED FILES
  mercurial/phases.py
  tests/test-phases.t

CHANGE DETAILS

diff --git a/tests/test-phases.t b/tests/test-phases.t
--- a/tests/test-phases.t
+++ b/tests/test-phases.t
@@ -48,13 +48,58 @@
   1 1 B
   0 1 A
 
-Draft commit are properly created over public one:
+Working directory phase is secret when its parent is secret.
+
+  $ hg phase --force --secret .
+  test-debug-phase: move rev 0: 1 -> 2
+  test-debug-phase: move rev 1: 1 -> 2
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:  draft -> 
secret
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  draft -> 
secret
+  $ hg log -r 'wdir()' -T '{phase}\n'
+  secret
+  $ hg log -r 'wdir() and public()' -T '{phase}\n'
+  $ hg log -r 'wdir() and draft()' -T '{phase}\n'
+  $ hg log -r 'wdir() and secret()' -T '{phase}\n'
+  secret
+
+Working directory phase is draft when its parent is draft.
+
+  $ hg phase --draft .
+  test-debug-phase: move rev 1: 2 -> 1
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  secret -> 
draft
+  $ hg log -r 'wdir()' -T '{phase}\n'
+  draft
+  $ hg log -r 'wdir() and public()' -T '{phase}\n'
+  $ hg log -r 'wdir() and draft()' -T '{phase}\n'
+  draft
+  $ hg log -r 'wdir() and secret()' -T '{phase}\n'
+
+Working directory phase is secret when a new commit will be created as secret,
+even if the parent is draft.
+
+  $ hg log -r 'wdir() and secret()' -T '{phase}\n' \
+  > --config phases.new-commit='secret'
+  secret
+
+Working directory phase is draft when its parent is public.
 
   $ hg phase --public .
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
   test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:  draft -> 
public
   test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  draft -> 
public
+  $ hg log -r 'wdir()' -T '{phase}\n'
+  draft
+  $ hg log -r 'wdir() and public()' -T '{phase}\n'
+  $ hg log -r 'wdir() and draft()' -T '{phase}\n'
+  draft
+  $ hg log -r 'wdir() and secret()' -T '{phase}\n'
+  $ hg log -r 'wdir() and secret()' -T '{phase}\n' \
+  > --config phases.new-commit='secret'
+  secret
+
+Draft commit are properly created over public one:
+
   $ hg phase
   1: public
   $ hglog
diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -112,6 +112,7 @@
 nullid,
 nullrev,
 short,
+wdirrev,
 )
 from .pycompat import (
 getattr,
@@ -242,6 +243,7 @@
 """return a smartset for the given phases"""
 self.loadphaserevs(repo)  # ensure phase's sets are loaded
 phases = set(phases)
+
 if public not in phases:
 # fast path: _phasesets contains the interesting sets,
 # might only need a union and post-filtering.
@@ -252,25 +254,44 @@
 revs = set.union(*[self._phasesets[p] for p in phases])
 if repo.changelog.filteredrevs:
 revs = revs - repo.changelog.filteredrevs
+
 if subset is None:
 return smartset.baseset(revs)
 else:
+if wdirrev in subset and repo[None].phase() in phases:
+# The working dir would never be in the cache, but it was
+# in the subset being filtered for its phase, so add it to
+# the output.
+revs.add(wdirrev)
+
 return subset & smartset.baseset(revs)
 else:
+# phases keeps all the *other* phases.
 phases = set(allphases).difference(phases)
 if not phases:
 return smartset.fullreposet(repo)
+
+# revs has the revisions in all *other* phases.
 if len(phases) == 1:
 [p] = phases
 revs = self._phasesets[p]
 else:
 revs = set.union(*[self._phasesets[p] for p in phases])
+
 if subset is None:
 subset = smartset.fullreposet(repo)
+
+if wdirrev in subset and repo[None].phase() in phases:
+# The working dir is in the subset being filtered, and its
+# phase is in the phases *not* being returned, so add it to the
+# set of revisions to filter out.
+revs.add(wdirrev)
+
 if not revs:
 return subset
 return subset.filter(lambda r: r not in revs)
 
+
 def copy(self):
 # Shallow copy meant to ensure isolation in
 # advance/retractboundary(), nothing more.



To: rdamazio, #hg-reviewers, marmoute
Cc: marmoute, mercurial-devel
___

D7631: absorb: allowing committed changes to be absorbed into their ancestors

2020-01-06 Thread rdamazio (Rodrigo Damazio Bovendorp)
rdamazio added a comment.


  In D7631#114393 , @mharbison72 
wrote:
  
  > In D7631#112604 , @rdamazio 
wrote:
  >
  >> In D7631#112414 , @quark 
wrote:
  >>
  >>> `--rev` seems ambiguous since there might be different kinds of revisions 
to specify - target and revisions to edit. Maybe something like `--source`, 
`--from`, `--target`?
  >>
  >> Done. Used `--source` to match `rebase`.
  >
  > Is `--exact` from `hg fold` a better model?  I don't feel strongly; I only 
mention it because `hg rebase -s` will take that revision and its descendants, 
so it's more like "stack" in my mind.  I'm not sure how many other commands 
have `-s` off the top of my head, but @martinvonz 
  > mentioned adding that to `hg fix` (probably in IRC), and I think mentioned 
the word "stack" in that context.  So I might not be the only one to get 
slightly tripped up by that.
  
  IMHO no, needing `--exact` is actually confusing to almost every user we've 
talked to, and they'd instead expect that to be the default behavior, with 
"fold up to this commit" being the one that needs a specific flag.
  
  >> I'm assuming no fundamental objections then? Removing the "RFC" part so it 
gets a proper review then.
  >
  > I like it.
  
  Thanks

INLINE COMMENTS

> mharbison72 wrote in absorb.py:1141
> Should it abort if multiple revisions are given, instead of picking the 
> latest?

I suspect other places may want something similar (e.g. it'd make sense in 
`rebase --dest`, so I changed revsingle to add the behavior.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7631/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7631

To: rdamazio, #hg-reviewers
Cc: mharbison72, martinvonz, pulkit, quark, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7631: absorb: allowing committed changes to be absorbed into their ancestors

2020-01-06 Thread rdamazio (Rodrigo Damazio Bovendorp)
rdamazio marked an inline comment as done.
rdamazio updated this revision to Diff 19048.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7631?vs=18873=19048

BRANCH
  default

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7631/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7631

AFFECTED FILES
  hgext/absorb.py
  mercurial/scmutil.py
  relnotes/next
  tests/test-absorb-rev.t
  tests/test-absorb.t

CHANGE DETAILS

diff --git a/tests/test-absorb.t b/tests/test-absorb.t
--- a/tests/test-absorb.t
+++ b/tests/test-absorb.t
@@ -143,7 +143,7 @@
   nothing applied
   [1]
 
-Insertaions:
+Insertions:
 
   $ cat > a << EOF
   > insert before 2b
diff --git a/tests/test-absorb.t b/tests/test-absorb-rev.t
copy from tests/test-absorb.t
copy to tests/test-absorb-rev.t
--- a/tests/test-absorb.t
+++ b/tests/test-absorb-rev.t
@@ -1,26 +1,14 @@
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > absorb=
+  > rebase=
+  > [experimental]
+  > evolution=createmarkers
   > EOF
 
-  $ sedi() { # workaround check-code
-  > pattern="$1"
-  > shift
-  > for i in "$@"; do
-  > sed "$pattern" "$i" > "$i".tmp
-  > mv "$i".tmp "$i"
-  > done
-  > }
-
   $ hg init repo1
   $ cd repo1
 
-Do not crash with empty repo:
-
-  $ hg absorb
-  abort: no mutable changeset to change
-  [255]
-
 Make some commits:
 
   $ for i in 1 2 3 4 5; do
@@ -45,9 +33,13 @@
   > 5e
   > EOF
 
+Commit that, too.
+
+  $ hg commit -qm "commit to absorb"
+
 Preview absorb changes:
 
-  $ hg absorb --print-changes --dry-run
+  $ hg absorb --print-changes --dry-run -s .
   showing changes for a
   @@ -0,2 +0,2 @@
   4ec16f8 -1
@@ -66,462 +58,126 @@
   5c5f952 commit 2
   4ec16f8 commit 1
 
+Try to absorb multiple revisions:
+
+  $ hg absorb --apply-changes -s .^+.
+  abort: revision set matched multiple revisions
+  [255]
+
+Add an uncommitted working directory change:
+
+  $ echo 6 >> a
+
 Run absorb:
 
-  $ hg absorb --apply-changes
-  saved backup bundle to * (glob)
+  $ hg absorb --apply-changes -s .
+  1 new orphan changesets
   2 of 2 chunk(s) applied
-  $ hg annotate a
-  0: 1a
-  1: 2b
-  2: 3
-  3: 4d
-  4: 5e
-
-Delete a few lines and related commits will be removed if they will be empty:
-
-  $ cat > a < 2b
-  > 4d
-  > EOF
-  $ echo y | hg absorb --config ui.interactive=1
-  showing changes for a
-  @@ -0,1 +0,0 @@
-  f548282 -1a
-  @@ -2,1 +1,0 @@
-  ff5d556 -3
-  @@ -4,1 +2,0 @@
-  84e5416 -5e
-  
-  3 changesets affected
-  84e5416 commit 5
-  ff5d556 commit 3
-  f548282 commit 1
-  apply changes (yn)?  y
-  saved backup bundle to * (glob)
-  3 of 3 chunk(s) applied
-  $ hg annotate a
-  1: 2b
-  2: 4d
-  $ hg log -T '{rev} {desc}\n' -Gp
-  @  2 commit 4
-  |  diff -r 1cae118c7ed8 -r 58a62bade1c6 a
-  |  --- a/a   Thu Jan 01 00:00:00 1970 +
-  |  +++ b/a   Thu Jan 01 00:00:00 1970 +
-  |  @@ -1,1 +1,2 @@
-  |   2b
-  |  +4d
-  |
-  o  1 commit 2
-  |  diff -r 84add69aeac0 -r 1cae118c7ed8 a
-  |  --- a/a   Thu Jan 01 00:00:00 1970 +
-  |  +++ b/a   Thu Jan 01 00:00:00 1970 +
-  |  @@ -0,0 +1,1 @@
-  |  +2b
-  |
-  o  0 commit 1
-  
-
-Non 1:1 map changes will be ignored:
-
-  $ echo 1 > a
-  $ hg absorb --apply-changes
-  nothing applied
-  [1]
-
-The prompt is not given if there are no changes to be applied, even if there
-are some changes that won't be applied:
-
-  $ hg absorb
-  showing changes for a
-  @@ -0,2 +0,1 @@
-  -2b
-  -4d
-  +1
-  
-  0 changesets affected
-  nothing applied
-  [1]
-
-Insertaions:
-
-  $ cat > a << EOF
-  > insert before 2b
-  > 2b
-  > 4d
-  > insert aftert 4d
-  > EOF
-  $ hg absorb -q --apply-changes
-  $ hg status
-  $ hg annotate a
-  1: insert before 2b
-  1: 2b
-  2: 4d
-  2: insert aftert 4d
-
-Bookmarks are moved:
-
-  $ hg bookmark -r 1 b1
-  $ hg bookmark -r 2 b2
-  $ hg bookmark ba
-  $ hg bookmarks
- b11:b35060a57a50
- b22:946e4bc87915
-   * ba2:946e4bc87915
-  $ sedi 's/insert/INSERT/' a
-  $ hg absorb -q --apply-changes
-  $ hg status
-  $ hg bookmarks
- b11:a4183e9b3d31
- b22:c9b20c925790
-   * ba2:c9b20c925790
-
-Non-modified files are ignored:
 
-  $ touch b
-  $ hg commit -A b -m b
-  $ touch c
-  $ hg add c
-  $ hg rm b
-  $ hg absorb --apply-changes
-  nothing applied
-  [1]
-  $ sedi 's/INSERT/Insert/' a
-  $ hg absorb --apply-changes
-  saved backup bundle to * (glob)
-  2 of 2 chunk(s) applied
-  $ hg status
-  A c
-  R b
-
-Public commits will not be changed:
-
-  $ hg phase -p 1
-  $ sedi 's/Insert/insert/' a
-  $ hg absorb -pn
-  showing changes for a
-  @@ -0,1 +0,1 @@
-  -Insert before 2b
-  +insert before 2b
-  @@ -3,1 +3,1 @@
-  85b4e0e -Insert aftert 4d
-  85b4e0e +insert aftert 4d
-  
-  1 changesets affected
-  85b4e0e commit 4
-  $ hg absorb 

D7630: absorb: make the absorbed changeset be automatically "evolved"

2020-01-06 Thread rdamazio (Rodrigo Damazio Bovendorp)
rdamazio updated this revision to Diff 19049.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7630?vs=18874=19049

BRANCH
  default

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7630/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7630

AFFECTED FILES
  hgext/absorb.py
  tests/test-absorb-rev.t

CHANGE DETAILS

diff --git a/tests/test-absorb-rev.t b/tests/test-absorb-rev.t
--- a/tests/test-absorb-rev.t
+++ b/tests/test-absorb-rev.t
@@ -71,10 +71,10 @@
 Run absorb:
 
   $ hg absorb --apply-changes -s .
-  1 new orphan changesets
   2 of 2 chunk(s) applied
 
 Check that the pending wdir change was left alone:
+TODO: The absorbed commit should have disappeared when it became empty.
 
   $ hg status
   M a
@@ -87,19 +87,11 @@
4d
5e
   +6
-  $ hg update -Cq .
-
-Rebase the absorbed revision on top of the destination (as evolve would):
-TODO: the evolve-type operation should happen automatically for the changeset
-being absorbed, and even through that the pending wdir change should be left
-alone.
-
-  $ hg rebase -d tip -r .
-  rebasing 5:1631091f9648 "commit to absorb"
-  note: not rebasing 5:1631091f9648 "commit to absorb", its destination 
already has all its changes
 
   $ hg log -G -T '{node|short} {desc} {instabilities}'
-  @  2f7ba78d6abc commit 5
+  @  9a0ec5cae1a1 commit to absorb
+  |
+  o  2f7ba78d6abc commit 5
   |
   o  04c8ba6df782 commit 4
   |
@@ -109,12 +101,15 @@
   |
   o  241ace8326d0 commit 1
   
-  $ hg annotate -c a
-  241ace8326d0: 1a
-  9b19176bb127: 2b
-  484c6ac0cea3: 3
-  04c8ba6df782: 4d
-  2f7ba78d6abc: 5e
+  $ hg annotate -c a -r 'wdir()'
+  241ace8326d0 : 1a
+  9b19176bb127 : 2b
+  484c6ac0cea3 : 3
+  04c8ba6df782 : 4d
+  2f7ba78d6abc : 5e
+  9a0ec5cae1a1+: 6
+
+  $ hg diff -c .
 
 Do it again, but this time with an unrelated commit checked out (plus working
 directory changes on top):
@@ -145,7 +140,6 @@
   2 changesets affected
   2f7ba78 commit 5
   04c8ba6 commit 4
-  1 new orphan changesets
   1 of 1 chunk(s) applied
 
   $ hg annotate -c a -r 'wdir()'
@@ -153,19 +147,16 @@
   dbce69d9fe03 : committed unrelated
   dbce69d9fe03+: pending wdir change
 
-
-  $ hg update -Cq .
-
-  $ hg rebase -d tip -r ${TOABSORB}
-  rebasing \d+:[0-9a-f]+ "commit to absorb 2" (re)
-  note: not rebasing \d+:[0-9a-f]+ "commit to absorb 2", its destination 
already has all its changes (re)
+  $ hg update -Cq tip
 
   $ hg log -G -T '{node|short} {desc} {instabilities}'
+  @  59655ff113fb commit to absorb 2
+  |
   o  789b01face13 commit 5
   |
   o  9c83c60f49f2 commit 4
   |
-  | @  dbce69d9fe03 unrelated commit
+  | o  dbce69d9fe03 unrelated commit
   | |
   o |  484c6ac0cea3 commit 3
   | |
@@ -174,7 +165,7 @@
   o  241ace8326d0 commit 1
   
 
-  $ hg annotate -c a -r tip
+  $ hg annotate -c a
   241ace8326d0: 1a
   9b19176bb127: 2b
   484c6ac0cea3: 3
diff --git a/hgext/absorb.py b/hgext/absorb.py
--- a/hgext/absorb.py
+++ b/hgext/absorb.py
@@ -34,6 +34,7 @@
 from __future__ import absolute_import
 
 import collections
+import itertools
 
 from mercurial.i18n import _
 from mercurial import (
@@ -662,16 +663,19 @@
 4. call commit, to commit changes to hg database
 """
 
-def __init__(self, stack, ui=None, opts=None):
+def __init__(self, stack, fixuptargets=[], ui=None, opts=None):
 """([ctx], ui or None) -> None
 
 stack: should be linear, and sorted by topo order - oldest first.
+fixuptargets: changeset contexts that need to be fixed up, but are not
+used for fixup computation.
 all commits in stack are considered mutable.
 """
 assert stack
 self.ui = ui or nullui()
 self.opts = opts or {}
 self.stack = stack
+self.fixuptargets = fixuptargets
 self.repo = stack[-1].repo().unfiltered()
 
 # following fields will be filled later
@@ -777,7 +781,7 @@
 # p1 which overrides the parent of the next commit, "None" means use
 # the original parent unchanged
 nextp1 = None
-for ctx in self.stack:
+for ctx in itertools.chain(self.stack, self.fixuptargets):
 memworkingcopy = self._getnewfilecontents(ctx)
 if not memworkingcopy and not lastcommitted:
 # nothing changed, nothing commited
@@ -1011,7 +1015,10 @@
 pats = ()
 if opts is None:
 opts = {}
-state = fixupstate(stack, ui=ui, opts=opts)
+fixuptargets = []
+if targetctx.rev() is not None:
+fixuptargets.append(targetctx)
+state = fixupstate(stack, fixuptargets=fixuptargets, ui=ui, opts=opts)
 matcher = scmutil.match(targetctx, pats, opts)
 if opts.get(b'interactive'):
 diff = patch.diff(repo, stack[-1].node(), targetctx.node(), matcher)



To: rdamazio, #hg-reviewers
Cc: martinvonz, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org

mercurial@43999: 2 new changesets (2 on stable)

2020-01-06 Thread Mercurial Commits
2 new changesets (2 on stable) in mercurial:

https://www.mercurial-scm.org/repo/hg/rev/453c4f07de0f
changeset:   43998:453c4f07de0f
branch:  stable
parent:  43947:b4c82b704180
user:Augie Fackler 
date:Mon Jan 06 14:58:31 2020 -0500
summary: Added tag 5.2.2 for changeset b4c82b704180

https://www.mercurial-scm.org/repo/hg/rev/b89144b81c5d
changeset:   43999:b89144b81c5d
branch:  stable
tag: tip
user:Augie Fackler 
date:Mon Jan 06 14:58:32 2020 -0500
summary: Added signature for changeset b4c82b704180

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


5.4sprint location/date finalization

2020-01-06 Thread Augie Fackler
Howdy folks,

https://www.mercurial-scm.org/wiki/5.4Sprint has three choices of location: 
Finland, Brussels, and Paris. Given the tentative availability of Paris, and 
the relative proximity of Brussels and Paris means Brussels is probably the 
better choice?

At this point I'd like people to fill in availability, especially any location 
constraints (eg "if it's not Paris I can't come" or "I won't bother if I can't 
go to Finland") so we can get this locked down in the next week so we have time 
for visa applications etc as needed.

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


mercurial@43997: 36 new changesets

2020-01-06 Thread Mercurial Commits
36 new changesets in mercurial:

https://www.mercurial-scm.org/repo/hg/rev/eebdd6709868
changeset:   43962:eebdd6709868
user:Kyle Lippincott 
date:Wed Dec 18 14:07:58 2019 -0800
summary: fix: fix handling of merge commits by using overlayworkingctx

https://www.mercurial-scm.org/repo/hg/rev/bbcf78c4ff90
changeset:   43963:bbcf78c4ff90
user:Pierre-Yves David 
date:Fri Dec 27 16:47:47 2019 +0100
summary: commitablectx: fix the default phase

https://www.mercurial-scm.org/repo/hg/rev/8f67735344ae
changeset:   43964:8f67735344ae
user:Matt Harbison 
date:Thu Dec 26 16:45:56 2019 -0500
summary: tests: convert the `root` arg of matchmod.match() to local path 
separators

https://www.mercurial-scm.org/repo/hg/rev/8a81fa44f7bb
changeset:   43965:8a81fa44f7bb
user:Matt Harbison 
date:Thu Dec 26 18:26:06 2019 -0500
summary: match: don't util.normpath() cwd

https://www.mercurial-scm.org/repo/hg/rev/f91834ecfdfd
changeset:   43966:f91834ecfdfd
user:Matt Harbison 
date:Fri Dec 27 02:05:01 2019 -0500
summary: narrow: move `testedwith` after module imports

https://www.mercurial-scm.org/repo/hg/rev/6c201f0d17b1
changeset:   43967:6c201f0d17b1
user:Matt Harbison 
date:Fri Dec 27 02:44:00 2019 -0500
summary: cleanup: drop unused import from pywatchman

https://www.mercurial-scm.org/repo/hg/rev/5ce6daa67658
changeset:   43968:5ce6daa67658
user:Matt Harbison 
date:Fri Dec 27 13:05:22 2019 -0500
summary: ancestor: drop an unused local variable assignment

https://www.mercurial-scm.org/repo/hg/rev/68b09ebf1c13
changeset:   43969:68b09ebf1c13
user:Matt Harbison 
date:Fri Dec 27 13:11:22 2019 -0500
summary: ancestor: drop another unused variable assignment

https://www.mercurial-scm.org/repo/hg/rev/3194cc8c8de0
changeset:   43970:3194cc8c8de0
user:Matt Harbison 
date:Fri Dec 27 13:13:33 2019 -0500
summary: bugzilla: drop an unused variable assignment

https://www.mercurial-scm.org/repo/hg/rev/6d2b5c4efdae
changeset:   43971:6d2b5c4efdae
user:Matt Harbison 
date:Fri Dec 27 13:18:19 2019 -0500
summary: changegroup: drop an unused variable assignment

https://www.mercurial-scm.org/repo/hg/rev/7a2c49a3cbae
changeset:   43972:7a2c49a3cbae
user:Matt Harbison 
date:Fri Dec 27 13:24:20 2019 -0500
summary: debug: drop unused variable assignments

https://www.mercurial-scm.org/repo/hg/rev/ca5bd34c597b
changeset:   43973:ca5bd34c597b
user:Matt Harbison 
date:Fri Dec 27 13:29:45 2019 -0500
summary: githelp: drop unused variable assignments

https://www.mercurial-scm.org/repo/hg/rev/a2ad5aeedfdf
changeset:   43974:a2ad5aeedfdf
user:Matt Harbison 
date:Fri Dec 27 13:42:52 2019 -0500
summary: perf: fix the time measurement for pathcopies relative to p2

https://www.mercurial-scm.org/repo/hg/rev/2bbb2f556ecf
changeset:   43975:2bbb2f556ecf
user:Matt Harbison 
date:Fri Dec 27 13:45:05 2019 -0500
summary: perf: drop an unnecessary `pass`

https://www.mercurial-scm.org/repo/hg/rev/943e34522b37
changeset:   43976:943e34522b37
user:Matt Harbison 
date:Fri Dec 27 13:47:17 2019 -0500
summary: perf: drop an unused variable assignment

https://www.mercurial-scm.org/repo/hg/rev/04e0e0e73892
changeset:   43977:04e0e0e73892
user:Matt Harbison 
date:Fri Dec 27 13:50:53 2019 -0500
summary: polib: drop an unused local function

https://www.mercurial-scm.org/repo/hg/rev/bd88407edc0e
changeset:   43978:bd88407edc0e
user:Matt Harbison 
date:Fri Dec 27 13:52:39 2019 -0500
summary: rebase: drop an unused variable assignment

https://www.mercurial-scm.org/repo/hg/rev/bdb357161d7a
changeset:   43979:bdb357161d7a
user:Matt Harbison 
date:Fri Dec 27 13:56:08 2019 -0500
summary: revlog: drop an unused variable assignment

https://www.mercurial-scm.org/repo/hg/rev/3e4294aa7944
changeset:   43980:3e4294aa7944
user:Matt Harbison 
date:Fri Dec 27 14:58:02 2019 -0500
summary: hgweb: drop an unused variable assignment

https://www.mercurial-scm.org/repo/hg/rev/414cb20e241e
changeset:   43981:414cb20e241e
user:Matt Harbison 
date:Fri Dec 27 16:30:14 2019 -0500
summary: shelve: drop an unused variable assignment

https://www.mercurial-scm.org/repo/hg/rev/bd3fa45c0662
changeset:   43982:bd3fa45c0662
user:Matt Harbison 
date:Fri Dec 27 16:57:28 2019 -0500
summary: run-tests: ensure the script exits when it fails to change 
directories

https://www.mercurial-scm.org/repo/hg/rev/236cec445be2
changeset:   43983:236cec445be2
user:Matt Harbison 
date:Fri Dec 27 17:53:56 2019 -0500
summary: absorb: avoid using a list comprehension to fill a list with fixed 
values

https://www.mercurial-scm.org/repo/hg/rev/3622f4fafd35
changeset:   43984:3622f4fafd35

D7797: rust-nodemap: pure Rust example

2020-01-06 Thread gracinet (Georges Racinet)
gracinet created this revision.
Herald added subscribers: mercurial-devel, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  To run, use `cargo run --release --example nodemap`
  
  This demonstrates that simple scenarios entirely written
  in Rust can content themselves with `NodeTree`.
  
  The example mmaps both the nodemap file and the revlog index.
  We had of course to include an implementation of `RevlogIndex`
  directly, which isn't much at this stage. It felt a bit
  prematurate to include it in the lib.
  
  Here are some first performance measurements, obtained with
  this example, on a clone of mozilla-central with 44
  changesets:
  
(create) Nodemap constructed in RAM in 153.638305ms
(query CAE63161B68962) found in 22.362us: Ok(Some(269489))
(bench) Did 3 queries in 36.418µs (mean 12.139µs)
(bench) Did 50 queries in 184.318µs (mean 3.686µs)
(bench) Did 10 queries in 31.053461ms (mean 310ns)
  
  To be fair, even between bench runs, results tend to depend whether
  the file is still in kernel caches, and it's not so easy to
  get back to a real cold start. The worst we've seen was in the
  50us ballpark.
  
  In any busy server setting, the pages would always be in RAM.
  
  We hope it's good enough not to be significantly slower on any
  concrete Mercurial operation than the C nodetree when fully in RAM,
  and of course this implementation has the serious headstart advantage
  of persistence.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7797

AFFECTED FILES
  rust/Cargo.lock
  rust/hg-core/Cargo.toml
  rust/hg-core/examples/nodemap/index.rs
  rust/hg-core/examples/nodemap/main.rs

CHANGE DETAILS

diff --git a/rust/hg-core/examples/nodemap/main.rs 
b/rust/hg-core/examples/nodemap/main.rs
new file mode 100644
--- /dev/null
+++ b/rust/hg-core/examples/nodemap/main.rs
@@ -0,0 +1,150 @@
+// Copyright 2019-2020 Georges Racinet 
+//
+// This software may be used and distributed according to the terms of the
+// GNU General Public License version 2 or any later version.
+
+extern crate clap;
+extern crate hg;
+extern crate memmap;
+
+use clap::*;
+use hg::revlog::node::*;
+use hg::revlog::nodemap::*;
+use hg::revlog::*;
+use memmap::MmapOptions;
+use rand::Rng;
+use std::fs::File;
+use std::io;
+use std::io::Write;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+use std::time::Instant;
+
+mod index;
+use index::Index;
+
+fn mmap_index(repo_path: ) -> Index {
+let mut path = PathBuf::from(repo_path);
+path.extend([".hg", "store", "00changelog.i"].iter());
+Index::load_mmap(path)
+}
+
+fn mmap_nodemap(path: ) -> NodeTree {
+let file = File::open(path).unwrap();
+let mmap = unsafe { MmapOptions::new().map().unwrap() };
+let len = mmap.len();
+NodeTree::load_bytes(Box::new(mmap), 0, len)
+}
+
+/// Scan the whole index and create the corresponding nodemap file at `path`
+fn create(index: , path: ) -> io::Result<()> {
+let mut file = File::create(path)?;
+let start = Instant::now();
+let mut nm = NodeTree::default();
+for rev in 0..index.len() {
+let rev = rev as Revision;
+nm.insert(index, index.node(rev).unwrap(), rev).unwrap();
+}
+eprintln!("Nodemap constructed in RAM in {:?}", start.elapsed());
+file.write(_readonly_and_added_bytes().1)?;
+eprintln!("Nodemap written to disk");
+Ok(())
+}
+
+fn query(index: , nm: , prefix: ) {
+let start = Instant::now();
+let res = nm.find_hex(index, prefix);
+println!("Result found in {:?}: {:?}", start.elapsed(), res);
+}
+
+fn bench(index: , nm: , queries: usize) {
+let len = index.len() as u32;
+let mut rng = rand::thread_rng();
+let nodes: Vec = (0..queries)
+.map(|_| {
+index
+.node((rng.gen::() % len) as Revision)
+.unwrap()
+.clone()
+})
+.collect();
+if queries < 10 {
+let nodes_hex: Vec =
+nodes.iter().map(|n| node_to_hex(n)).collect();
+println!("Nodes: {:?}", nodes_hex);
+}
+let mut last: Option = None;
+let start = Instant::now();
+for node in nodes.iter() {
+last = nm.find_bin(index, node.into()).unwrap();
+}
+let elapsed = start.elapsed();
+println!(
+"Did {} queries in {:?} (mean {:?}), last was {:?} with result {:?}",
+queries,
+elapsed,
+elapsed / (queries as u32),
+node_to_hex(nodes.last().unwrap()),
+last
+);
+}
+
+fn main() {
+let matches = App::new("Nodemap pure Rust example")
+.arg(
+Arg::with_name("REPOSITORY")
+.help("Path to the repository, always necessary for its index")
+.required(true),
+)
+.arg(
+Arg::with_name("NODEMAP_FILE")
+.help("Path to the nodemap file, independent of REPOSITORY")
+.required(true),

D7798: rust-nodemap: special case for prefixes of NULL_NODE

2020-01-06 Thread gracinet (Georges Racinet)
gracinet created this revision.
Herald added subscribers: mercurial-devel, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  We have to behave as though NULL_NODE was stored in the node tree,
  although we don't store it.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7798

AFFECTED FILES
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -13,8 +13,8 @@
 //! is used in a more abstract context.
 
 use super::{
-node::get_nybble, Node, NodeError, NodePrefix, NodePrefixRef, Revision,
-RevlogIndex,
+node::get_nybble, node::NULL_NODE, Node, NodeError, NodePrefix,
+NodePrefixRef, Revision, RevlogIndex, NULL_REVISION,
 };
 use std::fmt;
 use std::mem;
@@ -209,6 +209,31 @@
 })
 }
 
+/// validate that the candidate's node starts indeed with given prefix,
+/// and treat ambiguities related to `NULL_REVISION`.
+///
+/// From the data in the NodeTree, one can only conclude that some
+/// revision is the only one for a *subprefix* of the one being looked up.
+fn validate_candidate<'p>(
+idx:  RevlogIndex,
+prefix: NodePrefixRef<'p>,
+rev: Option,
+) -> Result, NodeMapError> {
+if prefix.is_prefix_of(_NODE) {
+// NULL_REVISION always matches a prefix made only of zeros
+// and any other *valid* result is an ambiguity
+match rev {
+None => Ok(Some(NULL_REVISION)),
+Some(r) => match has_prefix_or_none(idx, prefix, r)? {
+None => Ok(Some(NULL_REVISION)),
+_ => Err(NodeMapError::MultipleResults),
+},
+}
+} else {
+rev.map_or(Ok(None), |r| has_prefix_or_none(idx, prefix, r))
+}
+}
+
 impl NodeTree {
 /// Initiate a NodeTree from an immutable slice-like of `Block`
 ///
@@ -282,9 +307,6 @@
 }
 
 /// Main working method for `NodeTree` searches
-///
-/// This partial implementation lacks
-/// - special cases for NULL_REVISION
 fn lookup<'p>(
 ,
 prefix: NodePrefixRef<'p>,
@@ -519,9 +541,7 @@
 idx:  RevlogIndex,
 prefix: NodePrefixRef<'a>,
 ) -> Result, NodeMapError> {
-self.lookup(prefix.clone()).and_then(|opt| {
-opt.map_or(Ok(None), |rev| has_prefix_or_none(idx, prefix, rev))
-})
+validate_candidate(idx, prefix.clone(), self.lookup(prefix)?)
 }
 }
 
@@ -645,8 +665,9 @@
 
 assert_eq!(nt.find_hex(, "0"), Err(MultipleResults));
 assert_eq!(nt.find_hex(, "01"), Ok(Some(9)));
-assert_eq!(nt.find_hex(, "00"), Ok(Some(0)));
+assert_eq!(nt.find_hex(, "00"), Err(MultipleResults));
 assert_eq!(nt.find_hex(, "00a"), Ok(Some(0)));
+assert_eq!(nt.find_hex(, "000"), Ok(Some(NULL_REVISION)));
 }
 
 #[test]
@@ -665,7 +686,8 @@
 };
 assert_eq!(nt.find_hex(, "10")?, Some(1));
 assert_eq!(nt.find_hex(, "c")?, Some(2));
-assert_eq!(nt.find_hex(, "00")?, Some(0));
+assert_eq!(nt.find_hex(, "00"), Err(MultipleResults));
+assert_eq!(nt.find_hex(, "000")?, Some(NULL_REVISION));
 assert_eq!(nt.find_hex(, "01")?, Some(9));
 Ok(())
 }



To: gracinet, #hg-reviewers
Cc: durin42, kevincox, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7796: rust-nodemap: input/output primitives

2020-01-06 Thread gracinet (Georges Racinet)
gracinet created this revision.
Herald added subscribers: mercurial-devel, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  These allow to initiate a `NodeTree` from an immutable opaque
  sequence of bytes, which could be passed over from Python
  (extracted from a `PyBuffer`) or directly mmapped from a file.
  
  Conversely, we can consume
  a `NodeTree`, extracting the bytes that express what
  has been added to the immutable part, together with the
  original immutable part.
  This gives callers the choice to start a new Nodetree.
  After writing to disk, some would prefer to reread for
  best guarantees (very cheap if mmapping), some others will
  find it more convenient to grow the memory that was considered
  immutable in the `NodeTree` and continue from there.
  
  In `load_bytes`, we anticipate a bit on the file format for
  the final version, allowing an offset for fixed data at the
  beginning of the file.
  
  This is enough to build examples running on real data and
  start gathering performance hints.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7796

AFFECTED FILES
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -17,8 +17,10 @@
 RevlogIndex,
 };
 use std::fmt;
+use std::mem;
 use std::ops::Deref;
 use std::ops::Index;
+use std::slice;
 
 #[derive(Debug, PartialEq)]
 pub enum NodeMapError {
@@ -132,6 +134,8 @@
 #[derive(Clone, PartialEq)]
 pub struct Block([RawElement; 16]);
 
+pub const BLOCK_SIZE: usize = mem::size_of::();
+
 impl Block {
 fn new() -> Self {
 Block([-1; 16])
@@ -221,6 +225,57 @@
 }
 }
 
+/// Create from an opaque bunch of bytes
+///
+/// The created `NodeTreeBytes` is taken after the fixed `offset` from
+/// `buffer`, of which exactly `amount` bytes are used.
+///
+/// - `buffer` could be derived from `PyBuffer` and `Mmap` objects.
+/// - `offset` allows for the final file format to include fixed data
+///   (generation number, behavioural flags)
+/// - `amount` is expressed in bytes, and is not automatically derived from
+///   `bytes`, so that a caller that manages them atomically can perform
+///   temporary disk serializations and still rollback easily if needed.
+///   First use-case for this would be to support Mercurial shell hooks.
+///
+/// panics if `buffer` is smaller than `offset + amount`
+pub fn load_bytes(
+bytes: Box + Send>,
+offset: usize,
+amount: usize,
+) -> Self {
+NodeTree::new(Box::new(NodeTreeBytes::new(bytes, offset, amount)))
+}
+
+/// Retrieve added `Block` and the original immutable data
+pub fn into_readonly_and_added(
+self,
+) -> (Box + Send>, Vec) {
+let mut vec = self.growable;
+let readonly = self.readonly;
+if readonly.last() != Some() {
+vec.push(self.root);
+}
+(readonly, vec)
+}
+
+/// Retrieve added `Blocks` as bytes, ready to be written to persistent
+/// storage
+pub fn into_readonly_and_added_bytes(
+self,
+) -> (Box + Send>, Vec) {
+let (readonly, vec) = self.into_readonly_and_added();
+let bytes = unsafe {
+Vec::from_raw_parts(
+vec.as_ptr() as *mut u8,
+vec.len() * BLOCK_SIZE,
+vec.capacity() * BLOCK_SIZE,
+)
+};
+mem::forget(vec);
+(readonly, bytes)
+}
+
 /// Total number of blocks
 fn len() -> usize {
 self.readonly.len() + self.growable.len() + 1
@@ -366,6 +421,42 @@
 }
 }
 
+pub struct NodeTreeBytes {
+buffer: Box + Send>,
+offset: usize,
+len_in_blocks: usize,
+}
+
+impl NodeTreeBytes {
+fn new(
+buffer: Box + Send>,
+offset: usize,
+amount: usize,
+) -> Self {
+assert!(buffer.len() >= offset + amount);
+let len_in_blocks = amount / BLOCK_SIZE;
+NodeTreeBytes {
+buffer,
+offset,
+len_in_blocks,
+}
+}
+}
+
+impl Deref for NodeTreeBytes {
+type Target = [Block];
+
+fn deref() -> &[Block] {
+unsafe {
+slice::from_raw_parts(
+().as_ptr().offset(self.offset as isize)
+as *const Block,
+self.len_in_blocks,
+)
+}
+}
+}
+
 struct NodeTreeVisitor<'n, 'p> {
 nt: &'n NodeTree,
 prefix: NodePrefixRef<'p>,
@@ -710,4 +801,31 @@
 
 Ok(())
 }
+
+#[test]
+fn test_into_added_empty() {
+assert!(sample_nodetree().into_readonly_and_added().1.is_empty());
+assert!(sample_nodetree()
+.into_readonly_and_added_bytes()
+.1
+.is_empty());
+}

D7795: rust-nodemap: insert method

2020-01-06 Thread gracinet (Georges Racinet)
gracinet created this revision.
Herald added subscribers: mercurial-devel, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  In this implementation, we are in direct competition
  with the C version: this Rust version will have a clear
  startup advantage because it will read the data from disk,
  but the insertion happens all in memory for both.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7795

AFFECTED FILES
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -12,7 +12,10 @@
 //! Following existing implicit conventions, the "nodemap" terminology
 //! is used in a more abstract context.
 
-use super::{NodeError, NodePrefix, NodePrefixRef, Revision, RevlogIndex};
+use super::{
+node::get_nybble, Node, NodeError, NodePrefix, NodePrefixRef, Revision,
+RevlogIndex,
+};
 use std::fmt;
 use std::ops::Deref;
 use std::ops::Index;
@@ -51,6 +54,15 @@
 }
 }
 
+pub trait MutableNodeMap: NodeMap {
+fn insert(
+ self,
+index: ,
+node: ,
+rev: Revision,
+) -> Result<(), NodeMapError>;
+}
+
 /// Low level NodeTree [`Blocks`] elements
 ///
 /// These are exactly as for instance on persistent storage.
@@ -242,6 +254,116 @@
 done: false,
 }
 }
+/// Return a mutable reference for `Block` at index `idx`.
+///
+/// If `idx` lies in the immutable area, then the reference is to
+/// a newly appended copy.
+///
+/// Returns (new_idx, glen, mut_ref) where
+///
+/// - `new_idx` is the index of the mutable `Block`
+/// - `mut_ref` is a mutable reference to the mutable Block.
+/// - `glen` is the new length of `self.growable`
+///
+/// Note: the caller wouldn't be allowed to query `self.growable.len()`
+/// itself because of the mutable borrow taken with the returned `Block`
+fn mutable_block( self, idx: usize) -> (usize,  Block, usize) {
+let ro_blocks = 
+let ro_len = ro_blocks.len();
+let glen = self.growable.len();
+if idx < ro_len {
+// TODO OPTIM I think this makes two copies
+self.growable.push(ro_blocks[idx].clone());
+(glen + ro_len,  self.growable[glen], glen + 1)
+} else if glen + ro_len == idx {
+(idx,  self.root, glen)
+} else {
+(idx,  self.growable[idx - ro_len], glen)
+}
+}
+
+/// Main insertion method
+///
+/// This will dive in the node tree to find the deepest `Block` for
+/// `node`, split it as much as needed and record `node` in there.
+/// The method then backtracks, updating references in all the visited
+/// blocks from the root.
+///
+/// All the mutated `Block` are copied first to the growable part if
+/// needed. That happens for those in the immutable part except the root.
+pub fn insert(
+ self,
+index: ,
+node: ,
+rev: Revision,
+) -> Result<(), NodeMapError> {
+let ro_len = ();
+
+let mut visit_steps: Vec<(usize, u8, Option)> = self
+.visit(node.into())
+.map(|(_leaf, visit, nybble, rev_opt)| (visit, nybble, rev_opt))
+.collect();
+let read_nybbles = visit_steps.len();
+// visit_steps cannot be empty, since we always visit the root block
+let (deepest_idx, mut nybble, rev_opt) = visit_steps.pop().unwrap();
+let (mut block_idx, mut block, mut glen) =
+self.mutable_block(deepest_idx);
+
+match rev_opt {
+None => {
+// Free slot in the deepest block: no splitting has to be done
+block.write(nybble, Element::Rev(rev));
+}
+Some(old_rev) => {
+let old_node = index.node(old_rev).ok_or_else(|| {
+NodeMapError::RevisionNotInIndex(old_rev)
+})?;
+if old_node == node {
+return Ok(()); // avoid creating lots of useless blocks
+}
+
+// Looping over the tail of nybbles in both nodes, creating
+// new blocks until we find the difference
+let mut new_block_idx = ro_len + glen;
+for nybble_pos in read_nybbles..40 {
+block.write(nybble, Element::Block(new_block_idx));
+
+let new_nybble = get_nybble(nybble_pos, node);
+let old_nybble = get_nybble(nybble_pos, old_node);
+
+if old_nybble == new_nybble {
+self.growable.push(Block::new());
+block =  self.growable[glen];
+glen += 1;
+new_block_idx += 1;
+nybble = 

D7788: rust-node: binary Node and conversion utilities

2020-01-06 Thread gracinet (Georges Racinet)
gracinet created this revision.
Herald added subscribers: mercurial-devel, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Our choice of type makes sure that a `Node` has the exact
  wanted size. We'll use a different type for prefixes.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7788

AFFECTED FILES
  rust/hg-core/src/dirstate/dirs_multiset.rs
  rust/hg-core/src/matchers.rs
  rust/hg-core/src/revlog.rs
  rust/hg-core/src/revlog/node.rs
  rust/hg-core/src/utils.rs
  rust/hg-core/src/utils/hg_path.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/utils/hg_path.rs 
b/rust/hg-core/src/utils/hg_path.rs
--- a/rust/hg-core/src/utils/hg_path.rs
+++ b/rust/hg-core/src/utils/hg_path.rs
@@ -157,7 +157,7 @@
 return Err(HgPathError::ContainsNullByte(
 bytes.to_vec(),
 index,
-))
+));
 }
 b'/' => {
 if previous_byte.is_some() && previous_byte == Some(b'/') {
diff --git a/rust/hg-core/src/utils.rs b/rust/hg-core/src/utils.rs
--- a/rust/hg-core/src/utils.rs
+++ b/rust/hg-core/src/utils.rs
@@ -18,10 +18,7 @@
 /// use crate::hg::utils::replace_slice;
 /// let mut line = b"I hate writing tests!".to_vec();
 /// replace_slice( line, b"hate", b"love");
-/// assert_eq!(
-/// line,
-/// b"I love writing tests!".to_vec()
-/// );
+/// assert_eq!(line, b"I love writing tests!".to_vec());
 /// ```
 pub fn replace_slice(buf:  [T], from: &[T], to: &[T])
 where
@@ -66,18 +63,9 @@
 
 /// ```
 /// use hg::utils::SliceExt;
-/// assert_eq!(
-/// b"  to trim  ".trim(),
-/// b"to trim"
-/// );
-/// assert_eq!(
-/// b"to trim  ".trim(),
-/// b"to trim"
-/// );
-/// assert_eq!(
-/// b"  to trim".trim(),
-/// b"to trim"
-/// );
+/// assert_eq!(b"  to trim  ".trim(), b"to trim");
+/// assert_eq!(b"to trim  ".trim(), b"to trim");
+/// assert_eq!(b"  to trim".trim(), b"to trim");
 /// ```
 fn trim() -> &[u8] {
 self.trim_start().trim_end()
diff --git a/rust/hg-core/src/revlog/node.rs b/rust/hg-core/src/revlog/node.rs
new file mode 100644
--- /dev/null
+++ b/rust/hg-core/src/revlog/node.rs
@@ -0,0 +1,91 @@
+// Copyright 2019-2020 Georges Racinet 
+//
+// This software may be used and distributed according to the terms of the
+// GNU General Public License version 2 or any later version.
+
+//! Definitions and utilities for Revision nodes
+//!
+//! In Mercurial code base, it is customary to call "a node" the binary SHA
+//! of a revision.
+
+use std::num::ParseIntError;
+
+/// Binary revisions SHA
+pub type Node = [u8; 20];
+
+/// The node value for NULL_REVISION
+pub const NULL_NODE: Node = [0; 20];
+
+#[derive(Debug, PartialEq)]
+pub enum NodeError {
+ExactLengthRequired(String),
+NotHexadecimal,
+}
+
+pub fn node_from_hex(hex: ) -> Result {
+if hex.len() != 40 {
+return Err(NodeError::ExactLengthRequired(hex.to_string()));
+}
+let mut node = [0; 20];
+for i in 0..20 {
+node[i] = u8::from_str_radix([i * 2..i * 2 + 2], 16)?
+}
+Ok(node)
+}
+
+pub fn node_to_hex(n: ) -> String {
+let as_vec: Vec = n.iter().map(|b| format!("{:02x}", b)).collect();
+as_vec.join("")
+}
+
+/// Retrieve the `i`th half-byte from a bytes slice
+///
+/// This is also the `i`th hexadecimal digit in numeric form,
+/// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
+pub fn get_nybble(i: usize, s: &[u8]) -> u8 {
+if i % 2 == 0 {
+s[i / 2] >> 4
+} else {
+s[i / 2] & 0x0f
+}
+}
+
+impl From for NodeError {
+fn from(_: ParseIntError) -> Self {
+NodeError::NotHexadecimal
+}
+}
+
+#[cfg(test)]
+mod tests {
+use super::*;
+
+const SAMPLE_NODE_HEX:  = "0123456789abcdeffedcba9876543210deadbeef";
+
+#[test]
+fn test_node_from_hex() {
+assert_eq!(
+node_from_hex(SAMPLE_NODE_HEX),
+Ok([
+0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc,
+0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0xde, 0xad, 0xbe, 0xef
+])
+);
+let short = "0123456789abcdeffedcba9876543210";
+assert_eq!(
+node_from_hex(short),
+Err(NodeError::ExactLengthRequired(short.to_string())),
+);
+}
+
+#[test]
+fn test_node_to_hex() {
+assert_eq!(
+node_to_hex(&[
+0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc,
+0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0xde, 0xad, 0xbe, 0xef
+]),
+SAMPLE_NODE_HEX
+);
+}
+}
diff --git a/rust/hg-core/src/revlog.rs b/rust/hg-core/src/revlog.rs
--- a/rust/hg-core/src/revlog.rs
+++ b/rust/hg-core/src/revlog.rs
@@ -5,7 +5,9 @@
 // GNU General Public License version 2 or any later 

D7794: rust-nodemap: generic NodeTreeVisitor

2020-01-06 Thread gracinet (Georges Racinet)
gracinet created this revision.
Herald added subscribers: mercurial-devel, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This iterator will help avoid code duplication when we'll
  implement `insert()`, in which we will need to
  traverse the node tree, and to remember the visited blocks.
  
  The iterator converts the three variants of `Element` into the
  boolean `leaf` and `Option` instead of just emitting the
  variant it's seen. The motivation is to avoid a dead match arm
  in the future `insert()`.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7794

AFFECTED FILES
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -222,17 +222,58 @@
 ,
 prefix: NodePrefixRef<'p>,
 ) -> Result, NodeMapError> {
-let mut visit = self.len() - 1;
-for i in 0..prefix.len() {
-let nybble = prefix.get_nybble(i);
-match self[visit].read(nybble) {
-Element::None => return Ok(None),
-Element::Rev(r) => return Ok(Some(r)),
-Element::Block(idx) => visit = idx,
+for (leaf, _, _, opt) in self.visit(prefix) {
+if leaf {
+return Ok(opt);
 }
 }
 Err(NodeMapError::MultipleResults)
 }
+
+fn visit<'n, 'p>(
+&'n self,
+prefix: NodePrefixRef<'p>,
+) -> NodeTreeVisitor<'n, 'p> {
+NodeTreeVisitor {
+nt: self,
+prefix: prefix,
+visit: self.len() - 1,
+nybble_idx: 0,
+done: false,
+}
+}
+}
+
+struct NodeTreeVisitor<'n, 'p> {
+nt: &'n NodeTree,
+prefix: NodePrefixRef<'p>,
+visit: usize,
+nybble_idx: usize,
+done: bool,
+}
+
+impl<'n, 'p> Iterator for NodeTreeVisitor<'n, 'p> {
+type Item = (bool, usize, u8, Option);
+
+fn next( self) -> Option {
+if self.done || self.nybble_idx >= self.prefix.len() {
+return None;
+}
+
+let nybble = self.prefix.get_nybble(self.nybble_idx);
+let visit = self.visit;
+let (leaf, opt) = match self.nt[visit].read(nybble) {
+Element::None => (true, None),
+Element::Rev(r) => (true, Some(r)),
+Element::Block(idx) => {
+self.visit = idx;
+(false, None)
+}
+};
+self.nybble_idx += 1;
+self.done = leaf;
+Some((leaf, visit, nybble, opt))
+}
 }
 
 impl From> for NodeTree {



To: gracinet, #hg-reviewers
Cc: durin42, kevincox, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7793: rust-nodemap: mutable NodeTree data structure

2020-01-06 Thread gracinet (Georges Racinet)
gracinet created this revision.
Herald added subscribers: mercurial-devel, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Thanks to the previously indexing abstraction,
  the only difference in the lookup algorithm is that we
  don't need the special case for an empty NodeTree any more.
  
  We've considered making the mutable root an `Option`,
  but that leads to unpleasant checks and `unwrap()` unless we
  abstract it as typestate patterns (`NodeTree` and
  `NodeTree`) which seem exaggerated in that
  case.
  
  The initial copy of the root block is a very minor
  performance penalty, given that it typically occurs just once
  per transaction.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7793

AFFECTED FILES
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -148,16 +148,31 @@
 }
 }
 
-/// A 16-radix tree with the root block at the end
+/// A mutable 16-radix tree with the root block logically at the end
+///
+/// Because of the append only nature of our node trees, we need to
+/// keep the original untouched and store new blocks separately.
+///
+/// The mutable root `Block` is kept apart so that we don't have to rebump
+/// it on each insertion.
 pub struct NodeTree {
 readonly: Box + Send>,
+growable: Vec,
+root: Block,
 }
 
 impl Index for NodeTree {
 type Output = Block;
 
 fn index(, i: usize) ->  {
-[i]
+let ro_len = self.readonly.len();
+if i < ro_len {
+[i]
+} else if i == ro_len + self.growable.len() {
+
+} else {
+[i - ro_len]
+}
 }
 }
 
@@ -179,8 +194,24 @@
 }
 
 impl NodeTree {
+/// Initiate a NodeTree from an immutable slice-like of `Block`
+///
+/// We keep `readonly` and clone its root block if it isn't empty.
+fn new(readonly: Box + Send>) -> Self {
+let root = readonly
+.last()
+.map(|b| b.clone())
+.unwrap_or_else(|| Block::new());
+NodeTree {
+readonly: readonly,
+growable: Vec::new(),
+root: root,
+}
+}
+
+/// Total number of blocks
 fn len() -> usize {
-self.readonly.len()
+self.readonly.len() + self.growable.len() + 1
 }
 
 /// Main working method for `NodeTree` searches
@@ -191,11 +222,7 @@
 ,
 prefix: NodePrefixRef<'p>,
 ) -> Result, NodeMapError> {
-let len = self.len();
-if len == 0 {
-return Ok(None);
-}
-let mut visit = len - 1;
+let mut visit = self.len() - 1;
 for i in 0..prefix.len() {
 let nybble = prefix.get_nybble(i);
 match self[visit].read(nybble) {
@@ -210,16 +237,18 @@
 
 impl From> for NodeTree {
 fn from(vec: Vec) -> Self {
-NodeTree {
-readonly: Box::new(vec),
-}
+Self::new(Box::new(vec))
 }
 }
 
 impl fmt::Debug for NodeTree {
 fn fmt(, f:  fmt::Formatter<'_>) -> fmt::Result {
-let blocks: &[Block] = &*self.readonly;
-write!(f, "readonly: {:?}", blocks)
+let readonly: &[Block] = &*self.readonly;
+write!(
+f,
+"readonly: {:?}, growable: {:?}, root: {:?}",
+readonly, self.growable, self.root
+)
 }
 }
 
@@ -320,7 +349,9 @@
 assert_eq!(
 format!("{:?}", nt),
 "readonly: \
- [[0: Rev(9)], [0: Rev(0), 1: Rev(9)], [0: Block(1), 1: Rev(1)]]"
+ [[0: Rev(9)], [0: Rev(0), 1: Rev(9)], [0: Block(1), 1: Rev(1)]], \
+ growable: [], \
+ root: [0: Block(1), 1: Rev(1)]",
 );
 }
 
@@ -352,4 +383,24 @@
 assert_eq!(nt.find_hex(, "00a"), Ok(Some(0)));
 }
 
+#[test]
+fn test_mutated_find() -> Result<(), NodeMapError> {
+let mut idx = TestIndex::new();
+pad_insert( idx, 9, "012");
+pad_insert( idx, 0, "00a");
+pad_insert( idx, 2, "cafe");
+pad_insert( idx, 3, "15");
+pad_insert( idx, 1, "10");
+
+let nt = NodeTree {
+readonly: sample_nodetree().readonly,
+growable: vec![block![0: Rev(1), 5: Rev(3)]],
+root: block![0: Block(1), 1:Block(3), 12: Rev(2)],
+};
+assert_eq!(nt.find_hex(, "10")?, Some(1));
+assert_eq!(nt.find_hex(, "c")?, Some(2));
+assert_eq!(nt.find_hex(, "00")?, Some(0));
+assert_eq!(nt.find_hex(, "01")?, Some(9));
+Ok(())
+}
 }



To: gracinet, #hg-reviewers
Cc: durin42, kevincox, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7792: rust-nodemap: abstracting the indexing

2020-01-06 Thread gracinet (Georges Racinet)
gracinet created this revision.
Herald added subscribers: mercurial-devel, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  In the forthcoming mutable implementation, we'll have to visit
  node trees that are more complex than a single slice, although
  the algorithm will still be expressed in simple indexing terms.
  
  We still refrain using `#[inline]` indications as being
  premature optimizations, but we strongly hope the compiler will
  indeed inline most of the glue.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7792

AFFECTED FILES
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -15,6 +15,7 @@
 use super::{NodeError, NodePrefix, NodePrefixRef, Revision, RevlogIndex};
 use std::fmt;
 use std::ops::Deref;
+use std::ops::Index;
 
 #[derive(Debug, PartialEq)]
 pub enum NodeMapError {
@@ -152,6 +153,14 @@
 readonly: Box + Send>,
 }
 
+impl Index for NodeTree {
+type Output = Block;
+
+fn index(, i: usize) ->  {
+[i]
+}
+}
+
 /// Return `None` unless the `Node` for `rev` has given prefix in `index`.
 fn has_prefix_or_none<'p>(
 idx:  RevlogIndex,
@@ -170,6 +179,10 @@
 }
 
 impl NodeTree {
+fn len() -> usize {
+self.readonly.len()
+}
+
 /// Main working method for `NodeTree` searches
 ///
 /// This partial implementation lacks
@@ -178,14 +191,14 @@
 ,
 prefix: NodePrefixRef<'p>,
 ) -> Result, NodeMapError> {
-let blocks: &[Block] = &*self.readonly;
-if blocks.is_empty() {
+let len = self.len();
+if len == 0 {
 return Ok(None);
 }
-let mut visit = blocks.len() - 1;
+let mut visit = len - 1;
 for i in 0..prefix.len() {
 let nybble = prefix.get_nybble(i);
-match blocks[visit].read(nybble) {
+match self[visit].read(nybble) {
 Element::None => return Ok(None),
 Element::Rev(r) => return Ok(Some(r)),
 Element::Block(idx) => visit = idx,



To: gracinet, #hg-reviewers
Cc: durin42, kevincox, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7790: rust-node: handling binary Node prefix

2020-01-06 Thread gracinet (Georges Racinet)
gracinet created this revision.
Herald added subscribers: mercurial-devel, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Parallel to the inner signatures of the nodetree functions in
  revlog.c, we'll have to handle prefixes of `Node` in binary
  form.
  
  There's a complication due to the fact that we'll be sometimes
  interested in prefixes with an odd number of hexadecimal digits,
  which translates in binary form by a last byte in which only the
  highest weight 4 bits are considered.
  
  There are a few candidates for inlining here, but we refrain from
  such premature optimizations, letting the compiler decide.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7790

AFFECTED FILES
  rust/hg-core/src/revlog.rs
  rust/hg-core/src/revlog/node.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/node.rs b/rust/hg-core/src/revlog/node.rs
--- a/rust/hg-core/src/revlog/node.rs
+++ b/rust/hg-core/src/revlog/node.rs
@@ -19,6 +19,7 @@
 #[derive(Debug, PartialEq)]
 pub enum NodeError {
 ExactLengthRequired(String),
+PrefixTooLong(String),
 NotHexadecimal,
 }
 
@@ -56,6 +57,88 @@
 }
 }
 
+/// The beginning of a binary revision SHA.
+///
+/// Since it can potentially come from an hexadecimal representation with
+/// odd length, it needs to carry around whether the last 4 bits are relevant
+/// or not.
+#[derive(Debug, PartialEq)]
+pub struct NodePrefix {
+buf: Vec,
+is_odd: bool,
+}
+
+impl NodePrefix {
+/// Conversion from hexadecimal string representation
+pub fn from_hex(hex: ) -> Result {
+let len = hex.len();
+if len > 40 {
+return Err(NodeError::PrefixTooLong(hex.to_string()));
+}
+let is_odd = len % 2 == 1;
+let mut buf: Vec = Vec::with_capacity(20);
+for i in 0..len / 2 {
+buf.push(u8::from_str_radix([i * 2..i * 2 + 2], 16)?);
+}
+if is_odd {
+buf.push(u8::from_str_radix([len - 1..], 16)? << 4);
+}
+Ok(NodePrefix { buf, is_odd })
+}
+
+pub fn borrow<'a>(&'a self) -> NodePrefixRef<'a> {
+NodePrefixRef {
+buf: ,
+is_odd: self.is_odd,
+}
+}
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct NodePrefixRef<'a> {
+buf: &'a [u8],
+is_odd: bool,
+}
+
+impl<'a> NodePrefixRef<'a> {
+pub fn len() -> usize {
+if self.is_odd {
+self.buf.len() * 2 - 1
+} else {
+self.buf.len() * 2
+}
+}
+
+pub fn is_prefix_of(, node: ) -> bool {
+if self.is_odd {
+let buf = self.buf;
+let last_pos = buf.len() - 1;
+node.starts_with(buf.split_at(last_pos).0)
+&& node[last_pos] >> 4 == buf[last_pos] >> 4
+} else {
+node.starts_with(self.buf)
+}
+}
+
+/// Retrieve the `i`th half-byte from the prefix.
+///
+/// This is also the `i`th hexadecimal digit in numeric form,
+/// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
+pub fn get_nybble(, i: usize) -> u8 {
+get_nybble(i, self.buf)
+}
+}
+
+/// A shortcut for full `Node` references
+impl<'a> From<&'a Node> for NodePrefixRef<'a> {
+fn from(node: &'a Node) -> Self {
+NodePrefixRef {
+buf: &*node,
+is_odd: false,
+}
+}
+}
+
 #[cfg(test)]
 mod tests {
 use super::*;
@@ -88,4 +171,68 @@
 SAMPLE_NODE_HEX
 );
 }
+
+#[test]
+fn test_prefix_from_hex() -> Result<(), NodeError> {
+assert_eq!(
+NodePrefix::from_hex("0e1")?,
+NodePrefix {
+buf: vec![14, 16],
+is_odd: true
+}
+);
+assert_eq!(
+NodePrefix::from_hex("0e1a")?,
+NodePrefix {
+buf: vec![14, 26],
+is_odd: false
+}
+);
+
+// checking limit case
+assert_eq!(
+NodePrefix::from_hex(SAMPLE_NODE_HEX)?,
+NodePrefix {
+buf: node_from_hex(SAMPLE_NODE_HEX)?.iter().cloned().collect(),
+is_odd: false
+}
+);
+
+Ok(())
+}
+
+#[test]
+fn test_prefix_from_hex_errors() {
+assert_eq!(
+NodePrefix::from_hex("testgr"),
+Err(NodeError::NotHexadecimal)
+);
+let long = "0";
+match NodePrefix::from_hex(long)
+.expect_err("should be refused as too long")
+{
+NodeError::PrefixTooLong(s) => assert_eq!(s, long),
+err => panic!(format!("Should have been TooLong, got {:?}", err)),
+}
+}
+
+#[test]
+fn test_is_prefix_of() -> Result<(), NodeError> {
+let mut node: Node = [0; 20];
+node[0] = 0x12;
+node[1] = 0xca;
+

D7791: rust-nodemap: NodeMap trait with simplest implementor

2020-01-06 Thread gracinet (Georges Racinet)
gracinet created this revision.
Herald added subscribers: mercurial-devel, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  We're defining here only a small part of the immutable
  methods it will have at the end. This is so we can
  focus in the following changesets on the needed abstractions
  for a mutable append-only serializable version.
  
  The first implementor exposes the actual lookup
  algorithm in its simplest form. It will have to be expanded
  to account for the missing methods, and the special cases
  related to NULL_NODE.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7791

AFFECTED FILES
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -12,8 +12,43 @@
 //! Following existing implicit conventions, the "nodemap" terminology
 //! is used in a more abstract context.
 
-use super::Revision;
+use super::{NodeError, NodePrefix, NodePrefixRef, Revision, RevlogIndex};
 use std::fmt;
+use std::ops::Deref;
+
+#[derive(Debug, PartialEq)]
+pub enum NodeMapError {
+MultipleResults,
+InvalidNodePrefix(NodeError),
+/// A `Revision` stored in the nodemap could not be found in the index
+RevisionNotInIndex(Revision),
+}
+
+impl From for NodeMapError {
+fn from(err: NodeError) -> Self {
+NodeMapError::InvalidNodePrefix(err)
+}
+}
+
+/// Mapping system from Mercurial nodes to revision numbers.
+///
+/// Many methods in this trait work in conjunction with a `RevlogIndex`
+/// whose data should not be owned by the `NodeMap`.
+pub trait NodeMap {
+fn find_bin<'a>(
+,
+idx:  RevlogIndex,
+prefix: NodePrefixRef<'a>,
+) -> Result, NodeMapError>;
+
+fn find_hex(
+,
+idx:  RevlogIndex,
+prefix: ,
+) -> Result, NodeMapError> {
+self.find_bin(idx, NodePrefix::from_hex(prefix)?.borrow())
+}
+}
 
 /// Low level NodeTree [`Blocks`] elements
 ///
@@ -112,9 +147,87 @@
 }
 }
 
+/// A 16-radix tree with the root block at the end
+pub struct NodeTree {
+readonly: Box + Send>,
+}
+
+/// Return `None` unless the `Node` for `rev` has given prefix in `index`.
+fn has_prefix_or_none<'p>(
+idx:  RevlogIndex,
+prefix: NodePrefixRef<'p>,
+rev: Revision,
+) -> Result, NodeMapError> {
+idx.node(rev)
+.ok_or_else(|| NodeMapError::RevisionNotInIndex(rev))
+.map(|node| {
+if prefix.is_prefix_of(node) {
+Some(rev)
+} else {
+None
+}
+})
+}
+
+impl NodeTree {
+/// Main working method for `NodeTree` searches
+///
+/// This partial implementation lacks
+/// - special cases for NULL_REVISION
+fn lookup<'p>(
+,
+prefix: NodePrefixRef<'p>,
+) -> Result, NodeMapError> {
+let blocks: &[Block] = &*self.readonly;
+if blocks.is_empty() {
+return Ok(None);
+}
+let mut visit = blocks.len() - 1;
+for i in 0..prefix.len() {
+let nybble = prefix.get_nybble(i);
+match blocks[visit].read(nybble) {
+Element::None => return Ok(None),
+Element::Rev(r) => return Ok(Some(r)),
+Element::Block(idx) => visit = idx,
+}
+}
+Err(NodeMapError::MultipleResults)
+}
+}
+
+impl From> for NodeTree {
+fn from(vec: Vec) -> Self {
+NodeTree {
+readonly: Box::new(vec),
+}
+}
+}
+
+impl fmt::Debug for NodeTree {
+fn fmt(, f:  fmt::Formatter<'_>) -> fmt::Result {
+let blocks: &[Block] = &*self.readonly;
+write!(f, "readonly: {:?}", blocks)
+}
+}
+
+impl NodeMap for NodeTree {
+fn find_bin<'a>(
+,
+idx:  RevlogIndex,
+prefix: NodePrefixRef<'a>,
+) -> Result, NodeMapError> {
+self.lookup(prefix.clone()).and_then(|opt| {
+opt.map_or(Ok(None), |rev| has_prefix_or_none(idx, prefix, rev))
+})
+}
+}
+
 #[cfg(test)]
 mod tests {
+use super::NodeMapError::*;
 use super::*;
+use crate::revlog::node::{node_from_hex, Node};
+use std::collections::HashMap;
 
 /// Creates a `Block` using a syntax close to the `Debug` output
 macro_rules! block {
@@ -160,4 +273,70 @@
 assert_eq!(block.read(4), Element::Rev(1));
 }
 
+type TestIndex = HashMap;
+
+impl RevlogIndex for TestIndex {
+fn node(, rev: Revision) -> Option<> {
+self.get()
+}
+
+fn len() -> usize {
+self.len()
+}
+}
+
+/// Pad hexadecimal Node prefix with zeros on the right, then insert
+///
+/// This is just to avoid having to repeatedly write 40 hexadecimal
+/// digits for test data.
+fn pad_insert(idx:  TestIndex, rev: Revision, 

D7789: rust-revlog: a trait for the revlog index

2020-01-06 Thread gracinet (Georges Racinet)
gracinet created this revision.
Herald added subscribers: mercurial-devel, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  As explained in the doc comment, this is the minimum needed
  for our immediate concern, which is to implement a nodemap
  in Rust.
  
  The trait will be later implemented in `hg-cpython` by the
  index Python object implemented in C, thanks to exposition
  of the corresponding functions as a capsule.
  
  The `None` return cases in `node()` match what the `index_node()`
  C function does.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7789

AFFECTED FILES
  rust/hg-core/src/revlog.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog.rs b/rust/hg-core/src/revlog.rs
--- a/rust/hg-core/src/revlog.rs
+++ b/rust/hg-core/src/revlog.rs
@@ -40,3 +40,15 @@
 ParentOutOfRange(Revision),
 WorkingDirectoryUnsupported,
 }
+
+/// The Mercurial Revlog Index
+///
+/// This is currently limited to the minimal interface that is needed for
+/// the [`nodemap`](nodemap/index.html) module
+pub trait RevlogIndex {
+/// Total number of Revisions referenced in this index
+fn len() -> usize;
+
+/// Return the Node or `None` if rev is out of bounds or `NULL_REVISON`
+fn node(, rev: Revision) -> Option<>;
+}



To: gracinet, #hg-reviewers
Cc: durin42, kevincox, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7787: rust-nodemap: building blocks for nodetree structures

2020-01-06 Thread gracinet (Georges Racinet)
gracinet created this revision.
Herald added subscribers: mercurial-devel, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This is similar to `nodetreenode` in `revlog.c`. We give it
  a higher level feeling for ease of handling in Rust context
  and provide tools for tests and debugging.
  
  The encoding choice is dictated by our ultimate goal in this
  series, that is to make an append-only persistent version of
  `nodetree`: the 0th Block must be adressed from other Blocks.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7787

AFFECTED FILES
  rust/hg-core/src/revlog.rs
  rust/hg-core/src/revlog/nodemap.rs

CHANGE DETAILS

diff --git a/rust/hg-core/src/revlog/nodemap.rs 
b/rust/hg-core/src/revlog/nodemap.rs
new file mode 100644
--- /dev/null
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -0,0 +1,163 @@
+// Copyright 2018-2020 Georges Racinet 
+//   and Mercurial contributors
+//
+// This software may be used and distributed according to the terms of the
+// GNU General Public License version 2 or any later version.
+//! Indexing facilities for fast retrieval of `Revision` from `Node`
+//!
+//! This provides a variation on the radix tree with valency 16 that is
+//! provided as "nodetree" in revlog.c, ready for append-only persistence
+//! on disk.
+//!
+//! Following existing implicit conventions, the "nodemap" terminology
+//! is used in a more abstract context.
+
+use super::Revision;
+use std::fmt;
+
+/// Low level NodeTree [`Blocks`] elements
+///
+/// These are exactly as for instance on persistent storage.
+type RawElement = i32;
+
+/// High level representation of values in NodeTree
+/// [`Blocks`](struct.Block.html)
+///
+/// This is the high level representation that most algorithms should
+/// use.
+#[derive(Clone, Debug, Eq, PartialEq)]
+enum Element {
+Rev(Revision),
+Block(usize),
+None,
+}
+
+impl From for Element {
+/// Conversion from low level representation, after endianity conversion.
+///
+/// See [`Block`](struct.Block.html) for explanation about the encoding.
+fn from(raw: RawElement) -> Element {
+if raw >= 0 {
+Element::Block(raw as usize)
+} else if raw == -1 {
+Element::None
+} else {
+Element::Rev(-raw - 2)
+}
+}
+}
+
+impl From for RawElement {
+fn from(elt: Element) -> RawElement {
+match elt {
+Element::None => 0,
+Element::Block(i) => i as RawElement,
+Element::Rev(rev) => -rev - 2,
+}
+}
+}
+
+/// A logical block of the `NodeTree`, packed with a fixed size.
+///
+/// These are always used in container types implementing `Index`,
+/// such as ``
+///
+/// As an array of integers, its ith element encodes that the
+/// ith potential edge from the block, representing the ith hexadecimal digit
+/// (nybble) `i` is either:
+///
+/// - absent (value -1)
+/// - another `Block` in the same indexable container (value ≥ 0)
+///  - a `Revision` leaf (value ≤ -2)
+///
+/// Endianity has to be fixed for consistency on shared storage across
+/// different architectures.
+///
+/// A key difference with the C `nodetree` is that we need to be
+/// able to represent the [`Block`] at index 0, hence -1 is the empty marker
+/// rather than 0 and the `Revision` range upper limit of -2 instead of -1.
+///
+/// Another related difference is that `NULL_REVISION` (-1) is not
+/// represented at all, because we want an immutable empty nodetree
+/// to be valid.
+
+#[derive(Clone, PartialEq)]
+pub struct Block([RawElement; 16]);
+
+impl Block {
+fn new() -> Self {
+Block([-1; 16])
+}
+
+fn read(, nybble: u8) -> Element {
+Element::from(RawElement::from_be(self.0[nybble as usize]))
+}
+
+fn write( self, nybble: u8, elt: Element) {
+self.0[nybble as usize] = RawElement::to_be(elt.into())
+}
+}
+
+impl fmt::Debug for Block {
+/// sparse representation for testing and debugging purposes
+fn fmt(, f:  fmt::Formatter<'_>) -> fmt::Result {
+let mut inner: Vec = Vec::new();
+for i in 0..16 {
+let elt = self.read(i);
+if elt != Element::None {
+inner.push(format!("{:X}: {:?}", i, elt));
+}
+}
+write!(f, "[{}]", inner.join(", "))
+}
+}
+
+#[cfg(test)]
+mod tests {
+use super::*;
+
+/// Creates a `Block` using a syntax close to the `Debug` output
+macro_rules! block {
+[$($nybble:tt : $variant:ident($val:tt)),*] => (
+{
+let mut block = Block::new();
+$(block.write($nybble, Element::$variant($val)));*;
+block
+}
+)
+}
+
+#[test]
+fn test_block_debug() {
+let mut block = Block::new();
+block.write(1, Element::Rev(3));
+block.write(10, Element::Block(0));
+assert_eq!(format!("{:?}", 

mercurial@43961: 2 new changesets

2020-01-06 Thread Mercurial Commits
2 new changesets in mercurial:

https://www.mercurial-scm.org/repo/hg/rev/ab3fd8077f5e
changeset:   43960:ab3fd8077f5e
user:Georges Racinet 
date:Thu Dec 12 03:46:47 2019 +0100
summary: rust-index: add a `inner` method to the Index struct

https://www.mercurial-scm.org/repo/hg/rev/b69d5f3a41d0
changeset:   43961:b69d5f3a41d0
bookmark:@
tag: tip
user:Georges Racinet 
date:Mon Dec 23 10:02:50 2019 -0800
summary: rust-index: add a struct wrapping the C index

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