##// END OF EJS Templates
smartset: don't ignore hidden revs when intersecting...
Martin von Zweigbergk -
r52004:5ae05937 default
parent child Browse files
Show More
@@ -1,1138 +1,1141 b''
1 1 # smartset.py - data structure for revision set
2 2 #
3 3 # Copyright 2010 Olivia Mackall <olivia@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8
9 9 from . import (
10 10 encoding,
11 11 error,
12 12 pycompat,
13 13 util,
14 14 )
15 15 from .utils import stringutil
16 16
17 17
18 18 def _typename(o):
19 19 return pycompat.sysbytes(type(o).__name__).lstrip(b'_')
20 20
21 21
22 22 class abstractsmartset:
23 23 def __nonzero__(self):
24 24 """True if the smartset is not empty"""
25 25 raise NotImplementedError()
26 26
27 27 __bool__ = __nonzero__
28 28
29 29 def __contains__(self, rev):
30 30 """provide fast membership testing"""
31 31 raise NotImplementedError()
32 32
33 33 def __iter__(self):
34 34 """iterate the set in the order it is supposed to be iterated"""
35 35 raise NotImplementedError()
36 36
37 37 # Attributes containing a function to perform a fast iteration in a given
38 38 # direction. A smartset can have none, one, or both defined.
39 39 #
40 40 # Default value is None instead of a function returning None to avoid
41 41 # initializing an iterator just for testing if a fast method exists.
42 42 fastasc = None
43 43 fastdesc = None
44 44
45 45 def isascending(self):
46 46 """True if the set will iterate in ascending order"""
47 47 raise NotImplementedError()
48 48
49 49 def isdescending(self):
50 50 """True if the set will iterate in descending order"""
51 51 raise NotImplementedError()
52 52
53 53 def istopo(self):
54 54 """True if the set will iterate in topographical order"""
55 55 raise NotImplementedError()
56 56
57 57 def min(self):
58 58 """return the minimum element in the set"""
59 59 if self.fastasc is None:
60 60 v = min(self)
61 61 else:
62 62 for v in self.fastasc():
63 63 break
64 64 else:
65 65 raise ValueError(b'arg is an empty sequence')
66 66 self.min = lambda: v
67 67 return v
68 68
69 69 def max(self):
70 70 """return the maximum element in the set"""
71 71 if self.fastdesc is None:
72 72 return max(self)
73 73 else:
74 74 for v in self.fastdesc():
75 75 break
76 76 else:
77 77 raise ValueError(b'arg is an empty sequence')
78 78 self.max = lambda: v
79 79 return v
80 80
81 81 def first(self):
82 82 """return the first element in the set (user iteration perspective)
83 83
84 84 Return None if the set is empty"""
85 85 raise NotImplementedError()
86 86
87 87 def last(self):
88 88 """return the last element in the set (user iteration perspective)
89 89
90 90 Return None if the set is empty"""
91 91 raise NotImplementedError()
92 92
93 93 def __len__(self):
94 94 """return the length of the smartsets
95 95
96 96 This can be expensive on smartset that could be lazy otherwise."""
97 97 raise NotImplementedError()
98 98
99 99 def reverse(self):
100 100 """reverse the expected iteration order"""
101 101 raise NotImplementedError()
102 102
103 103 def sort(self, reverse=False):
104 104 """get the set to iterate in an ascending or descending order"""
105 105 raise NotImplementedError()
106 106
107 107 def __and__(self, other):
108 108 """Returns a new object with the intersection of the two collections.
109 109
110 110 This is part of the mandatory API for smartset."""
111 111 if isinstance(other, fullreposet):
112 112 return self
113 113 return self.filter(other.__contains__, condrepr=other, cache=False)
114 114
115 115 def __add__(self, other):
116 116 """Returns a new object with the union of the two collections.
117 117
118 118 This is part of the mandatory API for smartset."""
119 119 return addset(self, other)
120 120
121 121 def __sub__(self, other):
122 122 """Returns a new object with the substraction of the two collections.
123 123
124 124 This is part of the mandatory API for smartset."""
125 125 c = other.__contains__
126 126 return self.filter(
127 127 lambda r: not c(r), condrepr=(b'<not %r>', other), cache=False
128 128 )
129 129
130 130 def filter(self, condition, condrepr=None, cache=True):
131 131 """Returns this smartset filtered by condition as a new smartset.
132 132
133 133 `condition` is a callable which takes a revision number and returns a
134 134 boolean. Optional `condrepr` provides a printable representation of
135 135 the given `condition`.
136 136
137 137 This is part of the mandatory API for smartset."""
138 138 # builtin cannot be cached. but do not needs to
139 139 if cache and hasattr(condition, '__code__'):
140 140 condition = util.cachefunc(condition)
141 141 return filteredset(self, condition, condrepr)
142 142
143 143 def slice(self, start, stop):
144 144 """Return new smartset that contains selected elements from this set"""
145 145 if start < 0 or stop < 0:
146 146 raise error.ProgrammingError(b'negative index not allowed')
147 147 return self._slice(start, stop)
148 148
149 149 def _slice(self, start, stop):
150 150 # sub classes may override this. start and stop must not be negative,
151 151 # but start > stop is allowed, which should be an empty set.
152 152 ys = []
153 153 it = iter(self)
154 154 for x in range(start):
155 155 y = next(it, None)
156 156 if y is None:
157 157 break
158 158 for x in range(stop - start):
159 159 y = next(it, None)
160 160 if y is None:
161 161 break
162 162 ys.append(y)
163 163 return baseset(ys, datarepr=(b'slice=%d:%d %r', start, stop, self))
164 164
165 165
166 166 class baseset(abstractsmartset):
167 167 """Basic data structure that represents a revset and contains the basic
168 168 operation that it should be able to perform.
169 169
170 170 Every method in this class should be implemented by any smartset class.
171 171
172 172 This class could be constructed by an (unordered) set, or an (ordered)
173 173 list-like object. If a set is provided, it'll be sorted lazily.
174 174
175 175 >>> x = [4, 0, 7, 6]
176 176 >>> y = [5, 6, 7, 3]
177 177
178 178 Construct by a set:
179 179 >>> xs = baseset(set(x))
180 180 >>> ys = baseset(set(y))
181 181 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
182 182 [[0, 4, 6, 7, 3, 5], [6, 7], [0, 4]]
183 183 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
184 184 ['addset', 'baseset', 'baseset']
185 185
186 186 Construct by a list-like:
187 187 >>> xs = baseset(x)
188 188 >>> ys = baseset(i for i in y)
189 189 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
190 190 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
191 191 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
192 192 ['addset', 'filteredset', 'filteredset']
193 193
194 194 Populate "_set" fields in the lists so set optimization may be used:
195 195 >>> [1 in xs, 3 in ys]
196 196 [False, True]
197 197
198 198 Without sort(), results won't be changed:
199 199 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
200 200 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
201 201 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
202 202 ['addset', 'filteredset', 'filteredset']
203 203
204 204 With sort(), set optimization could be used:
205 205 >>> xs.sort(reverse=True)
206 206 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
207 207 [[7, 6, 4, 0, 5, 3], [7, 6], [4, 0]]
208 208 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
209 209 ['addset', 'baseset', 'baseset']
210 210
211 211 >>> ys.sort()
212 212 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
213 213 [[7, 6, 4, 0, 3, 5], [7, 6], [4, 0]]
214 214 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
215 215 ['addset', 'baseset', 'baseset']
216 216
217 217 istopo is preserved across set operations
218 218 >>> xs = baseset(set(x), istopo=True)
219 219 >>> rs = xs & ys
220 220 >>> type(rs).__name__
221 221 'baseset'
222 222 >>> rs._istopo
223 223 True
224 224 """
225 225
226 226 def __init__(self, data=(), datarepr=None, istopo=False):
227 227 """
228 228 datarepr: a tuple of (format, obj, ...), a function or an object that
229 229 provides a printable representation of the given data.
230 230 """
231 231 self._ascending = None
232 232 self._istopo = istopo
233 233 if isinstance(data, set):
234 234 # converting set to list has a cost, do it lazily
235 235 self._set = data
236 236 # set has no order we pick one for stability purpose
237 237 self._ascending = True
238 238 else:
239 239 if not isinstance(data, list):
240 240 data = list(data)
241 241 self._list = data
242 242 self._datarepr = datarepr
243 243
244 244 @util.propertycache
245 245 def _set(self):
246 246 return set(self._list)
247 247
248 248 @util.propertycache
249 249 def _asclist(self):
250 250 asclist = self._list[:]
251 251 asclist.sort()
252 252 return asclist
253 253
254 254 @util.propertycache
255 255 def _list(self):
256 256 # _list is only lazily constructed if we have _set
257 257 assert '_set' in self.__dict__
258 258 return list(self._set)
259 259
260 260 def __iter__(self):
261 261 if self._ascending is None:
262 262 return iter(self._list)
263 263 elif self._ascending:
264 264 return iter(self._asclist)
265 265 else:
266 266 return reversed(self._asclist)
267 267
268 268 def fastasc(self):
269 269 return iter(self._asclist)
270 270
271 271 def fastdesc(self):
272 272 return reversed(self._asclist)
273 273
274 274 @util.propertycache
275 275 def __contains__(self):
276 276 return self._set.__contains__
277 277
278 278 def __nonzero__(self):
279 279 return bool(len(self))
280 280
281 281 __bool__ = __nonzero__
282 282
283 283 def sort(self, reverse=False):
284 284 self._ascending = not bool(reverse)
285 285 self._istopo = False
286 286
287 287 def reverse(self):
288 288 if self._ascending is None:
289 289 self._list.reverse()
290 290 else:
291 291 self._ascending = not self._ascending
292 292 self._istopo = False
293 293
294 294 def __len__(self):
295 295 if '_list' in self.__dict__:
296 296 return len(self._list)
297 297 else:
298 298 return len(self._set)
299 299
300 300 def isascending(self):
301 301 """Returns True if the collection is ascending order, False if not.
302 302
303 303 This is part of the mandatory API for smartset."""
304 304 if len(self) <= 1:
305 305 return True
306 306 return self._ascending is not None and self._ascending
307 307
308 308 def isdescending(self):
309 309 """Returns True if the collection is descending order, False if not.
310 310
311 311 This is part of the mandatory API for smartset."""
312 312 if len(self) <= 1:
313 313 return True
314 314 return self._ascending is not None and not self._ascending
315 315
316 316 def istopo(self):
317 317 """Is the collection is in topographical order or not.
318 318
319 319 This is part of the mandatory API for smartset."""
320 320 if len(self) <= 1:
321 321 return True
322 322 return self._istopo
323 323
324 324 def first(self):
325 325 if self:
326 326 if self._ascending is None:
327 327 return self._list[0]
328 328 elif self._ascending:
329 329 return self._asclist[0]
330 330 else:
331 331 return self._asclist[-1]
332 332 return None
333 333
334 334 def last(self):
335 335 if self:
336 336 if self._ascending is None:
337 337 return self._list[-1]
338 338 elif self._ascending:
339 339 return self._asclist[-1]
340 340 else:
341 341 return self._asclist[0]
342 342 return None
343 343
344 344 def _fastsetop(self, other, op):
345 345 # try to use native set operations as fast paths
346 346 if (
347 347 type(other) is baseset
348 348 and '_set' in other.__dict__
349 349 and '_set' in self.__dict__
350 350 and self._ascending is not None
351 351 ):
352 352 s = baseset(
353 353 data=getattr(self._set, op)(other._set), istopo=self._istopo
354 354 )
355 355 s._ascending = self._ascending
356 356 else:
357 357 s = getattr(super(baseset, self), op)(other)
358 358 return s
359 359
360 360 def __and__(self, other):
361 361 return self._fastsetop(other, '__and__')
362 362
363 363 def __sub__(self, other):
364 364 return self._fastsetop(other, '__sub__')
365 365
366 366 def _slice(self, start, stop):
367 367 # creating new list should be generally cheaper than iterating items
368 368 if self._ascending is None:
369 369 return baseset(self._list[start:stop], istopo=self._istopo)
370 370
371 371 data = self._asclist
372 372 if not self._ascending:
373 373 start, stop = max(len(data) - stop, 0), max(len(data) - start, 0)
374 374 s = baseset(data[start:stop], istopo=self._istopo)
375 375 s._ascending = self._ascending
376 376 return s
377 377
378 378 @encoding.strmethod
379 379 def __repr__(self):
380 380 d = {None: b'', False: b'-', True: b'+'}[self._ascending]
381 381 s = stringutil.buildrepr(self._datarepr)
382 382 if not s:
383 383 l = self._list
384 384 # if _list has been built from a set, it might have a different
385 385 # order from one python implementation to another.
386 386 # We fallback to the sorted version for a stable output.
387 387 if self._ascending is not None:
388 388 l = self._asclist
389 389 s = pycompat.byterepr(l)
390 390 return b'<%s%s %s>' % (_typename(self), d, s)
391 391
392 392
393 393 class filteredset(abstractsmartset):
394 394 """Duck type for baseset class which iterates lazily over the revisions in
395 395 the subset and contains a function which tests for membership in the
396 396 revset
397 397 """
398 398
399 399 def __init__(self, subset, condition=lambda x: True, condrepr=None):
400 400 """
401 401 condition: a function that decide whether a revision in the subset
402 402 belongs to the revset or not.
403 403 condrepr: a tuple of (format, obj, ...), a function or an object that
404 404 provides a printable representation of the given condition.
405 405 """
406 406 self._subset = subset
407 407 self._condition = condition
408 408 self._condrepr = condrepr
409 409
410 410 def __contains__(self, x):
411 411 return x in self._subset and self._condition(x)
412 412
413 413 def __iter__(self):
414 414 return self._iterfilter(self._subset)
415 415
416 416 def _iterfilter(self, it):
417 417 cond = self._condition
418 418 for x in it:
419 419 if cond(x):
420 420 yield x
421 421
422 422 @property
423 423 def fastasc(self):
424 424 it = self._subset.fastasc
425 425 if it is None:
426 426 return None
427 427 return lambda: self._iterfilter(it())
428 428
429 429 @property
430 430 def fastdesc(self):
431 431 it = self._subset.fastdesc
432 432 if it is None:
433 433 return None
434 434 return lambda: self._iterfilter(it())
435 435
436 436 def __nonzero__(self):
437 437 fast = None
438 438 candidates = [
439 439 self.fastasc if self.isascending() else None,
440 440 self.fastdesc if self.isdescending() else None,
441 441 self.fastasc,
442 442 self.fastdesc,
443 443 ]
444 444 for candidate in candidates:
445 445 if candidate is not None:
446 446 fast = candidate
447 447 break
448 448
449 449 if fast is not None:
450 450 it = fast()
451 451 else:
452 452 it = self
453 453
454 454 for r in it:
455 455 return True
456 456 return False
457 457
458 458 __bool__ = __nonzero__
459 459
460 460 def __len__(self):
461 461 # Basic implementation to be changed in future patches.
462 462 # until this gets improved, we use generator expression
463 463 # here, since list comprehensions are free to call __len__ again
464 464 # causing infinite recursion
465 465 l = baseset(r for r in self)
466 466 return len(l)
467 467
468 468 def sort(self, reverse=False):
469 469 self._subset.sort(reverse=reverse)
470 470
471 471 def reverse(self):
472 472 self._subset.reverse()
473 473
474 474 def isascending(self):
475 475 return self._subset.isascending()
476 476
477 477 def isdescending(self):
478 478 return self._subset.isdescending()
479 479
480 480 def istopo(self):
481 481 return self._subset.istopo()
482 482
483 483 def first(self):
484 484 for x in self:
485 485 return x
486 486 return None
487 487
488 488 def last(self):
489 489 it = None
490 490 if self.isascending():
491 491 it = self.fastdesc
492 492 elif self.isdescending():
493 493 it = self.fastasc
494 494 if it is not None:
495 495 for x in it():
496 496 return x
497 497 return None # empty case
498 498 else:
499 499 x = None
500 500 for x in self:
501 501 pass
502 502 return x
503 503
504 504 @encoding.strmethod
505 505 def __repr__(self):
506 506 xs = [pycompat.byterepr(self._subset)]
507 507 s = stringutil.buildrepr(self._condrepr)
508 508 if s:
509 509 xs.append(s)
510 510 return b'<%s %s>' % (_typename(self), b', '.join(xs))
511 511
512 512
513 513 def _iterordered(ascending, iter1, iter2):
514 514 """produce an ordered iteration from two iterators with the same order
515 515
516 516 The ascending is used to indicated the iteration direction.
517 517 """
518 518 choice = max
519 519 if ascending:
520 520 choice = min
521 521
522 522 val1 = None
523 523 val2 = None
524 524 try:
525 525 # Consume both iterators in an ordered way until one is empty
526 526 while True:
527 527 if val1 is None:
528 528 val1 = next(iter1)
529 529 if val2 is None:
530 530 val2 = next(iter2)
531 531 n = choice(val1, val2)
532 532 yield n
533 533 if val1 == n:
534 534 val1 = None
535 535 if val2 == n:
536 536 val2 = None
537 537 except StopIteration:
538 538 # Flush any remaining values and consume the other one
539 539 it = iter2
540 540 if val1 is not None:
541 541 yield val1
542 542 it = iter1
543 543 elif val2 is not None:
544 544 # might have been equality and both are empty
545 545 yield val2
546 546 for val in it:
547 547 yield val
548 548
549 549
550 550 class addset(abstractsmartset):
551 551 """Represent the addition of two sets
552 552
553 553 Wrapper structure for lazily adding two structures without losing much
554 554 performance on the __contains__ method
555 555
556 556 If the ascending attribute is set, that means the two structures are
557 557 ordered in either an ascending or descending way. Therefore, we can add
558 558 them maintaining the order by iterating over both at the same time
559 559
560 560 >>> xs = baseset([0, 3, 2])
561 561 >>> ys = baseset([5, 2, 4])
562 562
563 563 >>> rs = addset(xs, ys)
564 564 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
565 565 (True, True, False, True, 0, 4)
566 566 >>> rs = addset(xs, baseset([]))
567 567 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
568 568 (True, True, False, 0, 2)
569 569 >>> rs = addset(baseset([]), baseset([]))
570 570 >>> bool(rs), 0 in rs, rs.first(), rs.last()
571 571 (False, False, None, None)
572 572
573 573 iterate unsorted:
574 574 >>> rs = addset(xs, ys)
575 575 >>> # (use generator because pypy could call len())
576 576 >>> list(x for x in rs) # without _genlist
577 577 [0, 3, 2, 5, 4]
578 578 >>> assert not rs._genlist
579 579 >>> len(rs)
580 580 5
581 581 >>> [x for x in rs] # with _genlist
582 582 [0, 3, 2, 5, 4]
583 583 >>> assert rs._genlist
584 584
585 585 iterate ascending:
586 586 >>> rs = addset(xs, ys, ascending=True)
587 587 >>> # (use generator because pypy could call len())
588 588 >>> list(x for x in rs), list(x for x in rs.fastasc()) # without _asclist
589 589 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
590 590 >>> assert not rs._asclist
591 591 >>> len(rs)
592 592 5
593 593 >>> [x for x in rs], [x for x in rs.fastasc()]
594 594 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
595 595 >>> assert rs._asclist
596 596
597 597 iterate descending:
598 598 >>> rs = addset(xs, ys, ascending=False)
599 599 >>> # (use generator because pypy could call len())
600 600 >>> list(x for x in rs), list(x for x in rs.fastdesc()) # without _asclist
601 601 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
602 602 >>> assert not rs._asclist
603 603 >>> len(rs)
604 604 5
605 605 >>> [x for x in rs], [x for x in rs.fastdesc()]
606 606 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
607 607 >>> assert rs._asclist
608 608
609 609 iterate ascending without fastasc:
610 610 >>> rs = addset(xs, generatorset(ys), ascending=True)
611 611 >>> assert rs.fastasc is None
612 612 >>> [x for x in rs]
613 613 [0, 2, 3, 4, 5]
614 614
615 615 iterate descending without fastdesc:
616 616 >>> rs = addset(generatorset(xs), ys, ascending=False)
617 617 >>> assert rs.fastdesc is None
618 618 >>> [x for x in rs]
619 619 [5, 4, 3, 2, 0]
620 620 """
621 621
622 622 def __init__(self, revs1, revs2, ascending=None):
623 623 self._r1 = revs1
624 624 self._r2 = revs2
625 625 self._iter = None
626 626 self._ascending = ascending
627 627 self._genlist = None
628 628 self._asclist = None
629 629
630 630 def __len__(self):
631 631 return len(self._list)
632 632
633 633 def __nonzero__(self):
634 634 return bool(self._r1) or bool(self._r2)
635 635
636 636 __bool__ = __nonzero__
637 637
638 638 @util.propertycache
639 639 def _list(self):
640 640 if not self._genlist:
641 641 self._genlist = baseset(iter(self))
642 642 return self._genlist
643 643
644 644 def __iter__(self):
645 645 """Iterate over both collections without repeating elements
646 646
647 647 If the ascending attribute is not set, iterate over the first one and
648 648 then over the second one checking for membership on the first one so we
649 649 dont yield any duplicates.
650 650
651 651 If the ascending attribute is set, iterate over both collections at the
652 652 same time, yielding only one value at a time in the given order.
653 653 """
654 654 if self._ascending is None:
655 655 if self._genlist:
656 656 return iter(self._genlist)
657 657
658 658 def arbitraryordergen():
659 659 for r in self._r1:
660 660 yield r
661 661 inr1 = self._r1.__contains__
662 662 for r in self._r2:
663 663 if not inr1(r):
664 664 yield r
665 665
666 666 return arbitraryordergen()
667 667 # try to use our own fast iterator if it exists
668 668 self._trysetasclist()
669 669 if self._ascending:
670 670 attr = 'fastasc'
671 671 else:
672 672 attr = 'fastdesc'
673 673 it = getattr(self, attr)
674 674 if it is not None:
675 675 return it()
676 676 # maybe half of the component supports fast
677 677 # get iterator for _r1
678 678 iter1 = getattr(self._r1, attr)
679 679 if iter1 is None:
680 680 # let's avoid side effect (not sure it matters)
681 681 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
682 682 else:
683 683 iter1 = iter1()
684 684 # get iterator for _r2
685 685 iter2 = getattr(self._r2, attr)
686 686 if iter2 is None:
687 687 # let's avoid side effect (not sure it matters)
688 688 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
689 689 else:
690 690 iter2 = iter2()
691 691 return _iterordered(self._ascending, iter1, iter2)
692 692
693 693 def _trysetasclist(self):
694 694 """populate the _asclist attribute if possible and necessary"""
695 695 if self._genlist is not None and self._asclist is None:
696 696 self._asclist = sorted(self._genlist)
697 697
698 698 @property
699 699 def fastasc(self):
700 700 self._trysetasclist()
701 701 if self._asclist is not None:
702 702 return self._asclist.__iter__
703 703 iter1 = self._r1.fastasc
704 704 iter2 = self._r2.fastasc
705 705 if None in (iter1, iter2):
706 706 return None
707 707 return lambda: _iterordered(True, iter1(), iter2())
708 708
709 709 @property
710 710 def fastdesc(self):
711 711 self._trysetasclist()
712 712 if self._asclist is not None:
713 713 return self._asclist.__reversed__
714 714 iter1 = self._r1.fastdesc
715 715 iter2 = self._r2.fastdesc
716 716 if None in (iter1, iter2):
717 717 return None
718 718 return lambda: _iterordered(False, iter1(), iter2())
719 719
720 720 def __contains__(self, x):
721 721 return x in self._r1 or x in self._r2
722 722
723 723 def sort(self, reverse=False):
724 724 """Sort the added set
725 725
726 726 For this we use the cached list with all the generated values and if we
727 727 know they are ascending or descending we can sort them in a smart way.
728 728 """
729 729 self._ascending = not reverse
730 730
731 731 def isascending(self):
732 732 return self._ascending is not None and self._ascending
733 733
734 734 def isdescending(self):
735 735 return self._ascending is not None and not self._ascending
736 736
737 737 def istopo(self):
738 738 # not worth the trouble asserting if the two sets combined are still
739 739 # in topographical order. Use the sort() predicate to explicitly sort
740 740 # again instead.
741 741 return False
742 742
743 743 def reverse(self):
744 744 if self._ascending is None:
745 745 self._list.reverse()
746 746 else:
747 747 self._ascending = not self._ascending
748 748
749 749 def first(self):
750 750 for x in self:
751 751 return x
752 752 return None
753 753
754 754 def last(self):
755 755 self.reverse()
756 756 val = self.first()
757 757 self.reverse()
758 758 return val
759 759
760 760 @encoding.strmethod
761 761 def __repr__(self):
762 762 d = {None: b'', False: b'-', True: b'+'}[self._ascending]
763 763 return b'<%s%s %r, %r>' % (_typename(self), d, self._r1, self._r2)
764 764
765 765
766 766 class generatorset(abstractsmartset):
767 767 """Wrap a generator for lazy iteration
768 768
769 769 Wrapper structure for generators that provides lazy membership and can
770 770 be iterated more than once.
771 771 When asked for membership it generates values until either it finds the
772 772 requested one or has gone through all the elements in the generator
773 773
774 774 >>> xs = generatorset([0, 1, 4], iterasc=True)
775 775 >>> assert xs.last() == xs.last()
776 776 >>> xs.last() # cached
777 777 4
778 778 """
779 779
780 780 def __new__(cls, gen, iterasc=None):
781 781 if iterasc is None:
782 782 typ = cls
783 783 elif iterasc:
784 784 typ = _generatorsetasc
785 785 else:
786 786 typ = _generatorsetdesc
787 787
788 788 return super(generatorset, cls).__new__(typ)
789 789
790 790 def __init__(self, gen, iterasc=None):
791 791 """
792 792 gen: a generator producing the values for the generatorset.
793 793 """
794 794 self._gen = gen
795 795 self._asclist = None
796 796 self._cache = {}
797 797 self._genlist = []
798 798 self._finished = False
799 799 self._ascending = True
800 800
801 801 def __nonzero__(self):
802 802 # Do not use 'for r in self' because it will enforce the iteration
803 803 # order (default ascending), possibly unrolling a whole descending
804 804 # iterator.
805 805 if self._genlist:
806 806 return True
807 807 for r in self._consumegen():
808 808 return True
809 809 return False
810 810
811 811 __bool__ = __nonzero__
812 812
813 813 def __contains__(self, x):
814 814 if x in self._cache:
815 815 return self._cache[x]
816 816
817 817 # Use new values only, as existing values would be cached.
818 818 for l in self._consumegen():
819 819 if l == x:
820 820 return True
821 821
822 822 self._cache[x] = False
823 823 return False
824 824
825 825 def __iter__(self):
826 826 if self._ascending:
827 827 it = self.fastasc
828 828 else:
829 829 it = self.fastdesc
830 830 if it is not None:
831 831 return it()
832 832 # we need to consume the iterator
833 833 for x in self._consumegen():
834 834 pass
835 835 # recall the same code
836 836 return iter(self)
837 837
838 838 def _iterator(self):
839 839 if self._finished:
840 840 return iter(self._genlist)
841 841
842 842 # We have to use this complex iteration strategy to allow multiple
843 843 # iterations at the same time. We need to be able to catch revision
844 844 # removed from _consumegen and added to genlist in another instance.
845 845 #
846 846 # Getting rid of it would provide an about 15% speed up on this
847 847 # iteration.
848 848 genlist = self._genlist
849 849 nextgen = self._consumegen()
850 850 _len, _next = len, next # cache global lookup
851 851
852 852 def gen():
853 853 i = 0
854 854 while True:
855 855 if i < _len(genlist):
856 856 yield genlist[i]
857 857 else:
858 858 try:
859 859 yield _next(nextgen)
860 860 except StopIteration:
861 861 return
862 862 i += 1
863 863
864 864 return gen()
865 865
866 866 def _consumegen(self):
867 867 cache = self._cache
868 868 genlist = self._genlist.append
869 869 for item in self._gen:
870 870 cache[item] = True
871 871 genlist(item)
872 872 yield item
873 873 if not self._finished:
874 874 self._finished = True
875 875 asc = self._genlist[:]
876 876 asc.sort()
877 877 self._asclist = asc
878 878 self.fastasc = asc.__iter__
879 879 self.fastdesc = asc.__reversed__
880 880
881 881 def __len__(self):
882 882 for x in self._consumegen():
883 883 pass
884 884 return len(self._genlist)
885 885
886 886 def sort(self, reverse=False):
887 887 self._ascending = not reverse
888 888
889 889 def reverse(self):
890 890 self._ascending = not self._ascending
891 891
892 892 def isascending(self):
893 893 return self._ascending
894 894
895 895 def isdescending(self):
896 896 return not self._ascending
897 897
898 898 def istopo(self):
899 899 # not worth the trouble asserting if the two sets combined are still
900 900 # in topographical order. Use the sort() predicate to explicitly sort
901 901 # again instead.
902 902 return False
903 903
904 904 def first(self):
905 905 if self._ascending:
906 906 it = self.fastasc
907 907 else:
908 908 it = self.fastdesc
909 909 if it is None:
910 910 # we need to consume all and try again
911 911 for x in self._consumegen():
912 912 pass
913 913 return self.first()
914 914 return next(it(), None)
915 915
916 916 def last(self):
917 917 if self._ascending:
918 918 it = self.fastdesc
919 919 else:
920 920 it = self.fastasc
921 921 if it is None:
922 922 # we need to consume all and try again
923 923 for x in self._consumegen():
924 924 pass
925 925 return self.last()
926 926 return next(it(), None)
927 927
928 928 @encoding.strmethod
929 929 def __repr__(self):
930 930 d = {False: b'-', True: b'+'}[self._ascending]
931 931 return b'<%s%s>' % (_typename(self), d)
932 932
933 933
934 934 class _generatorsetasc(generatorset):
935 935 """Special case of generatorset optimized for ascending generators."""
936 936
937 937 fastasc = generatorset._iterator
938 938
939 939 def __contains__(self, x):
940 940 if x in self._cache:
941 941 return self._cache[x]
942 942
943 943 # Use new values only, as existing values would be cached.
944 944 for l in self._consumegen():
945 945 if l == x:
946 946 return True
947 947 if l > x:
948 948 break
949 949
950 950 self._cache[x] = False
951 951 return False
952 952
953 953
954 954 class _generatorsetdesc(generatorset):
955 955 """Special case of generatorset optimized for descending generators."""
956 956
957 957 fastdesc = generatorset._iterator
958 958
959 959 def __contains__(self, x):
960 960 if x in self._cache:
961 961 return self._cache[x]
962 962
963 963 # Use new values only, as existing values would be cached.
964 964 for l in self._consumegen():
965 965 if l == x:
966 966 return True
967 967 if l < x:
968 968 break
969 969
970 970 self._cache[x] = False
971 971 return False
972 972
973 973
974 974 def spanset(repo, start=0, end=None):
975 975 """Create a spanset that represents a range of repository revisions
976 976
977 977 start: first revision included the set (default to 0)
978 978 end: first revision excluded (last+1) (default to len(repo))
979 979
980 980 Spanset will be descending if `end` < `start`.
981 981 """
982 982 if end is None:
983 983 end = len(repo)
984 984 ascending = start <= end
985 985 if not ascending:
986 986 start, end = end + 1, start + 1
987 987 return _spanset(start, end, ascending, repo.changelog.filteredrevs)
988 988
989 989
990 990 class _spanset(abstractsmartset):
991 991 """Duck type for baseset class which represents a range of revisions and
992 992 can work lazily and without having all the range in memory
993 993
994 994 Note that spanset(x, y) behave almost like range(x, y) except for two
995 995 notable points:
996 996 - when x < y it will be automatically descending,
997 997 - revision filtered with this repoview will be skipped.
998 998
999 999 """
1000 1000
1001 1001 def __init__(self, start, end, ascending, hiddenrevs):
1002 1002 self._start = start
1003 1003 self._end = end
1004 1004 self._ascending = ascending
1005 1005 self._hiddenrevs = hiddenrevs
1006 1006
1007 1007 def sort(self, reverse=False):
1008 1008 self._ascending = not reverse
1009 1009
1010 1010 def reverse(self):
1011 1011 self._ascending = not self._ascending
1012 1012
1013 1013 def istopo(self):
1014 1014 # not worth the trouble asserting if the two sets combined are still
1015 1015 # in topographical order. Use the sort() predicate to explicitly sort
1016 1016 # again instead.
1017 1017 return False
1018 1018
1019 1019 def _iterfilter(self, iterrange):
1020 1020 s = self._hiddenrevs
1021 1021 for r in iterrange:
1022 1022 if r not in s:
1023 1023 yield r
1024 1024
1025 1025 def __iter__(self):
1026 1026 if self._ascending:
1027 1027 return self.fastasc()
1028 1028 else:
1029 1029 return self.fastdesc()
1030 1030
1031 1031 def fastasc(self):
1032 1032 iterrange = range(self._start, self._end)
1033 1033 if self._hiddenrevs:
1034 1034 return self._iterfilter(iterrange)
1035 1035 return iter(iterrange)
1036 1036
1037 1037 def fastdesc(self):
1038 1038 iterrange = range(self._end - 1, self._start - 1, -1)
1039 1039 if self._hiddenrevs:
1040 1040 return self._iterfilter(iterrange)
1041 1041 return iter(iterrange)
1042 1042
1043 1043 def __contains__(self, rev):
1044 1044 hidden = self._hiddenrevs
1045 1045 return (self._start <= rev < self._end) and not (
1046 1046 hidden and rev in hidden
1047 1047 )
1048 1048
1049 1049 def __nonzero__(self):
1050 1050 for r in self:
1051 1051 return True
1052 1052 return False
1053 1053
1054 1054 __bool__ = __nonzero__
1055 1055
1056 1056 def __len__(self):
1057 1057 if not self._hiddenrevs:
1058 1058 return abs(self._end - self._start)
1059 1059 else:
1060 1060 count = 0
1061 1061 start = self._start
1062 1062 end = self._end
1063 1063 for rev in self._hiddenrevs:
1064 1064 if (end < rev <= start) or (start <= rev < end):
1065 1065 count += 1
1066 1066 return abs(self._end - self._start) - count
1067 1067
1068 1068 def isascending(self):
1069 1069 return self._ascending
1070 1070
1071 1071 def isdescending(self):
1072 1072 return not self._ascending
1073 1073
1074 1074 def first(self):
1075 1075 if self._ascending:
1076 1076 it = self.fastasc
1077 1077 else:
1078 1078 it = self.fastdesc
1079 1079 for x in it():
1080 1080 return x
1081 1081 return None
1082 1082
1083 1083 def last(self):
1084 1084 if self._ascending:
1085 1085 it = self.fastdesc
1086 1086 else:
1087 1087 it = self.fastasc
1088 1088 for x in it():
1089 1089 return x
1090 1090 return None
1091 1091
1092 1092 def _slice(self, start, stop):
1093 1093 if self._hiddenrevs:
1094 1094 # unoptimized since all hidden revisions in range has to be scanned
1095 1095 return super(_spanset, self)._slice(start, stop)
1096 1096 if self._ascending:
1097 1097 x = min(self._start + start, self._end)
1098 1098 y = min(self._start + stop, self._end)
1099 1099 else:
1100 1100 x = max(self._end - stop, self._start)
1101 1101 y = max(self._end - start, self._start)
1102 1102 return _spanset(x, y, self._ascending, self._hiddenrevs)
1103 1103
1104 1104 @encoding.strmethod
1105 1105 def __repr__(self):
1106 1106 d = {False: b'-', True: b'+'}[self._ascending]
1107 1107 return b'<%s%s %d:%d>' % (_typename(self), d, self._start, self._end)
1108 1108
1109 1109
1110 1110 class fullreposet(_spanset):
1111 1111 """a set containing all revisions in the repo
1112 1112
1113 1113 This class exists to host special optimization and magic to handle virtual
1114 1114 revisions such as "null".
1115 1115 """
1116 1116
1117 1117 def __init__(self, repo):
1118 1118 super(fullreposet, self).__init__(
1119 1119 0, len(repo), True, repo.changelog.filteredrevs
1120 1120 )
1121 1121
1122 1122 def __and__(self, other):
1123 1123 """As self contains the whole repo, all of the other set should also be
1124 1124 in self. Therefore `self & other = other`.
1125 1125
1126 1126 This boldly assumes the other contains valid revs only.
1127 1127 """
1128 1128 # other not a smartset, make is so
1129 1129 if not hasattr(other, 'isascending'):
1130 1130 # filter out hidden revision
1131 1131 # (this boldly assumes all smartset are pure)
1132 1132 #
1133 1133 # `other` was used with "&", let's assume this is a set like
1134 1134 # object.
1135 other = baseset(other - self._hiddenrevs)
1135 other = baseset(other)
1136
1137 if self._hiddenrevs:
1138 other = other - self._hiddenrevs
1136 1139
1137 1140 other.sort(reverse=self.isdescending())
1138 1141 return other
@@ -1,1148 +1,1146 b''
1 1 $ cat > $TESTTMP/hook.sh << 'EOF'
2 2 > echo "test-hook-close-phase: $HG_NODE: $HG_OLDPHASE -> $HG_PHASE"
3 3 > EOF
4 4
5 5 $ cat >> $HGRCPATH << EOF
6 6 > [extensions]
7 7 > phasereport=$TESTDIR/testlib/ext-phase-report.py
8 8 > [hooks]
9 9 > txnclose-phase.test = sh $TESTTMP/hook.sh
10 10 > EOF
11 11
12 12 $ hglog() { hg log -G --template "{rev} {phaseidx} {desc}\n" $*; }
13 13 $ mkcommit() {
14 14 > echo "$1" > "$1"
15 15 > hg add "$1"
16 16 > message="$1"
17 17 > shift
18 18 > hg ci -m "$message" $*
19 19 > }
20 20
21 21 $ hg init initialrepo
22 22 $ cd initialrepo
23 23
24 24 Cannot change null revision phase
25 25
26 26 $ hg phase --force --secret null
27 27 abort: cannot change null revision phase
28 28 [255]
29 29 $ hg phase null
30 30 -1: public
31 31
32 32 $ mkcommit A
33 33 test-debug-phase: new rev 0: x -> 1
34 34 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> draft
35 35
36 36 New commit are draft by default
37 37
38 38 $ hglog
39 39 @ 0 1 A
40 40
41 41
42 42 Following commit are draft too
43 43
44 44 $ mkcommit B
45 45 test-debug-phase: new rev 1: x -> 1
46 46 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> draft
47 47
48 48 $ hglog
49 49 @ 1 1 B
50 50 |
51 51 o 0 1 A
52 52
53 53
54 54 Working directory phase is secret when its parent is secret.
55 55
56 56 $ hg phase --force --secret .
57 57 test-debug-phase: move rev 0: 1 -> 2
58 58 test-debug-phase: move rev 1: 1 -> 2
59 59 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: draft -> secret
60 60 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> secret
61 61 $ hg log -r 'wdir()' -T '{phase}\n'
62 62 secret
63 63 $ hg log -r 'wdir() and public()' -T '{phase}\n'
64 64 $ hg log -r 'wdir() and draft()' -T '{phase}\n'
65 65 $ hg log -r 'wdir() and secret()' -T '{phase}\n'
66 66 secret
67 67
68 68 Working directory phase is draft when its parent is draft.
69 69
70 70 $ hg phase --draft .
71 71 test-debug-phase: move rev 1: 2 -> 1
72 72 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: secret -> draft
73 73 $ hg log -r 'wdir()' -T '{phase}\n'
74 74 draft
75 75 $ hg log -r 'wdir() and public()' -T '{phase}\n'
76 76 $ hg log -r 'wdir() and draft()' -T '{phase}\n'
77 77 draft
78 78 $ hg log -r 'wdir() and secret()' -T '{phase}\n'
79 79
80 80 Working directory phase is secret when a new commit will be created as secret,
81 81 even if the parent is draft.
82 82
83 83 $ hg log -r 'wdir() and secret()' -T '{phase}\n' \
84 84 > --config phases.new-commit='secret'
85 85 secret
86 86
87 87 Working directory phase is draft when its parent is public.
88 88
89 89 $ hg phase --public .
90 90 test-debug-phase: move rev 0: 1 -> 0
91 91 test-debug-phase: move rev 1: 1 -> 0
92 92 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: draft -> public
93 93 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> public
94 94 $ hg log -r 'wdir()' -T '{phase}\n'
95 95 draft
96 96 $ hg log -r 'wdir() and public()' -T '{phase}\n'
97 97 $ hg log -r 'wdir() and draft()' -T '{phase}\n'
98 98 draft
99 99 $ hg log -r 'wdir() and secret()' -T '{phase}\n'
100 100 $ hg log -r 'wdir() and secret()' -T '{phase}\n' \
101 101 > --config phases.new-commit='secret'
102 102 secret
103 103
104 104 Draft commit are properly created over public one:
105 105
106 106 $ hg phase
107 107 1: public
108 108 $ hglog
109 109 @ 1 0 B
110 110 |
111 111 o 0 0 A
112 112
113 113
114 114 $ mkcommit C
115 115 test-debug-phase: new rev 2: x -> 1
116 116 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> draft
117 117 $ mkcommit D
118 118 test-debug-phase: new rev 3: x -> 1
119 119 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> draft
120 120
121 121 $ hglog
122 122 @ 3 1 D
123 123 |
124 124 o 2 1 C
125 125 |
126 126 o 1 0 B
127 127 |
128 128 o 0 0 A
129 129
130 130
131 131 Test creating changeset as secret
132 132
133 133 $ mkcommit E --config phases.new-commit='secret'
134 134 test-debug-phase: new rev 4: x -> 2
135 135 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: -> secret
136 136 $ hglog
137 137 @ 4 2 E
138 138 |
139 139 o 3 1 D
140 140 |
141 141 o 2 1 C
142 142 |
143 143 o 1 0 B
144 144 |
145 145 o 0 0 A
146 146
147 147
148 148 Test the secret property is inherited
149 149
150 150 $ mkcommit H
151 151 test-debug-phase: new rev 5: x -> 2
152 152 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: -> secret
153 153 $ hglog
154 154 @ 5 2 H
155 155 |
156 156 o 4 2 E
157 157 |
158 158 o 3 1 D
159 159 |
160 160 o 2 1 C
161 161 |
162 162 o 1 0 B
163 163 |
164 164 o 0 0 A
165 165
166 166
167 167 Even on merge
168 168
169 169 $ hg up -q 1
170 170 $ mkcommit "B'"
171 171 test-debug-phase: new rev 6: x -> 1
172 172 created new head
173 173 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> draft
174 174 $ hglog
175 175 @ 6 1 B'
176 176 |
177 177 | o 5 2 H
178 178 | |
179 179 | o 4 2 E
180 180 | |
181 181 | o 3 1 D
182 182 | |
183 183 | o 2 1 C
184 184 |/
185 185 o 1 0 B
186 186 |
187 187 o 0 0 A
188 188
189 189 $ hg merge 4 # E
190 190 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
191 191 (branch merge, don't forget to commit)
192 192 $ hg phase
193 193 6: draft
194 194 4: secret
195 195 $ hg ci -m "merge B' and E"
196 196 test-debug-phase: new rev 7: x -> 2
197 197 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: -> secret
198 198
199 199 $ hglog
200 200 @ 7 2 merge B' and E
201 201 |\
202 202 | o 6 1 B'
203 203 | |
204 204 +---o 5 2 H
205 205 | |
206 206 o | 4 2 E
207 207 | |
208 208 o | 3 1 D
209 209 | |
210 210 o | 2 1 C
211 211 |/
212 212 o 1 0 B
213 213 |
214 214 o 0 0 A
215 215
216 216
217 217 Test secret changeset are not pushed
218 218
219 219 $ hg init ../push-dest
220 220 $ cat > ../push-dest/.hg/hgrc << EOF
221 221 > [phases]
222 222 > publish=False
223 223 > EOF
224 224 $ hg outgoing ../push-dest --template='{rev} {phase} {desc|firstline}\n'
225 225 comparing with ../push-dest
226 226 searching for changes
227 227 0 public A
228 228 1 public B
229 229 2 draft C
230 230 3 draft D
231 231 6 draft B'
232 232 $ hg outgoing -r 'branch(default)' ../push-dest --template='{rev} {phase} {desc|firstline}\n'
233 233 comparing with ../push-dest
234 234 searching for changes
235 235 0 public A
236 236 1 public B
237 237 2 draft C
238 238 3 draft D
239 239 6 draft B'
240 240
241 241 $ hg push ../push-dest -f # force because we push multiple heads
242 242 pushing to ../push-dest
243 243 searching for changes
244 244 adding changesets
245 245 adding manifests
246 246 adding file changes
247 247 added 5 changesets with 5 changes to 5 files (+1 heads)
248 248 test-debug-phase: new rev 0: x -> 0
249 249 test-debug-phase: new rev 1: x -> 0
250 250 test-debug-phase: new rev 2: x -> 1
251 251 test-debug-phase: new rev 3: x -> 1
252 252 test-debug-phase: new rev 4: x -> 1
253 253 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
254 254 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
255 255 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> draft
256 256 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> draft
257 257 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> draft
258 258 $ hglog
259 259 @ 7 2 merge B' and E
260 260 |\
261 261 | o 6 1 B'
262 262 | |
263 263 +---o 5 2 H
264 264 | |
265 265 o | 4 2 E
266 266 | |
267 267 o | 3 1 D
268 268 | |
269 269 o | 2 1 C
270 270 |/
271 271 o 1 0 B
272 272 |
273 273 o 0 0 A
274 274
275 275 $ cd ../push-dest
276 276 $ hglog
277 277 o 4 1 B'
278 278 |
279 279 | o 3 1 D
280 280 | |
281 281 | o 2 1 C
282 282 |/
283 283 o 1 0 B
284 284 |
285 285 o 0 0 A
286 286
287 287
288 288 (Issue3303)
289 289 Check that remote secret changeset are ignore when checking creation of remote heads
290 290
291 291 We add a secret head into the push destination. This secret head shadows a
292 292 visible shared between the initial repo and the push destination.
293 293
294 294 $ hg up -q 4 # B'
295 295 $ mkcommit Z --config phases.new-commit=secret
296 296 test-debug-phase: new rev 5: x -> 2
297 297 test-hook-close-phase: 2713879da13d6eea1ff22b442a5a87cb31a7ce6a: -> secret
298 298 $ hg phase .
299 299 5: secret
300 300
301 301 We now try to push a new public changeset that descend from the common public
302 302 head shadowed by the remote secret head.
303 303
304 304 $ cd ../initialrepo
305 305 $ hg up -q 6 #B'
306 306 $ mkcommit I
307 307 test-debug-phase: new rev 8: x -> 1
308 308 created new head
309 309 test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061: -> draft
310 310 $ hg push ../push-dest
311 311 pushing to ../push-dest
312 312 searching for changes
313 313 adding changesets
314 314 adding manifests
315 315 adding file changes
316 316 added 1 changesets with 1 changes to 1 files (+1 heads)
317 317 test-debug-phase: new rev 6: x -> 1
318 318 test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061: -> draft
319 319
320 320 :note: The "(+1 heads)" is wrong as we do not had any visible head
321 321
322 322 check that branch cache with "served" filter are properly computed and stored
323 323
324 324 $ ls ../push-dest/.hg/cache/branch2*
325 325 ../push-dest/.hg/cache/branch2-base
326 326 ../push-dest/.hg/cache/branch2-served
327 327 $ cat ../push-dest/.hg/cache/branch2-served
328 328 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
329 329 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
330 330 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
331 331 $ hg heads -R ../push-dest --template '{rev}:{node} {phase}\n' #update visible cache too
332 332 6:6d6770faffce199f1fddd1cf87f6f026138cf061 draft
333 333 5:2713879da13d6eea1ff22b442a5a87cb31a7ce6a secret
334 334 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e draft
335 335 $ ls ../push-dest/.hg/cache/branch2*
336 336 ../push-dest/.hg/cache/branch2-base
337 337 ../push-dest/.hg/cache/branch2-served
338 338 ../push-dest/.hg/cache/branch2-visible
339 339 $ cat ../push-dest/.hg/cache/branch2-served
340 340 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
341 341 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
342 342 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
343 343 $ cat ../push-dest/.hg/cache/branch2-visible
344 344 6d6770faffce199f1fddd1cf87f6f026138cf061 6
345 345 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
346 346 2713879da13d6eea1ff22b442a5a87cb31a7ce6a o default
347 347 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
348 348
349 349
350 350 Restore condition prior extra insertion.
351 351 $ hg -q --config extensions.mq= strip .
352 352 $ hg up -q 7
353 353 $ cd ..
354 354
355 355 Test secret changeset are not pull
356 356
357 357 $ hg init pull-dest
358 358 $ cd pull-dest
359 359 $ hg pull ../initialrepo
360 360 pulling from ../initialrepo
361 361 requesting all changes
362 362 adding changesets
363 363 adding manifests
364 364 adding file changes
365 365 added 5 changesets with 5 changes to 5 files (+1 heads)
366 366 new changesets 4a2df7238c3b:cf9fe039dfd6
367 367 test-debug-phase: new rev 0: x -> 0
368 368 test-debug-phase: new rev 1: x -> 0
369 369 test-debug-phase: new rev 2: x -> 0
370 370 test-debug-phase: new rev 3: x -> 0
371 371 test-debug-phase: new rev 4: x -> 0
372 372 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
373 373 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
374 374 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
375 375 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
376 376 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
377 377 (run 'hg heads' to see heads, 'hg merge' to merge)
378 378 $ hglog
379 379 o 4 0 B'
380 380 |
381 381 | o 3 0 D
382 382 | |
383 383 | o 2 0 C
384 384 |/
385 385 o 1 0 B
386 386 |
387 387 o 0 0 A
388 388
389 389 $ cd ..
390 390
391 391 But secret can still be bundled explicitly
392 392
393 393 $ cd initialrepo
394 394 $ hg bundle --base '4^' -r 'children(4)' ../secret-bundle.hg
395 395 4 changesets found
396 396 $ cd ..
397 397
398 398 Test secret changeset are not cloned
399 399 (during local clone)
400 400
401 401 $ hg clone -qU initialrepo clone-dest
402 402 test-debug-phase: new rev 0: x -> 0
403 403 test-debug-phase: new rev 1: x -> 0
404 404 test-debug-phase: new rev 2: x -> 0
405 405 test-debug-phase: new rev 3: x -> 0
406 406 test-debug-phase: new rev 4: x -> 0
407 407 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
408 408 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
409 409 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
410 410 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
411 411 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
412 412 $ hglog -R clone-dest
413 413 o 4 0 B'
414 414 |
415 415 | o 3 0 D
416 416 | |
417 417 | o 2 0 C
418 418 |/
419 419 o 1 0 B
420 420 |
421 421 o 0 0 A
422 422
423 423
424 424 Test summary
425 425
426 426 $ hg summary -R clone-dest --verbose
427 427 parent: -1:000000000000 (no revision checked out)
428 428 branch: default
429 429 commit: (clean)
430 430 update: 5 new changesets (update)
431 431 $ hg summary -R initialrepo
432 432 parent: 7:17a481b3bccb tip
433 433 merge B' and E
434 434 branch: default
435 435 commit: (clean) (secret)
436 436 update: 1 new changesets, 2 branch heads (merge)
437 437 phases: 3 draft, 3 secret
438 438 $ hg summary -R initialrepo --quiet
439 439 parent: 7:17a481b3bccb tip
440 440 update: 1 new changesets, 2 branch heads (merge)
441 441
442 442 Test revset
443 443
444 444 $ cd initialrepo
445 445 $ hglog -r 'public()'
446 446 o 1 0 B
447 447 |
448 448 o 0 0 A
449 449
450 450 $ hglog -r 'draft()'
451 451 o 6 1 B'
452 452 |
453 453 ~
454 454 o 3 1 D
455 455 |
456 456 o 2 1 C
457 457 |
458 458 ~
459 459 $ hglog -r 'secret()'
460 460 @ 7 2 merge B' and E
461 461 |\
462 462 | ~
463 463 | o 5 2 H
464 464 |/
465 465 o 4 2 E
466 466 |
467 467 ~
468 468
469 469 test that phase are displayed in log at debug level
470 470
471 471 $ hg log --debug
472 472 changeset: 7:17a481b3bccb796c0521ae97903d81c52bfee4af
473 473 tag: tip
474 474 phase: secret
475 475 parent: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
476 476 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
477 477 manifest: 7:5e724ffacba267b2ab726c91fc8b650710deaaa8
478 478 user: test
479 479 date: Thu Jan 01 00:00:00 1970 +0000
480 480 extra: branch=default
481 481 description:
482 482 merge B' and E
483 483
484 484
485 485 changeset: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
486 486 phase: draft
487 487 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
488 488 parent: -1:0000000000000000000000000000000000000000
489 489 manifest: 6:ab8bfef2392903058bf4ebb9e7746e8d7026b27a
490 490 user: test
491 491 date: Thu Jan 01 00:00:00 1970 +0000
492 492 files+: B'
493 493 extra: branch=default
494 494 description:
495 495 B'
496 496
497 497
498 498 changeset: 5:a030c6be5127abc010fcbff1851536552e6951a8
499 499 phase: secret
500 500 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
501 501 parent: -1:0000000000000000000000000000000000000000
502 502 manifest: 5:5c710aa854874fe3d5fa7192e77bdb314cc08b5a
503 503 user: test
504 504 date: Thu Jan 01 00:00:00 1970 +0000
505 505 files+: H
506 506 extra: branch=default
507 507 description:
508 508 H
509 509
510 510
511 511 changeset: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
512 512 phase: secret
513 513 parent: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
514 514 parent: -1:0000000000000000000000000000000000000000
515 515 manifest: 4:7173fd1c27119750b959e3a0f47ed78abe75d6dc
516 516 user: test
517 517 date: Thu Jan 01 00:00:00 1970 +0000
518 518 files+: E
519 519 extra: branch=default
520 520 description:
521 521 E
522 522
523 523
524 524 changeset: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
525 525 phase: draft
526 526 parent: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
527 527 parent: -1:0000000000000000000000000000000000000000
528 528 manifest: 3:6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c
529 529 user: test
530 530 date: Thu Jan 01 00:00:00 1970 +0000
531 531 files+: D
532 532 extra: branch=default
533 533 description:
534 534 D
535 535
536 536
537 537 changeset: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
538 538 phase: draft
539 539 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
540 540 parent: -1:0000000000000000000000000000000000000000
541 541 manifest: 2:66a5a01817fdf5239c273802b5b7618d051c89e4
542 542 user: test
543 543 date: Thu Jan 01 00:00:00 1970 +0000
544 544 files+: C
545 545 extra: branch=default
546 546 description:
547 547 C
548 548
549 549
550 550 changeset: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
551 551 phase: public
552 552 parent: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
553 553 parent: -1:0000000000000000000000000000000000000000
554 554 manifest: 1:cb5cbbc1bfbf24cc34b9e8c16914e9caa2d2a7fd
555 555 user: test
556 556 date: Thu Jan 01 00:00:00 1970 +0000
557 557 files+: B
558 558 extra: branch=default
559 559 description:
560 560 B
561 561
562 562
563 563 changeset: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
564 564 phase: public
565 565 parent: -1:0000000000000000000000000000000000000000
566 566 parent: -1:0000000000000000000000000000000000000000
567 567 manifest: 0:007d8c9d88841325f5c6b06371b35b4e8a2b1a83
568 568 user: test
569 569 date: Thu Jan 01 00:00:00 1970 +0000
570 570 files+: A
571 571 extra: branch=default
572 572 description:
573 573 A
574 574
575 575
576 576
577 577
578 578 (Issue3707)
579 579 test invalid phase name
580 580
581 581 $ mkcommit I --config phases.new-commit='babar'
582 582 transaction abort!
583 583 rollback completed
584 584 config error: phases.new-commit: not a valid phase name ('babar')
585 585 [30]
586 586 Test phase command
587 587 ===================
588 588
589 589 initial picture
590 590
591 591 $ hg log -G --template "{rev} {phase} {desc}\n"
592 592 @ 7 secret merge B' and E
593 593 |\
594 594 | o 6 draft B'
595 595 | |
596 596 +---o 5 secret H
597 597 | |
598 598 o | 4 secret E
599 599 | |
600 600 o | 3 draft D
601 601 | |
602 602 o | 2 draft C
603 603 |/
604 604 o 1 public B
605 605 |
606 606 o 0 public A
607 607
608 608
609 609 display changesets phase
610 610
611 611 (mixing -r and plain rev specification)
612 612
613 613 $ hg phase 1::4 -r 7
614 614 1: public
615 615 2: draft
616 616 3: draft
617 617 4: secret
618 618 7: secret
619 619
620 620
621 621 move changeset forward
622 622
623 623 (with -r option)
624 624
625 625 $ hg phase --public -r 2
626 626 test-debug-phase: move rev 2: 1 -> 0
627 627 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: draft -> public
628 628 $ hg log -G --template "{rev} {phase} {desc}\n"
629 629 @ 7 secret merge B' and E
630 630 |\
631 631 | o 6 draft B'
632 632 | |
633 633 +---o 5 secret H
634 634 | |
635 635 o | 4 secret E
636 636 | |
637 637 o | 3 draft D
638 638 | |
639 639 o | 2 public C
640 640 |/
641 641 o 1 public B
642 642 |
643 643 o 0 public A
644 644
645 645
646 646 move changeset backward
647 647
648 648 (without -r option)
649 649
650 650 $ hg phase --draft --force 2
651 651 test-debug-phase: move rev 2: 0 -> 1
652 652 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: public -> draft
653 653 $ hg log -G --template "{rev} {phase} {desc}\n"
654 654 @ 7 secret merge B' and E
655 655 |\
656 656 | o 6 draft B'
657 657 | |
658 658 +---o 5 secret H
659 659 | |
660 660 o | 4 secret E
661 661 | |
662 662 o | 3 draft D
663 663 | |
664 664 o | 2 draft C
665 665 |/
666 666 o 1 public B
667 667 |
668 668 o 0 public A
669 669
670 670
671 671 move changeset forward and backward
672 672
673 673 $ hg phase --draft --force 1::4
674 674 test-debug-phase: move rev 1: 0 -> 1
675 675 test-debug-phase: move rev 4: 2 -> 1
676 676 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: public -> draft
677 677 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: secret -> draft
678 678 $ hg log -G --template "{rev} {phase} {desc}\n"
679 679 @ 7 secret merge B' and E
680 680 |\
681 681 | o 6 draft B'
682 682 | |
683 683 +---o 5 secret H
684 684 | |
685 685 o | 4 draft E
686 686 | |
687 687 o | 3 draft D
688 688 | |
689 689 o | 2 draft C
690 690 |/
691 691 o 1 draft B
692 692 |
693 693 o 0 public A
694 694
695 695 test partial failure
696 696
697 697 $ hg phase --public 7
698 698 test-debug-phase: move rev 1: 1 -> 0
699 699 test-debug-phase: move rev 2: 1 -> 0
700 700 test-debug-phase: move rev 3: 1 -> 0
701 701 test-debug-phase: move rev 4: 1 -> 0
702 702 test-debug-phase: move rev 6: 1 -> 0
703 703 test-debug-phase: move rev 7: 2 -> 0
704 704 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> public
705 705 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: draft -> public
706 706 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: draft -> public
707 707 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: draft -> public
708 708 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: draft -> public
709 709 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: secret -> public
710 710 $ hg phase --draft '5 or 7'
711 711 test-debug-phase: move rev 5: 2 -> 1
712 712 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: secret -> draft
713 713 cannot move 1 changesets to a higher phase, use --force
714 714 phase changed for 1 changesets
715 715 [1]
716 716 $ hg log -G --template "{rev} {phase} {desc}\n"
717 717 @ 7 public merge B' and E
718 718 |\
719 719 | o 6 public B'
720 720 | |
721 721 +---o 5 draft H
722 722 | |
723 723 o | 4 public E
724 724 | |
725 725 o | 3 public D
726 726 | |
727 727 o | 2 public C
728 728 |/
729 729 o 1 public B
730 730 |
731 731 o 0 public A
732 732
733 733
734 734 test complete failure
735 735
736 736 $ hg phase --draft 7
737 737 cannot move 1 changesets to a higher phase, use --force
738 738 no phases changed
739 739 [1]
740 740
741 741 $ cd ..
742 742
743 743 test hidden changeset are not cloned as public (issue3935)
744 744
745 745 $ cd initialrepo
746 746
747 747 (enabling evolution)
748 748 $ cat >> $HGRCPATH << EOF
749 749 > [experimental]
750 750 > evolution.createmarkers=True
751 751 > EOF
752 752
753 753 (making a changeset hidden; H in that case)
754 754 $ hg debugobsolete `hg id --debug -r 5`
755 755 1 new obsolescence markers
756 756 obsoleted 1 changesets
757 757
758 758 $ cd ..
759 759 $ hg clone initialrepo clonewithobs
760 760 requesting all changes
761 761 adding changesets
762 762 adding manifests
763 763 adding file changes
764 764 added 7 changesets with 6 changes to 6 files
765 765 new changesets 4a2df7238c3b:17a481b3bccb
766 766 test-debug-phase: new rev 0: x -> 0
767 767 test-debug-phase: new rev 1: x -> 0
768 768 test-debug-phase: new rev 2: x -> 0
769 769 test-debug-phase: new rev 3: x -> 0
770 770 test-debug-phase: new rev 4: x -> 0
771 771 test-debug-phase: new rev 5: x -> 0
772 772 test-debug-phase: new rev 6: x -> 0
773 773 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
774 774 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
775 775 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
776 776 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
777 777 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: -> public
778 778 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
779 779 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: -> public
780 780 updating to branch default
781 781 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
782 782 $ cd clonewithobs
783 783 $ hg log -G --template "{rev} {phase} {desc}\n"
784 784 @ 6 public merge B' and E
785 785 |\
786 786 | o 5 public B'
787 787 | |
788 788 o | 4 public E
789 789 | |
790 790 o | 3 public D
791 791 | |
792 792 o | 2 public C
793 793 |/
794 794 o 1 public B
795 795 |
796 796 o 0 public A
797 797
798 798
799 799 test verify repo containing hidden changesets, which should not abort just
800 800 because repo.cancopy() is False
801 801
802 802 $ cd ../initialrepo
803 803 $ hg verify -q
804 804
805 805 $ cd ..
806 806
807 807 check whether HG_PENDING makes pending changes only in related
808 808 repositories visible to an external hook.
809 809
810 810 (emulate a transaction running concurrently by copied
811 811 .hg/phaseroots.pending in subsequent test)
812 812
813 813 $ cat > $TESTTMP/savepending.sh <<EOF
814 814 > cp .hg/store/phaseroots.pending .hg/store/phaseroots.pending.saved
815 815 > exit 1 # to avoid changing phase for subsequent tests
816 816 > EOF
817 817 $ cd push-dest
818 818 $ hg phase 6
819 819 6: draft
820 820 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" phase -f -s 6
821 821 abort: pretxnclose hook exited with status 1
822 822 [40]
823 823 $ cp .hg/store/phaseroots.pending.saved .hg/store/phaseroots.pending
824 824
825 825 (check (in)visibility of phaseroot while transaction running in repo)
826 826
827 827 $ cat > $TESTTMP/checkpending.sh <<EOF
828 828 > echo '@initialrepo'
829 829 > hg -R "$TESTTMP/initialrepo" phase 7
830 830 > echo '@push-dest'
831 831 > hg -R "$TESTTMP/push-dest" phase 6
832 832 > exit 1 # to avoid changing phase for subsequent tests
833 833 > EOF
834 834 $ cd ../initialrepo
835 835 $ hg phase 7
836 836 7: public
837 837 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" phase -f -s 7
838 838 @initialrepo
839 839 7: secret
840 840 @push-dest
841 841 6: draft
842 842 abort: pretxnclose hook exited with status 1
843 843 [40]
844 844
845 845 Check that pretxnclose-phase hook can control phase movement
846 846
847 847 $ hg phase --force b3325c91a4d9 --secret
848 848 test-debug-phase: move rev 3: 0 -> 2
849 849 test-debug-phase: move rev 4: 0 -> 2
850 850 test-debug-phase: move rev 5: 1 -> 2
851 851 test-debug-phase: move rev 7: 0 -> 2
852 852 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: public -> secret
853 853 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: public -> secret
854 854 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: draft -> secret
855 855 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: public -> secret
856 856 $ hg log -G -T phases
857 857 @ changeset: 7:17a481b3bccb
858 858 |\ tag: tip
859 859 | | phase: secret
860 860 | | parent: 6:cf9fe039dfd6
861 861 | | parent: 4:a603bfb5a83e
862 862 | | user: test
863 863 | | date: Thu Jan 01 00:00:00 1970 +0000
864 864 | | summary: merge B' and E
865 865 | |
866 866 | o changeset: 6:cf9fe039dfd6
867 867 | | phase: public
868 868 | | parent: 1:27547f69f254
869 869 | | user: test
870 870 | | date: Thu Jan 01 00:00:00 1970 +0000
871 871 | | summary: B'
872 872 | |
873 873 o | changeset: 4:a603bfb5a83e
874 874 | | phase: secret
875 875 | | user: test
876 876 | | date: Thu Jan 01 00:00:00 1970 +0000
877 877 | | summary: E
878 878 | |
879 879 o | changeset: 3:b3325c91a4d9
880 880 | | phase: secret
881 881 | | user: test
882 882 | | date: Thu Jan 01 00:00:00 1970 +0000
883 883 | | summary: D
884 884 | |
885 885 o | changeset: 2:f838bfaca5c7
886 886 |/ phase: public
887 887 | user: test
888 888 | date: Thu Jan 01 00:00:00 1970 +0000
889 889 | summary: C
890 890 |
891 891 o changeset: 1:27547f69f254
892 892 | phase: public
893 893 | user: test
894 894 | date: Thu Jan 01 00:00:00 1970 +0000
895 895 | summary: B
896 896 |
897 897 o changeset: 0:4a2df7238c3b
898 898 phase: public
899 899 user: test
900 900 date: Thu Jan 01 00:00:00 1970 +0000
901 901 summary: A
902 902
903 903
904 904 Install a hook that prevent b3325c91a4d9 to become public
905 905
906 906 $ cat >> .hg/hgrc << EOF
907 907 > [hooks]
908 908 > pretxnclose-phase.nopublish_D = sh -c "(echo \$HG_NODE| grep -v b3325c91a4d9>/dev/null) || [ 'public' != \$HG_PHASE ]"
909 909 > EOF
910 910
911 911 Try various actions. only the draft move should succeed
912 912
913 913 $ hg phase --public b3325c91a4d9
914 914 abort: pretxnclose-phase.nopublish_D hook exited with status 1
915 915 [40]
916 916 $ hg phase --public a603bfb5a83e
917 917 abort: pretxnclose-phase.nopublish_D hook exited with status 1
918 918 [40]
919 919 $ hg phase --draft 17a481b3bccb
920 920 test-debug-phase: move rev 3: 2 -> 1
921 921 test-debug-phase: move rev 4: 2 -> 1
922 922 test-debug-phase: move rev 7: 2 -> 1
923 923 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: secret -> draft
924 924 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: secret -> draft
925 925 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: secret -> draft
926 926 $ hg phase --public 17a481b3bccb
927 927 abort: pretxnclose-phase.nopublish_D hook exited with status 1
928 928 [40]
929 929
930 930 $ cd ..
931 931
932 932 Test for the "internal" phase
933 933 =============================
934 934
935 935 Check we deny its usage on older repository
936 936
937 937 $ hg init no-internal-phase --config format.use-internal-phase=no
938 938 $ cd no-internal-phase
939 939 $ hg debugrequires | grep internal-phase
940 940 [1]
941 941 $ echo X > X
942 942 $ hg add X
943 943 $ hg status
944 944 A X
945 945 $ hg --config "phases.new-commit=internal" commit -m "my test internal commit" 2>&1 | grep ProgrammingError
946 946 ** ProgrammingError: this repository does not support the internal phase
947 947 raise error.ProgrammingError(msg) (no-pyoxidizer !)
948 948 *ProgrammingError: this repository does not support the internal phase (glob)
949 949 $ hg --config "phases.new-commit=archived" commit -m "my test archived commit" 2>&1 | grep ProgrammingError
950 950 ** ProgrammingError: this repository does not support the archived phase
951 951 raise error.ProgrammingError(msg) (no-pyoxidizer !)
952 952 *ProgrammingError: this repository does not support the archived phase (glob)
953 953
954 954 $ cd ..
955 955
956 956 Check it works fine with repository that supports it.
957 957
958 958 $ hg init internal-phase --config format.use-internal-phase=yes
959 959 $ cd internal-phase
960 960 $ hg debugrequires | grep internal-phase
961 961 internal-phase-2
962 962 $ mkcommit A
963 963 test-debug-phase: new rev 0: x -> 1
964 964 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> draft
965 965
966 966 Commit an internal changesets
967 967
968 968 $ echo B > B
969 969 $ hg add B
970 970 $ hg status
971 971 A B
972 972 $ hg --config "phases.new-commit=internal" commit -m "my test internal commit"
973 973 test-debug-phase: new rev 1: x -> 96
974 974 test-hook-close-phase: c01c42dffc7f81223397e99652a0703f83e1c5ea: -> internal
975 975
976 976 The changeset is a working parent descendant.
977 977 Per the usual visibility rules, it is made visible.
978 978
979 979 $ hg log -G -l 3
980 980 @ changeset: 1:c01c42dffc7f
981 981 | tag: tip
982 982 | user: test
983 983 | date: Thu Jan 01 00:00:00 1970 +0000
984 984 | summary: my test internal commit
985 985 |
986 986 o changeset: 0:4a2df7238c3b
987 987 user: test
988 988 date: Thu Jan 01 00:00:00 1970 +0000
989 989 summary: A
990 990
991 991
992 992 Commit is hidden as expected
993 993
994 994 $ hg up 0
995 995 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
996 996 $ hg log -G
997 997 @ changeset: 0:4a2df7238c3b
998 998 tag: tip
999 999 user: test
1000 1000 date: Thu Jan 01 00:00:00 1970 +0000
1001 1001 summary: A
1002 1002
1003 1003 The hidden commit is an orphan but doesn't show up without --hidden
1004 1004
1005 1005 $ hg debugobsolete `hg id --debug -ir 0`
1006 1006 1 new obsolescence markers
1007 1007 obsoleted 1 changesets
1008 1008 $ hg --hidden log -G -r 'unstable()'
1009 1009 * changeset: 1:c01c42dffc7f
1010 1010 | tag: tip
1011 1011 ~ user: test
1012 1012 date: Thu Jan 01 00:00:00 1970 +0000
1013 1013 instability: orphan
1014 1014 summary: my test internal commit
1015 1015
1016 1016 $ hg log -G -r 'unstable()'
1017 abort: filtered revision '1' (known-bad-output !)
1018 [10]
1019 1017
1020 1018
1021 1019 Test for archived phase
1022 1020 -----------------------
1023 1021
1024 1022 Commit an archived changesets
1025 1023
1026 1024 $ cd ..
1027 1025 $ hg clone --quiet --pull internal-phase archived-phase \
1028 1026 > --config format.exp-archived-phase=yes \
1029 1027 > --config extensions.phasereport='!' \
1030 1028 > --config hooks.txnclose-phase.test=
1031 1029
1032 1030 $ cd archived-phase
1033 1031
1034 1032 $ echo B > B
1035 1033 $ hg add B
1036 1034 $ hg status
1037 1035 A B
1038 1036 $ hg --config "phases.new-commit=archived" commit -m "my test archived commit"
1039 1037 test-debug-phase: new rev 1: x -> 32
1040 1038 test-hook-close-phase: 8df5997c3361518f733d1ae67cd3adb9b0eaf125: -> archived
1041 1039
1042 1040 The changeset is a working parent descendant.
1043 1041 Per the usual visibility rules, it is made visible.
1044 1042
1045 1043 $ hg log -G -l 3
1046 1044 @ changeset: 1:8df5997c3361
1047 1045 | tag: tip
1048 1046 | user: test
1049 1047 | date: Thu Jan 01 00:00:00 1970 +0000
1050 1048 | summary: my test archived commit
1051 1049 |
1052 1050 o changeset: 0:4a2df7238c3b
1053 1051 user: test
1054 1052 date: Thu Jan 01 00:00:00 1970 +0000
1055 1053 summary: A
1056 1054
1057 1055
1058 1056 Commit is hidden as expected
1059 1057
1060 1058 $ hg up 0
1061 1059 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1062 1060 $ hg log -G
1063 1061 @ changeset: 0:4a2df7238c3b
1064 1062 tag: tip
1065 1063 user: test
1066 1064 date: Thu Jan 01 00:00:00 1970 +0000
1067 1065 summary: A
1068 1066
1069 1067 $ cd ..
1070 1068
1071 1069 Recommitting an exact match of a public commit shouldn't change it to
1072 1070 draft:
1073 1071
1074 1072 $ cd initialrepo
1075 1073 $ hg phase -r 2
1076 1074 2: public
1077 1075 $ hg up -C 1
1078 1076 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
1079 1077 $ mkcommit C
1080 1078 warning: commit already existed in the repository!
1081 1079 $ hg phase -r 2
1082 1080 2: public
1083 1081
1084 1082 Same, but for secret:
1085 1083
1086 1084 $ hg up 7
1087 1085 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1088 1086 $ mkcommit F -s
1089 1087 test-debug-phase: new rev 8: x -> 2
1090 1088 test-hook-close-phase: de414268ec5ce2330c590b942fbb5ff0b0ca1a0a: -> secret
1091 1089 $ hg up 7
1092 1090 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1093 1091 $ hg phase
1094 1092 7: draft
1095 1093 $ mkcommit F
1096 1094 test-debug-phase: new rev 8: x -> 2
1097 1095 warning: commit already existed in the repository!
1098 1096 test-hook-close-phase: de414268ec5ce2330c590b942fbb5ff0b0ca1a0a: -> secret
1099 1097 $ hg phase -r tip
1100 1098 8: secret
1101 1099
1102 1100 But what about obsoleted changesets?
1103 1101
1104 1102 $ hg up 4
1105 1103 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
1106 1104 $ mkcommit H
1107 1105 test-debug-phase: new rev 5: x -> 2
1108 1106 warning: commit already existed in the repository!
1109 1107 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: -> secret
1110 1108 $ hg phase -r 5
1111 1109 5: secret
1112 1110 $ hg par
1113 1111 changeset: 5:a030c6be5127
1114 1112 user: test
1115 1113 date: Thu Jan 01 00:00:00 1970 +0000
1116 1114 obsolete: pruned
1117 1115 summary: H
1118 1116
1119 1117 $ hg up tip
1120 1118 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1121 1119 $ cd ..
1122 1120
1123 1121 Testing that command line flags override configuration
1124 1122
1125 1123 $ hg init commit-overrides
1126 1124 $ cd commit-overrides
1127 1125
1128 1126 `hg commit --draft` overrides new-commit=secret
1129 1127
1130 1128 $ mkcommit A --config phases.new-commit='secret' --draft
1131 1129 test-debug-phase: new rev 0: x -> 1
1132 1130 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> draft
1133 1131 $ hglog
1134 1132 @ 0 1 A
1135 1133
1136 1134
1137 1135 `hg commit --secret` overrides new-commit=draft
1138 1136
1139 1137 $ mkcommit B --config phases.new-commit='draft' --secret
1140 1138 test-debug-phase: new rev 1: x -> 2
1141 1139 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> secret
1142 1140 $ hglog
1143 1141 @ 1 2 B
1144 1142 |
1145 1143 o 0 1 A
1146 1144
1147 1145
1148 1146 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now