##// END OF EJS Templates
phases: enforce internal phase support...
Boris Feld -
r39335:7775c1fb default
parent child Browse files
Show More
@@ -1,734 +1,741 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 132 allphases = range(internal + 1)
133 133 trackedphases = allphases[1:]
134 134 # record phase names
135 135 phasenames = [None] * len(allphases)
136 136 phasenames[:3] = ['public', 'draft', 'secret']
137 137 phasenames[internal] = 'internal'
138 138 # record phase property
139 139 mutablephases = tuple(allphases[1:])
140 140 remotehiddenphases = tuple(allphases[2:])
141 141 localhiddenphases = tuple(p for p in allphases if p & HIDEABLE_FLAG)
142 142
143 def supportinternal(repo):
144 """True if the internal phase can be used on a repository"""
145 return 'internal-phase' in repo.requirements
146
143 147 def _readroots(repo, phasedefaults=None):
144 148 """Read phase roots from disk
145 149
146 150 phasedefaults is a list of fn(repo, roots) callable, which are
147 151 executed if the phase roots file does not exist. When phases are
148 152 being initialized on an existing repository, this could be used to
149 153 set selected changesets phase to something else than public.
150 154
151 155 Return (roots, dirty) where dirty is true if roots differ from
152 156 what is being stored.
153 157 """
154 158 repo = repo.unfiltered()
155 159 dirty = False
156 160 roots = [set() for i in allphases]
157 161 try:
158 162 f, pending = txnutil.trypending(repo.root, repo.svfs, 'phaseroots')
159 163 try:
160 164 for line in f:
161 165 phase, nh = line.split()
162 166 roots[int(phase)].add(bin(nh))
163 167 finally:
164 168 f.close()
165 169 except IOError as inst:
166 170 if inst.errno != errno.ENOENT:
167 171 raise
168 172 if phasedefaults:
169 173 for f in phasedefaults:
170 174 roots = f(repo, roots)
171 175 dirty = True
172 176 return roots, dirty
173 177
174 178 def binaryencode(phasemapping):
175 179 """encode a 'phase -> nodes' mapping into a binary stream
176 180
177 181 Since phases are integer the mapping is actually a python list:
178 182 [[PUBLIC_HEADS], [DRAFTS_HEADS], [SECRET_HEADS]]
179 183 """
180 184 binarydata = []
181 185 for phase, nodes in enumerate(phasemapping):
182 186 for head in nodes:
183 187 binarydata.append(_fphasesentry.pack(phase, head))
184 188 return ''.join(binarydata)
185 189
186 190 def binarydecode(stream):
187 191 """decode a binary stream into a 'phase -> nodes' mapping
188 192
189 193 Since phases are integer the mapping is actually a python list."""
190 194 headsbyphase = [[] for i in allphases]
191 195 entrysize = _fphasesentry.size
192 196 while True:
193 197 entry = stream.read(entrysize)
194 198 if len(entry) < entrysize:
195 199 if entry:
196 200 raise error.Abort(_('bad phase-heads stream'))
197 201 break
198 202 phase, node = _fphasesentry.unpack(entry)
199 203 headsbyphase[phase].append(node)
200 204 return headsbyphase
201 205
202 206 def _trackphasechange(data, rev, old, new):
203 207 """add a phase move the <data> dictionnary
204 208
205 209 If data is None, nothing happens.
206 210 """
207 211 if data is None:
208 212 return
209 213 existing = data.get(rev)
210 214 if existing is not None:
211 215 old = existing[0]
212 216 data[rev] = (old, new)
213 217
214 218 class phasecache(object):
215 219 def __init__(self, repo, phasedefaults, _load=True):
216 220 if _load:
217 221 # Cheap trick to allow shallow-copy without copy module
218 222 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
219 223 self._loadedrevslen = 0
220 224 self._phasesets = None
221 225 self.filterunknown(repo)
222 226 self.opener = repo.svfs
223 227
224 228 def getrevset(self, repo, phases, subset=None):
225 229 """return a smartset for the given phases"""
226 230 self.loadphaserevs(repo) # ensure phase's sets are loaded
227 231 phases = set(phases)
228 232 if public not in phases:
229 233 # fast path: _phasesets contains the interesting sets,
230 234 # might only need a union and post-filtering.
231 235 if len(phases) == 1:
232 236 [p] = phases
233 237 revs = self._phasesets[p]
234 238 else:
235 239 revs = set.union(*[self._phasesets[p] for p in phases])
236 240 if repo.changelog.filteredrevs:
237 241 revs = revs - repo.changelog.filteredrevs
238 242 if subset is None:
239 243 return smartset.baseset(revs)
240 244 else:
241 245 return subset & smartset.baseset(revs)
242 246 else:
243 247 phases = set(allphases).difference(phases)
244 248 if not phases:
245 249 return smartset.fullreposet(repo)
246 250 if len(phases) == 1:
247 251 [p] = phases
248 252 revs = self._phasesets[p]
249 253 else:
250 254 revs = set.union(*[self._phasesets[p] for p in phases])
251 255 if subset is None:
252 256 subset = smartset.fullreposet(repo)
253 257 if not revs:
254 258 return subset
255 259 return subset.filter(lambda r: r not in revs)
256 260
257 261 def copy(self):
258 262 # Shallow copy meant to ensure isolation in
259 263 # advance/retractboundary(), nothing more.
260 264 ph = self.__class__(None, None, _load=False)
261 265 ph.phaseroots = self.phaseroots[:]
262 266 ph.dirty = self.dirty
263 267 ph.opener = self.opener
264 268 ph._loadedrevslen = self._loadedrevslen
265 269 ph._phasesets = self._phasesets
266 270 return ph
267 271
268 272 def replace(self, phcache):
269 273 """replace all values in 'self' with content of phcache"""
270 274 for a in ('phaseroots', 'dirty', 'opener', '_loadedrevslen',
271 275 '_phasesets'):
272 276 setattr(self, a, getattr(phcache, a))
273 277
274 278 def _getphaserevsnative(self, repo):
275 279 repo = repo.unfiltered()
276 280 nativeroots = []
277 281 for phase in trackedphases:
278 282 nativeroots.append(pycompat.maplist(repo.changelog.rev,
279 283 self.phaseroots[phase]))
280 284 return repo.changelog.computephases(nativeroots)
281 285
282 286 def _computephaserevspure(self, repo):
283 287 repo = repo.unfiltered()
284 288 cl = repo.changelog
285 289 self._phasesets = [set() for phase in allphases]
286 290 lowerroots = set()
287 291 for phase in reversed(trackedphases):
288 292 roots = pycompat.maplist(cl.rev, self.phaseroots[phase])
289 293 if roots:
290 294 ps = set(cl.descendants(roots))
291 295 for root in roots:
292 296 ps.add(root)
293 297 ps.difference_update(lowerroots)
294 298 lowerroots.update(ps)
295 299 self._phasesets[phase] = ps
296 300 self._loadedrevslen = len(cl)
297 301
298 302 def loadphaserevs(self, repo):
299 303 """ensure phase information is loaded in the object"""
300 304 if self._phasesets is None:
301 305 try:
302 306 res = self._getphaserevsnative(repo)
303 307 self._loadedrevslen, self._phasesets = res
304 308 except AttributeError:
305 309 self._computephaserevspure(repo)
306 310
307 311 def invalidate(self):
308 312 self._loadedrevslen = 0
309 313 self._phasesets = None
310 314
311 315 def phase(self, repo, rev):
312 316 # We need a repo argument here to be able to build _phasesets
313 317 # if necessary. The repository instance is not stored in
314 318 # phasecache to avoid reference cycles. The changelog instance
315 319 # is not stored because it is a filecache() property and can
316 320 # be replaced without us being notified.
317 321 if rev == nullrev:
318 322 return public
319 323 if rev < nullrev:
320 324 raise ValueError(_('cannot lookup negative revision'))
321 325 if rev >= self._loadedrevslen:
322 326 self.invalidate()
323 327 self.loadphaserevs(repo)
324 328 for phase in trackedphases:
325 329 if rev in self._phasesets[phase]:
326 330 return phase
327 331 return public
328 332
329 333 def write(self):
330 334 if not self.dirty:
331 335 return
332 336 f = self.opener('phaseroots', 'w', atomictemp=True, checkambig=True)
333 337 try:
334 338 self._write(f)
335 339 finally:
336 340 f.close()
337 341
338 342 def _write(self, fp):
339 343 for phase, roots in enumerate(self.phaseroots):
340 344 for h in sorted(roots):
341 345 fp.write('%i %s\n' % (phase, hex(h)))
342 346 self.dirty = False
343 347
344 348 def _updateroots(self, phase, newroots, tr):
345 349 self.phaseroots[phase] = newroots
346 350 self.invalidate()
347 351 self.dirty = True
348 352
349 353 tr.addfilegenerator('phase', ('phaseroots',), self._write)
350 354 tr.hookargs['phases_moved'] = '1'
351 355
352 356 def registernew(self, repo, tr, targetphase, nodes):
353 357 repo = repo.unfiltered()
354 358 self._retractboundary(repo, tr, targetphase, nodes)
355 359 if tr is not None and 'phases' in tr.changes:
356 360 phasetracking = tr.changes['phases']
357 361 torev = repo.changelog.rev
358 362 phase = self.phase
359 363 for n in nodes:
360 364 rev = torev(n)
361 365 revphase = phase(repo, rev)
362 366 _trackphasechange(phasetracking, rev, None, revphase)
363 367 repo.invalidatevolatilesets()
364 368
365 369 def advanceboundary(self, repo, tr, targetphase, nodes, dryrun=None):
366 370 """Set all 'nodes' to phase 'targetphase'
367 371
368 372 Nodes with a phase lower than 'targetphase' are not affected.
369 373
370 374 If dryrun is True, no actions will be performed
371 375
372 376 Returns a set of revs whose phase is changed or should be changed
373 377 """
374 378 # Be careful to preserve shallow-copied values: do not update
375 379 # phaseroots values, replace them.
376 380 if tr is None:
377 381 phasetracking = None
378 382 else:
379 383 phasetracking = tr.changes.get('phases')
380 384
381 385 repo = repo.unfiltered()
382 386
383 387 changes = set() # set of revisions to be changed
384 388 delroots = [] # set of root deleted by this path
385 389 for phase in pycompat.xrange(targetphase + 1, len(allphases)):
386 390 # filter nodes that are not in a compatible phase already
387 391 nodes = [n for n in nodes
388 392 if self.phase(repo, repo[n].rev()) >= phase]
389 393 if not nodes:
390 394 break # no roots to move anymore
391 395
392 396 olds = self.phaseroots[phase]
393 397
394 398 affected = repo.revs('%ln::%ln', olds, nodes)
395 399 changes.update(affected)
396 400 if dryrun:
397 401 continue
398 402 for r in affected:
399 403 _trackphasechange(phasetracking, r, self.phase(repo, r),
400 404 targetphase)
401 405
402 406 roots = set(ctx.node() for ctx in repo.set(
403 407 'roots((%ln::) - %ld)', olds, affected))
404 408 if olds != roots:
405 409 self._updateroots(phase, roots, tr)
406 410 # some roots may need to be declared for lower phases
407 411 delroots.extend(olds - roots)
408 412 if not dryrun:
409 413 # declare deleted root in the target phase
410 414 if targetphase != 0:
411 415 self._retractboundary(repo, tr, targetphase, delroots)
412 416 repo.invalidatevolatilesets()
413 417 return changes
414 418
415 419 def retractboundary(self, repo, tr, targetphase, nodes):
416 420 oldroots = self.phaseroots[:targetphase + 1]
417 421 if tr is None:
418 422 phasetracking = None
419 423 else:
420 424 phasetracking = tr.changes.get('phases')
421 425 repo = repo.unfiltered()
422 426 if (self._retractboundary(repo, tr, targetphase, nodes)
423 427 and phasetracking is not None):
424 428
425 429 # find the affected revisions
426 430 new = self.phaseroots[targetphase]
427 431 old = oldroots[targetphase]
428 432 affected = set(repo.revs('(%ln::) - (%ln::)', new, old))
429 433
430 434 # find the phase of the affected revision
431 435 for phase in pycompat.xrange(targetphase, -1, -1):
432 436 if phase:
433 437 roots = oldroots[phase]
434 438 revs = set(repo.revs('%ln::%ld', roots, affected))
435 439 affected -= revs
436 440 else: # public phase
437 441 revs = affected
438 442 for r in revs:
439 443 _trackphasechange(phasetracking, r, phase, targetphase)
440 444 repo.invalidatevolatilesets()
441 445
442 446 def _retractboundary(self, repo, tr, targetphase, nodes):
443 447 # Be careful to preserve shallow-copied values: do not update
444 448 # phaseroots values, replace them.
449 if targetphase == internal and not supportinternal(repo):
450 msg = 'this repository does not support the internal phase'
451 raise error.ProgrammingError(msg)
445 452
446 453 repo = repo.unfiltered()
447 454 currentroots = self.phaseroots[targetphase]
448 455 finalroots = oldroots = set(currentroots)
449 456 newroots = [n for n in nodes
450 457 if self.phase(repo, repo[n].rev()) < targetphase]
451 458 if newroots:
452 459
453 460 if nullid in newroots:
454 461 raise error.Abort(_('cannot change null revision phase'))
455 462 currentroots = currentroots.copy()
456 463 currentroots.update(newroots)
457 464
458 465 # Only compute new roots for revs above the roots that are being
459 466 # retracted.
460 467 minnewroot = min(repo[n].rev() for n in newroots)
461 468 aboveroots = [n for n in currentroots
462 469 if repo[n].rev() >= minnewroot]
463 470 updatedroots = repo.set('roots(%ln::)', aboveroots)
464 471
465 472 finalroots = set(n for n in currentroots if repo[n].rev() <
466 473 minnewroot)
467 474 finalroots.update(ctx.node() for ctx in updatedroots)
468 475 if finalroots != oldroots:
469 476 self._updateroots(targetphase, finalroots, tr)
470 477 return True
471 478 return False
472 479
473 480 def filterunknown(self, repo):
474 481 """remove unknown nodes from the phase boundary
475 482
476 483 Nothing is lost as unknown nodes only hold data for their descendants.
477 484 """
478 485 filtered = False
479 486 nodemap = repo.changelog.nodemap # to filter unknown nodes
480 487 for phase, nodes in enumerate(self.phaseroots):
481 488 missing = sorted(node for node in nodes if node not in nodemap)
482 489 if missing:
483 490 for mnode in missing:
484 491 repo.ui.debug(
485 492 'removing unknown node %s from %i-phase boundary\n'
486 493 % (short(mnode), phase))
487 494 nodes.symmetric_difference_update(missing)
488 495 filtered = True
489 496 if filtered:
490 497 self.dirty = True
491 498 # filterunknown is called by repo.destroyed, we may have no changes in
492 499 # root but _phasesets contents is certainly invalid (or at least we
493 500 # have not proper way to check that). related to issue 3858.
494 501 #
495 502 # The other caller is __init__ that have no _phasesets initialized
496 503 # anyway. If this change we should consider adding a dedicated
497 504 # "destroyed" function to phasecache or a proper cache key mechanism
498 505 # (see branchmap one)
499 506 self.invalidate()
500 507
501 508 def advanceboundary(repo, tr, targetphase, nodes, dryrun=None):
502 509 """Add nodes to a phase changing other nodes phases if necessary.
503 510
504 511 This function move boundary *forward* this means that all nodes
505 512 are set in the target phase or kept in a *lower* phase.
506 513
507 514 Simplify boundary to contains phase roots only.
508 515
509 516 If dryrun is True, no actions will be performed
510 517
511 518 Returns a set of revs whose phase is changed or should be changed
512 519 """
513 520 phcache = repo._phasecache.copy()
514 521 changes = phcache.advanceboundary(repo, tr, targetphase, nodes,
515 522 dryrun=dryrun)
516 523 if not dryrun:
517 524 repo._phasecache.replace(phcache)
518 525 return changes
519 526
520 527 def retractboundary(repo, tr, targetphase, nodes):
521 528 """Set nodes back to a phase changing other nodes phases if
522 529 necessary.
523 530
524 531 This function move boundary *backward* this means that all nodes
525 532 are set in the target phase or kept in a *higher* phase.
526 533
527 534 Simplify boundary to contains phase roots only."""
528 535 phcache = repo._phasecache.copy()
529 536 phcache.retractboundary(repo, tr, targetphase, nodes)
530 537 repo._phasecache.replace(phcache)
531 538
532 539 def registernew(repo, tr, targetphase, nodes):
533 540 """register a new revision and its phase
534 541
535 542 Code adding revisions to the repository should use this function to
536 543 set new changeset in their target phase (or higher).
537 544 """
538 545 phcache = repo._phasecache.copy()
539 546 phcache.registernew(repo, tr, targetphase, nodes)
540 547 repo._phasecache.replace(phcache)
541 548
542 549 def listphases(repo):
543 550 """List phases root for serialization over pushkey"""
544 551 # Use ordered dictionary so behavior is deterministic.
545 552 keys = util.sortdict()
546 553 value = '%i' % draft
547 554 cl = repo.unfiltered().changelog
548 555 for root in repo._phasecache.phaseroots[draft]:
549 556 if repo._phasecache.phase(repo, cl.rev(root)) <= draft:
550 557 keys[hex(root)] = value
551 558
552 559 if repo.publishing():
553 560 # Add an extra data to let remote know we are a publishing
554 561 # repo. Publishing repo can't just pretend they are old repo.
555 562 # When pushing to a publishing repo, the client still need to
556 563 # push phase boundary
557 564 #
558 565 # Push do not only push changeset. It also push phase data.
559 566 # New phase data may apply to common changeset which won't be
560 567 # push (as they are common). Here is a very simple example:
561 568 #
562 569 # 1) repo A push changeset X as draft to repo B
563 570 # 2) repo B make changeset X public
564 571 # 3) repo B push to repo A. X is not pushed but the data that
565 572 # X as now public should
566 573 #
567 574 # The server can't handle it on it's own as it has no idea of
568 575 # client phase data.
569 576 keys['publishing'] = 'True'
570 577 return keys
571 578
572 579 def pushphase(repo, nhex, oldphasestr, newphasestr):
573 580 """List phases root for serialization over pushkey"""
574 581 repo = repo.unfiltered()
575 582 with repo.lock():
576 583 currentphase = repo[nhex].phase()
577 584 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
578 585 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
579 586 if currentphase == oldphase and newphase < oldphase:
580 587 with repo.transaction('pushkey-phase') as tr:
581 588 advanceboundary(repo, tr, newphase, [bin(nhex)])
582 589 return True
583 590 elif currentphase == newphase:
584 591 # raced, but got correct result
585 592 return True
586 593 else:
587 594 return False
588 595
589 596 def subsetphaseheads(repo, subset):
590 597 """Finds the phase heads for a subset of a history
591 598
592 599 Returns a list indexed by phase number where each item is a list of phase
593 600 head nodes.
594 601 """
595 602 cl = repo.changelog
596 603
597 604 headsbyphase = [[] for i in allphases]
598 605 # No need to keep track of secret phase; any heads in the subset that
599 606 # are not mentioned are implicitly secret.
600 607 for phase in allphases[:secret]:
601 608 revset = "heads(%%ln & %s())" % phasenames[phase]
602 609 headsbyphase[phase] = [cl.node(r) for r in repo.revs(revset, subset)]
603 610 return headsbyphase
604 611
605 612 def updatephases(repo, trgetter, headsbyphase):
606 613 """Updates the repo with the given phase heads"""
607 614 # Now advance phase boundaries of all but secret phase
608 615 #
609 616 # run the update (and fetch transaction) only if there are actually things
610 617 # to update. This avoid creating empty transaction during no-op operation.
611 618
612 619 for phase in allphases[:-1]:
613 620 revset = '%ln - _phase(%s)'
614 621 heads = [c.node() for c in repo.set(revset, headsbyphase[phase], phase)]
615 622 if heads:
616 623 advanceboundary(repo, trgetter(), phase, heads)
617 624
618 625 def analyzeremotephases(repo, subset, roots):
619 626 """Compute phases heads and root in a subset of node from root dict
620 627
621 628 * subset is heads of the subset
622 629 * roots is {<nodeid> => phase} mapping. key and value are string.
623 630
624 631 Accept unknown element input
625 632 """
626 633 repo = repo.unfiltered()
627 634 # build list from dictionary
628 635 draftroots = []
629 636 nodemap = repo.changelog.nodemap # to filter unknown nodes
630 637 for nhex, phase in roots.iteritems():
631 638 if nhex == 'publishing': # ignore data related to publish option
632 639 continue
633 640 node = bin(nhex)
634 641 phase = int(phase)
635 642 if phase == public:
636 643 if node != nullid:
637 644 repo.ui.warn(_('ignoring inconsistent public root'
638 645 ' from remote: %s\n') % nhex)
639 646 elif phase == draft:
640 647 if node in nodemap:
641 648 draftroots.append(node)
642 649 else:
643 650 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
644 651 % (phase, nhex))
645 652 # compute heads
646 653 publicheads = newheads(repo, subset, draftroots)
647 654 return publicheads, draftroots
648 655
649 656 class remotephasessummary(object):
650 657 """summarize phase information on the remote side
651 658
652 659 :publishing: True is the remote is publishing
653 660 :publicheads: list of remote public phase heads (nodes)
654 661 :draftheads: list of remote draft phase heads (nodes)
655 662 :draftroots: list of remote draft phase root (nodes)
656 663 """
657 664
658 665 def __init__(self, repo, remotesubset, remoteroots):
659 666 unfi = repo.unfiltered()
660 667 self._allremoteroots = remoteroots
661 668
662 669 self.publishing = remoteroots.get('publishing', False)
663 670
664 671 ana = analyzeremotephases(repo, remotesubset, remoteroots)
665 672 self.publicheads, self.draftroots = ana
666 673 # Get the list of all "heads" revs draft on remote
667 674 dheads = unfi.set('heads(%ln::%ln)', self.draftroots, remotesubset)
668 675 self.draftheads = [c.node() for c in dheads]
669 676
670 677 def newheads(repo, heads, roots):
671 678 """compute new head of a subset minus another
672 679
673 680 * `heads`: define the first subset
674 681 * `roots`: define the second we subtract from the first"""
675 682 # prevent an import cycle
676 683 # phases > dagop > patch > copies > scmutil > obsolete > obsutil > phases
677 684 from . import dagop
678 685
679 686 repo = repo.unfiltered()
680 687 cl = repo.changelog
681 688 rev = cl.nodemap.get
682 689 if not roots:
683 690 return heads
684 691 if not heads or heads == [nullid]:
685 692 return []
686 693 # The logic operated on revisions, convert arguments early for convenience
687 694 new_heads = set(rev(n) for n in heads if n != nullid)
688 695 roots = [rev(n) for n in roots]
689 696 # compute the area we need to remove
690 697 affected_zone = repo.revs("(%ld::%ld)", roots, new_heads)
691 698 # heads in the area are no longer heads
692 699 new_heads.difference_update(affected_zone)
693 700 # revisions in the area have children outside of it,
694 701 # They might be new heads
695 702 candidates = repo.revs("parents(%ld + (%ld and merge())) and not null",
696 703 roots, affected_zone)
697 704 candidates -= affected_zone
698 705 if new_heads or candidates:
699 706 # remove candidate that are ancestors of other heads
700 707 new_heads.update(candidates)
701 708 prunestart = repo.revs("parents(%ld) and not null", new_heads)
702 709 pruned = dagop.reachableroots(repo, candidates, prunestart)
703 710 new_heads.difference_update(pruned)
704 711
705 712 return pycompat.maplist(cl.node, sorted(new_heads))
706 713
707 714 def newcommitphase(ui):
708 715 """helper to get the target phase of new commit
709 716
710 717 Handle all possible values for the phases.new-commit options.
711 718
712 719 """
713 720 v = ui.config('phases', 'new-commit')
714 721 try:
715 722 return phasenames.index(v)
716 723 except ValueError:
717 724 try:
718 725 return int(v)
719 726 except ValueError:
720 727 msg = _("phases.new-commit: not a valid phase name ('%s')")
721 728 raise error.ConfigError(msg % v)
722 729
723 730 def hassecret(repo):
724 731 """utility function that check if a repo have any secret changeset."""
725 732 return bool(repo._phasecache.phaseroots[2])
726 733
727 734 def preparehookargs(node, old, new):
728 735 if old is None:
729 736 old = ''
730 737 else:
731 738 old = phasenames[old]
732 739 return {'node': node,
733 740 'oldphase': old,
734 741 'phase': phasenames[new]}
@@ -1,876 +1,906 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 7 files, 8 changesets, 7 total revisions
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 $ hg init internal-phase
835 Check we deny its usage on older repository
836
837 $ hg init no-internal-phase --config format.internal-phase=no
838 $ cd no-internal-phase
839 $ cat .hg/requires
840 dotencode
841 fncache
842 generaldelta
843 revlogv1
844 store
845 $ echo X > X
846 $ hg add X
847 $ hg status
848 A X
849 $ hg --config "phases.new-commit=internal" commit -m "my test internal commit" 2>&1 | grep ProgrammingError
850 ** ProgrammingError: this repository does not support the internal phase
851 raise error.ProgrammingError(msg)
852 mercurial.error.ProgrammingError: this repository does not support the internal phase
853
854 $ cd ..
855
856 Check it works fine with repository that supports it.
857
858 $ hg init internal-phase --config format.internal-phase=yes
836 859 $ cd internal-phase
860 $ cat .hg/requires
861 dotencode
862 fncache
863 generaldelta
864 internal-phase
865 revlogv1
866 store
837 867 $ mkcommit A
838 868 test-debug-phase: new rev 0: x -> 1
839 869 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> draft
840 870
841 871 Commit an internal changesets
842 872
843 873 $ echo B > B
844 874 $ hg add B
845 875 $ hg status
846 876 A B
847 877 $ hg --config "phases.new-commit=internal" commit -m "my test internal commit"
848 878 test-debug-phase: new rev 1: x -> 96
849 879 test-hook-close-phase: c01c42dffc7f81223397e99652a0703f83e1c5ea: -> internal
850 880
851 881 Usual visibility rules apply when working directory parents
852 882
853 883 $ hg log -G -l 3
854 884 @ changeset: 1:c01c42dffc7f
855 885 | tag: tip
856 886 | user: test
857 887 | date: Thu Jan 01 00:00:00 1970 +0000
858 888 | summary: my test internal commit
859 889 |
860 890 o changeset: 0:4a2df7238c3b
861 891 user: test
862 892 date: Thu Jan 01 00:00:00 1970 +0000
863 893 summary: A
864 894
865 895
866 896 Commit is hidden as expected
867 897
868 898 $ hg up 0
869 899 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
870 900 $ hg log -G
871 901 @ changeset: 0:4a2df7238c3b
872 902 tag: tip
873 903 user: test
874 904 date: Thu Jan 01 00:00:00 1970 +0000
875 905 summary: A
876 906
General Comments 0
You need to be logged in to leave comments. Login now