D6825: hgext: start building a library for simple hooks
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
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
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
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)
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
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
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
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
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
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
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
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
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
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
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
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