D6825: hgext: start building a library for simple hooks

2020-02-15 Thread joerg.sonnenberger (Joerg Sonnenberger)
Herald added a subscriber: mjpieters.
joerg.sonnenberger retitled this revision from "contrib: start building a 
library for simple hooks" to "hgext: start building a library for simple hooks".
joerg.sonnenberger updated this revision to Diff 20249.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D6825?vs=16431&id=20249

BRANCH
  default

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

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

AFFECTED FILES
  hgext/hooklib/__init__.py
  hgext/hooklib/changeset_published.py
  hgext/hooklib/enforce_draft_commits.py
  hgext/hooklib/reject_merge_commits.py
  hgext/hooklib/reject_new_heads.py
  setup.py
  tests/test-help.t
  tests/test-hooklib-changeset_published.t
  tests/test-hooklib-enforce_draft_commits.t
  tests/test-hooklib-reject_merge_commits.t
  tests/test-hooklib-reject_new_heads.t
  tests/unwrap-message-id.py

CHANGE DETAILS

diff --git a/tests/unwrap-message-id.py b/tests/unwrap-message-id.py
--- a/tests/unwrap-message-id.py
+++ b/tests/unwrap-message-id.py
@@ -1,6 +1,8 @@
 from __future__ import absolute_import, print_function
 
-import re
 import sys
 
-print(re.sub(r"(?<=Message-Id:) \n ", " ", sys.stdin.read()), end="")
+for line in sys.stdin:
+if line in ("Message-Id: \n", "In-reply-to: \n"):
+line = line[:-2]
+print(line, end="")
diff --git a/tests/test-hooklib-reject_new_heads.t 
b/tests/test-hooklib-reject_new_heads.t
new file mode 100644
--- /dev/null
+++ b/tests/test-hooklib-reject_new_heads.t
@@ -0,0 +1,53 @@
+  $ cat <> $HGRCPATH
+  > [extensions]
+  > hooklib =
+  > 
+  > [phases]
+  > publish = False
+  > EOF
+  $ hg init a
+  $ hg --cwd a debugbuilddag '.:parent.*parent'
+  $ hg --cwd a log -G
+  o  changeset:   2:fa942426a6fd
+  |  tag: tip
+  |  parent:  0:1ea73414a91b
+  |  user:debugbuilddag
+  |  date:Thu Jan 01 00:00:02 1970 +
+  |  summary: r2
+  |
+  | o  changeset:   1:66f7d451a68b
+  |/   user:debugbuilddag
+  |date:Thu Jan 01 00:00:01 1970 +
+  |summary: r1
+  |
+  o  changeset:   0:1ea73414a91b
+ tag: parent
+ user:debugbuilddag
+ date:Thu Jan 01 00:00:00 1970 +
+ summary: r0
+  
+  $ hg init b
+  $ cat <> b/.hg/hgrc
+  > [hooks]
+  > pretxnclose.reject_new_heads = \
+  >   python:hgext.hooklib.reject_new_heads.hook
+  > EOF
+  $ hg --cwd b pull ../a
+  pulling from ../a
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  error: pretxnclose.reject_new_heads hook failed: Changes on branch 'default' 
resulted in multiple heads
+  transaction abort!
+  rollback completed
+  abort: Changes on branch 'default' resulted in multiple heads
+  [255]
+  $ hg --cwd b pull ../a -r 1ea73414a91b
+  pulling from ../a
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files
+  new changesets 1ea73414a91b (1 drafts)
+  (run 'hg update' to get a working copy)
diff --git a/tests/test-hooklib-reject_merge_commits.t 
b/tests/test-hooklib-reject_merge_commits.t
new file mode 100644
--- /dev/null
+++ b/tests/test-hooklib-reject_merge_commits.t
@@ -0,0 +1,78 @@
+  $ cat <> $HGRCPATH
+  > [extensions]
+  > hooklib =
+  > 
+  > [phases]
+  > publish = False
+  > EOF
+  $ hg init a
+  $ hg --cwd a debugbuilddag 
'.:parent.:childa*parent/childa> b/.hg/hgrc
+  > [hooks]
+  > pretxnchangegroup.reject_merge_commits = \
+  >   python:hgext.hooklib.reject_merge_commits.hook
+  > EOF
+  $ hg --cwd b pull ../a -r a6b287721c3b
+  pulling from ../a
+  adding changesets
+  adding manifests
+  adding file changes
+  error: pretxnchangegroup.reject_merge_commits hook failed: a6b287721c3b 
rejected as merge on the same branch. Please consider rebase.
+  transaction abort!
+  rollback completed
+  abort: a6b287721c3b rejected as merge on the same branch. Please consider 
rebase.
+  [255]
+  $ hg --cwd b pull ../a -r 1ea73414a91b
+  pulling from ../a
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files
+  new changesets 1ea73414a91b (1 drafts)
+  (run 'hg update' to get a working copy)
+  $ hg --cwd b pull ../a -r a9fb040caedd
+  pulling from ../a
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 0 changes to 0 files
+  new changesets 66f7d451a68b:a9fb040caedd (3 drafts)
+  (run 'hg update' to get a working copy)
diff --git a/tests/test-hooklib-enforce_draft_commits.t 
b/tests/test-hooklib-enforce_draft_commits.t
new file mode 100644
--- /dev/null
+++ b/tests/test-hooklib-enforce_draft_commits.t
@@ -0,0 +1,45 @@
+  $ cat <> $HGRCPATH
+  > [extensions]
+  > hooklib =
+  > 
+  > [phases]
+  > publish = False
+  > EOF
+  $ hg init a
+  $ hg --cwd a debugbuilddag .
+  $ hg --cwd a phase --public 0
+  $ hg init b
+  $ cat <> b/.hg/hgrc
+  > [hooks]
+  > pretxnclos

D8122: pyoxidizer: allow extensions to be loaded from the file system

2020-02-15 Thread indygreg (Gregory Szorc)
This revision is now accepted and ready to land.
indygreg added a comment.
indygreg accepted this revision.


  This simply tells PyOxidizer to load the `sys.meta_path` importer that 
performs filesystem-based importing, like a normal Python process. 
https://pyoxidizer.readthedocs.io/en/stable/config_api.html#pythoninterpreterconfig

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

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

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


D8125: transactions: convert changes['phases'] to list of ranges

2020-02-15 Thread joerg.sonnenberger (Joerg Sonnenberger)
joerg.sonnenberger created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Consecutive revisions are often in the same phase, especially public
  revisions. This means that a dictionary keyed by the revision for the
  phase transitions is highly redundant. Build a list of (range, (old,
  new)) entries instead and aggressively merge ranges with the same
  transition. For the test case in issue5691, this reduces memory use by
  ~20MB.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  mercurial/localrepo.py
  mercurial/phases.py
  mercurial/scmutil.py
  tests/testlib/ext-phase-report.py

CHANGE DETAILS

