revset: stop serializing node when using "%ln"
Turning hundred of thousand of node from node to hex and back can be slow… what
about we stop doing it?
In many case were we are using node id we should be using revision id. However
this is not a good reason to have a stupidly slow implementation of "%ln".
This caught my attention again because the phase discovery during push make an
extensive use of "%ln" or huge set. In absolute, that phase discovery probably
should use "%ld" and need to improves its algorithmic complexity, but improving
"%ln" seems simple and long overdue. This greatly speeds up `hg push` on
repository with many drafts.
Here are some relevant poulpe benchmarks:
### data-env-vars.name = mozilla-try-2023-03-22-zstd-sparse-revlog
# benchmark.name = hg.command.push
# bin-env-vars.hg.flavor = default
# bin-env-vars.hg.py-re2-module = default
# benchmark.variants.explicit-rev = all-out-heads
# benchmark.variants.issue6528 = disabled
# benchmark.variants.protocol = ssh
# benchmark.variants.reuse-external-delta-parent = default
## benchmark.variants.revs = any-1-extra-rev
before: 44.235070
after: 20.416329 (-53.85%, -23.82)
## benchmark.variants.revs = any-100-extra-rev
before: 49.234697
after: 26.519829 (-46.14%, -22.71)
### benchmark.name = hg.command.bundle
# bin-env-vars.hg.flavor = default
# bin-env-vars.hg.py-re2-module = default
# benchmark.variants.revs = all
# benchmark.variants.type = none-streamv2
## data-env-vars.name = heptapod-public-2024-03-25-zstd-sparse-revlog
before: 10.138396
after: 7.750458 (-23.55%, -2.39)
## data-env-vars.name = mercurial-public-2024-03-22-zstd-sparse-revlog
before: 1.263859
after: 0.700229 (-44.60%, -0.56)
## data-env-vars.name = mozilla-try-2023-03-22-zstd-sparse-revlog
before: 399.484481
after: 346.5089 (-13.26%, -52.98)
## data-env-vars.name = pypy-2024-03-22-zstd-sparse-revlog
before: 4.540080
after: 3.401700 (-25.07%, -1.14)
## data-env-vars.name = tryton-public-2024-03-22-zstd-sparse-revlog
before: 2.975765
after: 1.870798 (-37.13%, -1.10)
# Test admin commands
import functools
import unittest
from mercurial.i18n import _
from mercurial import error, ui as uimod
from mercurial import registrar
from mercurial.admin import verify
class TestAdminVerifyFindChecks(unittest.TestCase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ui = uimod.ui.load()
self.repo = b"fake-repo"
def cleanup_table(self):
self.table = {}
self.alias_table = {}
self.pyramid = {}
self.addCleanup(cleanup_table, self)
def setUp(self):
self.table = {}
self.alias_table = {}
self.pyramid = {}
check = registrar.verify_check(self.table, self.alias_table)
# mock some fake check method for tests purpose
@check(
b"test.dummy",
alias=b"dummy",
options=[],
)
def check_dummy(ui, repo, **options):
return options
@check(
b"test.fake",
alias=b"fake",
options=[
(b'a', False, _(b'a boolean value (default: False)')),
(b'b', True, _(b'a boolean value (default: True)')),
(b'c', [], _(b'a list')),
],
)
def check_fake(ui, repo, **options):
return options
# alias in the middle of a hierarchy
check(
b"test.noop",
alias=b"noop",
options=[],
)(verify.noop_func)
@check(
b"test.noop.deeper",
alias=b"deeper",
options=[
(b'y', True, _(b'a boolean value (default: True)')),
(b'z', [], _(b'a list')),
],
)
def check_noop_deeper(ui, repo, **options):
return options
# args wrapper utilities
def find_checks(self, name):
return verify.find_checks(
name=name,
table=self.table,
alias_table=self.alias_table,
full_pyramid=self.pyramid,
)
def pass_options(self, checks, options):
return verify.pass_options(
self.ui,
checks,
options,
table=self.table,
alias_table=self.alias_table,
full_pyramid=self.pyramid,
)
def get_checks(self, names, options):
return verify.get_checks(
self.repo,
self.ui,
names=names,
options=options,
table=self.table,
alias_table=self.alias_table,
full_pyramid=self.pyramid,
)
# tests find_checks
def test_find_checks_empty_name(self):
with self.assertRaises(error.InputError):
self.find_checks(name=b"")
def test_find_checks_wrong_name(self):
with self.assertRaises(error.InputError):
self.find_checks(name=b"unknown")
def test_find_checks_dummy(self):
name = b"test.dummy"
found = self.find_checks(name=name)
self.assertEqual(len(found), 1)
self.assertIn(name, found)
meth = found[name]
self.assertTrue(callable(meth))
self.assertEqual(len(meth.options), 0)
def test_find_checks_fake(self):
name = b"test.fake"
found = self.find_checks(name=name)
self.assertEqual(len(found), 1)
self.assertIn(name, found)
meth = found[name]
self.assertTrue(callable(meth))
self.assertEqual(len(meth.options), 3)
def test_find_checks_noop(self):
name = b"test.noop.deeper"
found = self.find_checks(name=name)
self.assertEqual(len(found), 1)
self.assertIn(name, found)
meth = found[name]
self.assertTrue(callable(meth))
self.assertEqual(len(meth.options), 2)
def test_find_checks_from_aliases(self):
found = self.find_checks(name=b"dummy")
self.assertEqual(len(found), 1)
self.assertIn(b"test.dummy", found)
found = self.find_checks(name=b"fake")
self.assertEqual(len(found), 1)
self.assertIn(b"test.fake", found)
found = self.find_checks(name=b"deeper")
self.assertEqual(len(found), 1)
self.assertIn(b"test.noop.deeper", found)
def test_find_checks_from_root(self):
found = self.find_checks(name=b"test")
self.assertEqual(len(found), 3)
self.assertIn(b"test.dummy", found)
self.assertIn(b"test.fake", found)
self.assertIn(b"test.noop.deeper", found)
def test_find_checks_from_intermediate(self):
found = self.find_checks(name=b"test.noop")
self.assertEqual(len(found), 1)
self.assertIn(b"test.noop.deeper", found)
def test_find_checks_from_parent_dot_name(self):
found = self.find_checks(name=b"noop.deeper")
self.assertEqual(len(found), 1)
self.assertIn(b"test.noop.deeper", found)
# tests pass_options
def test_pass_options_no_checks_no_options(self):
checks = {}
options = []
with self.assertRaises(error.Error):
self.pass_options(checks=checks, options=options)
def test_pass_options_fake_empty_options(self):
checks = self.find_checks(name=b"test.fake")
funcs = {
n: functools.partial(f, self.ui, self.repo)
for n, f in checks.items()
}
options = []
# should end with default options
expected_options = {"a": False, "b": True, "c": []}
func = self.pass_options(checks=funcs, options=options)
self.assertDictEqual(func[b"test.fake"].keywords, expected_options)
def test_pass_options_fake_non_existing_options(self):
checks = self.find_checks(name=b"test.fake")
funcs = {
n: functools.partial(f, self.ui, self.repo)
for n, f in checks.items()
}
with self.assertRaises(error.InputError):
options = [b"test.fake:boom=yes"]
self.pass_options(checks=funcs, options=options)
def test_pass_options_fake_unrelated_options(self):
checks = self.find_checks(name=b"test.fake")
funcs = {
n: functools.partial(f, self.ui, self.repo)
for n, f in checks.items()
}
options = [b"test.noop.deeper:y=yes"]
with self.assertRaises(error.InputError):
self.pass_options(checks=funcs, options=options)
def test_pass_options_fake_set_option(self):
checks = self.find_checks(name=b"test.fake")
funcs = {
n: functools.partial(f, self.ui, self.repo)
for n, f in checks.items()
}
options = [b"test.fake:a=yes"]
expected_options = {"a": True, "b": True, "c": []}
func = self.pass_options(checks=funcs, options=options)
self.assertDictEqual(func[b"test.fake"].keywords, expected_options)
def test_pass_options_fake_set_option_with_alias(self):
checks = self.find_checks(name=b"test.fake")
funcs = {
n: functools.partial(f, self.ui, self.repo)
for n, f in checks.items()
}
options = [b"fake:a=yes"]
expected_options = {"a": True, "b": True, "c": []}
func = self.pass_options(checks=funcs, options=options)
self.assertDictEqual(func[b"test.fake"].keywords, expected_options)
def test_pass_options_fake_set_all_option(self):
checks = self.find_checks(name=b"test.fake")
funcs = {
n: functools.partial(f, self.ui, self.repo)
for n, f in checks.items()
}
options = [b"test.fake:a=yes", b"test.fake:b=no", b"test.fake:c=0,1,2"]
expected_options = {"a": True, "b": False, "c": [b"0", b"1", b"2"]}
func = self.pass_options(checks=funcs, options=options)
self.assertDictEqual(func[b"test.fake"].keywords, expected_options)
def test_pass_options_fake_set_all_option_plus_unexisting(self):
checks = self.find_checks(name=b"test.fake")
funcs = {
n: functools.partial(f, self.ui, self.repo)
for n, f in checks.items()
}
options = [
b"test.fake:a=yes",
b"test.fake:b=no",
b"test.fake:c=0,1,2",
b"test.fake:d=0",
]
with self.assertRaises(error.InputError):
self.pass_options(checks=funcs, options=options)
def test_pass_options_fake_duplicate_option(self):
checks = self.find_checks(name=b"test.fake")
funcs = {
n: functools.partial(f, self.ui, self.repo)
for n, f in checks.items()
}
options = [
b"test.fake:a=yes",
b"test.fake:a=no",
]
with self.assertRaises(error.InputError):
self.pass_options(checks=funcs, options=options)
def test_pass_options_fake_set_malformed_option(self):
checks = self.find_checks(name=b"test.fake")
funcs = {
n: functools.partial(f, self.ui, self.repo)
for n, f in checks.items()
}
options = [
b"test.fake:ayes",
b"test.fake:b==no",
b"test.fake=",
b"test.fake:",
b"test.fa=ke:d=0",
b"test.fa=ke:d=0",
]
for opt in options:
with self.assertRaises(error.InputError):
self.pass_options(checks=funcs, options=[opt])
def test_pass_options_types(self):
checks = self.find_checks(name=b"test.fake")
funcs = {
n: functools.partial(f, self.ui, self.repo)
for n, f in checks.items()
}
# boolean, yes/no
options = [b"test.fake:a=yes", b"test.fake:b=no"]
expected_options = {"a": True, "b": False, "c": []}
func = self.pass_options(checks=funcs, options=options)
self.assertDictEqual(func[b"test.fake"].keywords, expected_options)
# boolean, 0/1
options = [b"test.fake:a=1", b"test.fake:b=0"]
expected_options = {"a": True, "b": False, "c": []}
func = self.pass_options(checks=funcs, options=options)
self.assertDictEqual(func[b"test.fake"].keywords, expected_options)
# boolean, true/false
options = [b"test.fake:a=true", b"test.fake:b=false"]
expected_options = {"a": True, "b": False, "c": []}
func = self.pass_options(checks=funcs, options=options)
self.assertDictEqual(func[b"test.fake"].keywords, expected_options)
# boolean, wrong type
options = [b"test.fake:a=si"]
with self.assertRaises(error.InputError):
self.pass_options(checks=funcs, options=options)
# lists
options = [b"test.fake:c=0,1,2"]
expected_options = {"a": False, "b": True, "c": [b"0", b"1", b"2"]}
func = self.pass_options(checks=funcs, options=options)
self.assertDictEqual(func[b"test.fake"].keywords, expected_options)
options = [b"test.fake:c=x,y,z"]
expected_options = {"a": False, "b": True, "c": [b"x", b"y", b"z"]}
func = self.pass_options(checks=funcs, options=options)
self.assertDictEqual(func[b"test.fake"].keywords, expected_options)
# tests get_checks
def test_get_checks_fake(self):
funcs = self.get_checks(
names=[b"test.fake"], options=[b"test.fake:a=yes"]
)
options = funcs.get(b"test.fake").keywords
expected_options = {"a": True, "b": True, "c": []}
self.assertDictEqual(options, expected_options)
def test_get_checks_multiple_mixed_with_defaults(self):
funcs = self.get_checks(
names=[b"test.fake", b"test.noop.deeper", b"test.dummy"],
options=[
b"test.noop.deeper:y=no",
b"test.noop.deeper:z=-1,0,1",
],
)
options = funcs.get(b"test.fake").keywords
expected_options = {"a": False, "b": True, "c": []}
self.assertDictEqual(options, expected_options)
options = funcs.get(b"test.noop.deeper").keywords
expected_options = {"y": False, "z": [b"-1", b"0", b"1"]}
self.assertDictEqual(options, expected_options)
options = funcs.get(b"test.dummy").keywords
expected_options = {}
self.assertDictEqual(options, expected_options)
def test_broken_pyramid(self):
"""Check that we detect pyramids that can't resolve"""
table = {}
alias_table = {}
pyramid = {}
check = registrar.verify_check(table, alias_table)
# Create two checks that clash
@check(b"test.wrong.intermediate")
def check_dummy(ui, repo, **options):
return options
@check(b"test.wrong.intermediate.thing")
def check_fake(ui, repo, **options):
return options
with self.assertRaises(error.ProgrammingError) as e:
verify.get_checks(
self.repo,
self.ui,
names=[b"test.wrong.intermediate"],
options=[],
table=table,
alias_table=alias_table,
full_pyramid=pyramid,
)
assert "`verify.noop_func`" in str(e.exception), str(e.exception)
if __name__ == '__main__':
import silenttestrunner
silenttestrunner.main(__name__)