Mercurial > hg
annotate mercurial/pvec.py @ 51523:ef369d16965d
branchcache: cleanup the final key generation after update
A lot of duplicated work seemed to be done, as we already update the tiprev and
tipnode when needed right before. So we simplify that part to focus on the
filtered hash.
See inline comment for details.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Sun, 25 Feb 2024 23:31:50 +0100 |
parents | f15cb5111a1e |
children | f4733654f144 |
rev | line source |
---|---|
16249 | 1 # pvec.py - probabilistic vector clocks for Mercurial |
2 # | |
46819
d4ba4d51f85f
contributor: change mentions of mpm to olivia
Raphaël Gomès <rgomes@octobus.net>
parents:
44602
diff
changeset
|
3 # Copyright 2012 Olivia Mackall <olivia@selenic.com> |
16249 | 4 # |
5 # This software may be used and distributed according to the terms of the | |
6 # GNU General Public License version 2 or any later version. | |
7 | |
8 ''' | |
9 A "pvec" is a changeset property based on the theory of vector clocks | |
10 that can be compared to discover relatedness without consulting a | |
11 graph. This can be useful for tasks like determining how a | |
12 disconnected patch relates to a repository. | |
13 | |
14 Currently a pvec consist of 448 bits, of which 24 are 'depth' and the | |
15 remainder are a bit vector. It is represented as a 70-character base85 | |
16 string. | |
17 | |
18 Construction: | |
19 | |
20 - a root changeset has a depth of 0 and a bit vector based on its hash | |
21 - a normal commit has a changeset where depth is increased by one and | |
22 one bit vector bit is flipped based on its hash | |
23 - a merge changeset pvec is constructed by copying changes from one pvec into | |
24 the other to balance its depth | |
25 | |
26 Properties: | |
27 | |
28 - for linear changes, difference in depth is always <= hamming distance | |
29 - otherwise, changes are probably divergent | |
30 - when hamming distance is < 200, we can reliably detect when pvecs are near | |
31 | |
32 Issues: | |
33 | |
34 - hamming distance ceases to work over distances of ~ 200 | |
35 - detecting divergence is less accurate when the common ancestor is very close | |
36 to either revision or total distance is high | |
37 - this could probably be improved by modeling the relation between | |
38 delta and hdist | |
39 | |
40 Uses: | |
41 | |
42 - a patch pvec can be used to locate the nearest available common ancestor for | |
43 resolving conflicts | |
44 - ordering of patches can be established without a DAG | |
45 - two head pvecs can be compared to determine whether push/pull/merge is needed | |
46 and approximately how many changesets are involved | |
47 - can be used to find a heuristic divergence measure between changesets on | |
48 different branches | |
49 ''' | |
50 | |
27501
983e93d88193
pvec: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents:
24339
diff
changeset
|
51 |
983e93d88193
pvec: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents:
24339
diff
changeset
|
52 from .node import nullrev |
983e93d88193
pvec: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents:
24339
diff
changeset
|
53 from . import ( |
38783
e7aa113b14f7
global: use pycompat.xrange()
Gregory Szorc <gregory.szorc@gmail.com>
parents:
32201
diff
changeset
|
54 pycompat, |
27501
983e93d88193
pvec: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents:
24339
diff
changeset
|
55 util, |
983e93d88193
pvec: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents:
24339
diff
changeset
|
56 ) |
16249 | 57 |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
58 _size = 448 # 70 chars b85-encoded |
43465
90aac60b6697
pvec: migrate to modern integer division
Augie Fackler <augie@google.com>
parents:
43463
diff
changeset
|
59 _bytes = _size // 8 |
16249 | 60 _depthbits = 24 |
43465
90aac60b6697
pvec: migrate to modern integer division
Augie Fackler <augie@google.com>
parents:
43463
diff
changeset
|
61 _depthbytes = _depthbits // 8 |
16249 | 62 _vecbytes = _bytes - _depthbytes |
63 _vecbits = _vecbytes * 8 | |
43465
90aac60b6697
pvec: migrate to modern integer division
Augie Fackler <augie@google.com>
parents:
43463
diff
changeset
|
64 _radius = (_vecbits - 30) // 2 # high probability vectors are related |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
65 |
16249 | 66 |
67 def _bin(bs): | |
68 '''convert a bytestring to a long''' | |
69 v = 0 | |
70 for b in bs: | |
71 v = v * 256 + ord(b) | |
72 return v | |
73 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
74 |
51287
f15cb5111a1e
pytype: move some type comment to proper annotation
Pierre-Yves David <pierre-yves.david@octobus.net>
parents:
50928
diff
changeset
|
75 def _str(v: int, l: int) -> bytes: |
43077
687b865b95ad
formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents:
43076
diff
changeset
|
76 bs = b"" |
49284
d44e3c45f0e4
py3: replace `pycompat.xrange` by `range`
Manuel Jacob <me@manueljacob.de>
parents:
48946
diff
changeset
|
77 for p in range(l): |
43463
271af23d01a9
pvec: fix overlooked chr() call
Augie Fackler <augie@google.com>
parents:
43115
diff
changeset
|
78 bs = pycompat.bytechr(v & 255) + bs |
16249 | 79 v >>= 8 |
80 return bs | |
81 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
82 |
16249 | 83 def _split(b): |
84 '''depth and bitvec''' | |
85 return _bin(b[:_depthbytes]), _bin(b[_depthbytes:]) | |
86 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
87 |
16249 | 88 def _join(depth, bitvec): |
89 return _str(depth, _depthbytes) + _str(bitvec, _vecbytes) | |
90 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
91 |
16249 | 92 def _hweight(x): |
93 c = 0 | |
94 while x: | |
95 if x & 1: | |
96 c += 1 | |
97 x >>= 1 | |
98 return c | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
99 |
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
100 |
49284
d44e3c45f0e4
py3: replace `pycompat.xrange` by `range`
Manuel Jacob <me@manueljacob.de>
parents:
48946
diff
changeset
|
101 _htab = [_hweight(x) for x in range(256)] |
16249 | 102 |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
103 |
16249 | 104 def _hamming(a, b): |
105 '''find the hamming distance between two longs''' | |
106 d = a ^ b | |
107 c = 0 | |
108 while d: | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
109 c += _htab[d & 0xFF] |
16249 | 110 d >>= 8 |
111 return c | |
112 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
113 |
16249 | 114 def _mergevec(x, y, c): |
115 # Ideally, this function would be x ^ y ^ ancestor, but finding | |
116 # ancestors is a nuisance. So instead we find the minimal number | |
117 # of changes to balance the depth and hamming distance | |
118 | |
119 d1, v1 = x | |
120 d2, v2 = y | |
121 if d1 < d2: | |
122 d1, d2, v1, v2 = d2, d1, v2, v1 | |
123 | |
124 hdist = _hamming(v1, v2) | |
125 ddist = d1 - d2 | |
126 v = v1 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
127 m = v1 ^ v2 # mask of different bits |
16249 | 128 i = 1 |
129 | |
130 if hdist > ddist: | |
131 # if delta = 10 and hdist = 100, then we need to go up 55 steps | |
132 # to the ancestor and down 45 | |
43465
90aac60b6697
pvec: migrate to modern integer division
Augie Fackler <augie@google.com>
parents:
43463
diff
changeset
|
133 changes = (hdist - ddist + 1) // 2 |
16249 | 134 else: |
135 # must make at least one change | |
136 changes = 1 | |
137 depth = d1 + changes | |
138 | |
139 # copy changes from v2 | |
140 if m: | |
141 while changes: | |
142 if m & i: | |
143 v ^= i | |
144 changes -= 1 | |
145 i <<= 1 | |
146 else: | |
147 v = _flipbit(v, c) | |
148 | |
149 return depth, v | |
150 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
151 |
16249 | 152 def _flipbit(v, node): |
153 # converting bit strings to longs is slow | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
154 bit = (hash(node) & 0xFFFFFFFF) % _vecbits |
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
155 return v ^ (1 << bit) |
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
156 |
16249 | 157 |
158 def ctxpvec(ctx): | |
159 '''construct a pvec for ctx while filling in the cache''' | |
24339
bcc319d936a3
pvec: replace 'ctx._repo' with 'ctx.repo()'
Matt Harbison <matt_harbison@yahoo.com>
parents:
18918
diff
changeset
|
160 r = ctx.repo() |
50928
d718eddf01d9
safehasattr: drop usage in favor of hasattr
Pierre-Yves David <pierre-yves.david@octobus.net>
parents:
49284
diff
changeset
|
161 if not hasattr(r, "_pveccache"): |
16249 | 162 r._pveccache = {} |
163 pvc = r._pveccache | |
164 if ctx.rev() not in pvc: | |
165 cl = r.changelog | |
49284
d44e3c45f0e4
py3: replace `pycompat.xrange` by `range`
Manuel Jacob <me@manueljacob.de>
parents:
48946
diff
changeset
|
166 for n in range(ctx.rev() + 1): |
16249 | 167 if n not in pvc: |
168 node = cl.node(n) | |
169 p1, p2 = cl.parentrevs(n) | |
170 if p1 == nullrev: | |
171 # start with a 'random' vector at root | |
172 pvc[n] = (0, _bin((node * 3)[:_vecbytes])) | |
173 elif p2 == nullrev: | |
174 d, v = pvc[p1] | |
175 pvc[n] = (d + 1, _flipbit(v, node)) | |
176 else: | |
177 pvc[n] = _mergevec(pvc[p1], pvc[p2], node) | |
178 bs = _join(*pvc[ctx.rev()]) | |
32201
4462a981e8df
base85: proxy through util module
Yuya Nishihara <yuya@tcha.org>
parents:
27501
diff
changeset
|
179 return pvec(util.b85encode(bs)) |
16249 | 180 |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
181 |
48946
642e31cb55f0
py3: use class X: instead of class X(object):
Gregory Szorc <gregory.szorc@gmail.com>
parents:
48875
diff
changeset
|
182 class pvec: |
16249 | 183 def __init__(self, hashorctx): |
43746
10662ac7849e
pvec: fix a `str` type conditional for py3
Matt Harbison <matt_harbison@yahoo.com>
parents:
43115
diff
changeset
|
184 if isinstance(hashorctx, bytes): |
16249 | 185 self._bs = hashorctx |
32201
4462a981e8df
base85: proxy through util module
Yuya Nishihara <yuya@tcha.org>
parents:
27501
diff
changeset
|
186 self._depth, self._vec = _split(util.b85decode(hashorctx)) |
16249 | 187 else: |
18918
5093d2a87ff6
pvec: use the correct name for an identifier
Bryan O'Sullivan <bryano@fb.com>
parents:
17424
diff
changeset
|
188 self._vec = ctxpvec(hashorctx) |
16249 | 189 |
190 def __str__(self): | |
191 return self._bs | |
192 | |
193 def __eq__(self, b): | |
194 return self._vec == b._vec and self._depth == b._depth | |
195 | |
196 def __lt__(self, b): | |
197 delta = b._depth - self._depth | |
198 if delta < 0: | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
199 return False # always correct |
16249 | 200 if _hamming(self._vec, b._vec) > delta: |
201 return False | |
202 return True | |
203 | |
204 def __gt__(self, b): | |
205 return b < self | |
206 | |
207 def __or__(self, b): | |
208 delta = abs(b._depth - self._depth) | |
209 if _hamming(self._vec, b._vec) <= delta: | |
210 return False | |
211 return True | |
212 | |
213 def __sub__(self, b): | |
214 if self | b: | |
43077
687b865b95ad
formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents:
43076
diff
changeset
|
215 raise ValueError(b"concurrent pvecs") |
16249 | 216 return self._depth - b._depth |
217 | |
218 def distance(self, b): | |
219 d = abs(b._depth - self._depth) | |
220 h = _hamming(self._vec, b._vec) | |
221 return max(d, h) | |
222 | |
223 def near(self, b): | |
224 dist = abs(b.depth - self._depth) | |
225 if dist > _radius or _hamming(self._vec, b._vec) > _radius: | |
226 return False |