diff --git a/tests/testlib/ext-phase-report.py 
b/tests/testlib/ext-phase-report.py
--- a/tests/testlib/ext-phase-report.py
+++ b/tests/testlib/ext-phase-report.py
@@ -5,21 +5,22 @@
 
 def reposetup(ui, repo):
 def reportphasemove(tr):
-for rev, move in sorted(tr.changes[b'phases'].items()):
-if move[0] is None:
-ui.write(
-(
-b'test-debug-phase: new rev %d:  x -> %d\n'
-% (rev, move[1])
+for revs, move in sorted(tr.changes[b"phases"], key=lambda r: r[0][0]):
+for rev in revs:
+if move[0] is None:
+ui.write(
+(
+b'test-debug-phase: new rev %d:  x -> %d\n'
+% (rev, move[1])
+)
 )
-)
-else:
-ui.write(
-(
-b'test-debug-phase: move rev %d: %d -> %d\n'
-% (rev, move[0], move[1])
+else:
+ui.write(
+(
+b'test-debug-phase: move rev %d: %d -> %d\n'
+% (rev, move[0], move[1])
+)
 )
-)
 
 class reportphaserepo(repo.__class__):
 def transaction(self, *args, **kwargs):
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -2047,14 +2047,11 @@
 pull/unbundle.
 """
 origrepolen = tr.changes.get(b'origrepolen', len(repo))
-phasetracking = tr.changes.get(b'phases', {})
-if not phasetracking:
-return
-published = [
-rev
-for rev, (old, new) in pycompat.iteritems(phasetracking)
-if new == phases.public and rev < origrepolen
-]
+published = []
+for revs, (old, new) in tr.changes.get(b'phases', []):
+if new != phases.public:
+continue
+published.extend(rev for rev in revs if rev < origrepolen)
 if not published:
 return
 repo.ui.status(
diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -217,16 +217,98 @@
 
 
 def _trackphasechange(data, rev, old, new):
-"""add a phase move the  dictionnary
+"""add a phase move to the  list of ranges
 
 If data is None, nothing happens.
 """
 if data is None:
 return
-existing = data.get(rev)
-if existing is not None:
-old = existing[0]
-data[rev] = (old, new)
+
+# If data is empty, create a one-revision range and done
+if not data:
+data.insert(0, (pycompat.xrange(rev, rev + 1), (old, new)))
+return
+
+def insert(data, idx, rev, t):
+merge_before = False
+if idx:
+r1, t1 = data[idx - 1]
+merge_before = r1[-1] + 1 == rev and t1 == t
+merge_after = False
+if idx < len(data):
+r2, t2 = data[idx]
+merge_after = r2[0] == rev + 1 and t2 == t
+
+if merge_before and merge_after:
+data[idx - 1] = (pycompat.xrange(r1[0], r2[-1] + 1), t)
+data.pop(idx)
+elif merge_before:
+data[idx - 1] = (pycompat.xrange(r1[0], rev + 1), t)
+elif merge_after:
+data[idx] = (pycompat.xrange(rev, r2[-1] + 1), t)
+else:
+data.insert(idx, (pycompat.xrange(rev, rev + 1), t))
+
+def split_range(data, idx, rev, t):
+r1, t1 = data[idx]
+if t == t1:
+return
+t = (t1[0], t[1])
+if len(r1) == 1:
+data.pop(idx)
+insert(data, idx, rev, t)
+elif r1[0] == rev:
+data[idx] = (pycompat.xrange(rev + 1, r1[-1] + 1), t1)
+insert(data, idx, rev, t)
+elif r1[-1] == rev:
+data[idx] = (pycompat.xrange(r1[0], rev), t1)
+insert(data, idx + 1, rev, t)
+else:
+data[i

D8124: bookmarks: avoid traceback when two pushes race to delete the same bookmark

2020-02-15 Thread valentin.gatienbaron (Valentin Gatien-Baron)
valentin.gatienbaron created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  `hg push -f -B remote-only-bookmark` can raise server-side in
  `bookmarks._del` (specifically in `self._refmap.pop(mark)`), if the
  remote-only bookmark got deleted concurrently.
  
  Fix this by simply not deleting the non-existent bookmark in that
  case.
  
  For avoidance of doubt, refusing to delete a bookmark that doesn't
  exist when the push starts is taking care of elsewhere; no change of
  behavior there.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  mercurial/bookmarks.py
  relnotes/next

CHANGE DETAILS

diff --git a/relnotes/next b/relnotes/next
--- a/relnotes/next
+++ b/relnotes/next
@@ -18,6 +18,7 @@
 
 == Bug Fixes  ==
 
+ * Fix server exception when concurrent pushes delete the same bookmark
 
 == Backwards Compatibility Changes ==
 
diff --git a/mercurial/bookmarks.py b/mercurial/bookmarks.py
--- a/mercurial/bookmarks.py
+++ b/mercurial/bookmarks.py
@@ -173,6 +173,8 @@
 nrefs.sort()
 
 def _del(self, mark):
+if mark not in self._refmap:
+return
 self._clean = False
 node = self._refmap.pop(mark)
 nrefs = self._nodemap[node]



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


D8117: bookmarks: prevent pushes of divergent bookmarks (foo@remote)

2020-02-15 Thread valentin.gatienbaron (Valentin Gatien-Baron)
valentin.gatienbaron updated this revision to Diff 20246.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D8117?vs=20211&id=20246

BRANCH
  default

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

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

AFFECTED FILES
  mercurial/bookmarks.py
  mercurial/bundle2.py
  mercurial/exchange.py
  relnotes/next
  tests/test-bookmarks-pushpull.t

CHANGE DETAILS

diff --git a/tests/test-bookmarks-pushpull.t b/tests/test-bookmarks-pushpull.t
--- a/tests/test-bookmarks-pushpull.t
+++ b/tests/test-bookmarks-pushpull.t
@@ -328,6 +328,17 @@
 
 #endif
 
+Divergent bookmark cannot be exported
+
+  $ hg book W@default
+  $ hg push -B W@default ../a
+  pushing to ../a
+  searching for changes
+  cannot push divergent bookmark W@default!
+  no changes found
+  [2]
+  $ hg book -d W@default
+
 export the active bookmark
 
   $ hg bookmark V
diff --git a/relnotes/next b/relnotes/next
--- a/relnotes/next
+++ b/relnotes/next
@@ -18,6 +18,7 @@
 
 == Bug Fixes  ==
 
+ * prevent pushes of divergent bookmarks (foo@remote)
 
 == Backwards Compatibility Changes ==
 
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -856,7 +856,11 @@
 for b, scid, dcid in addsrc:
 if b in explicit:
 explicit.remove(b)
-pushop.outbookmarks.append((b, b'', scid))
+if bookmod.isdivergent(b):
+pushop.ui.warn(_(b'cannot push divergent bookmark %s!\n') % b)
+pushop.bkresult = 2
+else:
+pushop.outbookmarks.append((b, b'', scid))
 # search for overwritten bookmark
 for b, scid, dcid in list(advdst) + list(diverge) + list(differ):
 if b in explicit:
diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -2368,6 +2368,11 @@
 b'prepushkey', throw=True, **pycompat.strkwargs(hookargs)
 )
 
+for book, node in changes:
+if bookmarks.isdivergent(book):
+msg = _(b'cannot accept divergent bookmark %s!') % book
+raise error.Abort(msg)
+
 bookstore.applychanges(op.repo, op.gettransaction(), changes)
 
 if pushkeycompat:
diff --git a/mercurial/bookmarks.py b/mercurial/bookmarks.py
--- a/mercurial/bookmarks.py
+++ b/mercurial/bookmarks.py
@@ -483,6 +483,8 @@
 
 
 def pushbookmark(repo, key, old, new):
+if isdivergent(key):
+return False
 if bookmarksinstore(repo):
 wlock = util.nullcontextmanager()
 else:



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


D8123: relnotes: add entry about previous `hg recover` change

2020-02-15 Thread valentin.gatienbaron (Valentin Gatien-Baron)
valentin.gatienbaron created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  relnotes/next

CHANGE DETAILS

diff --git a/relnotes/next b/relnotes/next
--- a/relnotes/next
+++ b/relnotes/next
@@ -26,6 +26,8 @@
can use the new `conflictparents()` revset for finding the other
parent during a conflict.
 
+ * `hg recover` does not verify the validity of the whole repository
+   anymore. You can pass `--verify` or call `hg verify` if necessary.
 
 == Internal API Changes ==
 



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


D8035: copy: add experimental support for marking committed copies

2020-02-15 Thread martinvonz (Martin von Zweigbergk)
Closed by commit rHG9dab3fa64325: copy: add experimental support for marking 
committed copies (authored by martinvonz).
This revision was automatically updated to reflect the committed changes.
This revision was not accepted when it landed; it landed in state "Needs 
Review".

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D8035?vs=20233&id=20244

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

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

AFFECTED FILES
  mercurial/cmdutil.py
  mercurial/commands.py
  relnotes/next
  tests/test-rename-after-merge.t
  tests/test-rename-rev.t
  tests/test-rename.t

CHANGE DETAILS

diff --git a/tests/test-rename.t b/tests/test-rename-rev.t
copy from tests/test-rename.t
copy to tests/test-rename-rev.t
--- a/tests/test-rename.t
+++ b/tests/test-rename-rev.t
@@ -6,693 +6,51 @@
   $ echo d1/b > d1/b
   $ echo d2/b > d2/b
   $ hg add d1/a d1/b d1/ba d1/d11/a1 d2/b
-  $ hg commit -m "1"
-
-rename a single file
-
-  $ hg rename d1/d11/a1 d2/c
-  $ hg --config ui.portablefilenames=abort rename d1/a d1/con.xml
-  abort: filename contains 'con', which is reserved on Windows: d1/con.xml
-  [255]
-  $ hg sum
-  parent: 0:9b4b6e7b2c26 tip
-   1
-  branch: default
-  commit: 1 renamed
-  update: (current)
-  phases: 1 draft
-  $ hg status -C
-  A d2/c
-d1/d11/a1
-  R d1/d11/a1
-  $ hg update -C
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ rm d2/c
-
-rename a single file using absolute paths
-
-  $ hg rename `pwd`/d1/d11/a1 `pwd`/d2/c
-  $ hg status -C
-  A d2/c
-d1/d11/a1
-  R d1/d11/a1
-  $ hg update -C
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ rm d2/c
+  $ hg commit -m "intial"
 
-rename --after a single file
 
-  $ mv d1/d11/a1 d2/c
-  $ hg rename --after d1/d11/a1 d2/c
-  $ hg status -C
-  A d2/c
-d1/d11/a1
-  R d1/d11/a1
-  $ hg update -C
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ rm d2/c
-
-rename --after a single file when src and tgt already tracked
-
-  $ mv d1/d11/a1 d2/c
-  $ hg addrem -s 0
-  removing d1/d11/a1
-  adding d2/c
-  $ hg rename --after d1/d11/a1 d2/c
-  $ hg status -C
-  A d2/c
-d1/d11/a1
-  R d1/d11/a1
-  $ hg update -C
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ rm d2/c
-
-rename --after a single file to a nonexistent target filename
-
-  $ hg rename --after d1/a dummy
-  d1/a: not recording move - dummy does not exist
-  [1]
-
-move a single file to an existing directory
+Test single file
 
-  $ hg rename d1/d11/a1 d2
-  $ hg status -C
-  A d2/a1
-d1/d11/a1
-  R d1/d11/a1
-  $ hg update -C
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ rm d2/a1
-
-move --after a single file to an existing directory
-
-  $ mv d1/d11/a1 d2
-  $ hg rename --after d1/d11/a1 d2
-  $ hg status -C
-  A d2/a1
-d1/d11/a1
-  R d1/d11/a1
-  $ hg update -C
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ rm d2/a1
-
-rename a file using a relative path
-
-  $ (cd d1/d11; hg rename ../../d2/b e)
-  $ hg status -C
-  A d1/d11/e
-d2/b
-  R d2/b
-  $ hg update -C
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ rm d1/d11/e
-
-rename --after a file using a relative path
-
-  $ (cd d1/d11; mv ../../d2/b e; hg rename --after ../../d2/b e)
-  $ hg status -C
-  A d1/d11/e
-d2/b
-  R d2/b
-  $ hg update -C
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ rm d1/d11/e
-
-rename directory d1 as d3
-
-  $ hg rename d1/ d3
-  moving d1/a to d3/a
-  moving d1/b to d3/b
-  moving d1/ba to d3/ba
-  moving d1/d11/a1 to d3/d11/a1
-  $ hg status -C
-  A d3/a
-d1/a
-  A d3/b
-d1/b
-  A d3/ba
-d1/ba
-  A d3/d11/a1
-d1/d11/a1
-  R d1/a
-  R d1/b
-  R d1/ba
-  R d1/d11/a1
-  $ hg update -C
-  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ rm -rf d3
-
-rename --after directory d1 as d3
-
-  $ mv d1 d3
-  $ hg rename --after d1 d3
-  moving d1/a to d3/a
-  moving d1/b to d3/b
-  moving d1/ba to d3/ba
-  moving d1/d11/a1 to d3/d11/a1
-  $ hg status -C
-  A d3/a
-d1/a
-  A d3/b
+# One recoded copy, one copy to record after commit
+  $ hg cp d1/b d1/c
+  $ cp d1/b d1/d
+  $ hg add d1/d
+  $ hg ci -m 'copy d1/b to d1/c and d1/d'
+  $ hg st -C --change .
+  A d1/c
 d1/b
-  A d3/ba
-d1/ba
-  A d3/d11/a1
-d1/d11/a1
-  R d1/a
-  R d1/b
-  R d1/ba
-  R d1/d11/a1
-  $ hg update -C
-  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ rm -rf d3
-
-move a directory using a relative path
-
-  $ (cd d2; mkdir d3; hg rename ../d1/d11 d3)
-  moving ../d1/d11/a1 to d3/d11/a1
-  $ hg status -C
-  A d2/d3/d11/a1
-d1/d11/a1
-  R d1/d11/a1
-  $ hg update -C
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ rm -rf d2/d3
-
-move --after a directory using a relative path
-
-  $ (cd 

D8030: copy: add experimetal support for unmarking committed copies

2020-02-15 Thread martinvonz (Martin von Zweigbergk)
Closed by commit rHG7c4b98a4e536: copy: add experimetal support for unmarking 
committed copies (authored by martinvonz).
This revision was automatically updated to reflect the committed changes.
This revision was not accepted when it landed; it landed in state "Needs 
Review".

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D8030?vs=20231&id=20242

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

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

AFFECTED FILES
  mercurial/cmdutil.py
  mercurial/commands.py
  mercurial/context.py
  relnotes/next
  tests/test-completion.t
  tests/test-copy.t
  tests/test-rename-after-merge.t

CHANGE DETAILS

diff --git a/tests/test-rename-after-merge.t b/tests/test-rename-after-merge.t
--- a/tests/test-rename-after-merge.t
+++ b/tests/test-rename-after-merge.t
@@ -120,4 +120,10 @@
   $ hg log -r tip -C -v | grep copies
   copies:  b2 (b1)
 
+Test unmarking copies in merge commit
+
+  $ hg copy --forget --at-rev . b2
+  abort: cannot unmark copy in merge commit
+  [255]
+
   $ cd ..
diff --git a/tests/test-copy.t b/tests/test-copy.t
--- a/tests/test-copy.t
+++ b/tests/test-copy.t
@@ -319,5 +319,56 @@
   A dir2/bar
   A dir2/foo
   ? dir2/untracked
+# Clean up for next test
+  $ hg forget dir2
+  removing dir2/bar
+  removing dir2/foo
+  $ rm -r dir2
+
+Test uncopy on committed copies
+
+# Commit some copies
+  $ hg cp bar baz
+  $ hg cp bar qux
+  $ hg ci -m copies
+  $ hg st -C --change .
+  A baz
+bar
+  A qux
+bar
+  $ base=$(hg log -r '.^' -T '{rev}')
+  $ hg log -G -T '{rev}:{node|short} {desc}\n' -r $base:
+  @  5:a612dc2edfda copies
+  |
+  o  4:4800b1f1f38e add dir/
+  |
+  ~
+# Add a dirty change on top to show that it's unaffected
+  $ echo dirty >> baz
+  $ hg st
+  M baz
+  $ cat baz
+  bleah
+  dirty
+  $ hg copy --forget --at-rev . baz
+  saved backup bundle to 
$TESTTMP/part2/.hg/strip-backup/a612dc2edfda-e36b4448-uncopy.hg
+# The unwanted copy is no longer recorded, but the unrelated one is
+  $ hg st -C --change .
+  A baz
+  A qux
+bar
+# The old commit is gone and we have updated to the new commit
+  $ hg log -G -T '{rev}:{node|short} {desc}\n' -r $base:
+  @  5:c45090e5effe copies
+  |
+  o  4:4800b1f1f38e add dir/
+  |
+  ~
+# Working copy still has the uncommitted change
+  $ hg st
+  M baz
+  $ cat baz
+  bleah
+  dirty
 
   $ cd ..
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -257,7 +257,7 @@
   commit: addremove, close-branch, amend, secret, edit, force-close-branch, 
interactive, include, exclude, message, logfile, date, user, subrepos
   config: untrusted, edit, local, global, template
   continue: dry-run
-  copy: forget, after, force, include, exclude, dry-run
+  copy: forget, after, at-rev, force, include, exclude, dry-run
   debugancestor: 
   debugapplystreamclonebundle: 
   debugbuilddag: mergeable-file, overwritten-file, new-file
diff --git a/relnotes/next b/relnotes/next
--- a/relnotes/next
+++ b/relnotes/next
@@ -17,6 +17,9 @@
 
 == New Experimental Features ==
 
+ * Use `hg copy --forget --at-rev REV` to unmark already committed
+   copies.
+
 
 == Bug Fixes  ==
 
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -2487,6 +2487,17 @@
 editor=editor,
 )
 
+def tomemctx_for_amend(self, precursor):
+extra = precursor.extra().copy()
+extra[b'amend_source'] = precursor.hex()
+return self.tomemctx(
+text=precursor.description(),
+branch=precursor.branch(),
+extra=extra,
+date=precursor.date(),
+user=precursor.user(),
+)
+
 def isdirty(self, path):
 return path in self._cache
 
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -2312,6 +2312,13 @@
 (b'', b'forget', None, _(b'unmark a file as copied')),
 (b'A', b'after', None, _(b'record a copy that has already occurred')),
 (
+b'',
+b'at-rev',
+b'',
+_(b'unmark copies in the given revision (EXPERIMENTAL)'),
+_(b'REV'),
+),
+(
 b'f',
 b'force',
 None,
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1427,14 +1427,33 @@
 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
 
 if forget:
-match = scmutil.match(wctx, pats, opts)
-
-current_copies = wctx.p1copies()
-current_copies.update(wctx.p2copies())
-
-for f in wctx.walk(match):
+rev = opts[b'at_rev']
+if rev:
+ctx = scmutil.revsingle(repo, rev)
+else:
+ctx = repo[None]
+if ctx.rev() is None:
+new_ctx = ctx
+

D7894: nodemap: introduce an option to use mmap to read the nodemap mapping

2020-02-15 Thread marmoute (Pierre-Yves David)
Closed by commit rHGf7459da77f23: nodemap: introduce an option to use mmap to 
read the nodemap mapping (authored by marmoute).
This revision was automatically updated to reflect the committed changes.
This revision was not accepted when it landed; it landed in state "Needs 
Revision".

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7894?vs=20165&id=20240

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

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

AFFECTED FILES
  mercurial/configitems.py
  mercurial/debugcommands.py
  mercurial/localrepo.py
  mercurial/revlog.py
  mercurial/revlogutils/nodemap.py
  tests/test-persistent-nodemap.t

CHANGE DETAILS

diff --git a/tests/test-persistent-nodemap.t b/tests/test-persistent-nodemap.t
--- a/tests/test-persistent-nodemap.t
+++ b/tests/test-persistent-nodemap.t
@@ -84,3 +84,37 @@
   $ hg debugnodemap --check
   revision in index:   5002
   revision in nodemap: 5002
+
+Test code path without mmap
+---
+
+  $ echo bar > bar
+  $ hg add bar
+  $ hg ci -m 'bar' --config experimental.exp-persistent-nodemap.mmap=no
+
+  $ hg debugnodemap --check --config 
experimental.exp-persistent-nodemap.mmap=yes
+  revision in index:   5003
+  revision in nodemap: 5003
+  $ hg debugnodemap --check --config 
experimental.exp-persistent-nodemap.mmap=no
+  revision in index:   5003
+  revision in nodemap: 5003
+
+
+#if pure
+  $ hg debugnodemap --metadata
+  uid:  (glob)
+  tip-rev: 5002
+  data-length: 123328
+  data-unused: 384
+  $ f --sha256 .hg/store/00changelog-*.nd --size
+  .hg/store/00changelog-.nd: size=123328, 
sha256=10d26e9776b6596af0f89143a54eba8cc581e929c38242a02a7b0760698c6c70 (glob)
+
+#else
+  $ hg debugnodemap --metadata
+  uid:  (glob)
+  tip-rev: 5002
+  data-length: 122944
+  data-unused: 0
+  $ f --sha256 .hg/store/00changelog-*.nd --size
+  .hg/store/00changelog-.nd: size=122944, 
sha256=755976b22b64ab680401b45395953504e64e7fa8c31ac570f58dee21e15f9bc0 (glob)
+#endif
diff --git a/mercurial/revlogutils/nodemap.py b/mercurial/revlogutils/nodemap.py
--- a/mercurial/revlogutils/nodemap.py
+++ b/mercurial/revlogutils/nodemap.py
@@ -8,6 +8,7 @@
 
 from __future__ import absolute_import
 
+import errno
 import os
 import re
 import struct
@@ -45,11 +46,18 @@
 docket.data_unused = data_unused
 
 filename = _rawdata_filepath(revlog, docket)
-data = revlog.opener.tryread(filename)
+use_mmap = revlog.opener.options.get("exp-persistent-nodemap.mmap")
+try:
+with revlog.opener(filename) as fd:
+if use_mmap:
+data = util.buffer(util.mmapread(fd, data_length))
+else:
+data = fd.read(data_length)
+except OSError as e:
+if e.errno != errno.ENOENT:
+raise
 if len(data) < data_length:
 return None
-elif len(data) > data_length:
-data = data[:data_length]
 return docket, data
 
 
@@ -81,6 +89,8 @@
 
 can_incremental = util.safehasattr(revlog.index, 
"nodemap_data_incremental")
 ondisk_docket = revlog._nodemap_docket
+feed_data = util.safehasattr(revlog.index, "update_nodemap_data")
+use_mmap = revlog.opener.options.get("exp-persistent-nodemap.mmap")
 
 data = None
 # first attemp an incremental update of the data
@@ -97,12 +107,18 @@
 datafile = _rawdata_filepath(revlog, target_docket)
 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
 # store vfs
+new_length = target_docket.data_length + len(data)
 with revlog.opener(datafile, b'r+') as fd:
 fd.seek(target_docket.data_length)
 fd.write(data)
-fd.seek(0)
-new_data = fd.read(target_docket.data_length + len(data))
-target_docket.data_length += len(data)
+if feed_data:
+if use_mmap:
+fd.seek(0)
+new_data = fd.read(new_length)
+else:
+fd.flush()
+new_data = util.buffer(util.mmapread(fd, new_length))
+target_docket.data_length = new_length
 target_docket.data_unused += data_changed_count
 
 if data is None:
@@ -115,9 +131,14 @@
 data = persistent_data(revlog.index)
 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
 # store vfs
-new_data = data
-with revlog.opener(datafile, b'w') as fd:
+with revlog.opener(datafile, b'w+') as fd:
 fd.write(data)
+if feed_data:
+if use_mmap:
+new_data = data
+else:
+fd.flush()
+new_data = util.buffer(util.mmapread(fd, len(data)))
 target_docket.data_length = len(data)
 ta

D8029: copy: add option to unmark file as copied

2020-02-15 Thread martinvonz (Martin von Zweigbergk)
Closed by commit rHG8be0c63535b5: copy: add option to unmark file as copied 
(authored by martinvonz).
This revision was automatically updated to reflect the committed changes.
This revision was not accepted when it landed; it landed in state "Needs 
Review".

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D8029?vs=20204&id=20241

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

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

AFFECTED FILES
  mercurial/cmdutil.py
  mercurial/commands.py
  relnotes/next
  tests/test-completion.t
  tests/test-copy.t

CHANGE DETAILS

diff --git a/tests/test-copy.t b/tests/test-copy.t
--- a/tests/test-copy.t
+++ b/tests/test-copy.t
@@ -262,5 +262,62 @@
   xyzzy: not overwriting - file exists
   ('hg copy --after' to record the copy)
   [1]
+  $ hg co -qC .
+  $ rm baz xyzzy
+
+
+Test unmarking copy of a single file
+
+# Set up by creating a copy
+  $ hg cp bar baz
+# Test uncopying a non-existent file
+  $ hg copy --forget non-existent
+  non-existent: $ENOENT$
+# Test uncopying an tracked but unrelated file
+  $ hg copy --forget foo
+  foo: not unmarking as copy - file is not marked as copied
+# Test uncopying a copy source
+  $ hg copy --forget bar
+  bar: not unmarking as copy - file is not marked as copied
+# baz should still be marked as a copy
+  $ hg st -C
+  A baz
+bar
+# Test the normal case
+  $ hg copy --forget baz
+  $ hg st -C
+  A baz
+# Test uncopy with matching an non-matching patterns
+  $ hg cp bar baz --after
+  $ hg copy --forget bar baz
+  bar: not unmarking as copy - file is not marked as copied
+  $ hg st -C
+  A baz
+# Test uncopy with no exact matches
+  $ hg cp bar baz --after
+  $ hg copy --forget .
+  $ hg st -C
+  A baz
+  $ hg forget baz
+  $ rm baz
+
+Test unmarking copy of a directory
+
+  $ mkdir dir
+  $ echo foo > dir/foo
+  $ echo bar > dir/bar
+  $ hg add dir
+  adding dir/bar
+  adding dir/foo
+  $ hg ci -m 'add dir/'
+  $ hg cp dir dir2
+  copying dir/bar to dir2/bar
+  copying dir/foo to dir2/foo
+  $ touch dir2/untracked
+  $ hg copy --forget dir2
+  $ hg st -C
+  A dir2/bar
+  A dir2/foo
+  ? dir2/untracked
 
   $ cd ..
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -257,7 +257,7 @@
   commit: addremove, close-branch, amend, secret, edit, force-close-branch, 
interactive, include, exclude, message, logfile, date, user, subrepos
   config: untrusted, edit, local, global, template
   continue: dry-run
-  copy: after, force, include, exclude, dry-run
+  copy: forget, after, force, include, exclude, dry-run
   debugancestor: 
   debugapplystreamclonebundle: 
   debugbuilddag: mergeable-file, overwritten-file, new-file
diff --git a/relnotes/next b/relnotes/next
--- a/relnotes/next
+++ b/relnotes/next
@@ -12,6 +12,8 @@
commits that are being merged, when there are conflicts. Also works
for conflicts caused by e.g. `hg graft`.
 
+ * `hg copy --forget` can be used to unmark a file as copied.
+
 
 == New Experimental Features ==
 
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -2309,6 +2309,7 @@
 @command(
 b'copy|cp',
 [
+(b'', b'forget', None, _(b'unmark a file as copied')),
 (b'A', b'after', None, _(b'record a copy that has already occurred')),
 (
 b'f',
@@ -2333,8 +2334,11 @@
 exist in the working directory. If invoked with -A/--after, the
 operation is recorded, but no copying is performed.
 
-This command takes effect with the next commit. To undo a copy
-before that, see :hg:`revert`.
+To undo marking a file as copied, use --forget. With that option,
+all given (positional) arguments are unmarked as copies. The destination
+file(s) will be left in place (still tracked).
+
+This command takes effect with the next commit.
 
 Returns 0 on success, 1 if errors are encountered.
 """
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1410,12 +1410,15 @@
 
 
 def copy(ui, repo, pats, opts, rename=False):
+check_incompatible_arguments(opts, b'forget', [b'dry_run'])
+
 # called with the repo lock held
 #
 # hgsep => pathname that uses "/" to separate directories
 # ossep => pathname that uses os.sep to separate directories
 cwd = repo.getcwd()
 targets = {}
+forget = opts.get(b"forget")
 after = opts.get(b"after")
 dryrun = opts.get(b"dry_run")
 ctx = repo[None]
@@ -1423,6 +1426,24 @@
 
 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
 
+if forget:
+match = scmutil.match(wctx, pats, opts)
+
+current_copies = wctx.p1copies()
+current_copies.update(wctx.p2copies())
+
+for f in wctx.walk(match):
+if f in current_copies:
+wctx[f].markcopied(None)
+ 

D8033: copy: move argument validation a little earlier

2020-02-15 Thread martinvonz (Martin von Zweigbergk)
Closed by commit rHGd8b49bf6cfec: copy: move argument validation a little 
earlier (authored by martinvonz).
This revision was automatically updated to reflect the committed changes.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D8033?vs=20232&id=20243

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

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

AFFECTED FILES
  mercurial/cmdutil.py

CHANGE DETAILS

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1477,6 +1477,13 @@
 
 return
 
+pats = scmutil.expandpats(pats)
+if not pats:
+raise error.Abort(_(b'no source or destination specified'))
+if len(pats) == 1:
+raise error.Abort(_(b'no destination specified'))
+dest = pats.pop()
+
 if opts.get(b'at_rev'):
 raise error.Abort(_("--at-rev is only supported with --forget"))
 
@@ -1715,12 +1722,6 @@
 res = lambda p: dest
 return res
 
-pats = scmutil.expandpats(pats)
-if not pats:
-raise error.Abort(_(b'no source or destination specified'))
-if len(pats) == 1:
-raise error.Abort(_(b'no destination specified'))
-dest = pats.pop()
 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
 if not destdirexists:
 if len(pats) > 1 or matchmod.patkind(pats[0]):



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


D8098: rust-nodemap: a method for full invalidation

2020-02-15 Thread Raphaël Gomès
Alphare updated this revision to Diff 20239.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D8098?vs=20220&id=20239

BRANCH
  default

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

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

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
@@ -572,11 +572,25 @@
 Ok(())
 }
 
+/// Make the whole `NodeTree` logically empty, without touching the
+/// immutable part.
+pub fn invalidate_all(&mut self) -> () {
+self.root = Block::new();
+self.growable = Vec::new();
+self.masked_inner_blocks = self.readonly.len()
+}
+
 /// Return the number of blocks in the readonly part that are currently
 /// masked in the mutable part.
 ///
 /// The `NodeTree` structure has no efficient way to know how many blocks
 /// are already unreachable in the readonly part.
+///
+/// After a call to `invalidate_all()`, the returned number can be actually
+/// bigger than the whole readonly part, a conventional way to mean that
+/// all the readonly blocks have been masked. This is what is really
+/// useful to the caller and does not require to know how many were
+/// actually unreachable to begin with.
 pub fn masked_readonly_blocks(&self) -> usize {
 if let Some(readonly_root) = self.readonly.last() {
 if readonly_root == &self.root {
@@ -1057,6 +1071,27 @@
 }
 
 #[test]
+fn test_invalidate_all() -> Result<(), NodeMapError> {
+let mut idx = TestNtIndex::new();
+idx.insert(0, "1234")?;
+idx.insert(1, "1235")?;
+idx.insert(2, "131")?;
+idx.insert(3, "cafe")?;
+let mut idx = idx.commit();
+
+idx.nt.invalidate_all();
+
+assert_eq!(idx.find_hex("1234")?, None);
+assert_eq!(idx.find_hex("1235")?, None);
+assert_eq!(idx.find_hex("131")?, None);
+assert_eq!(idx.find_hex("cafe")?, None);
+// all the readonly blocks have been masked, this is the
+// conventional expected response
+assert_eq!(idx.nt.masked_readonly_blocks(), idx.nt.readonly.len() + 1);
+Ok(())
+}
+
+#[test]
 fn test_into_added_empty() {
 assert!(sample_nodetree().into_readonly_and_added().1.is_empty());
 assert!(sample_nodetree()



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


D7819: rust-nodemap: core implementation for shortest

2020-02-15 Thread Raphaël Gomès
Alphare updated this revision to Diff 20237.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7819?vs=20218&id=20237

BRANCH
  default

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

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

AFFECTED FILES
  rust/hg-core/src/revlog/node.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
--- a/rust/hg-core/src/revlog/nodemap.rs
+++ b/rust/hg-core/src/revlog/nodemap.rs
@@ -17,6 +17,7 @@
 RevlogIndex, NULL_REVISION,
 };
 
+use std::cmp::max;
 use std::fmt;
 use std::mem;
 use std::ops::Deref;
@@ -98,6 +99,42 @@
 ) -> Result, NodeMapError> {
 self.find_bin(idx, NodePrefix::from_hex(prefix)?.borrow())
 }
+
+/// Give the size of the shortest node prefix that determines
+/// the revision uniquely.
+///
+/// From a binary node prefix, if it is matched in the node map, this
+/// returns the number of hexadecimal digits that would had sufficed
+/// to find the revision uniquely.
+///
+/// Returns `None` if no `Revision` could be found for the prefix.
+///
+/// If several Revisions match the given prefix, a [`MultipleResults`]
+/// error is returned.
+fn unique_prefix_len_bin<'a>(
+&self,
+idx: &impl RevlogIndex,
+node_prefix: NodePrefixRef<'a>,
+) -> Result, NodeMapError>;
+
+/// Same as `unique_prefix_len_bin`, with the hexadecimal representation
+/// of the prefix as input.
+fn unique_prefix_len_hex(
+&self,
+idx: &impl RevlogIndex,
+prefix: &str,
+) -> Result, NodeMapError> {
+self.unique_prefix_len_bin(idx, NodePrefix::from_hex(prefix)?.borrow())
+}
+
+/// Same as `unique_prefix_len_bin`, with a full `Node` as input
+fn unique_prefix_len_node(
+&self,
+idx: &impl RevlogIndex,
+node: &Node,
+) -> Result, NodeMapError> {
+self.unique_prefix_len_bin(idx, node.into())
+}
 }
 
 pub trait MutableNodeMap: NodeMap {
@@ -277,20 +314,24 @@
 fn validate_candidate(
 idx: &impl RevlogIndex,
 prefix: NodePrefixRef,
-rev: Option,
-) -> Result, NodeMapError> {
-if prefix.is_prefix_of(&NULL_NODE) {
-// NULL_REVISION always matches a prefix made only of zeros
+candidate: (Option, usize),
+) -> Result<(Option, usize), NodeMapError> {
+let (rev, steps) = candidate;
+if let Some(nz_nybble) = prefix.first_different_nybble(&NULL_NODE) {
+rev.map_or(Ok((None, steps)), |r| {
+has_prefix_or_none(idx, prefix, r)
+.map(|opt| (opt, max(steps, nz_nybble + 1)))
+})
+} else {
+// the prefix is only made of zeros; NULL_REVISION always matches it
 // and any other *valid* result is an ambiguity
 match rev {
-None => Ok(Some(NULL_REVISION)),
+None => Ok((Some(NULL_REVISION), steps + 1)),
 Some(r) => match has_prefix_or_none(idx, prefix, r)? {
-None => Ok(Some(NULL_REVISION)),
+None => Ok((Some(NULL_REVISION), steps + 1)),
 _ => Err(NodeMapError::MultipleResults),
 },
 }
-} else {
-rev.map_or(Ok(None), |r| has_prefix_or_none(idx, prefix, r))
 }
 }
 
@@ -384,13 +425,26 @@
 }
 
 /// Main working method for `NodeTree` searches
+///
+/// The first returned value is the result of analysing `NodeTree` data
+/// *alone*: whereas `None` guarantees that the given prefix is absent
+/// from the `NodeTree` data (but still could match `NULL_NODE`), with
+/// `Some(rev)`, it is to be understood that `rev` is the unique `Revision`
+/// that could match the prefix. Actually, all that can be inferred from
+/// the `NodeTree` data is that `rev` is the revision with the longest
+/// common node prefix with the given prefix.
+///
+/// The second returned value is the size of the smallest subprefix
+/// of `prefix` that would give the same result, i.e. not the
+/// `MultipleResults` error variant (again, using only the data of the
+/// `NodeTree`).
 fn lookup<'p>(
 &self,
 prefix: NodePrefixRef<'p>,
-) -> Result, NodeMapError> {
-for visit_item in self.visit(prefix) {
+) -> Result<(Option, usize), NodeMapError> {
+for (i, visit_item) in self.visit(prefix).enumerate() {
 if let Some(opt) = visit_item.final_revision() {
-return Ok(opt);
+return Ok((opt, i + 1));
 }
 }
 Err(NodeMapError::MultipleResults)
@@ -635,6 +689,16 @@
 prefix: NodePrefixRef<'a>,
 ) -> Result, NodeMapError> {
 validate_candidate(idx, prefix.clone(), self.lookup(prefix)?)
+.map(|(opt, _shortest)| opt)
+}
+
+fn unique_prefix_len_bin<'a>(
+&self,
+idx: &imp

D8097: rust-nodemap: accounting for dead blocks

2020-02-15 Thread Raphaël Gomès
Alphare updated this revision to Diff 20238.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D8097?vs=20219&id=20238

BRANCH
  default

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

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

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
@@ -272,6 +272,7 @@
 readonly: Box + Send>,
 growable: Vec,
 root: Block,
+masked_inner_blocks: usize,
 }
 
 impl Index for NodeTree {
@@ -348,6 +349,7 @@
 readonly: readonly,
 growable: Vec::new(),
 root: root,
+masked_inner_blocks: 0,
 }
 }
 
@@ -480,6 +482,7 @@
 let ro_len = ro_blocks.len();
 let glen = self.growable.len();
 if idx < ro_len {
+self.masked_inner_blocks += 1;
 // TODO OPTIM I think this makes two copies
 self.growable.push(ro_blocks[idx].clone());
 (glen + ro_len, &mut self.growable[glen], glen + 1)
@@ -568,6 +571,22 @@
 }
 Ok(())
 }
+
+/// Return the number of blocks in the readonly part that are currently
+/// masked in the mutable part.
+///
+/// The `NodeTree` structure has no efficient way to know how many blocks
+/// are already unreachable in the readonly part.
+pub fn masked_readonly_blocks(&self) -> usize {
+if let Some(readonly_root) = self.readonly.last() {
+if readonly_root == &self.root {
+return 0;
+}
+} else {
+return 0;
+}
+self.masked_inner_blocks + 1
+}
 }
 
 pub struct NodeTreeBytes {
@@ -850,6 +869,7 @@
 readonly: sample_nodetree().readonly,
 growable: vec![block![0: Rev(1), 5: Rev(3)]],
 root: block![0: Block(1), 1:Block(3), 12: Rev(2)],
+masked_inner_blocks: 1,
 };
 assert_eq!(nt.find_hex(&idx, "10")?, Some(1));
 assert_eq!(nt.find_hex(&idx, "c")?, Some(2));
@@ -858,6 +878,7 @@
 assert_eq!(nt.find_hex(&idx, "000")?, Some(NULL_REVISION));
 assert_eq!(nt.unique_prefix_len_hex(&idx, "000")?, Some(3));
 assert_eq!(nt.find_hex(&idx, "01")?, Some(9));
+assert_eq!(nt.masked_readonly_blocks(), 2);
 Ok(())
 }
 
@@ -947,6 +968,8 @@
 assert_eq!(idx.find_hex("1a345")?, Some(3));
 assert_eq!(idx.find_hex("1a341")?, None);
 
+// there's no readonly block to mask
+assert_eq!(idx.nt.masked_readonly_blocks(), 0);
 Ok(())
 }
 
@@ -1008,6 +1031,8 @@
 assert_eq!(idx.find_hex("1235")?, Some(1));
 assert_eq!(idx.find_hex("131")?, Some(2));
 assert_eq!(idx.find_hex("cafe")?, Some(3));
+// we did not add anything since init from readonly
+assert_eq!(idx.nt.masked_readonly_blocks(), 0);
 
 idx.insert(4, "123A")?;
 assert_eq!(idx.find_hex("1234")?, Some(0));
@@ -1015,12 +1040,18 @@
 assert_eq!(idx.find_hex("131")?, Some(2));
 assert_eq!(idx.find_hex("cafe")?, Some(3));
 assert_eq!(idx.find_hex("123A")?, Some(4));
+// we masked blocks for all prefixes of "123", including the root
+assert_eq!(idx.nt.masked_readonly_blocks(), 4);
 
+eprintln!("{:?}", idx.nt);
 idx.insert(5, "c0")?;
 assert_eq!(idx.find_hex("cafe")?, Some(3));
 assert_eq!(idx.find_hex("c0")?, Some(5));
 assert_eq!(idx.find_hex("c1")?, None);
 assert_eq!(idx.find_hex("1234")?, Some(0));
+// inserting "c0" is just splitting the 'c' slot of the mutable root,
+// it doesn't mask anything
+assert_eq!(idx.nt.masked_readonly_blocks(), 4);
 
 Ok(())
 }



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


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

2020-02-15 Thread Raphaël Gomès
Alphare updated this revision to Diff 20236.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7798?vs=20217&id=20236

BRANCH
  default

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

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,7 +13,8 @@
 //! is used in a more abstract context.
 
 use super::{
-Node, NodeError, NodePrefix, NodePrefixRef, Revision, RevlogIndex,
+node::NULL_NODE, Node, NodeError, NodePrefix, NodePrefixRef, Revision,
+RevlogIndex, NULL_REVISION,
 };
 
 use std::fmt;
@@ -268,6 +269,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(
+idx: &impl RevlogIndex,
+prefix: NodePrefixRef,
+rev: Option,
+) -> Result, NodeMapError> {
+if prefix.is_prefix_of(&NULL_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`
 ///
@@ -358,8 +384,6 @@
 }
 
 /// Main working method for `NodeTree` searches
-///
-/// This partial implementation lacks special cases for NULL_REVISION
 fn lookup<'p>(
 &self,
 prefix: NodePrefixRef<'p>,
@@ -610,9 +634,7 @@
 idx: &impl 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)?)
 }
 }
 
@@ -745,8 +767,9 @@
 
 assert_eq!(nt.find_hex(&idx, "0"), Err(MultipleResults));
 assert_eq!(nt.find_hex(&idx, "01"), Ok(Some(9)));
-assert_eq!(nt.find_hex(&idx, "00"), Ok(Some(0)));
+assert_eq!(nt.find_hex(&idx, "00"), Err(MultipleResults));
 assert_eq!(nt.find_hex(&idx, "00a"), Ok(Some(0)));
+assert_eq!(nt.find_hex(&idx, "000"), Ok(Some(NULL_REVISION)));
 }
 
 #[test]
@@ -765,7 +788,8 @@
 };
 assert_eq!(nt.find_hex(&idx, "10")?, Some(1));
 assert_eq!(nt.find_hex(&idx, "c")?, Some(2));
-assert_eq!(nt.find_hex(&idx, "00")?, Some(0));
+assert_eq!(nt.find_hex(&idx, "00"), Err(MultipleResults));
+assert_eq!(nt.find_hex(&idx, "000")?, Some(NULL_REVISION));
 assert_eq!(nt.find_hex(&idx, "01")?, Some(9));
 Ok(())
 }



To: gracinet, #hg-reviewers, kevincox, marmoute
Cc: marmoute, 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-02-15 Thread Raphaël Gomès
Alphare updated this revision to Diff 20235.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7796?vs=20215&id=20235

BRANCH
  default

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

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 @@
 };
 
 use std::fmt;
+use std::mem;
 use std::ops::Deref;
 use std::ops::Index;
+use std::slice;
 
 #[derive(Debug, PartialEq)]
 pub enum NodeMapError {
@@ -172,20 +174,40 @@
 /// represented at all, because we want an immutable empty nodetree
 /// to be valid.
 
-#[derive(Clone, PartialEq)]
-pub struct Block([RawElement; 16]);
+#[derive(Copy, Clone)]
+pub struct Block([u8; BLOCK_SIZE]);
+
+/// Not derivable for arrays of length >32 until const generics are stable
+impl PartialEq for Block {
+fn eq(&self, other: &Self) -> bool {
+&self.0[..] == &other.0[..]
+}
+}
+
+pub const BLOCK_SIZE: usize = 64;
 
 impl Block {
 fn new() -> Self {
-Block([-1; 16])
+Block([255u8; BLOCK_SIZE])
 }
 
 fn get(&self, nybble: u8) -> Element {
-Element::from(RawElement::from_be(self.0[nybble as usize]))
+let index = nybble as usize * mem::size_of::();
+Element::from(RawElement::from_be_bytes([
+self.0[index],
+self.0[index + 1],
+self.0[index + 2],
+self.0[index + 3],
+]))
 }
 
 fn set(&mut self, nybble: u8, element: Element) {
-self.0[nybble as usize] = RawElement::to_be(element.into())
+let values = RawElement::to_be_bytes(element.into());
+let index = nybble as usize * mem::size_of::();
+self.0[index] = values[0];
+self.0[index + 1] = values[1];
+self.0[index + 2] = values[2];
+self.0[index + 3] = values[3];
 }
 }
 
@@ -230,9 +252,9 @@
 }
 
 /// Return `None` unless the `Node` for `rev` has given prefix in `index`.
-fn has_prefix_or_none<'p>(
+fn has_prefix_or_none(
 idx: &impl RevlogIndex,
-prefix: NodePrefixRef<'p>,
+prefix: NodePrefixRef,
 rev: Revision,
 ) -> Result, NodeMapError> {
 idx.node(rev)
@@ -262,6 +284,66 @@
 }
 }
 
+/// Create from an opaque bunch of bytes
+///
+/// The created `NodeTreeBytes` 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 `amount`
+pub fn load_bytes(
+bytes: Box + Send>,
+amount: usize,
+) -> Self {
+NodeTree::new(Box::new(NodeTreeBytes::new(bytes, 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(&self.root) {
+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();
+// Prevent running `v`'s destructor so we are in complete control
+// of the allocation.
+let vec = mem::ManuallyDrop::new(vec);
+
+// Transmute the `Vec` to a `Vec`. Blocks are contiguous
+// bytes, so this is perfectly safe.
+let bytes = unsafe {
+// Assert that `Block` hasn't been changed and has no padding
+let _: [u8; 4 * mem::size_of::()] =
+std::mem::transmute([Block::new(); 4]);
+
+// /!\ Any use of `vec` after this is use-after-free.
+Vec::from_raw_parts(
+vec.as_ptr() as *mut u8,
+vec.len() * BLOCK_SIZE,
+vec.capacity() * BLOCK_SIZE,
+)
+};
+(readonly, bytes)
+}
+
 /// Total number of blocks
 fn len(&self) -> usize {
 self.readonly.len() + self.growable.len() + 1
@@ -410,6 +492,38 @@
 }
 }
 
+pub struct NodeTreeBytes {
+buffer: Box + Send>,
+len_in_blocks: usize,
+}
+
+impl NodeTreeBytes {
+fn new(
+buffer: Bo