Mercurial > hg
annotate mercurial/pvec.py @ 52285:94cf83d9a2c9
sslutil: drop support for Python prior to 3.7
There's also a block of code around line 47 related to `ssl.HAS_TLSv1` to
determine the supported protocols that references "Python 3.7", but I'm not
altering that because the commit referenced there wasn't landed until just prior
to the 3.9 release, and I'm not sure what flavors of py38 might not have a
backport.
Avoid de-indenting for now for a clearer text diff.
author | Matt Harbison <matt_harbison@yahoo.com> |
---|---|
date | Fri, 08 Nov 2024 19:48:06 -0500 |
parents | f4733654f144 |
children |
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 | |
51863
f4733654f144
typing: add `from __future__ import annotations` to most files
Matt Harbison <matt_harbison@yahoo.com>
parents:
51287
diff
changeset
|
51 from __future__ import annotations |
27501
983e93d88193
pvec: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents:
24339
diff
changeset
|
52 |
983e93d88193
pvec: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents:
24339
diff
changeset
|
53 from .node import nullrev |
983e93d88193
pvec: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents:
24339
diff
changeset
|
54 from . import ( |
38783
e7aa113b14f7
global: use pycompat.xrange()
Gregory Szorc <gregory.szorc@gmail.com>
parents:
32201
diff
changeset
|
55 pycompat, |
27501
983e93d88193
pvec: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents:
24339
diff
changeset
|
56 util, |
983e93d88193
pvec: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents:
24339
diff
changeset
|
57 ) |
16249 | 58 |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
59 _size = 448 # 70 chars b85-encoded |
43465
90aac60b6697
pvec: migrate to modern integer division
Augie Fackler <augie@google.com>
parents:
43463
diff
changeset
|
60 _bytes = _size // 8 |
16249 | 61 _depthbits = 24 |
43465
90aac60b6697
pvec: migrate to modern integer division
Augie Fackler <augie@google.com>
parents:
43463
diff
changeset
|
62 _depthbytes = _depthbits // 8 |
16249 | 63 _vecbytes = _bytes - _depthbytes |
64 _vecbits = _vecbytes * 8 | |
43465
90aac60b6697
pvec: migrate to modern integer division
Augie Fackler <augie@google.com>
parents:
43463
diff
changeset
|
65 _radius = (_vecbits - 30) // 2 # high probability vectors are related |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
66 |
16249 | 67 |
68 def _bin(bs): | |
69 '''convert a bytestring to a long''' | |
70 v = 0 | |
71 for b in bs: | |
72 v = v * 256 + ord(b) | |
73 return v | |
74 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
75 |
51287
f15cb5111a1e
pytype: move some type comment to proper annotation
Pierre-Yves David <pierre-yves.david@octobus.net>
parents:
50928
diff
changeset
|
76 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
|
77 bs = b"" |
49284
d44e3c45f0e4
py3: replace `pycompat.xrange` by `range`
Manuel Jacob <me@manueljacob.de>
parents:
48946
diff
changeset
|
78 for p in range(l): |
43463
271af23d01a9
pvec: fix overlooked chr() call
Augie Fackler <augie@google.com>
parents:
43115
diff
changeset
|
79 bs = pycompat.bytechr(v & 255) + bs |
16249 | 80 v >>= 8 |
81 return bs | |
82 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
83 |
16249 | 84 def _split(b): |
85 '''depth and bitvec''' | |
86 return _bin(b[:_depthbytes]), _bin(b[_depthbytes:]) | |
87 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
88 |
16249 | 89 def _join(depth, bitvec): |
90 return _str(depth, _depthbytes) + _str(bitvec, _vecbytes) | |
91 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
92 |
16249 | 93 def _hweight(x): |
94 c = 0 | |
95 while x: | |
96 if x & 1: | |
97 c += 1 | |
98 x >>= 1 | |
99 return c | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
100 |
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
101 |
49284
d44e3c45f0e4
py3: replace `pycompat.xrange` by `range`
Manuel Jacob <me@manueljacob.de>
parents:
48946
diff
changeset
|
102 _htab = [_hweight(x) for x in range(256)] |
16249 | 103 |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
104 |
16249 | 105 def _hamming(a, b): |
106 '''find the hamming distance between two longs''' | |
107 d = a ^ b | |
108 c = 0 | |
109 while d: | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
110 c += _htab[d & 0xFF] |
16249 | 111 d >>= 8 |
112 return c | |
113 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
114 |
16249 | 115 def _mergevec(x, y, c): |
116 # Ideally, this function would be x ^ y ^ ancestor, but finding | |
117 # ancestors is a nuisance. So instead we find the minimal number | |
118 # of changes to balance the depth and hamming distance | |
119 | |
120 d1, v1 = x | |
121 d2, v2 = y | |
122 if d1 < d2: | |
123 d1, d2, v1, v2 = d2, d1, v2, v1 | |
124 | |
125 hdist = _hamming(v1, v2) | |
126 ddist = d1 - d2 | |
127 v = v1 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
128 m = v1 ^ v2 # mask of different bits |
16249 | 129 i = 1 |
130 | |
131 if hdist > ddist: | |
132 # if delta = 10 and hdist = 100, then we need to go up 55 steps | |
133 # to the ancestor and down 45 | |
43465
90aac60b6697
pvec: migrate to modern integer division
Augie Fackler <augie@google.com>
parents:
43463
diff
changeset
|
134 changes = (hdist - ddist + 1) // 2 |
16249 | 135 else: |
136 # must make at least one change | |
137 changes = 1 | |
138 depth = d1 + changes | |
139 | |
140 # copy changes from v2 | |
141 if m: | |
142 while changes: | |
143 if m & i: | |
144 v ^= i | |
145 changes -= 1 | |
146 i <<= 1 | |
147 else: | |
148 v = _flipbit(v, c) | |
149 | |
150 return depth, v | |
151 | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
152 |
16249 | 153 def _flipbit(v, node): |
154 # converting bit strings to longs is slow | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
155 bit = (hash(node) & 0xFFFFFFFF) % _vecbits |
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
156 return v ^ (1 << bit) |
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
157 |
16249 | 158 |
159 def ctxpvec(ctx): | |
160 '''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
|
161 r = ctx.repo() |
50928
d718eddf01d9
safehasattr: drop usage in favor of hasattr
Pierre-Yves David <pierre-yves.david@octobus.net>
parents:
49284
diff
changeset
|
162 if not hasattr(r, "_pveccache"): |
16249 | 163 r._pveccache = {} |
164 pvc = r._pveccache | |
165 if ctx.rev() not in pvc: | |
166 cl = r.changelog | |
49284
d44e3c45f0e4
py3: replace `pycompat.xrange` by `range`
Manuel Jacob <me@manueljacob.de>
parents:
48946
diff
changeset
|
167 for n in range(ctx.rev() + 1): |
16249 | 168 if n not in pvc: |
169 node = cl.node(n) | |
170 p1, p2 = cl.parentrevs(n) | |
171 if p1 == nullrev: | |
172 # start with a 'random' vector at root | |
173 pvc[n] = (0, _bin((node * 3)[:_vecbytes])) | |
174 elif p2 == nullrev: | |
175 d, v = pvc[p1] | |
176 pvc[n] = (d + 1, _flipbit(v, node)) | |
177 else: | |
178 pvc[n] = _mergevec(pvc[p1], pvc[p2], node) | |
179 bs = _join(*pvc[ctx.rev()]) | |
32201
4462a981e8df
base85: proxy through util module
Yuya Nishihara <yuya@tcha.org>
parents:
27501
diff
changeset
|
180 return pvec(util.b85encode(bs)) |
16249 | 181 |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
182 |
48946
642e31cb55f0
py3: use class X: instead of class X(object):
Gregory Szorc <gregory.szorc@gmail.com>
parents:
48875
diff
changeset
|
183 class pvec: |
16249 | 184 def __init__(self, hashorctx): |
43746
10662ac7849e
pvec: fix a `str` type conditional for py3
Matt Harbison <matt_harbison@yahoo.com>
parents:
43115
diff
changeset
|
185 if isinstance(hashorctx, bytes): |
16249 | 186 self._bs = hashorctx |
32201
4462a981e8df
base85: proxy through util module
Yuya Nishihara <yuya@tcha.org>
parents:
27501
diff
changeset
|
187 self._depth, self._vec = _split(util.b85decode(hashorctx)) |
16249 | 188 else: |
18918
5093d2a87ff6
pvec: use the correct name for an identifier
Bryan O'Sullivan <bryano@fb.com>
parents:
17424
diff
changeset
|
189 self._vec = ctxpvec(hashorctx) |
16249 | 190 |
191 def __str__(self): | |
192 return self._bs | |
193 | |
194 def __eq__(self, b): | |
195 return self._vec == b._vec and self._depth == b._depth | |
196 | |
197 def __lt__(self, b): | |
198 delta = b._depth - self._depth | |
199 if delta < 0: | |
43076
2372284d9457
formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents:
38783
diff
changeset
|
200 return False # always correct |
16249 | 201 if _hamming(self._vec, b._vec) > delta: |
202 return False | |
203 return True | |
204 | |
205 def __gt__(self, b): | |
206 return b < self | |
207 | |
208 def __or__(self, b): | |
209 delta = abs(b._depth - self._depth) | |
210 if _hamming(self._vec, b._vec) <= delta: | |
211 return False | |
212 return True | |
213 | |
214 def __sub__(self, b): | |
215 if self | b: | |
43077
687b865b95ad
formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents:
43076
diff
changeset
|
216 raise ValueError(b"concurrent pvecs") |
16249 | 217 return self._depth - b._depth |
218 | |
219 def distance(self, b): | |
220 d = abs(b._depth - self._depth) | |
221 h = _hamming(self._vec, b._vec) | |
222 return max(d, h) | |
223 | |
224 def near(self, b): | |
225 dist = abs(b.depth - self._depth) | |
226 if dist > _radius or _hamming(self._vec, b._vec) > _radius: | |
227 return False |