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