# HG changeset patch # User Pierre-Yves David # Date 1658719806 -7200 # Node ID 6dbe74669eba452d33ea5d86f13a25dff74b0327 # Parent cd21f2b4226f686ab902cbbe5f884ae9ad259ff2 sort-revset: introduce a `random` variant This new `sort` variant allows to shuffle any revset. It also allow for randomly picking element using `first`. diff -r cd21f2b4226f -r 6dbe74669eba mercurial/revset.py --- a/mercurial/revset.py Thu Aug 25 05:12:25 2022 +0200 +++ b/mercurial/revset.py Mon Jul 25 05:30:06 2022 +0200 @@ -7,7 +7,10 @@ import binascii +import functools +import random import re +import sys from .i18n import _ from .pycompat import getattr @@ -2347,6 +2350,15 @@ return subset & s.filter(filter, condrepr=b'') +MAXINT = sys.maxsize +MININT = -MAXINT - 1 + + +def pick_random(c, gen=random): + # exists as its own function to make it possible to overwrite the seed + return gen.randint(MININT, MAXINT) + + _sortkeyfuncs = { b'rev': scmutil.intrev, b'branch': lambda c: c.branch(), @@ -2355,12 +2367,17 @@ b'author': lambda c: c.user(), b'date': lambda c: c.date()[0], b'node': scmutil.binnode, + b'random': pick_random, } def _getsortargs(x): """Parse sort options into (set, [(key, reverse)], opts)""" - args = getargsdict(x, b'sort', b'set keys topo.firstbranch') + args = getargsdict( + x, + b'sort', + b'set keys topo.firstbranch random.seed', + ) if b'set' not in args: # i18n: "sort" is a keyword raise error.ParseError(_(b'sort requires one or two arguments')) @@ -2400,6 +2417,20 @@ ) ) + if b'random.seed' in args: + if any(k == b'random' for k, reverse in keyflags): + s = args[b'random.seed'] + seed = getstring(s, _(b"random.seed must be a string")) + opts[b'random.seed'] = seed + else: + # i18n: "random" and "random.seed" are keywords + raise error.ParseError( + _( + b'random.seed can only be used ' + b'when using the random sort key' + ) + ) + return args[b'set'], keyflags, opts @@ -2419,11 +2450,14 @@ - ``date`` for the commit date - ``topo`` for a reverse topographical sort - ``node`` the nodeid of the revision + - ``random`` randomly shuffle revisions The ``topo`` sort order cannot be combined with other sort keys. This sort takes one optional argument, ``topo.firstbranch``, which takes a revset that specifies what topographical branches to prioritize in the sort. + The ``random`` sort takes one optional ``random.seed`` argument to control + the pseudo-randomness of the result. """ s, keyflags, opts = _getsortargs(x) revs = getset(repo, subset, s, order) @@ -2448,7 +2482,12 @@ # sort() is guaranteed to be stable ctxs = [repo[r] for r in revs] for k, reverse in reversed(keyflags): - ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse) + func = _sortkeyfuncs[k] + if k == b'random' and b'random.seed' in opts: + seed = opts[b'random.seed'] + r = random.Random(seed) + func = functools.partial(func, gen=r) + ctxs.sort(key=func, reverse=reverse) return baseset([c.rev() for c in ctxs]) diff -r cd21f2b4226f -r 6dbe74669eba tests/test-revset.t --- a/tests/test-revset.t Thu Aug 25 05:12:25 2022 +0200 +++ b/tests/test-revset.t Mon Jul 25 05:30:06 2022 +0200 @@ -2974,6 +2974,25 @@ 1 b11 m12 u111 112 7200 0 b12 m111 u112 111 10800 +random sort + + $ hg log --rev 'sort(all(), "random")' | wc -l + \s*8 (re) + $ hg log --rev 'sort(all(), "-random")' | wc -l + \s*8 (re) + $ hg log --rev 'sort(all(), "random", random.seed=celeste)' + 6 b111 t2 tu 130 0 + 7 b111 t3 tu 130 0 + 4 b111 m112 u111 110 14400 + 3 b112 m111 u11 120 0 + 5 b111 t1 tu 130 0 + 0 b12 m111 u112 111 10800 + 1 b11 m12 u111 112 7200 + 2 b111 m11 u12 111 3600 + $ hg log --rev 'first(sort(all(), "random", random.seed=celeste))' + 6 b111 t2 tu 130 0 + + topographical sorting can't be combined with other sort keys, and you can't use the topo.firstbranch option when topo sort is not active: