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