##// END OF EJS Templates
py3: do not pass a list of iterators to computephasesmapsets()
Yuya Nishihara -
r36642:31187662 default
parent child Browse files
Show More
@@ -1,679 +1,680 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 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 nativeroots.append(map(repo.changelog.rev, self.phaseroots[phase]))
265 nativeroots.append(pycompat.maplist(repo.changelog.rev,
266 self.phaseroots[phase]))
266 267 return repo.changelog.computephases(nativeroots)
267 268
268 269 def _computephaserevspure(self, repo):
269 270 repo = repo.unfiltered()
270 271 cl = repo.changelog
271 272 self._phasesets = [set() for phase in allphases]
272 273 roots = pycompat.maplist(cl.rev, self.phaseroots[secret])
273 274 if roots:
274 275 ps = set(cl.descendants(roots))
275 276 for root in roots:
276 277 ps.add(root)
277 278 self._phasesets[secret] = ps
278 279 roots = pycompat.maplist(cl.rev, self.phaseroots[draft])
279 280 if roots:
280 281 ps = set(cl.descendants(roots))
281 282 for root in roots:
282 283 ps.add(root)
283 284 ps.difference_update(self._phasesets[secret])
284 285 self._phasesets[draft] = ps
285 286 self._loadedrevslen = len(cl)
286 287
287 288 def loadphaserevs(self, repo):
288 289 """ensure phase information is loaded in the object"""
289 290 if self._phasesets is None:
290 291 try:
291 292 res = self._getphaserevsnative(repo)
292 293 self._loadedrevslen, self._phasesets = res
293 294 except AttributeError:
294 295 self._computephaserevspure(repo)
295 296
296 297 def invalidate(self):
297 298 self._loadedrevslen = 0
298 299 self._phasesets = None
299 300
300 301 def phase(self, repo, rev):
301 302 # We need a repo argument here to be able to build _phasesets
302 303 # if necessary. The repository instance is not stored in
303 304 # phasecache to avoid reference cycles. The changelog instance
304 305 # is not stored because it is a filecache() property and can
305 306 # be replaced without us being notified.
306 307 if rev == nullrev:
307 308 return public
308 309 if rev < nullrev:
309 310 raise ValueError(_('cannot lookup negative revision'))
310 311 if rev >= self._loadedrevslen:
311 312 self.invalidate()
312 313 self.loadphaserevs(repo)
313 314 for phase in trackedphases:
314 315 if rev in self._phasesets[phase]:
315 316 return phase
316 317 return public
317 318
318 319 def write(self):
319 320 if not self.dirty:
320 321 return
321 322 f = self.opener('phaseroots', 'w', atomictemp=True, checkambig=True)
322 323 try:
323 324 self._write(f)
324 325 finally:
325 326 f.close()
326 327
327 328 def _write(self, fp):
328 329 for phase, roots in enumerate(self.phaseroots):
329 330 for h in sorted(roots):
330 331 fp.write('%i %s\n' % (phase, hex(h)))
331 332 self.dirty = False
332 333
333 334 def _updateroots(self, phase, newroots, tr):
334 335 self.phaseroots[phase] = newroots
335 336 self.invalidate()
336 337 self.dirty = True
337 338
338 339 tr.addfilegenerator('phase', ('phaseroots',), self._write)
339 340 tr.hookargs['phases_moved'] = '1'
340 341
341 342 def registernew(self, repo, tr, targetphase, nodes):
342 343 repo = repo.unfiltered()
343 344 self._retractboundary(repo, tr, targetphase, nodes)
344 345 if tr is not None and 'phases' in tr.changes:
345 346 phasetracking = tr.changes['phases']
346 347 torev = repo.changelog.rev
347 348 phase = self.phase
348 349 for n in nodes:
349 350 rev = torev(n)
350 351 revphase = phase(repo, rev)
351 352 _trackphasechange(phasetracking, rev, None, revphase)
352 353 repo.invalidatevolatilesets()
353 354
354 355 def advanceboundary(self, repo, tr, targetphase, nodes):
355 356 """Set all 'nodes' to phase 'targetphase'
356 357
357 358 Nodes with a phase lower than 'targetphase' are not affected.
358 359 """
359 360 # Be careful to preserve shallow-copied values: do not update
360 361 # phaseroots values, replace them.
361 362 if tr is None:
362 363 phasetracking = None
363 364 else:
364 365 phasetracking = tr.changes.get('phases')
365 366
366 367 repo = repo.unfiltered()
367 368
368 369 delroots = [] # set of root deleted by this path
369 370 for phase in xrange(targetphase + 1, len(allphases)):
370 371 # filter nodes that are not in a compatible phase already
371 372 nodes = [n for n in nodes
372 373 if self.phase(repo, repo[n].rev()) >= phase]
373 374 if not nodes:
374 375 break # no roots to move anymore
375 376
376 377 olds = self.phaseroots[phase]
377 378
378 379 affected = repo.revs('%ln::%ln', olds, nodes)
379 380 for r in affected:
380 381 _trackphasechange(phasetracking, r, self.phase(repo, r),
381 382 targetphase)
382 383
383 384 roots = set(ctx.node() for ctx in repo.set(
384 385 'roots((%ln::) - %ld)', olds, affected))
385 386 if olds != roots:
386 387 self._updateroots(phase, roots, tr)
387 388 # some roots may need to be declared for lower phases
388 389 delroots.extend(olds - roots)
389 390 # declare deleted root in the target phase
390 391 if targetphase != 0:
391 392 self._retractboundary(repo, tr, targetphase, delroots)
392 393 repo.invalidatevolatilesets()
393 394
394 395 def retractboundary(self, repo, tr, targetphase, nodes):
395 396 oldroots = self.phaseroots[:targetphase + 1]
396 397 if tr is None:
397 398 phasetracking = None
398 399 else:
399 400 phasetracking = tr.changes.get('phases')
400 401 repo = repo.unfiltered()
401 402 if (self._retractboundary(repo, tr, targetphase, nodes)
402 403 and phasetracking is not None):
403 404
404 405 # find the affected revisions
405 406 new = self.phaseroots[targetphase]
406 407 old = oldroots[targetphase]
407 408 affected = set(repo.revs('(%ln::) - (%ln::)', new, old))
408 409
409 410 # find the phase of the affected revision
410 411 for phase in xrange(targetphase, -1, -1):
411 412 if phase:
412 413 roots = oldroots[phase]
413 414 revs = set(repo.revs('%ln::%ld', roots, affected))
414 415 affected -= revs
415 416 else: # public phase
416 417 revs = affected
417 418 for r in revs:
418 419 _trackphasechange(phasetracking, r, phase, targetphase)
419 420 repo.invalidatevolatilesets()
420 421
421 422 def _retractboundary(self, repo, tr, targetphase, nodes):
422 423 # Be careful to preserve shallow-copied values: do not update
423 424 # phaseroots values, replace them.
424 425
425 426 repo = repo.unfiltered()
426 427 currentroots = self.phaseroots[targetphase]
427 428 finalroots = oldroots = set(currentroots)
428 429 newroots = [n for n in nodes
429 430 if self.phase(repo, repo[n].rev()) < targetphase]
430 431 if newroots:
431 432
432 433 if nullid in newroots:
433 434 raise error.Abort(_('cannot change null revision phase'))
434 435 currentroots = currentroots.copy()
435 436 currentroots.update(newroots)
436 437
437 438 # Only compute new roots for revs above the roots that are being
438 439 # retracted.
439 440 minnewroot = min(repo[n].rev() for n in newroots)
440 441 aboveroots = [n for n in currentroots
441 442 if repo[n].rev() >= minnewroot]
442 443 updatedroots = repo.set('roots(%ln::)', aboveroots)
443 444
444 445 finalroots = set(n for n in currentroots if repo[n].rev() <
445 446 minnewroot)
446 447 finalroots.update(ctx.node() for ctx in updatedroots)
447 448 if finalroots != oldroots:
448 449 self._updateroots(targetphase, finalroots, tr)
449 450 return True
450 451 return False
451 452
452 453 def filterunknown(self, repo):
453 454 """remove unknown nodes from the phase boundary
454 455
455 456 Nothing is lost as unknown nodes only hold data for their descendants.
456 457 """
457 458 filtered = False
458 459 nodemap = repo.changelog.nodemap # to filter unknown nodes
459 460 for phase, nodes in enumerate(self.phaseroots):
460 461 missing = sorted(node for node in nodes if node not in nodemap)
461 462 if missing:
462 463 for mnode in missing:
463 464 repo.ui.debug(
464 465 'removing unknown node %s from %i-phase boundary\n'
465 466 % (short(mnode), phase))
466 467 nodes.symmetric_difference_update(missing)
467 468 filtered = True
468 469 if filtered:
469 470 self.dirty = True
470 471 # filterunknown is called by repo.destroyed, we may have no changes in
471 472 # root but _phasesets contents is certainly invalid (or at least we
472 473 # have not proper way to check that). related to issue 3858.
473 474 #
474 475 # The other caller is __init__ that have no _phasesets initialized
475 476 # anyway. If this change we should consider adding a dedicated
476 477 # "destroyed" function to phasecache or a proper cache key mechanism
477 478 # (see branchmap one)
478 479 self.invalidate()
479 480
480 481 def advanceboundary(repo, tr, targetphase, nodes):
481 482 """Add nodes to a phase changing other nodes phases if necessary.
482 483
483 484 This function move boundary *forward* this means that all nodes
484 485 are set in the target phase or kept in a *lower* phase.
485 486
486 487 Simplify boundary to contains phase roots only."""
487 488 phcache = repo._phasecache.copy()
488 489 phcache.advanceboundary(repo, tr, targetphase, nodes)
489 490 repo._phasecache.replace(phcache)
490 491
491 492 def retractboundary(repo, tr, targetphase, nodes):
492 493 """Set nodes back to a phase changing other nodes phases if
493 494 necessary.
494 495
495 496 This function move boundary *backward* this means that all nodes
496 497 are set in the target phase or kept in a *higher* phase.
497 498
498 499 Simplify boundary to contains phase roots only."""
499 500 phcache = repo._phasecache.copy()
500 501 phcache.retractboundary(repo, tr, targetphase, nodes)
501 502 repo._phasecache.replace(phcache)
502 503
503 504 def registernew(repo, tr, targetphase, nodes):
504 505 """register a new revision and its phase
505 506
506 507 Code adding revisions to the repository should use this function to
507 508 set new changeset in their target phase (or higher).
508 509 """
509 510 phcache = repo._phasecache.copy()
510 511 phcache.registernew(repo, tr, targetphase, nodes)
511 512 repo._phasecache.replace(phcache)
512 513
513 514 def listphases(repo):
514 515 """List phases root for serialization over pushkey"""
515 516 # Use ordered dictionary so behavior is deterministic.
516 517 keys = util.sortdict()
517 518 value = '%i' % draft
518 519 cl = repo.unfiltered().changelog
519 520 for root in repo._phasecache.phaseroots[draft]:
520 521 if repo._phasecache.phase(repo, cl.rev(root)) <= draft:
521 522 keys[hex(root)] = value
522 523
523 524 if repo.publishing():
524 525 # Add an extra data to let remote know we are a publishing
525 526 # repo. Publishing repo can't just pretend they are old repo.
526 527 # When pushing to a publishing repo, the client still need to
527 528 # push phase boundary
528 529 #
529 530 # Push do not only push changeset. It also push phase data.
530 531 # New phase data may apply to common changeset which won't be
531 532 # push (as they are common). Here is a very simple example:
532 533 #
533 534 # 1) repo A push changeset X as draft to repo B
534 535 # 2) repo B make changeset X public
535 536 # 3) repo B push to repo A. X is not pushed but the data that
536 537 # X as now public should
537 538 #
538 539 # The server can't handle it on it's own as it has no idea of
539 540 # client phase data.
540 541 keys['publishing'] = 'True'
541 542 return keys
542 543
543 544 def pushphase(repo, nhex, oldphasestr, newphasestr):
544 545 """List phases root for serialization over pushkey"""
545 546 repo = repo.unfiltered()
546 547 with repo.lock():
547 548 currentphase = repo[nhex].phase()
548 549 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
549 550 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
550 551 if currentphase == oldphase and newphase < oldphase:
551 552 with repo.transaction('pushkey-phase') as tr:
552 553 advanceboundary(repo, tr, newphase, [bin(nhex)])
553 554 return True
554 555 elif currentphase == newphase:
555 556 # raced, but got correct result
556 557 return True
557 558 else:
558 559 return False
559 560
560 561 def subsetphaseheads(repo, subset):
561 562 """Finds the phase heads for a subset of a history
562 563
563 564 Returns a list indexed by phase number where each item is a list of phase
564 565 head nodes.
565 566 """
566 567 cl = repo.changelog
567 568
568 569 headsbyphase = [[] for i in allphases]
569 570 # No need to keep track of secret phase; any heads in the subset that
570 571 # are not mentioned are implicitly secret.
571 572 for phase in allphases[:-1]:
572 573 revset = "heads(%%ln & %s())" % phasenames[phase]
573 574 headsbyphase[phase] = [cl.node(r) for r in repo.revs(revset, subset)]
574 575 return headsbyphase
575 576
576 577 def updatephases(repo, trgetter, headsbyphase):
577 578 """Updates the repo with the given phase heads"""
578 579 # Now advance phase boundaries of all but secret phase
579 580 #
580 581 # run the update (and fetch transaction) only if there are actually things
581 582 # to update. This avoid creating empty transaction during no-op operation.
582 583
583 584 for phase in allphases[:-1]:
584 585 revset = '%%ln - %s()' % phasenames[phase]
585 586 heads = [c.node() for c in repo.set(revset, headsbyphase[phase])]
586 587 if heads:
587 588 advanceboundary(repo, trgetter(), phase, heads)
588 589
589 590 def analyzeremotephases(repo, subset, roots):
590 591 """Compute phases heads and root in a subset of node from root dict
591 592
592 593 * subset is heads of the subset
593 594 * roots is {<nodeid> => phase} mapping. key and value are string.
594 595
595 596 Accept unknown element input
596 597 """
597 598 repo = repo.unfiltered()
598 599 # build list from dictionary
599 600 draftroots = []
600 601 nodemap = repo.changelog.nodemap # to filter unknown nodes
601 602 for nhex, phase in roots.iteritems():
602 603 if nhex == 'publishing': # ignore data related to publish option
603 604 continue
604 605 node = bin(nhex)
605 606 phase = int(phase)
606 607 if phase == public:
607 608 if node != nullid:
608 609 repo.ui.warn(_('ignoring inconsistent public root'
609 610 ' from remote: %s\n') % nhex)
610 611 elif phase == draft:
611 612 if node in nodemap:
612 613 draftroots.append(node)
613 614 else:
614 615 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
615 616 % (phase, nhex))
616 617 # compute heads
617 618 publicheads = newheads(repo, subset, draftroots)
618 619 return publicheads, draftroots
619 620
620 621 class remotephasessummary(object):
621 622 """summarize phase information on the remote side
622 623
623 624 :publishing: True is the remote is publishing
624 625 :publicheads: list of remote public phase heads (nodes)
625 626 :draftheads: list of remote draft phase heads (nodes)
626 627 :draftroots: list of remote draft phase root (nodes)
627 628 """
628 629
629 630 def __init__(self, repo, remotesubset, remoteroots):
630 631 unfi = repo.unfiltered()
631 632 self._allremoteroots = remoteroots
632 633
633 634 self.publishing = remoteroots.get('publishing', False)
634 635
635 636 ana = analyzeremotephases(repo, remotesubset, remoteroots)
636 637 self.publicheads, self.draftroots = ana
637 638 # Get the list of all "heads" revs draft on remote
638 639 dheads = unfi.set('heads(%ln::%ln)', self.draftroots, remotesubset)
639 640 self.draftheads = [c.node() for c in dheads]
640 641
641 642 def newheads(repo, heads, roots):
642 643 """compute new head of a subset minus another
643 644
644 645 * `heads`: define the first subset
645 646 * `roots`: define the second we subtract from the first"""
646 647 repo = repo.unfiltered()
647 648 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
648 649 heads, roots, roots, heads)
649 650 return [c.node() for c in revset]
650 651
651 652
652 653 def newcommitphase(ui):
653 654 """helper to get the target phase of new commit
654 655
655 656 Handle all possible values for the phases.new-commit options.
656 657
657 658 """
658 659 v = ui.config('phases', 'new-commit')
659 660 try:
660 661 return phasenames.index(v)
661 662 except ValueError:
662 663 try:
663 664 return int(v)
664 665 except ValueError:
665 666 msg = _("phases.new-commit: not a valid phase name ('%s')")
666 667 raise error.ConfigError(msg % v)
667 668
668 669 def hassecret(repo):
669 670 """utility function that check if a repo have any secret changeset."""
670 671 return bool(repo._phasecache.phaseroots[2])
671 672
672 673 def preparehookargs(node, old, new):
673 674 if old is None:
674 675 old = ''
675 676 else:
676 677 old = phasenames[old]
677 678 return {'node': node,
678 679 'oldphase': old,
679 680 'phase': phasenames[new]}
General Comments 0
You need to be logged in to leave comments. Login now