##// END OF EJS Templates
obsolete: clarify that 'successorssets' returns the latest successors...
Pierre-Yves David -
r26265:077f20ee default
parent child Browse files
Show More
@@ -1,1252 +1,1252 b''
1 1 # obsolete.py - obsolete markers handling
2 2 #
3 3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 4 # Logilab SA <contact@logilab.fr>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 """Obsolete marker handling
10 10
11 11 An obsolete marker maps an old changeset to a list of new
12 12 changesets. If the list of new changesets is empty, the old changeset
13 13 is said to be "killed". Otherwise, the old changeset is being
14 14 "replaced" by the new changesets.
15 15
16 16 Obsolete markers can be used to record and distribute changeset graph
17 17 transformations performed by history rewrite operations, and help
18 18 building new tools to reconcile conflicting rewrite actions. To
19 19 facilitate conflict resolution, markers include various annotations
20 20 besides old and news changeset identifiers, such as creation date or
21 21 author name.
22 22
23 23 The old obsoleted changeset is called a "precursor" and possible
24 24 replacements are called "successors". Markers that used changeset X as
25 25 a precursor are called "successor markers of X" because they hold
26 26 information about the successors of X. Markers that use changeset Y as
27 27 a successors are call "precursor markers of Y" because they hold
28 28 information about the precursors of Y.
29 29
30 30 Examples:
31 31
32 32 - When changeset A is replaced by changeset A', one marker is stored:
33 33
34 34 (A, (A',))
35 35
36 36 - When changesets A and B are folded into a new changeset C, two markers are
37 37 stored:
38 38
39 39 (A, (C,)) and (B, (C,))
40 40
41 41 - When changeset A is simply "pruned" from the graph, a marker is created:
42 42
43 43 (A, ())
44 44
45 45 - When changeset A is split into B and C, a single marker are used:
46 46
47 47 (A, (C, C))
48 48
49 49 We use a single marker to distinguish the "split" case from the "divergence"
50 50 case. If two independent operations rewrite the same changeset A in to A' and
51 51 A'', we have an error case: divergent rewriting. We can detect it because
52 52 two markers will be created independently:
53 53
54 54 (A, (B,)) and (A, (C,))
55 55
56 56 Format
57 57 ------
58 58
59 59 Markers are stored in an append-only file stored in
60 60 '.hg/store/obsstore'.
61 61
62 62 The file starts with a version header:
63 63
64 64 - 1 unsigned byte: version number, starting at zero.
65 65
66 66 The header is followed by the markers. Marker format depend of the version. See
67 67 comment associated with each format for details.
68 68
69 69 """
70 70 import struct
71 71 import util, base85, node, parsers
72 72 import phases
73 73 from i18n import _
74 74
75 75 _pack = struct.pack
76 76 _unpack = struct.unpack
77 77 _calcsize = struct.calcsize
78 78 propertycache = util.propertycache
79 79
80 80 # the obsolete feature is not mature enough to be enabled by default.
81 81 # you have to rely on third party extension extension to enable this.
82 82 _enabled = False
83 83
84 84 # Options for obsolescence
85 85 createmarkersopt = 'createmarkers'
86 86 allowunstableopt = 'allowunstable'
87 87 exchangeopt = 'exchange'
88 88
89 89 ### obsolescence marker flag
90 90
91 91 ## bumpedfix flag
92 92 #
93 93 # When a changeset A' succeed to a changeset A which became public, we call A'
94 94 # "bumped" because it's a successors of a public changesets
95 95 #
96 96 # o A' (bumped)
97 97 # |`:
98 98 # | o A
99 99 # |/
100 100 # o Z
101 101 #
102 102 # The way to solve this situation is to create a new changeset Ad as children
103 103 # of A. This changeset have the same content than A'. So the diff from A to A'
104 104 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
105 105 #
106 106 # o Ad
107 107 # |`:
108 108 # | x A'
109 109 # |'|
110 110 # o | A
111 111 # |/
112 112 # o Z
113 113 #
114 114 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
115 115 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
116 116 # This flag mean that the successors express the changes between the public and
117 117 # bumped version and fix the situation, breaking the transitivity of
118 118 # "bumped" here.
119 119 bumpedfix = 1
120 120 usingsha256 = 2
121 121
122 122 ## Parsing and writing of version "0"
123 123 #
124 124 # The header is followed by the markers. Each marker is made of:
125 125 #
126 126 # - 1 uint8 : number of new changesets "N", can be zero.
127 127 #
128 128 # - 1 uint32: metadata size "M" in bytes.
129 129 #
130 130 # - 1 byte: a bit field. It is reserved for flags used in common
131 131 # obsolete marker operations, to avoid repeated decoding of metadata
132 132 # entries.
133 133 #
134 134 # - 20 bytes: obsoleted changeset identifier.
135 135 #
136 136 # - N*20 bytes: new changesets identifiers.
137 137 #
138 138 # - M bytes: metadata as a sequence of nul-terminated strings. Each
139 139 # string contains a key and a value, separated by a colon ':', without
140 140 # additional encoding. Keys cannot contain '\0' or ':' and values
141 141 # cannot contain '\0'.
142 142 _fm0version = 0
143 143 _fm0fixed = '>BIB20s'
144 144 _fm0node = '20s'
145 145 _fm0fsize = _calcsize(_fm0fixed)
146 146 _fm0fnodesize = _calcsize(_fm0node)
147 147
148 148 def _fm0readmarkers(data, off):
149 149 # Loop on markers
150 150 l = len(data)
151 151 while off + _fm0fsize <= l:
152 152 # read fixed part
153 153 cur = data[off:off + _fm0fsize]
154 154 off += _fm0fsize
155 155 numsuc, mdsize, flags, pre = _unpack(_fm0fixed, cur)
156 156 # read replacement
157 157 sucs = ()
158 158 if numsuc:
159 159 s = (_fm0fnodesize * numsuc)
160 160 cur = data[off:off + s]
161 161 sucs = _unpack(_fm0node * numsuc, cur)
162 162 off += s
163 163 # read metadata
164 164 # (metadata will be decoded on demand)
165 165 metadata = data[off:off + mdsize]
166 166 if len(metadata) != mdsize:
167 167 raise util.Abort(_('parsing obsolete marker: metadata is too '
168 168 'short, %d bytes expected, got %d')
169 169 % (mdsize, len(metadata)))
170 170 off += mdsize
171 171 metadata = _fm0decodemeta(metadata)
172 172 try:
173 173 when, offset = metadata.pop('date', '0 0').split(' ')
174 174 date = float(when), int(offset)
175 175 except ValueError:
176 176 date = (0., 0)
177 177 parents = None
178 178 if 'p2' in metadata:
179 179 parents = (metadata.pop('p1', None), metadata.pop('p2', None))
180 180 elif 'p1' in metadata:
181 181 parents = (metadata.pop('p1', None),)
182 182 elif 'p0' in metadata:
183 183 parents = ()
184 184 if parents is not None:
185 185 try:
186 186 parents = tuple(node.bin(p) for p in parents)
187 187 # if parent content is not a nodeid, drop the data
188 188 for p in parents:
189 189 if len(p) != 20:
190 190 parents = None
191 191 break
192 192 except TypeError:
193 193 # if content cannot be translated to nodeid drop the data.
194 194 parents = None
195 195
196 196 metadata = tuple(sorted(metadata.iteritems()))
197 197
198 198 yield (pre, sucs, flags, metadata, date, parents)
199 199
200 200 def _fm0encodeonemarker(marker):
201 201 pre, sucs, flags, metadata, date, parents = marker
202 202 if flags & usingsha256:
203 203 raise util.Abort(_('cannot handle sha256 with old obsstore format'))
204 204 metadata = dict(metadata)
205 205 time, tz = date
206 206 metadata['date'] = '%r %i' % (time, tz)
207 207 if parents is not None:
208 208 if not parents:
209 209 # mark that we explicitly recorded no parents
210 210 metadata['p0'] = ''
211 211 for i, p in enumerate(parents):
212 212 metadata['p%i' % (i + 1)] = node.hex(p)
213 213 metadata = _fm0encodemeta(metadata)
214 214 numsuc = len(sucs)
215 215 format = _fm0fixed + (_fm0node * numsuc)
216 216 data = [numsuc, len(metadata), flags, pre]
217 217 data.extend(sucs)
218 218 return _pack(format, *data) + metadata
219 219
220 220 def _fm0encodemeta(meta):
221 221 """Return encoded metadata string to string mapping.
222 222
223 223 Assume no ':' in key and no '\0' in both key and value."""
224 224 for key, value in meta.iteritems():
225 225 if ':' in key or '\0' in key:
226 226 raise ValueError("':' and '\0' are forbidden in metadata key'")
227 227 if '\0' in value:
228 228 raise ValueError("':' is forbidden in metadata value'")
229 229 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
230 230
231 231 def _fm0decodemeta(data):
232 232 """Return string to string dictionary from encoded version."""
233 233 d = {}
234 234 for l in data.split('\0'):
235 235 if l:
236 236 key, value = l.split(':')
237 237 d[key] = value
238 238 return d
239 239
240 240 ## Parsing and writing of version "1"
241 241 #
242 242 # The header is followed by the markers. Each marker is made of:
243 243 #
244 244 # - uint32: total size of the marker (including this field)
245 245 #
246 246 # - float64: date in seconds since epoch
247 247 #
248 248 # - int16: timezone offset in minutes
249 249 #
250 250 # - uint16: a bit field. It is reserved for flags used in common
251 251 # obsolete marker operations, to avoid repeated decoding of metadata
252 252 # entries.
253 253 #
254 254 # - uint8: number of successors "N", can be zero.
255 255 #
256 256 # - uint8: number of parents "P", can be zero.
257 257 #
258 258 # 0: parents data stored but no parent,
259 259 # 1: one parent stored,
260 260 # 2: two parents stored,
261 261 # 3: no parent data stored
262 262 #
263 263 # - uint8: number of metadata entries M
264 264 #
265 265 # - 20 or 32 bytes: precursor changeset identifier.
266 266 #
267 267 # - N*(20 or 32) bytes: successors changesets identifiers.
268 268 #
269 269 # - P*(20 or 32) bytes: parents of the precursors changesets.
270 270 #
271 271 # - M*(uint8, uint8): size of all metadata entries (key and value)
272 272 #
273 273 # - remaining bytes: the metadata, each (key, value) pair after the other.
274 274 _fm1version = 1
275 275 _fm1fixed = '>IdhHBBB20s'
276 276 _fm1nodesha1 = '20s'
277 277 _fm1nodesha256 = '32s'
278 278 _fm1nodesha1size = _calcsize(_fm1nodesha1)
279 279 _fm1nodesha256size = _calcsize(_fm1nodesha256)
280 280 _fm1fsize = _calcsize(_fm1fixed)
281 281 _fm1parentnone = 3
282 282 _fm1parentshift = 14
283 283 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
284 284 _fm1metapair = 'BB'
285 285 _fm1metapairsize = _calcsize('BB')
286 286
287 287 def _fm1purereadmarkers(data, off):
288 288 # make some global constants local for performance
289 289 noneflag = _fm1parentnone
290 290 sha2flag = usingsha256
291 291 sha1size = _fm1nodesha1size
292 292 sha2size = _fm1nodesha256size
293 293 sha1fmt = _fm1nodesha1
294 294 sha2fmt = _fm1nodesha256
295 295 metasize = _fm1metapairsize
296 296 metafmt = _fm1metapair
297 297 fsize = _fm1fsize
298 298 unpack = _unpack
299 299
300 300 # Loop on markers
301 301 stop = len(data) - _fm1fsize
302 302 ufixed = struct.Struct(_fm1fixed).unpack
303 303
304 304 while off <= stop:
305 305 # read fixed part
306 306 o1 = off + fsize
307 307 t, secs, tz, flags, numsuc, numpar, nummeta, prec = ufixed(data[off:o1])
308 308
309 309 if flags & sha2flag:
310 310 # FIXME: prec was read as a SHA1, needs to be amended
311 311
312 312 # read 0 or more successors
313 313 if numsuc == 1:
314 314 o2 = o1 + sha2size
315 315 sucs = (data[o1:o2],)
316 316 else:
317 317 o2 = o1 + sha2size * numsuc
318 318 sucs = unpack(sha2fmt * numsuc, data[o1:o2])
319 319
320 320 # read parents
321 321 if numpar == noneflag:
322 322 o3 = o2
323 323 parents = None
324 324 elif numpar == 1:
325 325 o3 = o2 + sha2size
326 326 parents = (data[o2:o3],)
327 327 else:
328 328 o3 = o2 + sha2size * numpar
329 329 parents = unpack(sha2fmt * numpar, data[o2:o3])
330 330 else:
331 331 # read 0 or more successors
332 332 if numsuc == 1:
333 333 o2 = o1 + sha1size
334 334 sucs = (data[o1:o2],)
335 335 else:
336 336 o2 = o1 + sha1size * numsuc
337 337 sucs = unpack(sha1fmt * numsuc, data[o1:o2])
338 338
339 339 # read parents
340 340 if numpar == noneflag:
341 341 o3 = o2
342 342 parents = None
343 343 elif numpar == 1:
344 344 o3 = o2 + sha1size
345 345 parents = (data[o2:o3],)
346 346 else:
347 347 o3 = o2 + sha1size * numpar
348 348 parents = unpack(sha1fmt * numpar, data[o2:o3])
349 349
350 350 # read metadata
351 351 off = o3 + metasize * nummeta
352 352 metapairsize = unpack('>' + (metafmt * nummeta), data[o3:off])
353 353 metadata = []
354 354 for idx in xrange(0, len(metapairsize), 2):
355 355 o1 = off + metapairsize[idx]
356 356 o2 = o1 + metapairsize[idx + 1]
357 357 metadata.append((data[off:o1], data[o1:o2]))
358 358 off = o2
359 359
360 360 yield (prec, sucs, flags, tuple(metadata), (secs, tz * 60), parents)
361 361
362 362 def _fm1encodeonemarker(marker):
363 363 pre, sucs, flags, metadata, date, parents = marker
364 364 # determine node size
365 365 _fm1node = _fm1nodesha1
366 366 if flags & usingsha256:
367 367 _fm1node = _fm1nodesha256
368 368 numsuc = len(sucs)
369 369 numextranodes = numsuc
370 370 if parents is None:
371 371 numpar = _fm1parentnone
372 372 else:
373 373 numpar = len(parents)
374 374 numextranodes += numpar
375 375 formatnodes = _fm1node * numextranodes
376 376 formatmeta = _fm1metapair * len(metadata)
377 377 format = _fm1fixed + formatnodes + formatmeta
378 378 # tz is stored in minutes so we divide by 60
379 379 tz = date[1]//60
380 380 data = [None, date[0], tz, flags, numsuc, numpar, len(metadata), pre]
381 381 data.extend(sucs)
382 382 if parents is not None:
383 383 data.extend(parents)
384 384 totalsize = _calcsize(format)
385 385 for key, value in metadata:
386 386 lk = len(key)
387 387 lv = len(value)
388 388 data.append(lk)
389 389 data.append(lv)
390 390 totalsize += lk + lv
391 391 data[0] = totalsize
392 392 data = [_pack(format, *data)]
393 393 for key, value in metadata:
394 394 data.append(key)
395 395 data.append(value)
396 396 return ''.join(data)
397 397
398 398 def _fm1readmarkers(data, off):
399 399 native = getattr(parsers, 'fm1readmarkers', None)
400 400 if not native:
401 401 return _fm1purereadmarkers(data, off)
402 402 stop = len(data) - _fm1fsize
403 403 return native(data, off, stop)
404 404
405 405 # mapping to read/write various marker formats
406 406 # <version> -> (decoder, encoder)
407 407 formats = {_fm0version: (_fm0readmarkers, _fm0encodeonemarker),
408 408 _fm1version: (_fm1readmarkers, _fm1encodeonemarker)}
409 409
410 410 @util.nogc
411 411 def _readmarkers(data):
412 412 """Read and enumerate markers from raw data"""
413 413 off = 0
414 414 diskversion = _unpack('>B', data[off:off + 1])[0]
415 415 off += 1
416 416 if diskversion not in formats:
417 417 raise util.Abort(_('parsing obsolete marker: unknown version %r')
418 418 % diskversion)
419 419 return diskversion, formats[diskversion][0](data, off)
420 420
421 421 def encodemarkers(markers, addheader=False, version=_fm0version):
422 422 # Kept separate from flushmarkers(), it will be reused for
423 423 # markers exchange.
424 424 encodeone = formats[version][1]
425 425 if addheader:
426 426 yield _pack('>B', version)
427 427 for marker in markers:
428 428 yield encodeone(marker)
429 429
430 430
431 431 class marker(object):
432 432 """Wrap obsolete marker raw data"""
433 433
434 434 def __init__(self, repo, data):
435 435 # the repo argument will be used to create changectx in later version
436 436 self._repo = repo
437 437 self._data = data
438 438 self._decodedmeta = None
439 439
440 440 def __hash__(self):
441 441 return hash(self._data)
442 442
443 443 def __eq__(self, other):
444 444 if type(other) != type(self):
445 445 return False
446 446 return self._data == other._data
447 447
448 448 def precnode(self):
449 449 """Precursor changeset node identifier"""
450 450 return self._data[0]
451 451
452 452 def succnodes(self):
453 453 """List of successor changesets node identifiers"""
454 454 return self._data[1]
455 455
456 456 def parentnodes(self):
457 457 """Parents of the precursors (None if not recorded)"""
458 458 return self._data[5]
459 459
460 460 def metadata(self):
461 461 """Decoded metadata dictionary"""
462 462 return dict(self._data[3])
463 463
464 464 def date(self):
465 465 """Creation date as (unixtime, offset)"""
466 466 return self._data[4]
467 467
468 468 def flags(self):
469 469 """The flags field of the marker"""
470 470 return self._data[2]
471 471
472 472 @util.nogc
473 473 def _addsuccessors(successors, markers):
474 474 for mark in markers:
475 475 successors.setdefault(mark[0], set()).add(mark)
476 476
477 477 @util.nogc
478 478 def _addprecursors(precursors, markers):
479 479 for mark in markers:
480 480 for suc in mark[1]:
481 481 precursors.setdefault(suc, set()).add(mark)
482 482
483 483 @util.nogc
484 484 def _addchildren(children, markers):
485 485 for mark in markers:
486 486 parents = mark[5]
487 487 if parents is not None:
488 488 for p in parents:
489 489 children.setdefault(p, set()).add(mark)
490 490
491 491 def _checkinvalidmarkers(markers):
492 492 """search for marker with invalid data and raise error if needed
493 493
494 494 Exist as a separated function to allow the evolve extension for a more
495 495 subtle handling.
496 496 """
497 497 for mark in markers:
498 498 if node.nullid in mark[1]:
499 499 raise util.Abort(_('bad obsolescence marker detected: '
500 500 'invalid successors nullid'))
501 501
502 502 class obsstore(object):
503 503 """Store obsolete markers
504 504
505 505 Markers can be accessed with two mappings:
506 506 - precursors[x] -> set(markers on precursors edges of x)
507 507 - successors[x] -> set(markers on successors edges of x)
508 508 - children[x] -> set(markers on precursors edges of children(x)
509 509 """
510 510
511 511 fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
512 512 # prec: nodeid, precursor changesets
513 513 # succs: tuple of nodeid, successor changesets (0-N length)
514 514 # flag: integer, flag field carrying modifier for the markers (see doc)
515 515 # meta: binary blob, encoded metadata dictionary
516 516 # date: (float, int) tuple, date of marker creation
517 517 # parents: (tuple of nodeid) or None, parents of precursors
518 518 # None is used when no data has been recorded
519 519
520 520 def __init__(self, svfs, defaultformat=_fm1version, readonly=False):
521 521 # caches for various obsolescence related cache
522 522 self.caches = {}
523 523 self._all = []
524 524 self.svfs = svfs
525 525 data = svfs.tryread('obsstore')
526 526 self._version = defaultformat
527 527 self._readonly = readonly
528 528 if data:
529 529 self._version, markers = _readmarkers(data)
530 530 self._addmarkers(markers)
531 531
532 532 def __iter__(self):
533 533 return iter(self._all)
534 534
535 535 def __len__(self):
536 536 return len(self._all)
537 537
538 538 def __nonzero__(self):
539 539 return bool(self._all)
540 540
541 541 def create(self, transaction, prec, succs=(), flag=0, parents=None,
542 542 date=None, metadata=None):
543 543 """obsolete: add a new obsolete marker
544 544
545 545 * ensuring it is hashable
546 546 * check mandatory metadata
547 547 * encode metadata
548 548
549 549 If you are a human writing code creating marker you want to use the
550 550 `createmarkers` function in this module instead.
551 551
552 552 return True if a new marker have been added, False if the markers
553 553 already existed (no op).
554 554 """
555 555 if metadata is None:
556 556 metadata = {}
557 557 if date is None:
558 558 if 'date' in metadata:
559 559 # as a courtesy for out-of-tree extensions
560 560 date = util.parsedate(metadata.pop('date'))
561 561 else:
562 562 date = util.makedate()
563 563 if len(prec) != 20:
564 564 raise ValueError(prec)
565 565 for succ in succs:
566 566 if len(succ) != 20:
567 567 raise ValueError(succ)
568 568 if prec in succs:
569 569 raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
570 570
571 571 metadata = tuple(sorted(metadata.iteritems()))
572 572
573 573 marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
574 574 return bool(self.add(transaction, [marker]))
575 575
576 576 def add(self, transaction, markers):
577 577 """Add new markers to the store
578 578
579 579 Take care of filtering duplicate.
580 580 Return the number of new marker."""
581 581 if self._readonly:
582 582 raise util.Abort('creating obsolete markers is not enabled on this '
583 583 'repo')
584 584 known = set(self._all)
585 585 new = []
586 586 for m in markers:
587 587 if m not in known:
588 588 known.add(m)
589 589 new.append(m)
590 590 if new:
591 591 f = self.svfs('obsstore', 'ab')
592 592 try:
593 593 offset = f.tell()
594 594 transaction.add('obsstore', offset)
595 595 # offset == 0: new file - add the version header
596 596 for bytes in encodemarkers(new, offset == 0, self._version):
597 597 f.write(bytes)
598 598 finally:
599 599 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
600 600 # call 'filecacheentry.refresh()' here
601 601 f.close()
602 602 self._addmarkers(new)
603 603 # new marker *may* have changed several set. invalidate the cache.
604 604 self.caches.clear()
605 605 # records the number of new markers for the transaction hooks
606 606 previous = int(transaction.hookargs.get('new_obsmarkers', '0'))
607 607 transaction.hookargs['new_obsmarkers'] = str(previous + len(new))
608 608 return len(new)
609 609
610 610 def mergemarkers(self, transaction, data):
611 611 """merge a binary stream of markers inside the obsstore
612 612
613 613 Returns the number of new markers added."""
614 614 version, markers = _readmarkers(data)
615 615 return self.add(transaction, markers)
616 616
617 617 @propertycache
618 618 def successors(self):
619 619 successors = {}
620 620 _addsuccessors(successors, self._all)
621 621 return successors
622 622
623 623 @propertycache
624 624 def precursors(self):
625 625 precursors = {}
626 626 _addprecursors(precursors, self._all)
627 627 return precursors
628 628
629 629 @propertycache
630 630 def children(self):
631 631 children = {}
632 632 _addchildren(children, self._all)
633 633 return children
634 634
635 635 def _cached(self, attr):
636 636 return attr in self.__dict__
637 637
638 638 def _addmarkers(self, markers):
639 639 markers = list(markers) # to allow repeated iteration
640 640 self._all.extend(markers)
641 641 if self._cached('successors'):
642 642 _addsuccessors(self.successors, markers)
643 643 if self._cached('precursors'):
644 644 _addprecursors(self.precursors, markers)
645 645 if self._cached('children'):
646 646 _addchildren(self.children, markers)
647 647 _checkinvalidmarkers(markers)
648 648
649 649 def relevantmarkers(self, nodes):
650 650 """return a set of all obsolescence markers relevant to a set of nodes.
651 651
652 652 "relevant" to a set of nodes mean:
653 653
654 654 - marker that use this changeset as successor
655 655 - prune marker of direct children on this changeset
656 656 - recursive application of the two rules on precursors of these markers
657 657
658 658 It is a set so you cannot rely on order."""
659 659
660 660 pendingnodes = set(nodes)
661 661 seenmarkers = set()
662 662 seennodes = set(pendingnodes)
663 663 precursorsmarkers = self.precursors
664 664 children = self.children
665 665 while pendingnodes:
666 666 direct = set()
667 667 for current in pendingnodes:
668 668 direct.update(precursorsmarkers.get(current, ()))
669 669 pruned = [m for m in children.get(current, ()) if not m[1]]
670 670 direct.update(pruned)
671 671 direct -= seenmarkers
672 672 pendingnodes = set([m[0] for m in direct])
673 673 seenmarkers |= direct
674 674 pendingnodes -= seennodes
675 675 seennodes |= pendingnodes
676 676 return seenmarkers
677 677
678 678 def commonversion(versions):
679 679 """Return the newest version listed in both versions and our local formats.
680 680
681 681 Returns None if no common version exists.
682 682 """
683 683 versions.sort(reverse=True)
684 684 # search for highest version known on both side
685 685 for v in versions:
686 686 if v in formats:
687 687 return v
688 688 return None
689 689
690 690 # arbitrary picked to fit into 8K limit from HTTP server
691 691 # you have to take in account:
692 692 # - the version header
693 693 # - the base85 encoding
694 694 _maxpayload = 5300
695 695
696 696 def _pushkeyescape(markers):
697 697 """encode markers into a dict suitable for pushkey exchange
698 698
699 699 - binary data is base85 encoded
700 700 - split in chunks smaller than 5300 bytes"""
701 701 keys = {}
702 702 parts = []
703 703 currentlen = _maxpayload * 2 # ensure we create a new part
704 704 for marker in markers:
705 705 nextdata = _fm0encodeonemarker(marker)
706 706 if (len(nextdata) + currentlen > _maxpayload):
707 707 currentpart = []
708 708 currentlen = 0
709 709 parts.append(currentpart)
710 710 currentpart.append(nextdata)
711 711 currentlen += len(nextdata)
712 712 for idx, part in enumerate(reversed(parts)):
713 713 data = ''.join([_pack('>B', _fm0version)] + part)
714 714 keys['dump%i' % idx] = base85.b85encode(data)
715 715 return keys
716 716
717 717 def listmarkers(repo):
718 718 """List markers over pushkey"""
719 719 if not repo.obsstore:
720 720 return {}
721 721 return _pushkeyescape(sorted(repo.obsstore))
722 722
723 723 def pushmarker(repo, key, old, new):
724 724 """Push markers over pushkey"""
725 725 if not key.startswith('dump'):
726 726 repo.ui.warn(_('unknown key: %r') % key)
727 727 return 0
728 728 if old:
729 729 repo.ui.warn(_('unexpected old value for %r') % key)
730 730 return 0
731 731 data = base85.b85decode(new)
732 732 lock = repo.lock()
733 733 try:
734 734 tr = repo.transaction('pushkey: obsolete markers')
735 735 try:
736 736 repo.obsstore.mergemarkers(tr, data)
737 737 tr.close()
738 738 return 1
739 739 finally:
740 740 tr.release()
741 741 finally:
742 742 lock.release()
743 743
744 744 def getmarkers(repo, nodes=None):
745 745 """returns markers known in a repository
746 746
747 747 If <nodes> is specified, only markers "relevant" to those nodes are are
748 748 returned"""
749 749 if nodes is None:
750 750 rawmarkers = repo.obsstore
751 751 else:
752 752 rawmarkers = repo.obsstore.relevantmarkers(nodes)
753 753
754 754 for markerdata in rawmarkers:
755 755 yield marker(repo, markerdata)
756 756
757 757 def relevantmarkers(repo, node):
758 758 """all obsolete markers relevant to some revision"""
759 759 for markerdata in repo.obsstore.relevantmarkers(node):
760 760 yield marker(repo, markerdata)
761 761
762 762
763 763 def precursormarkers(ctx):
764 764 """obsolete marker marking this changeset as a successors"""
765 765 for data in ctx.repo().obsstore.precursors.get(ctx.node(), ()):
766 766 yield marker(ctx.repo(), data)
767 767
768 768 def successormarkers(ctx):
769 769 """obsolete marker making this changeset obsolete"""
770 770 for data in ctx.repo().obsstore.successors.get(ctx.node(), ()):
771 771 yield marker(ctx.repo(), data)
772 772
773 773 def allsuccessors(obsstore, nodes, ignoreflags=0):
774 774 """Yield node for every successor of <nodes>.
775 775
776 776 Some successors may be unknown locally.
777 777
778 778 This is a linear yield unsuited to detecting split changesets. It includes
779 779 initial nodes too."""
780 780 remaining = set(nodes)
781 781 seen = set(remaining)
782 782 while remaining:
783 783 current = remaining.pop()
784 784 yield current
785 785 for mark in obsstore.successors.get(current, ()):
786 786 # ignore marker flagged with specified flag
787 787 if mark[2] & ignoreflags:
788 788 continue
789 789 for suc in mark[1]:
790 790 if suc not in seen:
791 791 seen.add(suc)
792 792 remaining.add(suc)
793 793
794 794 def allprecursors(obsstore, nodes, ignoreflags=0):
795 795 """Yield node for every precursors of <nodes>.
796 796
797 797 Some precursors may be unknown locally.
798 798
799 799 This is a linear yield unsuited to detecting folded changesets. It includes
800 800 initial nodes too."""
801 801
802 802 remaining = set(nodes)
803 803 seen = set(remaining)
804 804 while remaining:
805 805 current = remaining.pop()
806 806 yield current
807 807 for mark in obsstore.precursors.get(current, ()):
808 808 # ignore marker flagged with specified flag
809 809 if mark[2] & ignoreflags:
810 810 continue
811 811 suc = mark[0]
812 812 if suc not in seen:
813 813 seen.add(suc)
814 814 remaining.add(suc)
815 815
816 816 def foreground(repo, nodes):
817 817 """return all nodes in the "foreground" of other node
818 818
819 819 The foreground of a revision is anything reachable using parent -> children
820 820 or precursor -> successor relation. It is very similar to "descendant" but
821 821 augmented with obsolescence information.
822 822
823 823 Beware that possible obsolescence cycle may result if complex situation.
824 824 """
825 825 repo = repo.unfiltered()
826 826 foreground = set(repo.set('%ln::', nodes))
827 827 if repo.obsstore:
828 828 # We only need this complicated logic if there is obsolescence
829 829 # XXX will probably deserve an optimised revset.
830 830 nm = repo.changelog.nodemap
831 831 plen = -1
832 832 # compute the whole set of successors or descendants
833 833 while len(foreground) != plen:
834 834 plen = len(foreground)
835 835 succs = set(c.node() for c in foreground)
836 836 mutable = [c.node() for c in foreground if c.mutable()]
837 837 succs.update(allsuccessors(repo.obsstore, mutable))
838 838 known = (n for n in succs if n in nm)
839 839 foreground = set(repo.set('%ln::', known))
840 840 return set(c.node() for c in foreground)
841 841
842 842
843 843 def successorssets(repo, initialnode, cache=None):
844 """Return all set of successors of initial nodes
844 """Return set of all latest successors of initial nodes
845 845
846 846 The successors set of a changeset A are the group of revisions that succeed
847 847 A. It succeeds A as a consistent whole, each revision being only a partial
848 848 replacement. The successors set contains non-obsolete changesets only.
849 849
850 850 This function returns the full list of successor sets which is why it
851 851 returns a list of tuples and not just a single tuple. Each tuple is a valid
852 852 successors set. Note that (A,) may be a valid successors set for changeset A
853 853 (see below).
854 854
855 855 In most cases, a changeset A will have a single element (e.g. the changeset
856 856 A is replaced by A') in its successors set. Though, it is also common for a
857 857 changeset A to have no elements in its successor set (e.g. the changeset
858 858 has been pruned). Therefore, the returned list of successors sets will be
859 859 [(A',)] or [], respectively.
860 860
861 861 When a changeset A is split into A' and B', however, it will result in a
862 862 successors set containing more than a single element, i.e. [(A',B')].
863 863 Divergent changesets will result in multiple successors sets, i.e. [(A',),
864 864 (A'')].
865 865
866 866 If a changeset A is not obsolete, then it will conceptually have no
867 867 successors set. To distinguish this from a pruned changeset, the successor
868 868 set will contain itself only, i.e. [(A,)].
869 869
870 870 Finally, successors unknown locally are considered to be pruned (obsoleted
871 871 without any successors).
872 872
873 873 The optional `cache` parameter is a dictionary that may contain precomputed
874 874 successors sets. It is meant to reuse the computation of a previous call to
875 875 `successorssets` when multiple calls are made at the same time. The cache
876 876 dictionary is updated in place. The caller is responsible for its life
877 877 span. Code that makes multiple calls to `successorssets` *must* use this
878 878 cache mechanism or suffer terrible performance.
879 879 """
880 880
881 881 succmarkers = repo.obsstore.successors
882 882
883 883 # Stack of nodes we search successors sets for
884 884 toproceed = [initialnode]
885 885 # set version of above list for fast loop detection
886 886 # element added to "toproceed" must be added here
887 887 stackedset = set(toproceed)
888 888 if cache is None:
889 889 cache = {}
890 890
891 891 # This while loop is the flattened version of a recursive search for
892 892 # successors sets
893 893 #
894 894 # def successorssets(x):
895 895 # successors = directsuccessors(x)
896 896 # ss = [[]]
897 897 # for succ in directsuccessors(x):
898 898 # # product as in itertools cartesian product
899 899 # ss = product(ss, successorssets(succ))
900 900 # return ss
901 901 #
902 902 # But we can not use plain recursive calls here:
903 903 # - that would blow the python call stack
904 904 # - obsolescence markers may have cycles, we need to handle them.
905 905 #
906 906 # The `toproceed` list act as our call stack. Every node we search
907 907 # successors set for are stacked there.
908 908 #
909 909 # The `stackedset` is set version of this stack used to check if a node is
910 910 # already stacked. This check is used to detect cycles and prevent infinite
911 911 # loop.
912 912 #
913 913 # successors set of all nodes are stored in the `cache` dictionary.
914 914 #
915 915 # After this while loop ends we use the cache to return the successors sets
916 916 # for the node requested by the caller.
917 917 while toproceed:
918 918 # Every iteration tries to compute the successors sets of the topmost
919 919 # node of the stack: CURRENT.
920 920 #
921 921 # There are four possible outcomes:
922 922 #
923 923 # 1) We already know the successors sets of CURRENT:
924 924 # -> mission accomplished, pop it from the stack.
925 925 # 2) Node is not obsolete:
926 926 # -> the node is its own successors sets. Add it to the cache.
927 927 # 3) We do not know successors set of direct successors of CURRENT:
928 928 # -> We add those successors to the stack.
929 929 # 4) We know successors sets of all direct successors of CURRENT:
930 930 # -> We can compute CURRENT successors set and add it to the
931 931 # cache.
932 932 #
933 933 current = toproceed[-1]
934 934 if current in cache:
935 935 # case (1): We already know the successors sets
936 936 stackedset.remove(toproceed.pop())
937 937 elif current not in succmarkers:
938 938 # case (2): The node is not obsolete.
939 939 if current in repo:
940 940 # We have a valid last successors.
941 941 cache[current] = [(current,)]
942 942 else:
943 943 # Final obsolete version is unknown locally.
944 944 # Do not count that as a valid successors
945 945 cache[current] = []
946 946 else:
947 947 # cases (3) and (4)
948 948 #
949 949 # We proceed in two phases. Phase 1 aims to distinguish case (3)
950 950 # from case (4):
951 951 #
952 952 # For each direct successors of CURRENT, we check whether its
953 953 # successors sets are known. If they are not, we stack the
954 954 # unknown node and proceed to the next iteration of the while
955 955 # loop. (case 3)
956 956 #
957 957 # During this step, we may detect obsolescence cycles: a node
958 958 # with unknown successors sets but already in the call stack.
959 959 # In such a situation, we arbitrary set the successors sets of
960 960 # the node to nothing (node pruned) to break the cycle.
961 961 #
962 962 # If no break was encountered we proceed to phase 2.
963 963 #
964 964 # Phase 2 computes successors sets of CURRENT (case 4); see details
965 965 # in phase 2 itself.
966 966 #
967 967 # Note the two levels of iteration in each phase.
968 968 # - The first one handles obsolescence markers using CURRENT as
969 969 # precursor (successors markers of CURRENT).
970 970 #
971 971 # Having multiple entry here means divergence.
972 972 #
973 973 # - The second one handles successors defined in each marker.
974 974 #
975 975 # Having none means pruned node, multiple successors means split,
976 976 # single successors are standard replacement.
977 977 #
978 978 for mark in sorted(succmarkers[current]):
979 979 for suc in mark[1]:
980 980 if suc not in cache:
981 981 if suc in stackedset:
982 982 # cycle breaking
983 983 cache[suc] = []
984 984 else:
985 985 # case (3) If we have not computed successors sets
986 986 # of one of those successors we add it to the
987 987 # `toproceed` stack and stop all work for this
988 988 # iteration.
989 989 toproceed.append(suc)
990 990 stackedset.add(suc)
991 991 break
992 992 else:
993 993 continue
994 994 break
995 995 else:
996 996 # case (4): we know all successors sets of all direct
997 997 # successors
998 998 #
999 999 # Successors set contributed by each marker depends on the
1000 1000 # successors sets of all its "successors" node.
1001 1001 #
1002 1002 # Each different marker is a divergence in the obsolescence
1003 1003 # history. It contributes successors sets distinct from other
1004 1004 # markers.
1005 1005 #
1006 1006 # Within a marker, a successor may have divergent successors
1007 1007 # sets. In such a case, the marker will contribute multiple
1008 1008 # divergent successors sets. If multiple successors have
1009 1009 # divergent successors sets, a Cartesian product is used.
1010 1010 #
1011 1011 # At the end we post-process successors sets to remove
1012 1012 # duplicated entry and successors set that are strict subset of
1013 1013 # another one.
1014 1014 succssets = []
1015 1015 for mark in sorted(succmarkers[current]):
1016 1016 # successors sets contributed by this marker
1017 1017 markss = [[]]
1018 1018 for suc in mark[1]:
1019 1019 # cardinal product with previous successors
1020 1020 productresult = []
1021 1021 for prefix in markss:
1022 1022 for suffix in cache[suc]:
1023 1023 newss = list(prefix)
1024 1024 for part in suffix:
1025 1025 # do not duplicated entry in successors set
1026 1026 # first entry wins.
1027 1027 if part not in newss:
1028 1028 newss.append(part)
1029 1029 productresult.append(newss)
1030 1030 markss = productresult
1031 1031 succssets.extend(markss)
1032 1032 # remove duplicated and subset
1033 1033 seen = []
1034 1034 final = []
1035 1035 candidate = sorted(((set(s), s) for s in succssets if s),
1036 1036 key=lambda x: len(x[1]), reverse=True)
1037 1037 for setversion, listversion in candidate:
1038 1038 for seenset in seen:
1039 1039 if setversion.issubset(seenset):
1040 1040 break
1041 1041 else:
1042 1042 final.append(listversion)
1043 1043 seen.append(setversion)
1044 1044 final.reverse() # put small successors set first
1045 1045 cache[current] = final
1046 1046 return cache[initialnode]
1047 1047
1048 1048 def _knownrevs(repo, nodes):
1049 1049 """yield revision numbers of known nodes passed in parameters
1050 1050
1051 1051 Unknown revisions are silently ignored."""
1052 1052 torev = repo.changelog.nodemap.get
1053 1053 for n in nodes:
1054 1054 rev = torev(n)
1055 1055 if rev is not None:
1056 1056 yield rev
1057 1057
1058 1058 # mapping of 'set-name' -> <function to compute this set>
1059 1059 cachefuncs = {}
1060 1060 def cachefor(name):
1061 1061 """Decorator to register a function as computing the cache for a set"""
1062 1062 def decorator(func):
1063 1063 assert name not in cachefuncs
1064 1064 cachefuncs[name] = func
1065 1065 return func
1066 1066 return decorator
1067 1067
1068 1068 def getrevs(repo, name):
1069 1069 """Return the set of revision that belong to the <name> set
1070 1070
1071 1071 Such access may compute the set and cache it for future use"""
1072 1072 repo = repo.unfiltered()
1073 1073 if not repo.obsstore:
1074 1074 return frozenset()
1075 1075 if name not in repo.obsstore.caches:
1076 1076 repo.obsstore.caches[name] = cachefuncs[name](repo)
1077 1077 return repo.obsstore.caches[name]
1078 1078
1079 1079 # To be simple we need to invalidate obsolescence cache when:
1080 1080 #
1081 1081 # - new changeset is added:
1082 1082 # - public phase is changed
1083 1083 # - obsolescence marker are added
1084 1084 # - strip is used a repo
1085 1085 def clearobscaches(repo):
1086 1086 """Remove all obsolescence related cache from a repo
1087 1087
1088 1088 This remove all cache in obsstore is the obsstore already exist on the
1089 1089 repo.
1090 1090
1091 1091 (We could be smarter here given the exact event that trigger the cache
1092 1092 clearing)"""
1093 1093 # only clear cache is there is obsstore data in this repo
1094 1094 if 'obsstore' in repo._filecache:
1095 1095 repo.obsstore.caches.clear()
1096 1096
1097 1097 @cachefor('obsolete')
1098 1098 def _computeobsoleteset(repo):
1099 1099 """the set of obsolete revisions"""
1100 1100 obs = set()
1101 1101 getrev = repo.changelog.nodemap.get
1102 1102 getphase = repo._phasecache.phase
1103 1103 for n in repo.obsstore.successors:
1104 1104 rev = getrev(n)
1105 1105 if rev is not None and getphase(repo, rev):
1106 1106 obs.add(rev)
1107 1107 return obs
1108 1108
1109 1109 @cachefor('unstable')
1110 1110 def _computeunstableset(repo):
1111 1111 """the set of non obsolete revisions with obsolete parents"""
1112 1112 revs = [(ctx.rev(), ctx) for ctx in
1113 1113 repo.set('(not public()) and (not obsolete())')]
1114 1114 revs.sort(key=lambda x:x[0])
1115 1115 unstable = set()
1116 1116 for rev, ctx in revs:
1117 1117 # A rev is unstable if one of its parent is obsolete or unstable
1118 1118 # this works since we traverse following growing rev order
1119 1119 if any((x.obsolete() or (x.rev() in unstable))
1120 1120 for x in ctx.parents()):
1121 1121 unstable.add(rev)
1122 1122 return unstable
1123 1123
1124 1124 @cachefor('suspended')
1125 1125 def _computesuspendedset(repo):
1126 1126 """the set of obsolete parents with non obsolete descendants"""
1127 1127 suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
1128 1128 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
1129 1129
1130 1130 @cachefor('extinct')
1131 1131 def _computeextinctset(repo):
1132 1132 """the set of obsolete parents without non obsolete descendants"""
1133 1133 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
1134 1134
1135 1135
1136 1136 @cachefor('bumped')
1137 1137 def _computebumpedset(repo):
1138 1138 """the set of revs trying to obsolete public revisions"""
1139 1139 bumped = set()
1140 1140 # util function (avoid attribute lookup in the loop)
1141 1141 phase = repo._phasecache.phase # would be faster to grab the full list
1142 1142 public = phases.public
1143 1143 cl = repo.changelog
1144 1144 torev = cl.nodemap.get
1145 1145 for ctx in repo.set('(not public()) and (not obsolete())'):
1146 1146 rev = ctx.rev()
1147 1147 # We only evaluate mutable, non-obsolete revision
1148 1148 node = ctx.node()
1149 1149 # (future) A cache of precursors may worth if split is very common
1150 1150 for pnode in allprecursors(repo.obsstore, [node],
1151 1151 ignoreflags=bumpedfix):
1152 1152 prev = torev(pnode) # unfiltered! but so is phasecache
1153 1153 if (prev is not None) and (phase(repo, prev) <= public):
1154 1154 # we have a public precursors
1155 1155 bumped.add(rev)
1156 1156 break # Next draft!
1157 1157 return bumped
1158 1158
1159 1159 @cachefor('divergent')
1160 1160 def _computedivergentset(repo):
1161 1161 """the set of rev that compete to be the final successors of some revision.
1162 1162 """
1163 1163 divergent = set()
1164 1164 obsstore = repo.obsstore
1165 1165 newermap = {}
1166 1166 for ctx in repo.set('(not public()) - obsolete()'):
1167 1167 mark = obsstore.precursors.get(ctx.node(), ())
1168 1168 toprocess = set(mark)
1169 1169 seen = set()
1170 1170 while toprocess:
1171 1171 prec = toprocess.pop()[0]
1172 1172 if prec in seen:
1173 1173 continue # emergency cycle hanging prevention
1174 1174 seen.add(prec)
1175 1175 if prec not in newermap:
1176 1176 successorssets(repo, prec, newermap)
1177 1177 newer = [n for n in newermap[prec] if n]
1178 1178 if len(newer) > 1:
1179 1179 divergent.add(ctx.rev())
1180 1180 break
1181 1181 toprocess.update(obsstore.precursors.get(prec, ()))
1182 1182 return divergent
1183 1183
1184 1184
1185 1185 def createmarkers(repo, relations, flag=0, date=None, metadata=None):
1186 1186 """Add obsolete markers between changesets in a repo
1187 1187
1188 1188 <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
1189 1189 tuple. `old` and `news` are changectx. metadata is an optional dictionary
1190 1190 containing metadata for this marker only. It is merged with the global
1191 1191 metadata specified through the `metadata` argument of this function,
1192 1192
1193 1193 Trying to obsolete a public changeset will raise an exception.
1194 1194
1195 1195 Current user and date are used except if specified otherwise in the
1196 1196 metadata attribute.
1197 1197
1198 1198 This function operates within a transaction of its own, but does
1199 1199 not take any lock on the repo.
1200 1200 """
1201 1201 # prepare metadata
1202 1202 if metadata is None:
1203 1203 metadata = {}
1204 1204 if 'user' not in metadata:
1205 1205 metadata['user'] = repo.ui.username()
1206 1206 tr = repo.transaction('add-obsolescence-marker')
1207 1207 try:
1208 1208 for rel in relations:
1209 1209 prec = rel[0]
1210 1210 sucs = rel[1]
1211 1211 localmetadata = metadata.copy()
1212 1212 if 2 < len(rel):
1213 1213 localmetadata.update(rel[2])
1214 1214
1215 1215 if not prec.mutable():
1216 1216 raise util.Abort("cannot obsolete public changeset: %s"
1217 1217 % prec,
1218 1218 hint='see "hg help phases" for details')
1219 1219 nprec = prec.node()
1220 1220 nsucs = tuple(s.node() for s in sucs)
1221 1221 npare = None
1222 1222 if not nsucs:
1223 1223 npare = tuple(p.node() for p in prec.parents())
1224 1224 if nprec in nsucs:
1225 1225 raise util.Abort("changeset %s cannot obsolete itself" % prec)
1226 1226 repo.obsstore.create(tr, nprec, nsucs, flag, parents=npare,
1227 1227 date=date, metadata=localmetadata)
1228 1228 repo.filteredrevcache.clear()
1229 1229 tr.close()
1230 1230 finally:
1231 1231 tr.release()
1232 1232
1233 1233 def isenabled(repo, option):
1234 1234 """Returns True if the given repository has the given obsolete option
1235 1235 enabled.
1236 1236 """
1237 1237 result = set(repo.ui.configlist('experimental', 'evolution'))
1238 1238 if 'all' in result:
1239 1239 return True
1240 1240
1241 1241 # For migration purposes, temporarily return true if the config hasn't been
1242 1242 # set but _enabled is true.
1243 1243 if len(result) == 0 and _enabled:
1244 1244 return True
1245 1245
1246 1246 # createmarkers must be enabled if other options are enabled
1247 1247 if ((allowunstableopt in result or exchangeopt in result) and
1248 1248 not createmarkersopt in result):
1249 1249 raise util.Abort(_("'createmarkers' obsolete option must be enabled "
1250 1250 "if other obsolete options are enabled"))
1251 1251
1252 1252 return option in result
General Comments 0
You need to be logged in to leave comments. Login now