##// END OF EJS Templates
subsetmaker: stabilize the computation of `scratch` subset...
marmoute -
r49877:18622379 default
parent child Browse files
Show More
@@ -1,170 +1,172 b''
1 1 """revset to select sample of repository
2 2
3 3 Hopefully this is useful to create interesting discovery cases.
4 4 """
5 5
6 6 import collections
7 7 import random
8 8
9 9 from mercurial.i18n import _
10 10
11 11 from mercurial import (
12 12 registrar,
13 13 revset,
14 14 revsetlang,
15 15 smartset,
16 16 )
17 17
18 18 revsetpredicate = registrar.revsetpredicate()
19 19
20 20
21 21 @revsetpredicate(b'subsetspec("<spec>")')
22 22 def subsetmarkerspec(repo, subset, x):
23 23 """use a shorthand spec as used by search-discovery-case
24 24
25 25 Supported format are:
26 26
27 27 - "scratch-count-seed": not scratch(all(), count, "seed")
28 28 - "randomantichain-seed": ::randomantichain(all(), "seed")
29 29 - "rev-REV": "::REV"
30 30 """
31 31 args = revsetlang.getargs(
32 32 x, 0, 1, _(b'subsetspec("spec") required an argument')
33 33 )
34 34
35 35 spec = revsetlang.getstring(args[0], _(b"spec should be a string"))
36 36 case = spec.split(b'-')
37 37 t = case[0]
38 38 if t == b'scratch':
39 39 spec_revset = b'not scratch(all(), %s, "%s")' % (case[1], case[2])
40 40 elif t == b'randomantichain':
41 41 spec_revset = b'::randomantichain(all(), "%s")' % case[1]
42 42 elif t == b'rev':
43 43 spec_revset = b'::%d' % case[1]
44 44 else:
45 45 assert False, spec
46 46
47 47 selected = repo.revs(spec_revset)
48 48
49 49 return selected & subset
50 50
51 51
52 52 @revsetpredicate(b'scratch(REVS, <count>, [seed])')
53 53 def scratch(repo, subset, x):
54 54 """randomly remove <count> revision from the repository top
55 55
56 56 This subset is created by recursively picking changeset starting from the
57 57 heads. It can be summarized using the following algorithm::
58 58
59 59 selected = set()
60 60 for i in range(<count>):
61 61 unselected = repo.revs("not <selected>")
62 62 candidates = repo.revs("heads(<unselected>)")
63 63 pick = random.choice(candidates)
64 64 selected.add(pick)
65 65 """
66 66 m = _(b"scratch expects revisions, count argument and an optional seed")
67 67 args = revsetlang.getargs(x, 2, 3, m)
68 68 if len(args) == 2:
69 69 x, n = args
70 70 rand = random
71 71 elif len(args) == 3:
72 72 x, n, seed = args
73 73 seed = revsetlang.getinteger(seed, _(b"seed should be a number"))
74 74 rand = random.Random(seed)
75 75 else:
76 76 assert False
77 77
78 78 n = revsetlang.getinteger(n, _(b"scratch expects a number"))
79 79
80 80 selected = set()
81 81 heads = set()
82 82 children_count = collections.defaultdict(lambda: 0)
83 83 parents = repo.changelog._uncheckedparentrevs
84 84
85 85 baseset = revset.getset(repo, smartset.fullreposet(repo), x)
86 86 baseset.sort()
87 87 for r in baseset:
88 88 heads.add(r)
89 89
90 90 p1, p2 = parents(r)
91 91 if p1 >= 0:
92 92 heads.discard(p1)
93 93 children_count[p1] += 1
94 94 if p2 >= 0:
95 95 heads.discard(p2)
96 96 children_count[p2] += 1
97 97
98 98 for h in heads:
99 99 assert children_count[h] == 0
100 100
101 101 selected = set()
102 102 for x in range(n):
103 103 if not heads:
104 104 break
105 pick = rand.choice(list(heads))
105 pickable = list(heads)
106 pickable.sort()
107 pick = rand.choice(pickable)
106 108 heads.remove(pick)
107 109 assert pick not in selected
108 110 selected.add(pick)
109 111 p1, p2 = parents(pick)
110 112 if p1 in children_count:
111 113 assert p1 in children_count
112 114 children_count[p1] -= 1
113 115 assert children_count[p1] >= 0
114 116 if children_count[p1] == 0:
115 117 assert p1 not in selected, (r, p1)
116 118 heads.add(p1)
117 119 if p2 in children_count:
118 120 assert p2 in children_count
119 121 children_count[p2] -= 1
120 122 assert children_count[p2] >= 0
121 123 if children_count[p2] == 0:
122 124 assert p2 not in selected, (r, p2)
123 125 heads.add(p2)
124 126
125 127 return smartset.baseset(selected) & subset
126 128
127 129
128 130 @revsetpredicate(b'randomantichain(REVS, [seed])')
129 131 def antichain(repo, subset, x):
130 132 """Pick a random anti-chain in the repository
131 133
132 134 A antichain is a set of changeset where there isn't any element that is
133 135 either a descendant or ancestors of any other element in the set. In other
134 136 word, all the elements are independant. It can be summarized with the
135 137 following algorithm::
136 138
137 139 selected = set()
138 140 unselected = repo.revs('all()')
139 141 while unselected:
140 142 pick = random.choice(unselected)
141 143 selected.add(pick)
142 144 unselected -= repo.revs('::<pick> + <pick>::')
143 145 """
144 146
145 147 args = revsetlang.getargs(
146 148 x, 1, 2, _(b"randomantichain expects revisions and an optional seed")
147 149 )
148 150 if len(args) == 1:
149 151 (x,) = args
150 152 rand = random
151 153 elif len(args) == 2:
152 154 x, seed = args
153 155 seed = revsetlang.getinteger(seed, _(b"seed should be a number"))
154 156 rand = random.Random(seed)
155 157 else:
156 158 assert False
157 159
158 160 selected = set()
159 161
160 162 baseset = revset.getset(repo, smartset.fullreposet(repo), x)
161 163 undecided = baseset
162 164
163 165 while undecided:
164 166 pick = rand.choice(list(undecided))
165 167 selected.add(pick)
166 168 undecided = repo.revs(
167 169 '%ld and not (::%ld or %ld::head())', baseset, selected, selected
168 170 )
169 171
170 172 return smartset.baseset(selected) & subset
General Comments 0
You need to be logged in to leave comments. Login now