##// END OF EJS Templates
phase: add an archived phase...
Boris Feld -
r40463:49c7b701 stable
parent child Browse files
Show More
@@ -1,741 +1,744 b''
1 1 """ Mercurial phases support code
2 2
3 3 ---
4 4
5 5 Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
6 6 Logilab SA <contact@logilab.fr>
7 7 Augie Fackler <durin42@gmail.com>
8 8
9 9 This software may be used and distributed according to the terms
10 10 of the GNU General Public License version 2 or any later version.
11 11
12 12 ---
13 13
14 14 This module implements most phase logic in mercurial.
15 15
16 16
17 17 Basic Concept
18 18 =============
19 19
20 20 A 'changeset phase' is an indicator that tells us how a changeset is
21 21 manipulated and communicated. The details of each phase is described
22 22 below, here we describe the properties they have in common.
23 23
24 24 Like bookmarks, phases are not stored in history and thus are not
25 25 permanent and leave no audit trail.
26 26
27 27 First, no changeset can be in two phases at once. Phases are ordered,
28 28 so they can be considered from lowest to highest. The default, lowest
29 29 phase is 'public' - this is the normal phase of existing changesets. A
30 30 child changeset can not be in a lower phase than its parents.
31 31
32 32 These phases share a hierarchy of traits:
33 33
34 34 immutable shared
35 35 public: X X
36 36 draft: X
37 37 secret:
38 38
39 39 Local commits are draft by default.
40 40
41 41 Phase Movement and Exchange
42 42 ===========================
43 43
44 44 Phase data is exchanged by pushkey on pull and push. Some servers have
45 45 a publish option set, we call such a server a "publishing server".
46 46 Pushing a draft changeset to a publishing server changes the phase to
47 47 public.
48 48
49 49 A small list of fact/rules define the exchange of phase:
50 50
51 51 * old client never changes server states
52 52 * pull never changes server states
53 53 * publish and old server changesets are seen as public by client
54 54 * any secret changeset seen in another repository is lowered to at
55 55 least draft
56 56
57 57 Here is the final table summing up the 49 possible use cases of phase
58 58 exchange:
59 59
60 60 server
61 61 old publish non-publish
62 62 N X N D P N D P
63 63 old client
64 64 pull
65 65 N - X/X - X/D X/P - X/D X/P
66 66 X - X/X - X/D X/P - X/D X/P
67 67 push
68 68 X X/X X/X X/P X/P X/P X/D X/D X/P
69 69 new client
70 70 pull
71 71 N - P/X - P/D P/P - D/D P/P
72 72 D - P/X - P/D P/P - D/D P/P
73 73 P - P/X - P/D P/P - P/D P/P
74 74 push
75 75 D P/X P/X P/P P/P P/P D/D D/D P/P
76 76 P P/X P/X P/P P/P P/P P/P P/P P/P
77 77
78 78 Legend:
79 79
80 80 A/B = final state on client / state on server
81 81
82 82 * N = new/not present,
83 83 * P = public,
84 84 * D = draft,
85 85 * X = not tracked (i.e., the old client or server has no internal
86 86 way of recording the phase.)
87 87
88 88 passive = only pushes
89 89
90 90
91 91 A cell here can be read like this:
92 92
93 93 "When a new client pushes a draft changeset (D) to a publishing
94 94 server where it's not present (N), it's marked public on both
95 95 sides (P/P)."
96 96
97 97 Note: old client behave as a publishing server with draft only content
98 98 - other people see it as public
99 99 - content is pushed as draft
100 100
101 101 """
102 102
103 103 from __future__ import absolute_import
104 104
105 105 import errno
106 106 import struct
107 107
108 108 from .i18n import _
109 109 from .node import (
110 110 bin,
111 111 hex,
112 112 nullid,
113 113 nullrev,
114 114 short,
115 115 )
116 116 from . import (
117 117 error,
118 118 pycompat,
119 119 smartset,
120 120 txnutil,
121 121 util,
122 122 )
123 123
124 124 _fphasesentry = struct.Struct('>i20s')
125 125
126 126 INTERNAL_FLAG = 64 # Phases for mercurial internal usage only
127 127 HIDEABLE_FLAG = 32 # Phases that are hideable
128 128
129 129 # record phase index
130 130 public, draft, secret = range(3)
131 131 internal = INTERNAL_FLAG | HIDEABLE_FLAG
132 archived = HIDEABLE_FLAG
132 133 allphases = range(internal + 1)
133 134 trackedphases = allphases[1:]
134 135 # record phase names
135 136 phasenames = [None] * len(allphases)
136 137 phasenames[:3] = ['public', 'draft', 'secret']
138 phasenames[archived] = 'archived'
137 139 phasenames[internal] = 'internal'
138 140 # record phase property
139 141 mutablephases = tuple(allphases[1:])
140 142 remotehiddenphases = tuple(allphases[2:])
141 143 localhiddenphases = tuple(p for p in allphases if p & HIDEABLE_FLAG)
142 144
143 145 def supportinternal(repo):
144 146 """True if the internal phase can be used on a repository"""
145 147 return 'internal-phase' in repo.requirements
146 148
147 149 def _readroots(repo, phasedefaults=None):
148 150 """Read phase roots from disk
149 151
150 152 phasedefaults is a list of fn(repo, roots) callable, which are
151 153 executed if the phase roots file does not exist. When phases are
152 154 being initialized on an existing repository, this could be used to
153 155 set selected changesets phase to something else than public.
154 156
155 157 Return (roots, dirty) where dirty is true if roots differ from
156 158 what is being stored.
157 159 """
158 160 repo = repo.unfiltered()
159 161 dirty = False
160 162 roots = [set() for i in allphases]
161 163 try:
162 164 f, pending = txnutil.trypending(repo.root, repo.svfs, 'phaseroots')
163 165 try:
164 166 for line in f:
165 167 phase, nh = line.split()
166 168 roots[int(phase)].add(bin(nh))
167 169 finally:
168 170 f.close()
169 171 except IOError as inst:
170 172 if inst.errno != errno.ENOENT:
171 173 raise
172 174 if phasedefaults:
173 175 for f in phasedefaults:
174 176 roots = f(repo, roots)
175 177 dirty = True
176 178 return roots, dirty
177 179
178 180 def binaryencode(phasemapping):
179 181 """encode a 'phase -> nodes' mapping into a binary stream
180 182
181 183 Since phases are integer the mapping is actually a python list:
182 184 [[PUBLIC_HEADS], [DRAFTS_HEADS], [SECRET_HEADS]]
183 185 """
184 186 binarydata = []
185 187 for phase, nodes in enumerate(phasemapping):
186 188 for head in nodes:
187 189 binarydata.append(_fphasesentry.pack(phase, head))
188 190 return ''.join(binarydata)
189 191
190 192 def binarydecode(stream):
191 193 """decode a binary stream into a 'phase -> nodes' mapping
192 194
193 195 Since phases are integer the mapping is actually a python list."""
194 196 headsbyphase = [[] for i in allphases]
195 197 entrysize = _fphasesentry.size
196 198 while True:
197 199 entry = stream.read(entrysize)
198 200 if len(entry) < entrysize:
199 201 if entry:
200 202 raise error.Abort(_('bad phase-heads stream'))
201 203 break
202 204 phase, node = _fphasesentry.unpack(entry)
203 205 headsbyphase[phase].append(node)
204 206 return headsbyphase
205 207
206 208 def _trackphasechange(data, rev, old, new):
207 209 """add a phase move the <data> dictionnary
208 210
209 211 If data is None, nothing happens.
210 212 """
211 213 if data is None:
212 214 return
213 215 existing = data.get(rev)
214 216 if existing is not None:
215 217 old = existing[0]
216 218 data[rev] = (old, new)
217 219
218 220 class phasecache(object):
219 221 def __init__(self, repo, phasedefaults, _load=True):
220 222 if _load:
221 223 # Cheap trick to allow shallow-copy without copy module
222 224 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
223 225 self._loadedrevslen = 0
224 226 self._phasesets = None
225 227 self.filterunknown(repo)
226 228 self.opener = repo.svfs
227 229
228 230 def getrevset(self, repo, phases, subset=None):
229 231 """return a smartset for the given phases"""
230 232 self.loadphaserevs(repo) # ensure phase's sets are loaded
231 233 phases = set(phases)
232 234 if public not in phases:
233 235 # fast path: _phasesets contains the interesting sets,
234 236 # might only need a union and post-filtering.
235 237 if len(phases) == 1:
236 238 [p] = phases
237 239 revs = self._phasesets[p]
238 240 else:
239 241 revs = set.union(*[self._phasesets[p] for p in phases])
240 242 if repo.changelog.filteredrevs:
241 243 revs = revs - repo.changelog.filteredrevs
242 244 if subset is None:
243 245 return smartset.baseset(revs)
244 246 else:
245 247 return subset & smartset.baseset(revs)
246 248 else:
247 249 phases = set(allphases).difference(phases)
248 250 if not phases:
249 251 return smartset.fullreposet(repo)
250 252 if len(phases) == 1:
251 253 [p] = phases
252 254 revs = self._phasesets[p]
253 255 else:
254 256 revs = set.union(*[self._phasesets[p] for p in phases])
255 257 if subset is None:
256 258 subset = smartset.fullreposet(repo)
257 259 if not revs:
258 260 return subset
259 261 return subset.filter(lambda r: r not in revs)
260 262
261 263 def copy(self):
262 264 # Shallow copy meant to ensure isolation in
263 265 # advance/retractboundary(), nothing more.
264 266 ph = self.__class__(None, None, _load=False)
265 267 ph.phaseroots = self.phaseroots[:]
266 268 ph.dirty = self.dirty
267 269 ph.opener = self.opener
268 270 ph._loadedrevslen = self._loadedrevslen
269 271 ph._phasesets = self._phasesets
270 272 return ph
271 273
272 274 def replace(self, phcache):
273 275 """replace all values in 'self' with content of phcache"""
274 276 for a in ('phaseroots', 'dirty', 'opener', '_loadedrevslen',
275 277 '_phasesets'):
276 278 setattr(self, a, getattr(phcache, a))
277 279
278 280 def _getphaserevsnative(self, repo):
279 281 repo = repo.unfiltered()
280 282 nativeroots = []
281 283 for phase in trackedphases:
282 284 nativeroots.append(pycompat.maplist(repo.changelog.rev,
283 285 self.phaseroots[phase]))
284 286 return repo.changelog.computephases(nativeroots)
285 287
286 288 def _computephaserevspure(self, repo):
287 289 repo = repo.unfiltered()
288 290 cl = repo.changelog
289 291 self._phasesets = [set() for phase in allphases]
290 292 lowerroots = set()
291 293 for phase in reversed(trackedphases):
292 294 roots = pycompat.maplist(cl.rev, self.phaseroots[phase])
293 295 if roots:
294 296 ps = set(cl.descendants(roots))
295 297 for root in roots:
296 298 ps.add(root)
297 299 ps.difference_update(lowerroots)
298 300 lowerroots.update(ps)
299 301 self._phasesets[phase] = ps
300 302 self._loadedrevslen = len(cl)
301 303
302 304 def loadphaserevs(self, repo):
303 305 """ensure phase information is loaded in the object"""
304 306 if self._phasesets is None:
305 307 try:
306 308 res = self._getphaserevsnative(repo)
307 309 self._loadedrevslen, self._phasesets = res
308 310 except AttributeError:
309 311 self._computephaserevspure(repo)
310 312
311 313 def invalidate(self):
312 314 self._loadedrevslen = 0
313 315 self._phasesets = None
314 316
315 317 def phase(self, repo, rev):
316 318 # We need a repo argument here to be able to build _phasesets
317 319 # if necessary. The repository instance is not stored in
318 320 # phasecache to avoid reference cycles. The changelog instance
319 321 # is not stored because it is a filecache() property and can
320 322 # be replaced without us being notified.
321 323 if rev == nullrev:
322 324 return public
323 325 if rev < nullrev:
324 326 raise ValueError(_('cannot lookup negative revision'))
325 327 if rev >= self._loadedrevslen:
326 328 self.invalidate()
327 329 self.loadphaserevs(repo)
328 330 for phase in trackedphases:
329 331 if rev in self._phasesets[phase]:
330 332 return phase
331 333 return public
332 334
333 335 def write(self):
334 336 if not self.dirty:
335 337 return
336 338 f = self.opener('phaseroots', 'w', atomictemp=True, checkambig=True)
337 339 try:
338 340 self._write(f)
339 341 finally:
340 342 f.close()
341 343
342 344 def _write(self, fp):
343 345 for phase, roots in enumerate(self.phaseroots):
344 346 for h in sorted(roots):
345 347 fp.write('%i %s\n' % (phase, hex(h)))
346 348 self.dirty = False
347 349
348 350 def _updateroots(self, phase, newroots, tr):
349 351 self.phaseroots[phase] = newroots
350 352 self.invalidate()
351 353 self.dirty = True
352 354
353 355 tr.addfilegenerator('phase', ('phaseroots',), self._write)
354 356 tr.hookargs['phases_moved'] = '1'
355 357
356 358 def registernew(self, repo, tr, targetphase, nodes):
357 359 repo = repo.unfiltered()
358 360 self._retractboundary(repo, tr, targetphase, nodes)
359 361 if tr is not None and 'phases' in tr.changes:
360 362 phasetracking = tr.changes['phases']
361 363 torev = repo.changelog.rev
362 364 phase = self.phase
363 365 for n in nodes:
364 366 rev = torev(n)
365 367 revphase = phase(repo, rev)
366 368 _trackphasechange(phasetracking, rev, None, revphase)
367 369 repo.invalidatevolatilesets()
368 370
369 371 def advanceboundary(self, repo, tr, targetphase, nodes, dryrun=None):
370 372 """Set all 'nodes' to phase 'targetphase'
371 373
372 374 Nodes with a phase lower than 'targetphase' are not affected.
373 375
374 376 If dryrun is True, no actions will be performed
375 377
376 378 Returns a set of revs whose phase is changed or should be changed
377 379 """
378 380 # Be careful to preserve shallow-copied values: do not update
379 381 # phaseroots values, replace them.
380 382 if tr is None:
381 383 phasetracking = None
382 384 else:
383 385 phasetracking = tr.changes.get('phases')
384 386
385 387 repo = repo.unfiltered()
386 388
387 389 changes = set() # set of revisions to be changed
388 390 delroots = [] # set of root deleted by this path
389 391 for phase in pycompat.xrange(targetphase + 1, len(allphases)):
390 392 # filter nodes that are not in a compatible phase already
391 393 nodes = [n for n in nodes
392 394 if self.phase(repo, repo[n].rev()) >= phase]
393 395 if not nodes:
394 396 break # no roots to move anymore
395 397
396 398 olds = self.phaseroots[phase]
397 399
398 400 affected = repo.revs('%ln::%ln', olds, nodes)
399 401 changes.update(affected)
400 402 if dryrun:
401 403 continue
402 404 for r in affected:
403 405 _trackphasechange(phasetracking, r, self.phase(repo, r),
404 406 targetphase)
405 407
406 408 roots = set(ctx.node() for ctx in repo.set(
407 409 'roots((%ln::) - %ld)', olds, affected))
408 410 if olds != roots:
409 411 self._updateroots(phase, roots, tr)
410 412 # some roots may need to be declared for lower phases
411 413 delroots.extend(olds - roots)
412 414 if not dryrun:
413 415 # declare deleted root in the target phase
414 416 if targetphase != 0:
415 417 self._retractboundary(repo, tr, targetphase, delroots)
416 418 repo.invalidatevolatilesets()
417 419 return changes
418 420
419 421 def retractboundary(self, repo, tr, targetphase, nodes):
420 422 oldroots = self.phaseroots[:targetphase + 1]
421 423 if tr is None:
422 424 phasetracking = None
423 425 else:
424 426 phasetracking = tr.changes.get('phases')
425 427 repo = repo.unfiltered()
426 428 if (self._retractboundary(repo, tr, targetphase, nodes)
427 429 and phasetracking is not None):
428 430
429 431 # find the affected revisions
430 432 new = self.phaseroots[targetphase]
431 433 old = oldroots[targetphase]
432 434 affected = set(repo.revs('(%ln::) - (%ln::)', new, old))
433 435
434 436 # find the phase of the affected revision
435 437 for phase in pycompat.xrange(targetphase, -1, -1):
436 438 if phase:
437 439 roots = oldroots[phase]
438 440 revs = set(repo.revs('%ln::%ld', roots, affected))
439 441 affected -= revs
440 442 else: # public phase
441 443 revs = affected
442 444 for r in revs:
443 445 _trackphasechange(phasetracking, r, phase, targetphase)
444 446 repo.invalidatevolatilesets()
445 447
446 448 def _retractboundary(self, repo, tr, targetphase, nodes):
447 449 # Be careful to preserve shallow-copied values: do not update
448 450 # phaseroots values, replace them.
449 if targetphase == internal and not supportinternal(repo):
450 msg = 'this repository does not support the internal phase'
451 if targetphase in (archived, internal) and not supportinternal(repo):
452 name = phasenames[targetphase]
453 msg = 'this repository does not support the %s phase' % name
451 454 raise error.ProgrammingError(msg)
452 455
453 456 repo = repo.unfiltered()
454 457 currentroots = self.phaseroots[targetphase]
455 458 finalroots = oldroots = set(currentroots)
456 459 newroots = [n for n in nodes
457 460 if self.phase(repo, repo[n].rev()) < targetphase]
458 461 if newroots:
459 462
460 463 if nullid in newroots:
461 464 raise error.Abort(_('cannot change null revision phase'))
462 465 currentroots = currentroots.copy()
463 466 currentroots.update(newroots)
464 467
465 468 # Only compute new roots for revs above the roots that are being
466 469 # retracted.
467 470 minnewroot = min(repo[n].rev() for n in newroots)
468 471 aboveroots = [n for n in currentroots
469 472 if repo[n].rev() >= minnewroot]
470 473 updatedroots = repo.set('roots(%ln::)', aboveroots)
471 474
472 475 finalroots = set(n for n in currentroots if repo[n].rev() <
473 476 minnewroot)
474 477 finalroots.update(ctx.node() for ctx in updatedroots)
475 478 if finalroots != oldroots:
476 479 self._updateroots(targetphase, finalroots, tr)
477 480 return True
478 481 return False
479 482
480 483 def filterunknown(self, repo):
481 484 """remove unknown nodes from the phase boundary
482 485
483 486 Nothing is lost as unknown nodes only hold data for their descendants.
484 487 """
485 488 filtered = False
486 489 nodemap = repo.changelog.nodemap # to filter unknown nodes
487 490 for phase, nodes in enumerate(self.phaseroots):
488 491 missing = sorted(node for node in nodes if node not in nodemap)
489 492 if missing:
490 493 for mnode in missing:
491 494 repo.ui.debug(
492 495 'removing unknown node %s from %i-phase boundary\n'
493 496 % (short(mnode), phase))
494 497 nodes.symmetric_difference_update(missing)
495 498 filtered = True
496 499 if filtered:
497 500 self.dirty = True
498 501 # filterunknown is called by repo.destroyed, we may have no changes in
499 502 # root but _phasesets contents is certainly invalid (or at least we
500 503 # have not proper way to check that). related to issue 3858.
501 504 #
502 505 # The other caller is __init__ that have no _phasesets initialized
503 506 # anyway. If this change we should consider adding a dedicated
504 507 # "destroyed" function to phasecache or a proper cache key mechanism
505 508 # (see branchmap one)
506 509 self.invalidate()
507 510
508 511 def advanceboundary(repo, tr, targetphase, nodes, dryrun=None):
509 512 """Add nodes to a phase changing other nodes phases if necessary.
510 513
511 514 This function move boundary *forward* this means that all nodes
512 515 are set in the target phase or kept in a *lower* phase.
513 516
514 517 Simplify boundary to contains phase roots only.
515 518
516 519 If dryrun is True, no actions will be performed
517 520
518 521 Returns a set of revs whose phase is changed or should be changed
519 522 """
520 523 phcache = repo._phasecache.copy()
521 524 changes = phcache.advanceboundary(repo, tr, targetphase, nodes,
522 525 dryrun=dryrun)
523 526 if not dryrun:
524 527 repo._phasecache.replace(phcache)
525 528 return changes
526 529
527 530 def retractboundary(repo, tr, targetphase, nodes):
528 531 """Set nodes back to a phase changing other nodes phases if
529 532 necessary.
530 533
531 534 This function move boundary *backward* this means that all nodes
532 535 are set in the target phase or kept in a *higher* phase.
533 536
534 537 Simplify boundary to contains phase roots only."""
535 538 phcache = repo._phasecache.copy()
536 539 phcache.retractboundary(repo, tr, targetphase, nodes)
537 540 repo._phasecache.replace(phcache)
538 541
539 542 def registernew(repo, tr, targetphase, nodes):
540 543 """register a new revision and its phase
541 544
542 545 Code adding revisions to the repository should use this function to
543 546 set new changeset in their target phase (or higher).
544 547 """
545 548 phcache = repo._phasecache.copy()
546 549 phcache.registernew(repo, tr, targetphase, nodes)
547 550 repo._phasecache.replace(phcache)
548 551
549 552 def listphases(repo):
550 553 """List phases root for serialization over pushkey"""
551 554 # Use ordered dictionary so behavior is deterministic.
552 555 keys = util.sortdict()
553 556 value = '%i' % draft
554 557 cl = repo.unfiltered().changelog
555 558 for root in repo._phasecache.phaseroots[draft]:
556 559 if repo._phasecache.phase(repo, cl.rev(root)) <= draft:
557 560 keys[hex(root)] = value
558 561
559 562 if repo.publishing():
560 563 # Add an extra data to let remote know we are a publishing
561 564 # repo. Publishing repo can't just pretend they are old repo.
562 565 # When pushing to a publishing repo, the client still need to
563 566 # push phase boundary
564 567 #
565 568 # Push do not only push changeset. It also push phase data.
566 569 # New phase data may apply to common changeset which won't be
567 570 # push (as they are common). Here is a very simple example:
568 571 #
569 572 # 1) repo A push changeset X as draft to repo B
570 573 # 2) repo B make changeset X public
571 574 # 3) repo B push to repo A. X is not pushed but the data that
572 575 # X as now public should
573 576 #
574 577 # The server can't handle it on it's own as it has no idea of
575 578 # client phase data.
576 579 keys['publishing'] = 'True'
577 580 return keys
578 581
579 582 def pushphase(repo, nhex, oldphasestr, newphasestr):
580 583 """List phases root for serialization over pushkey"""
581 584 repo = repo.unfiltered()
582 585 with repo.lock():
583 586 currentphase = repo[nhex].phase()
584 587 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
585 588 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
586 589 if currentphase == oldphase and newphase < oldphase:
587 590 with repo.transaction('pushkey-phase') as tr:
588 591 advanceboundary(repo, tr, newphase, [bin(nhex)])
589 592 return True
590 593 elif currentphase == newphase:
591 594 # raced, but got correct result
592 595 return True
593 596 else:
594 597 return False
595 598
596 599 def subsetphaseheads(repo, subset):
597 600 """Finds the phase heads for a subset of a history
598 601
599 602 Returns a list indexed by phase number where each item is a list of phase
600 603 head nodes.
601 604 """
602 605 cl = repo.changelog
603 606
604 607 headsbyphase = [[] for i in allphases]
605 608 # No need to keep track of secret phase; any heads in the subset that
606 609 # are not mentioned are implicitly secret.
607 610 for phase in allphases[:secret]:
608 611 revset = "heads(%%ln & %s())" % phasenames[phase]
609 612 headsbyphase[phase] = [cl.node(r) for r in repo.revs(revset, subset)]
610 613 return headsbyphase
611 614
612 615 def updatephases(repo, trgetter, headsbyphase):
613 616 """Updates the repo with the given phase heads"""
614 617 # Now advance phase boundaries of all but secret phase
615 618 #
616 619 # run the update (and fetch transaction) only if there are actually things
617 620 # to update. This avoid creating empty transaction during no-op operation.
618 621
619 622 for phase in allphases[:-1]:
620 623 revset = '%ln - _phase(%s)'
621 624 heads = [c.node() for c in repo.set(revset, headsbyphase[phase], phase)]
622 625 if heads:
623 626 advanceboundary(repo, trgetter(), phase, heads)
624 627
625 628 def analyzeremotephases(repo, subset, roots):
626 629 """Compute phases heads and root in a subset of node from root dict
627 630
628 631 * subset is heads of the subset
629 632 * roots is {<nodeid> => phase} mapping. key and value are string.
630 633
631 634 Accept unknown element input
632 635 """
633 636 repo = repo.unfiltered()
634 637 # build list from dictionary
635 638 draftroots = []
636 639 nodemap = repo.changelog.nodemap # to filter unknown nodes
637 640 for nhex, phase in roots.iteritems():
638 641 if nhex == 'publishing': # ignore data related to publish option
639 642 continue
640 643 node = bin(nhex)
641 644 phase = int(phase)
642 645 if phase == public:
643 646 if node != nullid:
644 647 repo.ui.warn(_('ignoring inconsistent public root'
645 648 ' from remote: %s\n') % nhex)
646 649 elif phase == draft:
647 650 if node in nodemap:
648 651 draftroots.append(node)
649 652 else:
650 653 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
651 654 % (phase, nhex))
652 655 # compute heads
653 656 publicheads = newheads(repo, subset, draftroots)
654 657 return publicheads, draftroots
655 658
656 659 class remotephasessummary(object):
657 660 """summarize phase information on the remote side
658 661
659 662 :publishing: True is the remote is publishing
660 663 :publicheads: list of remote public phase heads (nodes)
661 664 :draftheads: list of remote draft phase heads (nodes)
662 665 :draftroots: list of remote draft phase root (nodes)
663 666 """
664 667
665 668 def __init__(self, repo, remotesubset, remoteroots):
666 669 unfi = repo.unfiltered()
667 670 self._allremoteroots = remoteroots
668 671
669 672 self.publishing = remoteroots.get('publishing', False)
670 673
671 674 ana = analyzeremotephases(repo, remotesubset, remoteroots)
672 675 self.publicheads, self.draftroots = ana
673 676 # Get the list of all "heads" revs draft on remote
674 677 dheads = unfi.set('heads(%ln::%ln)', self.draftroots, remotesubset)
675 678 self.draftheads = [c.node() for c in dheads]
676 679
677 680 def newheads(repo, heads, roots):
678 681 """compute new head of a subset minus another
679 682
680 683 * `heads`: define the first subset
681 684 * `roots`: define the second we subtract from the first"""
682 685 # prevent an import cycle
683 686 # phases > dagop > patch > copies > scmutil > obsolete > obsutil > phases
684 687 from . import dagop
685 688
686 689 repo = repo.unfiltered()
687 690 cl = repo.changelog
688 691 rev = cl.nodemap.get
689 692 if not roots:
690 693 return heads
691 694 if not heads or heads == [nullid]:
692 695 return []
693 696 # The logic operated on revisions, convert arguments early for convenience
694 697 new_heads = set(rev(n) for n in heads if n != nullid)
695 698 roots = [rev(n) for n in roots]
696 699 # compute the area we need to remove
697 700 affected_zone = repo.revs("(%ld::%ld)", roots, new_heads)
698 701 # heads in the area are no longer heads
699 702 new_heads.difference_update(affected_zone)
700 703 # revisions in the area have children outside of it,
701 704 # They might be new heads
702 705 candidates = repo.revs("parents(%ld + (%ld and merge())) and not null",
703 706 roots, affected_zone)
704 707 candidates -= affected_zone
705 708 if new_heads or candidates:
706 709 # remove candidate that are ancestors of other heads
707 710 new_heads.update(candidates)
708 711 prunestart = repo.revs("parents(%ld) and not null", new_heads)
709 712 pruned = dagop.reachableroots(repo, candidates, prunestart)
710 713 new_heads.difference_update(pruned)
711 714
712 715 return pycompat.maplist(cl.node, sorted(new_heads))
713 716
714 717 def newcommitphase(ui):
715 718 """helper to get the target phase of new commit
716 719
717 720 Handle all possible values for the phases.new-commit options.
718 721
719 722 """
720 723 v = ui.config('phases', 'new-commit')
721 724 try:
722 725 return phasenames.index(v)
723 726 except ValueError:
724 727 try:
725 728 return int(v)
726 729 except ValueError:
727 730 msg = _("phases.new-commit: not a valid phase name ('%s')")
728 731 raise error.ConfigError(msg % v)
729 732
730 733 def hassecret(repo):
731 734 """utility function that check if a repo have any secret changeset."""
732 735 return bool(repo._phasecache.phaseroots[2])
733 736
734 737 def preparehookargs(node, old, new):
735 738 if old is None:
736 739 old = ''
737 740 else:
738 741 old = phasenames[old]
739 742 return {'node': node,
740 743 'oldphase': old,
741 744 'phase': phasenames[new]}
@@ -1,906 +1,953 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 --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 Following commit are draft too
42 42
43 43 $ mkcommit B
44 44 test-debug-phase: new rev 1: x -> 1
45 45 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> draft
46 46
47 47 $ hglog
48 48 1 1 B
49 49 0 1 A
50 50
51 51 Draft commit are properly created over public one:
52 52
53 53 $ hg phase --public .
54 54 test-debug-phase: move rev 0: 1 -> 0
55 55 test-debug-phase: move rev 1: 1 -> 0
56 56 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: draft -> public
57 57 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> public
58 58 $ hg phase
59 59 1: public
60 60 $ hglog
61 61 1 0 B
62 62 0 0 A
63 63
64 64 $ mkcommit C
65 65 test-debug-phase: new rev 2: x -> 1
66 66 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> draft
67 67 $ mkcommit D
68 68 test-debug-phase: new rev 3: x -> 1
69 69 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> draft
70 70
71 71 $ hglog
72 72 3 1 D
73 73 2 1 C
74 74 1 0 B
75 75 0 0 A
76 76
77 77 Test creating changeset as secret
78 78
79 79 $ mkcommit E --config phases.new-commit='secret'
80 80 test-debug-phase: new rev 4: x -> 2
81 81 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: -> secret
82 82 $ hglog
83 83 4 2 E
84 84 3 1 D
85 85 2 1 C
86 86 1 0 B
87 87 0 0 A
88 88
89 89 Test the secret property is inherited
90 90
91 91 $ mkcommit H
92 92 test-debug-phase: new rev 5: x -> 2
93 93 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: -> secret
94 94 $ hglog
95 95 5 2 H
96 96 4 2 E
97 97 3 1 D
98 98 2 1 C
99 99 1 0 B
100 100 0 0 A
101 101
102 102 Even on merge
103 103
104 104 $ hg up -q 1
105 105 $ mkcommit "B'"
106 106 test-debug-phase: new rev 6: x -> 1
107 107 created new head
108 108 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> draft
109 109 $ hglog
110 110 6 1 B'
111 111 5 2 H
112 112 4 2 E
113 113 3 1 D
114 114 2 1 C
115 115 1 0 B
116 116 0 0 A
117 117 $ hg merge 4 # E
118 118 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
119 119 (branch merge, don't forget to commit)
120 120 $ hg phase
121 121 6: draft
122 122 4: secret
123 123 $ hg ci -m "merge B' and E"
124 124 test-debug-phase: new rev 7: x -> 2
125 125 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: -> secret
126 126
127 127 $ hglog
128 128 7 2 merge B' and E
129 129 6 1 B'
130 130 5 2 H
131 131 4 2 E
132 132 3 1 D
133 133 2 1 C
134 134 1 0 B
135 135 0 0 A
136 136
137 137 Test secret changeset are not pushed
138 138
139 139 $ hg init ../push-dest
140 140 $ cat > ../push-dest/.hg/hgrc << EOF
141 141 > [phases]
142 142 > publish=False
143 143 > EOF
144 144 $ hg outgoing ../push-dest --template='{rev} {phase} {desc|firstline}\n'
145 145 comparing with ../push-dest
146 146 searching for changes
147 147 0 public A
148 148 1 public B
149 149 2 draft C
150 150 3 draft D
151 151 6 draft B'
152 152 $ hg outgoing -r 'branch(default)' ../push-dest --template='{rev} {phase} {desc|firstline}\n'
153 153 comparing with ../push-dest
154 154 searching for changes
155 155 0 public A
156 156 1 public B
157 157 2 draft C
158 158 3 draft D
159 159 6 draft B'
160 160
161 161 $ hg push ../push-dest -f # force because we push multiple heads
162 162 pushing to ../push-dest
163 163 searching for changes
164 164 adding changesets
165 165 adding manifests
166 166 adding file changes
167 167 added 5 changesets with 5 changes to 5 files (+1 heads)
168 168 test-debug-phase: new rev 0: x -> 0
169 169 test-debug-phase: new rev 1: x -> 0
170 170 test-debug-phase: new rev 2: x -> 1
171 171 test-debug-phase: new rev 3: x -> 1
172 172 test-debug-phase: new rev 4: x -> 1
173 173 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
174 174 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
175 175 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> draft
176 176 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> draft
177 177 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> draft
178 178 $ hglog
179 179 7 2 merge B' and E
180 180 6 1 B'
181 181 5 2 H
182 182 4 2 E
183 183 3 1 D
184 184 2 1 C
185 185 1 0 B
186 186 0 0 A
187 187 $ cd ../push-dest
188 188 $ hglog
189 189 4 1 B'
190 190 3 1 D
191 191 2 1 C
192 192 1 0 B
193 193 0 0 A
194 194
195 195 (Issue3303)
196 196 Check that remote secret changeset are ignore when checking creation of remote heads
197 197
198 198 We add a secret head into the push destination. This secret head shadows a
199 199 visible shared between the initial repo and the push destination.
200 200
201 201 $ hg up -q 4 # B'
202 202 $ mkcommit Z --config phases.new-commit=secret
203 203 test-debug-phase: new rev 5: x -> 2
204 204 test-hook-close-phase: 2713879da13d6eea1ff22b442a5a87cb31a7ce6a: -> secret
205 205 $ hg phase .
206 206 5: secret
207 207
208 208 We now try to push a new public changeset that descend from the common public
209 209 head shadowed by the remote secret head.
210 210
211 211 $ cd ../initialrepo
212 212 $ hg up -q 6 #B'
213 213 $ mkcommit I
214 214 test-debug-phase: new rev 8: x -> 1
215 215 created new head
216 216 test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061: -> draft
217 217 $ hg push ../push-dest
218 218 pushing to ../push-dest
219 219 searching for changes
220 220 adding changesets
221 221 adding manifests
222 222 adding file changes
223 223 added 1 changesets with 1 changes to 1 files (+1 heads)
224 224 test-debug-phase: new rev 6: x -> 1
225 225 test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061: -> draft
226 226
227 227 :note: The "(+1 heads)" is wrong as we do not had any visible head
228 228
229 229 check that branch cache with "served" filter are properly computed and stored
230 230
231 231 $ ls ../push-dest/.hg/cache/branch2*
232 232 ../push-dest/.hg/cache/branch2-base
233 233 ../push-dest/.hg/cache/branch2-served
234 234 $ cat ../push-dest/.hg/cache/branch2-served
235 235 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
236 236 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
237 237 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
238 238 $ hg heads -R ../push-dest --template '{rev}:{node} {phase}\n' #update visible cache too
239 239 6:6d6770faffce199f1fddd1cf87f6f026138cf061 draft
240 240 5:2713879da13d6eea1ff22b442a5a87cb31a7ce6a secret
241 241 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e draft
242 242 $ ls ../push-dest/.hg/cache/branch2*
243 243 ../push-dest/.hg/cache/branch2-base
244 244 ../push-dest/.hg/cache/branch2-served
245 245 ../push-dest/.hg/cache/branch2-visible
246 246 $ cat ../push-dest/.hg/cache/branch2-served
247 247 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
248 248 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
249 249 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
250 250 $ cat ../push-dest/.hg/cache/branch2-visible
251 251 6d6770faffce199f1fddd1cf87f6f026138cf061 6
252 252 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
253 253 2713879da13d6eea1ff22b442a5a87cb31a7ce6a o default
254 254 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
255 255
256 256
257 257 Restore condition prior extra insertion.
258 258 $ hg -q --config extensions.mq= strip .
259 259 $ hg up -q 7
260 260 $ cd ..
261 261
262 262 Test secret changeset are not pull
263 263
264 264 $ hg init pull-dest
265 265 $ cd pull-dest
266 266 $ hg pull ../initialrepo
267 267 pulling from ../initialrepo
268 268 requesting all changes
269 269 adding changesets
270 270 adding manifests
271 271 adding file changes
272 272 added 5 changesets with 5 changes to 5 files (+1 heads)
273 273 new changesets 4a2df7238c3b:cf9fe039dfd6
274 274 test-debug-phase: new rev 0: x -> 0
275 275 test-debug-phase: new rev 1: x -> 0
276 276 test-debug-phase: new rev 2: x -> 0
277 277 test-debug-phase: new rev 3: x -> 0
278 278 test-debug-phase: new rev 4: x -> 0
279 279 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
280 280 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
281 281 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
282 282 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
283 283 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
284 284 (run 'hg heads' to see heads, 'hg merge' to merge)
285 285 $ hglog
286 286 4 0 B'
287 287 3 0 D
288 288 2 0 C
289 289 1 0 B
290 290 0 0 A
291 291 $ cd ..
292 292
293 293 But secret can still be bundled explicitly
294 294
295 295 $ cd initialrepo
296 296 $ hg bundle --base '4^' -r 'children(4)' ../secret-bundle.hg
297 297 4 changesets found
298 298 $ cd ..
299 299
300 300 Test secret changeset are not cloned
301 301 (during local clone)
302 302
303 303 $ hg clone -qU initialrepo clone-dest
304 304 test-debug-phase: new rev 0: x -> 0
305 305 test-debug-phase: new rev 1: x -> 0
306 306 test-debug-phase: new rev 2: x -> 0
307 307 test-debug-phase: new rev 3: x -> 0
308 308 test-debug-phase: new rev 4: x -> 0
309 309 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
310 310 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
311 311 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
312 312 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
313 313 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
314 314 $ hglog -R clone-dest
315 315 4 0 B'
316 316 3 0 D
317 317 2 0 C
318 318 1 0 B
319 319 0 0 A
320 320
321 321 Test summary
322 322
323 323 $ hg summary -R clone-dest --verbose
324 324 parent: -1:000000000000 (no revision checked out)
325 325 branch: default
326 326 commit: (clean)
327 327 update: 5 new changesets (update)
328 328 $ hg summary -R initialrepo
329 329 parent: 7:17a481b3bccb tip
330 330 merge B' and E
331 331 branch: default
332 332 commit: (clean) (secret)
333 333 update: 1 new changesets, 2 branch heads (merge)
334 334 phases: 3 draft, 3 secret
335 335 $ hg summary -R initialrepo --quiet
336 336 parent: 7:17a481b3bccb tip
337 337 update: 1 new changesets, 2 branch heads (merge)
338 338
339 339 Test revset
340 340
341 341 $ cd initialrepo
342 342 $ hglog -r 'public()'
343 343 0 0 A
344 344 1 0 B
345 345 $ hglog -r 'draft()'
346 346 2 1 C
347 347 3 1 D
348 348 6 1 B'
349 349 $ hglog -r 'secret()'
350 350 4 2 E
351 351 5 2 H
352 352 7 2 merge B' and E
353 353
354 354 test that phase are displayed in log at debug level
355 355
356 356 $ hg log --debug
357 357 changeset: 7:17a481b3bccb796c0521ae97903d81c52bfee4af
358 358 tag: tip
359 359 phase: secret
360 360 parent: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
361 361 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
362 362 manifest: 7:5e724ffacba267b2ab726c91fc8b650710deaaa8
363 363 user: test
364 364 date: Thu Jan 01 00:00:00 1970 +0000
365 365 files+: C D E
366 366 extra: branch=default
367 367 description:
368 368 merge B' and E
369 369
370 370
371 371 changeset: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
372 372 phase: draft
373 373 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
374 374 parent: -1:0000000000000000000000000000000000000000
375 375 manifest: 6:ab8bfef2392903058bf4ebb9e7746e8d7026b27a
376 376 user: test
377 377 date: Thu Jan 01 00:00:00 1970 +0000
378 378 files+: B'
379 379 extra: branch=default
380 380 description:
381 381 B'
382 382
383 383
384 384 changeset: 5:a030c6be5127abc010fcbff1851536552e6951a8
385 385 phase: secret
386 386 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
387 387 parent: -1:0000000000000000000000000000000000000000
388 388 manifest: 5:5c710aa854874fe3d5fa7192e77bdb314cc08b5a
389 389 user: test
390 390 date: Thu Jan 01 00:00:00 1970 +0000
391 391 files+: H
392 392 extra: branch=default
393 393 description:
394 394 H
395 395
396 396
397 397 changeset: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
398 398 phase: secret
399 399 parent: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
400 400 parent: -1:0000000000000000000000000000000000000000
401 401 manifest: 4:7173fd1c27119750b959e3a0f47ed78abe75d6dc
402 402 user: test
403 403 date: Thu Jan 01 00:00:00 1970 +0000
404 404 files+: E
405 405 extra: branch=default
406 406 description:
407 407 E
408 408
409 409
410 410 changeset: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
411 411 phase: draft
412 412 parent: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
413 413 parent: -1:0000000000000000000000000000000000000000
414 414 manifest: 3:6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c
415 415 user: test
416 416 date: Thu Jan 01 00:00:00 1970 +0000
417 417 files+: D
418 418 extra: branch=default
419 419 description:
420 420 D
421 421
422 422
423 423 changeset: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
424 424 phase: draft
425 425 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
426 426 parent: -1:0000000000000000000000000000000000000000
427 427 manifest: 2:66a5a01817fdf5239c273802b5b7618d051c89e4
428 428 user: test
429 429 date: Thu Jan 01 00:00:00 1970 +0000
430 430 files+: C
431 431 extra: branch=default
432 432 description:
433 433 C
434 434
435 435
436 436 changeset: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
437 437 phase: public
438 438 parent: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
439 439 parent: -1:0000000000000000000000000000000000000000
440 440 manifest: 1:cb5cbbc1bfbf24cc34b9e8c16914e9caa2d2a7fd
441 441 user: test
442 442 date: Thu Jan 01 00:00:00 1970 +0000
443 443 files+: B
444 444 extra: branch=default
445 445 description:
446 446 B
447 447
448 448
449 449 changeset: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
450 450 phase: public
451 451 parent: -1:0000000000000000000000000000000000000000
452 452 parent: -1:0000000000000000000000000000000000000000
453 453 manifest: 0:007d8c9d88841325f5c6b06371b35b4e8a2b1a83
454 454 user: test
455 455 date: Thu Jan 01 00:00:00 1970 +0000
456 456 files+: A
457 457 extra: branch=default
458 458 description:
459 459 A
460 460
461 461
462 462
463 463
464 464 (Issue3707)
465 465 test invalid phase name
466 466
467 467 $ mkcommit I --config phases.new-commit='babar'
468 468 transaction abort!
469 469 rollback completed
470 470 abort: phases.new-commit: not a valid phase name ('babar')
471 471 [255]
472 472 Test phase command
473 473 ===================
474 474
475 475 initial picture
476 476
477 477 $ hg log -G --template "{rev} {phase} {desc}\n"
478 478 @ 7 secret merge B' and E
479 479 |\
480 480 | o 6 draft B'
481 481 | |
482 482 +---o 5 secret H
483 483 | |
484 484 o | 4 secret E
485 485 | |
486 486 o | 3 draft D
487 487 | |
488 488 o | 2 draft C
489 489 |/
490 490 o 1 public B
491 491 |
492 492 o 0 public A
493 493
494 494
495 495 display changesets phase
496 496
497 497 (mixing -r and plain rev specification)
498 498
499 499 $ hg phase 1::4 -r 7
500 500 1: public
501 501 2: draft
502 502 3: draft
503 503 4: secret
504 504 7: secret
505 505
506 506
507 507 move changeset forward
508 508
509 509 (with -r option)
510 510
511 511 $ hg phase --public -r 2
512 512 test-debug-phase: move rev 2: 1 -> 0
513 513 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: draft -> public
514 514 $ hg log -G --template "{rev} {phase} {desc}\n"
515 515 @ 7 secret merge B' and E
516 516 |\
517 517 | o 6 draft B'
518 518 | |
519 519 +---o 5 secret H
520 520 | |
521 521 o | 4 secret E
522 522 | |
523 523 o | 3 draft D
524 524 | |
525 525 o | 2 public C
526 526 |/
527 527 o 1 public B
528 528 |
529 529 o 0 public A
530 530
531 531
532 532 move changeset backward
533 533
534 534 (without -r option)
535 535
536 536 $ hg phase --draft --force 2
537 537 test-debug-phase: move rev 2: 0 -> 1
538 538 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: public -> draft
539 539 $ hg log -G --template "{rev} {phase} {desc}\n"
540 540 @ 7 secret merge B' and E
541 541 |\
542 542 | o 6 draft B'
543 543 | |
544 544 +---o 5 secret H
545 545 | |
546 546 o | 4 secret E
547 547 | |
548 548 o | 3 draft D
549 549 | |
550 550 o | 2 draft C
551 551 |/
552 552 o 1 public B
553 553 |
554 554 o 0 public A
555 555
556 556
557 557 move changeset forward and backward
558 558
559 559 $ hg phase --draft --force 1::4
560 560 test-debug-phase: move rev 1: 0 -> 1
561 561 test-debug-phase: move rev 4: 2 -> 1
562 562 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: public -> draft
563 563 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: secret -> draft
564 564 $ hg log -G --template "{rev} {phase} {desc}\n"
565 565 @ 7 secret merge B' and E
566 566 |\
567 567 | o 6 draft B'
568 568 | |
569 569 +---o 5 secret H
570 570 | |
571 571 o | 4 draft E
572 572 | |
573 573 o | 3 draft D
574 574 | |
575 575 o | 2 draft C
576 576 |/
577 577 o 1 draft B
578 578 |
579 579 o 0 public A
580 580
581 581 test partial failure
582 582
583 583 $ hg phase --public 7
584 584 test-debug-phase: move rev 1: 1 -> 0
585 585 test-debug-phase: move rev 2: 1 -> 0
586 586 test-debug-phase: move rev 3: 1 -> 0
587 587 test-debug-phase: move rev 4: 1 -> 0
588 588 test-debug-phase: move rev 6: 1 -> 0
589 589 test-debug-phase: move rev 7: 2 -> 0
590 590 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> public
591 591 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: draft -> public
592 592 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: draft -> public
593 593 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: draft -> public
594 594 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: draft -> public
595 595 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: secret -> public
596 596 $ hg phase --draft '5 or 7'
597 597 test-debug-phase: move rev 5: 2 -> 1
598 598 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: secret -> draft
599 599 cannot move 1 changesets to a higher phase, use --force
600 600 phase changed for 1 changesets
601 601 [1]
602 602 $ hg log -G --template "{rev} {phase} {desc}\n"
603 603 @ 7 public merge B' and E
604 604 |\
605 605 | o 6 public B'
606 606 | |
607 607 +---o 5 draft H
608 608 | |
609 609 o | 4 public E
610 610 | |
611 611 o | 3 public D
612 612 | |
613 613 o | 2 public C
614 614 |/
615 615 o 1 public B
616 616 |
617 617 o 0 public A
618 618
619 619
620 620 test complete failure
621 621
622 622 $ hg phase --draft 7
623 623 cannot move 1 changesets to a higher phase, use --force
624 624 no phases changed
625 625 [1]
626 626
627 627 $ cd ..
628 628
629 629 test hidden changeset are not cloned as public (issue3935)
630 630
631 631 $ cd initialrepo
632 632
633 633 (enabling evolution)
634 634 $ cat >> $HGRCPATH << EOF
635 635 > [experimental]
636 636 > evolution.createmarkers=True
637 637 > EOF
638 638
639 639 (making a changeset hidden; H in that case)
640 640 $ hg debugobsolete `hg id --debug -r 5`
641 641 obsoleted 1 changesets
642 642
643 643 $ cd ..
644 644 $ hg clone initialrepo clonewithobs
645 645 requesting all changes
646 646 adding changesets
647 647 adding manifests
648 648 adding file changes
649 649 added 7 changesets with 6 changes to 6 files
650 650 new changesets 4a2df7238c3b:17a481b3bccb
651 651 test-debug-phase: new rev 0: x -> 0
652 652 test-debug-phase: new rev 1: x -> 0
653 653 test-debug-phase: new rev 2: x -> 0
654 654 test-debug-phase: new rev 3: x -> 0
655 655 test-debug-phase: new rev 4: x -> 0
656 656 test-debug-phase: new rev 5: x -> 0
657 657 test-debug-phase: new rev 6: x -> 0
658 658 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
659 659 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
660 660 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
661 661 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
662 662 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: -> public
663 663 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
664 664 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: -> public
665 665 updating to branch default
666 666 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
667 667 $ cd clonewithobs
668 668 $ hg log -G --template "{rev} {phase} {desc}\n"
669 669 @ 6 public merge B' and E
670 670 |\
671 671 | o 5 public B'
672 672 | |
673 673 o | 4 public E
674 674 | |
675 675 o | 3 public D
676 676 | |
677 677 o | 2 public C
678 678 |/
679 679 o 1 public B
680 680 |
681 681 o 0 public A
682 682
683 683
684 684 test verify repo containing hidden changesets, which should not abort just
685 685 because repo.cancopy() is False
686 686
687 687 $ cd ../initialrepo
688 688 $ hg verify
689 689 checking changesets
690 690 checking manifests
691 691 crosschecking files in changesets and manifests
692 692 checking files
693 693 checked 8 changesets with 7 changes to 7 files
694 694
695 695 $ cd ..
696 696
697 697 check whether HG_PENDING makes pending changes only in related
698 698 repositories visible to an external hook.
699 699
700 700 (emulate a transaction running concurrently by copied
701 701 .hg/phaseroots.pending in subsequent test)
702 702
703 703 $ cat > $TESTTMP/savepending.sh <<EOF
704 704 > cp .hg/store/phaseroots.pending .hg/store/phaseroots.pending.saved
705 705 > exit 1 # to avoid changing phase for subsequent tests
706 706 > EOF
707 707 $ cd push-dest
708 708 $ hg phase 6
709 709 6: draft
710 710 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" phase -f -s 6
711 711 transaction abort!
712 712 rollback completed
713 713 abort: pretxnclose hook exited with status 1
714 714 [255]
715 715 $ cp .hg/store/phaseroots.pending.saved .hg/store/phaseroots.pending
716 716
717 717 (check (in)visibility of phaseroot while transaction running in repo)
718 718
719 719 $ cat > $TESTTMP/checkpending.sh <<EOF
720 720 > echo '@initialrepo'
721 721 > hg -R "$TESTTMP/initialrepo" phase 7
722 722 > echo '@push-dest'
723 723 > hg -R "$TESTTMP/push-dest" phase 6
724 724 > exit 1 # to avoid changing phase for subsequent tests
725 725 > EOF
726 726 $ cd ../initialrepo
727 727 $ hg phase 7
728 728 7: public
729 729 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" phase -f -s 7
730 730 @initialrepo
731 731 7: secret
732 732 @push-dest
733 733 6: draft
734 734 transaction abort!
735 735 rollback completed
736 736 abort: pretxnclose hook exited with status 1
737 737 [255]
738 738
739 739 Check that pretxnclose-phase hook can control phase movement
740 740
741 741 $ hg phase --force b3325c91a4d9 --secret
742 742 test-debug-phase: move rev 3: 0 -> 2
743 743 test-debug-phase: move rev 4: 0 -> 2
744 744 test-debug-phase: move rev 5: 1 -> 2
745 745 test-debug-phase: move rev 7: 0 -> 2
746 746 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: public -> secret
747 747 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: public -> secret
748 748 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: draft -> secret
749 749 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: public -> secret
750 750 $ hg log -G -T phases
751 751 @ changeset: 7:17a481b3bccb
752 752 |\ tag: tip
753 753 | | phase: secret
754 754 | | parent: 6:cf9fe039dfd6
755 755 | | parent: 4:a603bfb5a83e
756 756 | | user: test
757 757 | | date: Thu Jan 01 00:00:00 1970 +0000
758 758 | | summary: merge B' and E
759 759 | |
760 760 | o changeset: 6:cf9fe039dfd6
761 761 | | phase: public
762 762 | | parent: 1:27547f69f254
763 763 | | user: test
764 764 | | date: Thu Jan 01 00:00:00 1970 +0000
765 765 | | summary: B'
766 766 | |
767 767 o | changeset: 4:a603bfb5a83e
768 768 | | phase: secret
769 769 | | user: test
770 770 | | date: Thu Jan 01 00:00:00 1970 +0000
771 771 | | summary: E
772 772 | |
773 773 o | changeset: 3:b3325c91a4d9
774 774 | | phase: secret
775 775 | | user: test
776 776 | | date: Thu Jan 01 00:00:00 1970 +0000
777 777 | | summary: D
778 778 | |
779 779 o | changeset: 2:f838bfaca5c7
780 780 |/ phase: public
781 781 | user: test
782 782 | date: Thu Jan 01 00:00:00 1970 +0000
783 783 | summary: C
784 784 |
785 785 o changeset: 1:27547f69f254
786 786 | phase: public
787 787 | user: test
788 788 | date: Thu Jan 01 00:00:00 1970 +0000
789 789 | summary: B
790 790 |
791 791 o changeset: 0:4a2df7238c3b
792 792 phase: public
793 793 user: test
794 794 date: Thu Jan 01 00:00:00 1970 +0000
795 795 summary: A
796 796
797 797
798 798 Install a hook that prevent b3325c91a4d9 to become public
799 799
800 800 $ cat >> .hg/hgrc << EOF
801 801 > [hooks]
802 802 > pretxnclose-phase.nopublish_D = sh -c "(echo \$HG_NODE| grep -v b3325c91a4d9>/dev/null) || [ 'public' != \$HG_PHASE ]"
803 803 > EOF
804 804
805 805 Try various actions. only the draft move should succeed
806 806
807 807 $ hg phase --public b3325c91a4d9
808 808 transaction abort!
809 809 rollback completed
810 810 abort: pretxnclose-phase.nopublish_D hook exited with status 1
811 811 [255]
812 812 $ hg phase --public a603bfb5a83e
813 813 transaction abort!
814 814 rollback completed
815 815 abort: pretxnclose-phase.nopublish_D hook exited with status 1
816 816 [255]
817 817 $ hg phase --draft 17a481b3bccb
818 818 test-debug-phase: move rev 3: 2 -> 1
819 819 test-debug-phase: move rev 4: 2 -> 1
820 820 test-debug-phase: move rev 7: 2 -> 1
821 821 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: secret -> draft
822 822 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: secret -> draft
823 823 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: secret -> draft
824 824 $ hg phase --public 17a481b3bccb
825 825 transaction abort!
826 826 rollback completed
827 827 abort: pretxnclose-phase.nopublish_D hook exited with status 1
828 828 [255]
829 829
830 830 $ cd ..
831 831
832 832 Test for the "internal" phase
833 833 =============================
834 834
835 835 Check we deny its usage on older repository
836 836
837 837 $ hg init no-internal-phase --config format.internal-phase=no
838 838 $ cd no-internal-phase
839 839 $ cat .hg/requires
840 840 dotencode
841 841 fncache
842 842 generaldelta
843 843 revlogv1
844 844 store
845 845 $ echo X > X
846 846 $ hg add X
847 847 $ hg status
848 848 A X
849 849 $ hg --config "phases.new-commit=internal" commit -m "my test internal commit" 2>&1 | grep ProgrammingError
850 850 ** ProgrammingError: this repository does not support the internal phase
851 851 raise error.ProgrammingError(msg)
852 852 mercurial.error.ProgrammingError: this repository does not support the internal phase
853 $ hg --config "phases.new-commit=archived" commit -m "my test archived commit" 2>&1 | grep ProgrammingError
854 ** ProgrammingError: this repository does not support the archived phase
855 raise error.ProgrammingError(msg)
856 mercurial.error.ProgrammingError: this repository does not support the archived phase
853 857
854 858 $ cd ..
855 859
856 860 Check it works fine with repository that supports it.
857 861
858 862 $ hg init internal-phase --config format.internal-phase=yes
859 863 $ cd internal-phase
860 864 $ cat .hg/requires
861 865 dotencode
862 866 fncache
863 867 generaldelta
864 868 internal-phase
865 869 revlogv1
866 870 store
867 871 $ mkcommit A
868 872 test-debug-phase: new rev 0: x -> 1
869 873 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> draft
870 874
871 875 Commit an internal changesets
872 876
873 877 $ echo B > B
874 878 $ hg add B
875 879 $ hg status
876 880 A B
877 881 $ hg --config "phases.new-commit=internal" commit -m "my test internal commit"
878 882 test-debug-phase: new rev 1: x -> 96
879 883 test-hook-close-phase: c01c42dffc7f81223397e99652a0703f83e1c5ea: -> internal
880 884
881 Usual visibility rules apply when working directory parents
885 The changeset is a working parent descendant.
886 Per the usual visibility rules, it is made visible.
882 887
883 888 $ hg log -G -l 3
884 889 @ changeset: 1:c01c42dffc7f
885 890 | tag: tip
886 891 | user: test
887 892 | date: Thu Jan 01 00:00:00 1970 +0000
888 893 | summary: my test internal commit
889 894 |
890 895 o changeset: 0:4a2df7238c3b
891 896 user: test
892 897 date: Thu Jan 01 00:00:00 1970 +0000
893 898 summary: A
894 899
895 900
896 901 Commit is hidden as expected
897 902
898 903 $ hg up 0
899 904 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
900 905 $ hg log -G
901 906 @ changeset: 0:4a2df7238c3b
902 907 tag: tip
903 908 user: test
904 909 date: Thu Jan 01 00:00:00 1970 +0000
905 910 summary: A
906 911
912
913 Test for archived phase
914 -----------------------
915
916 Commit an archived changesets
917
918 $ echo B > B
919 $ hg add B
920 $ hg status
921 A B
922 $ hg --config "phases.new-commit=archived" commit -m "my test archived commit"
923 test-debug-phase: new rev 2: x -> 32
924 test-hook-close-phase: 8df5997c3361518f733d1ae67cd3adb9b0eaf125: -> archived
925
926 The changeset is a working parent descendant.
927 Per the usual visibility rules, it is made visible.
928
929 $ hg log -G -l 3
930 @ changeset: 2:8df5997c3361
931 | tag: tip
932 | parent: 0:4a2df7238c3b
933 | user: test
934 | date: Thu Jan 01 00:00:00 1970 +0000
935 | summary: my test archived commit
936 |
937 o changeset: 0:4a2df7238c3b
938 user: test
939 date: Thu Jan 01 00:00:00 1970 +0000
940 summary: A
941
942
943 Commit is hidden as expected
944
945 $ hg up 0
946 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
947 $ hg log -G
948 @ changeset: 0:4a2df7238c3b
949 tag: tip
950 user: test
951 date: Thu Jan 01 00:00:00 1970 +0000
952 summary: A
953
General Comments 0
You need to be logged in to leave comments. Login now