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