nodemap: gate the feature behind a new requirement
Now that the feature is working smoothly, a question was still open, should we
gate the feature behind a new requirement or just treat it as a cache to be
warmed by those who can and ignored by other.
The advantage of using the cache approach is a transparent upgrade/downgrade
story, making the feature easier to move to. However having out of date cache
can come with a significant performance hit for process who expect an up to
date cache but found none. In this case the file needs to be stored under
`.hg/cache`.
The "requirement" approach guarantee that the persistent nodemap is up to date.
However, it comes with a less flexible activation story since an explicite
upgrade is required. In this case the file can be stored in `.hg/store`.
This wiki page is relevant to this questions:
https://www.mercurial-scm.org/wiki/ComputedIndexPlan
So which one should we take? Another element came into plan, the persistent
nodemap use the `add` method of the transaction, it is used to keep track of a
file content before a transaction in case we need to rollback it back. It turns
out that the `transaction.add` API does not support file stored anywhere than
`.hg/store`. Making it support file stored elsewhere is possible, require a
change in on disk transaction format. Updating on disk file requires…
introducing a new requirements.
As a result, we pick the second option "gating the persistent nodemap behind a
new requirements".
Differential Revision: https://phab.mercurial-scm.org/D8417
Setup:
$ cat > eval.py <<EOF
> from __future__ import absolute_import
> import filecmp
> from mercurial import commands, context, pycompat, registrar
> cmdtable = {}
> command = registrar.command(cmdtable)
> @command(b'eval', [], b'hg eval CMD')
> def eval_(ui, repo, *cmds, **opts):
> cmd = b" ".join(cmds)
> res = pycompat.bytestr(eval(cmd, globals(), locals()))
> ui.warn(b"%s" % res)
> EOF
$ echo "[extensions]" >> $HGRCPATH
$ echo "eval=`pwd`/eval.py" >> $HGRCPATH
Arbitraryfilectx.cmp does not follow symlinks:
$ mkdir case1
$ cd case1
$ hg init
#if symlink
$ printf "A" > real_A
$ printf "foo" > A
$ printf "foo" > B
$ ln -s A sym_A
$ hg add .
adding A
adding B
adding real_A
adding sym_A
$ hg commit -m "base"
#else
$ hg import -q --bypass - <<EOF
> # HG changeset patch
> # User test
> # Date 0 0
> base
>
> diff --git a/A b/A
> new file mode 100644
> --- /dev/null
> +++ b/A
> @@ -0,0 +1,1 @@
> +foo
> \ No newline at end of file
> diff --git a/B b/B
> new file mode 100644
> --- /dev/null
> +++ b/B
> @@ -0,0 +1,1 @@
> +foo
> \ No newline at end of file
> diff --git a/real_A b/real_A
> new file mode 100644
> --- /dev/null
> +++ b/real_A
> @@ -0,0 +1,1 @@
> +A
> \ No newline at end of file
> diff --git a/sym_A b/sym_A
> new file mode 120000
> --- /dev/null
> +++ b/sym_A
> @@ -0,0 +1,1 @@
> +A
> \ No newline at end of file
> EOF
$ hg up -q
#endif
These files are different and should return True (different):
(Note that filecmp.cmp's return semantics are inverted from ours, so we invert
for simplicity):
$ hg eval "context.arbitraryfilectx(b'A', repo).cmp(repo[None][b'real_A'])"
True (no-eol)
$ hg eval "not filecmp.cmp(b'A', b'real_A')"
True (no-eol)
These files are identical and should return False (same):
$ hg eval "context.arbitraryfilectx(b'A', repo).cmp(repo[None][b'A'])"
False (no-eol)
$ hg eval "context.arbitraryfilectx(b'A', repo).cmp(repo[None][b'B'])"
False (no-eol)
$ hg eval "not filecmp.cmp(b'A', b'B')"
False (no-eol)
This comparison should also return False, since A and sym_A are substantially
the same in the eyes of ``filectx.cmp``, which looks at data only.
$ hg eval "context.arbitraryfilectx(b'real_A', repo).cmp(repo[None][b'sym_A'])"
False (no-eol)
A naive use of filecmp on those two would wrongly return True, since it follows
the symlink to "A", which has different contents.
#if symlink
$ hg eval "not filecmp.cmp(b'real_A', b'sym_A')"
True (no-eol)
#else
$ hg eval "not filecmp.cmp(b'real_A', b'sym_A')"
False (no-eol)
#endif