mercurial/phases.py
changeset 16657 b6081c2c4647
parent 16626 503e674fb545
child 16658 6b3d31d04a69
equal deleted inserted replaced
16656:4ae3ba9e4d7a 16657:b6081c2c4647
    97 - content is pushed as draft
    97 - content is pushed as draft
    98 
    98 
    99 """
    99 """
   100 
   100 
   101 import errno
   101 import errno
   102 from node import nullid, bin, hex, short
   102 from node import nullid, nullrev, bin, hex, short
   103 from i18n import _
   103 from i18n import _
   104 
   104 
   105 allphases = public, draft, secret = range(3)
   105 allphases = public, draft, secret = range(3)
   106 trackedphases = allphases[1:]
   106 trackedphases = allphases[1:]
   107 phasenames = ['public', 'draft', 'secret']
   107 phasenames = ['public', 'draft', 'secret']
   122                     % (short(mnode), phase))
   122                     % (short(mnode), phase))
   123             nodes.symmetric_difference_update(missing)
   123             nodes.symmetric_difference_update(missing)
   124             updated = True
   124             updated = True
   125     return updated
   125     return updated
   126 
   126 
   127 def readroots(repo, phasedefaults=None):
   127 def _readroots(repo, phasedefaults=None):
   128     """Read phase roots from disk
   128     """Read phase roots from disk
   129 
   129 
   130     phasedefaults is a list of fn(repo, roots) callable, which are
   130     phasedefaults is a list of fn(repo, roots) callable, which are
   131     executed if the phase roots file does not exist. When phases are
   131     executed if the phase roots file does not exist. When phases are
   132     being initialized on an existing repository, this could be used to
   132     being initialized on an existing repository, this could be used to
   154         dirty = True
   154         dirty = True
   155     if _filterunknown(repo.ui, repo.changelog, roots):
   155     if _filterunknown(repo.ui, repo.changelog, roots):
   156         dirty = True
   156         dirty = True
   157     return roots, dirty
   157     return roots, dirty
   158 
   158 
   159 def writeroots(repo, phaseroots):
   159 class phasecache(object):
   160     """Write phase roots from disk"""
   160     def __init__(self, repo, phasedefaults):
   161     f = repo.sopener('phaseroots', 'w', atomictemp=True)
   161         self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
   162     try:
   162         self.opener = repo.sopener
   163         for phase, roots in enumerate(phaseroots):
   163         self._phaserevs = None
   164             for h in roots:
   164 
   165                 f.write('%i %s\n' % (phase, hex(h)))
   165     def getphaserevs(self, repo, rebuild=False):
   166     finally:
   166         if rebuild or self._phaserevs is None:
   167         f.close()
   167             revs = [public] * len(repo.changelog)
       
   168             for phase in trackedphases:
       
   169                 roots = map(repo.changelog.rev, self.phaseroots[phase])
       
   170                 if roots:
       
   171                     for rev in roots:
       
   172                         revs[rev] = phase
       
   173                     for rev in repo.changelog.descendants(*roots):
       
   174                         revs[rev] = phase
       
   175             self._phaserevs = revs
       
   176         return self._phaserevs
       
   177 
       
   178     def invalidatephaserevs(self):
       
   179         self._phaserevs = None
       
   180 
       
   181     def phase(self, repo, rev):
       
   182         # We need a repo argument here to be able to build _phaserev
       
   183         # if necessary. The repository instance is not stored in
       
   184         # phasecache to avoid reference cycles. The changelog instance
       
   185         # is not stored because it is a filecache() property and can
       
   186         # be replaced without us being notified.
       
   187         if rev == nullrev:
       
   188             return public
       
   189         if self._phaserevs is None or rev >= len(self._phaserevs):
       
   190             self._phaserevs = self.getphaserevs(repo, rebuild=True)
       
   191         return self._phaserevs[rev]
       
   192 
       
   193     def write(self):
       
   194         if not self.dirty:
       
   195             return
       
   196         f = self.opener('phaseroots', 'w', atomictemp=True)
       
   197         try:
       
   198             for phase, roots in enumerate(self.phaseroots):
       
   199                 for h in roots:
       
   200                     f.write('%i %s\n' % (phase, hex(h)))
       
   201         finally:
       
   202             f.close()
       
   203         self.dirty = False
   168 
   204 
   169 def advanceboundary(repo, targetphase, nodes):
   205 def advanceboundary(repo, targetphase, nodes):
   170     """Add nodes to a phase changing other nodes phases if necessary.
   206     """Add nodes to a phase changing other nodes phases if necessary.
   171 
   207 
   172     This function move boundary *forward* this means that all nodes are set
   208     This function move boundary *forward* this means that all nodes are set
   173     in the target phase or kept in a *lower* phase.
   209     in the target phase or kept in a *lower* phase.
   174 
   210 
   175     Simplify boundary to contains phase roots only."""
   211     Simplify boundary to contains phase roots only."""
       
   212     phcache = repo._phasecache
       
   213 
   176     delroots = [] # set of root deleted by this path
   214     delroots = [] # set of root deleted by this path
   177     for phase in xrange(targetphase + 1, len(allphases)):
   215     for phase in xrange(targetphase + 1, len(allphases)):
   178         # filter nodes that are not in a compatible phase already
   216         # filter nodes that are not in a compatible phase already
   179         # XXX rev phase cache might have been invalidated by a previous loop
   217         # XXX rev phase cache might have been invalidated by a previous loop
   180         # XXX we need to be smarter here
   218         # XXX we need to be smarter here
   181         nodes = [n for n in nodes if repo[n].phase() >= phase]
   219         nodes = [n for n in nodes if repo[n].phase() >= phase]
   182         if not nodes:
   220         if not nodes:
   183             break # no roots to move anymore
   221             break # no roots to move anymore
   184         roots = repo._phaseroots[phase]
   222         roots = phcache.phaseroots[phase]
   185         olds = roots.copy()
   223         olds = roots.copy()
   186         ctxs = list(repo.set('roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
   224         ctxs = list(repo.set('roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
   187         roots.clear()
   225         roots.clear()
   188         roots.update(ctx.node() for ctx in ctxs)
   226         roots.update(ctx.node() for ctx in ctxs)
   189         if olds != roots:
   227         if olds != roots:
   190             # invalidate cache (we probably could be smarter here
   228             # invalidate cache (we probably could be smarter here
   191             if '_phaserev' in vars(repo):
   229             phcache.invalidatephaserevs()
   192                 del repo._phaserev
   230             phcache.dirty = True
   193             repo._dirtyphases = True
       
   194             # some roots may need to be declared for lower phases
   231             # some roots may need to be declared for lower phases
   195             delroots.extend(olds - roots)
   232             delroots.extend(olds - roots)
   196         # declare deleted root in the target phase
   233         # declare deleted root in the target phase
   197         if targetphase != 0:
   234         if targetphase != 0:
   198             retractboundary(repo, targetphase, delroots)
   235             retractboundary(repo, targetphase, delroots)
   203 
   240 
   204     This function move boundary *backward* this means that all nodes are set
   241     This function move boundary *backward* this means that all nodes are set
   205     in the target phase or kept in a *higher* phase.
   242     in the target phase or kept in a *higher* phase.
   206 
   243 
   207     Simplify boundary to contains phase roots only."""
   244     Simplify boundary to contains phase roots only."""
   208     currentroots = repo._phaseroots[targetphase]
   245     phcache = repo._phasecache
       
   246 
       
   247     currentroots = phcache.phaseroots[targetphase]
   209     newroots = [n for n in nodes if repo[n].phase() < targetphase]
   248     newroots = [n for n in nodes if repo[n].phase() < targetphase]
   210     if newroots:
   249     if newroots:
   211         currentroots.update(newroots)
   250         currentroots.update(newroots)
   212         ctxs = repo.set('roots(%ln::)', currentroots)
   251         ctxs = repo.set('roots(%ln::)', currentroots)
   213         currentroots.intersection_update(ctx.node() for ctx in ctxs)
   252         currentroots.intersection_update(ctx.node() for ctx in ctxs)
   214         if '_phaserev' in vars(repo):
   253         phcache.invalidatephaserevs()
   215             del repo._phaserev
   254         phcache.dirty = True
   216         repo._dirtyphases = True
       
   217 
   255 
   218 
   256 
   219 def listphases(repo):
   257 def listphases(repo):
   220     """List phases root for serialisation over pushkey"""
   258     """List phases root for serialisation over pushkey"""
   221     keys = {}
   259     keys = {}
   222     value = '%i' % draft
   260     value = '%i' % draft
   223     for root in repo._phaseroots[draft]:
   261     for root in repo._phasecache.phaseroots[draft]:
   224         keys[hex(root)] = value
   262         keys[hex(root)] = value
   225 
   263 
   226     if repo.ui.configbool('phases', 'publish', True):
   264     if repo.ui.configbool('phases', 'publish', True):
   227         # Add an extra data to let remote know we are a publishing repo.
   265         # Add an extra data to let remote know we are a publishing repo.
   228         # Publishing repo can't just pretend they are old repo. When pushing to
   266         # Publishing repo can't just pretend they are old repo. When pushing to
   261         lock.release()
   299         lock.release()
   262 
   300 
   263 def visibleheads(repo):
   301 def visibleheads(repo):
   264     """return the set of visible head of this repo"""
   302     """return the set of visible head of this repo"""
   265     # XXX we want a cache on this
   303     # XXX we want a cache on this
   266     sroots = repo._phaseroots[secret]
   304     sroots = repo._phasecache.phaseroots[secret]
   267     if sroots:
   305     if sroots:
   268         # XXX very slow revset. storing heads or secret "boundary" would help.
   306         # XXX very slow revset. storing heads or secret "boundary" would help.
   269         revset = repo.set('heads(not (%ln::))', sroots)
   307         revset = repo.set('heads(not (%ln::))', sroots)
   270 
   308 
   271         vheads = [ctx.node() for ctx in revset]
   309         vheads = [ctx.node() for ctx in revset]
   277 
   315 
   278 def visiblebranchmap(repo):
   316 def visiblebranchmap(repo):
   279     """return a branchmap for the visible set"""
   317     """return a branchmap for the visible set"""
   280     # XXX Recomputing this data on the fly is very slow.  We should build a
   318     # XXX Recomputing this data on the fly is very slow.  We should build a
   281     # XXX cached version while computin the standard branchmap version.
   319     # XXX cached version while computin the standard branchmap version.
   282     sroots = repo._phaseroots[secret]
   320     sroots = repo._phasecache.phaseroots[secret]
   283     if sroots:
   321     if sroots:
   284         vbranchmap = {}
   322         vbranchmap = {}
   285         for branch, nodes in  repo.branchmap().iteritems():
   323         for branch, nodes in  repo.branchmap().iteritems():
   286             # search for secret heads.
   324             # search for secret heads.
   287             for n in nodes:
   325             for n in nodes: