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