hgext/inhibit.py
branchmercurial-3.4
changeset 1607 3c7f98753e37
parent 1599 dcf145d0ce21
child 1608 e359d33856c3
equal deleted inserted replaced
1599:dcf145d0ce21 1607:3c7f98753e37
     1 """reduce the changesets evolution feature scope for early and noob friendly ui
       
     2 
       
     3 the full scale changeset evolution have some massive bleeding edge and it is
       
     4 very easy for people not very intimate with the concept to end up in intricate
       
     5 situation. in order to get some of the benefit sooner, this extension is
       
     6 disabling some of the less polished aspect of evolution. it should gradually
       
     7 get thinner and thinner as changeset evolution will get more polished. this
       
     8 extension is only recommended for large scale organisations. individual user
       
     9 should probably stick on using evolution in its current state, understand its
       
    10 concept and provide feedback
       
    11 
       
    12 This extension provides the ability to "inhibit" obsolescence markers. obsolete 
       
    13 revision can be cheaply brought back to life that way. 
       
    14 However as the inhibitor are not fitting in an append only model, this is 
       
    15 incompatible with sharing mutable history.
       
    16 """
       
    17 from mercurial import localrepo
       
    18 from mercurial import obsolete
       
    19 from mercurial import extensions
       
    20 from mercurial import cmdutil
       
    21 from mercurial import error
       
    22 from mercurial import scmutil
       
    23 from mercurial import commands
       
    24 from mercurial import lock as lockmod
       
    25 from mercurial import bookmarks
       
    26 from mercurial import util
       
    27 from mercurial.i18n import _
       
    28 
       
    29 cmdtable = {}
       
    30 command = cmdutil.command(cmdtable)
       
    31 
       
    32 def _inhibitenabled(repo):
       
    33     return util.safehasattr(repo, '_obsinhibit')
       
    34 
       
    35 def reposetup(ui, repo):
       
    36 
       
    37     class obsinhibitedrepo(repo.__class__):
       
    38 
       
    39         @localrepo.storecache('obsinhibit')
       
    40         def _obsinhibit(self):
       
    41             # XXX we should make sure it is invalidated by transaction failure
       
    42             obsinhibit = set()
       
    43             raw = self.svfs.tryread('obsinhibit')
       
    44             for i in xrange(0, len(raw), 20):
       
    45                 obsinhibit.add(raw[i:i+20])
       
    46             return obsinhibit
       
    47 
       
    48         def commit(self, *args, **kwargs):
       
    49             newnode = super(obsinhibitedrepo, self).commit(*args, **kwargs)
       
    50             if newnode is not None:
       
    51                 _inhibitmarkers(repo, [newnode])
       
    52             return newnode
       
    53 
       
    54     repo.__class__ = obsinhibitedrepo
       
    55 
       
    56 def _update(orig, ui, repo, *args, **kwargs):
       
    57     """
       
    58     When moving to a commit we want to inhibit any obsolete commit affecting
       
    59     the changeset we are updating to. In other words we don't want any visible
       
    60     commit to be obsolete.
       
    61     """
       
    62     wlock = None
       
    63     try:
       
    64         # Evolve is running a hook on lock release to display a warning message 
       
    65         # if the workind dir's parent is obsolete.
       
    66         # We take the lock here to make sure that we inhibit the parent before
       
    67         # that hook get a chance to run.
       
    68         wlock = repo.wlock()
       
    69         res = orig(ui, repo, *args, **kwargs)
       
    70         newhead = repo['.'].node()
       
    71         _inhibitmarkers(repo, [newhead])
       
    72         return res
       
    73     finally:
       
    74         lockmod.release(wlock)
       
    75 
       
    76 def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs):
       
    77     """ Add inhibition markers to every obsolete bookmarks """
       
    78     repo = bkmstoreinst._repo
       
    79     bkmstorenodes = [repo[v].node() for v in bkmstoreinst.values()]
       
    80     _inhibitmarkers(repo, bkmstorenodes)
       
    81     return orig(bkmstoreinst, *args, **kwargs)
       
    82 
       
    83 def _bookmark(orig, ui, repo, *bookmarks, **opts):
       
    84     """ Add a -D option to the bookmark command, map it to prune -B """
       
    85     haspruneopt = opts.get('prune', False)
       
    86     if not haspruneopt:
       
    87         return orig(ui, repo, *bookmarks, **opts)
       
    88 
       
    89     # Call prune -B
       
    90     evolve = extensions.find('evolve')
       
    91     optsdict = {
       
    92         'new': [],
       
    93         'succ': [],
       
    94         'rev': [],
       
    95         'bookmark': bookmarks[0],
       
    96         'keep': None,
       
    97         'biject': False,
       
    98     }
       
    99     evolve.cmdprune(ui, repo, **optsdict)
       
   100 
       
   101 # obsolescence inhibitor
       
   102 ########################
       
   103 
       
   104 def _schedulewrite(tr, obsinhibit):
       
   105     """Make sure on disk content will be updated on transaction commit"""
       
   106     def writer(fp):
       
   107         """Serialize the inhibited list to disk.
       
   108         """
       
   109         raw = ''.join(obsinhibit)
       
   110         fp.write(raw)
       
   111     tr.addfilegenerator('obsinhibit', ('obsinhibit',), writer)
       
   112     tr.hookargs['obs_inbihited'] = '1'
       
   113 
       
   114 def _filterpublic(repo, nodes):
       
   115     """filter out inhibitor on public changeset
       
   116 
       
   117     Public changesets are already immune to obsolescence"""
       
   118     getrev = repo.changelog.nodemap.get
       
   119     getphase = repo._phasecache.phase
       
   120     return (n for n in repo._obsinhibit
       
   121             if getrev(n) is not None and getphase(repo, getrev(n)))
       
   122 
       
   123 def _inhibitmarkers(repo, nodes):
       
   124     """add marker inhibitor for all obsolete revision under <nodes>
       
   125 
       
   126     Content of <nodes> and all mutable ancestors are considered. Marker for
       
   127     obsolete revision only are created.
       
   128     """
       
   129     if not _inhibitenabled(repo):
       
   130         return
       
   131 
       
   132     newinhibit = repo.set('::%ln and obsolete()', nodes)
       
   133     if newinhibit:
       
   134         lock = tr = None
       
   135         try:
       
   136             lock = repo.lock()
       
   137             tr = repo.transaction('obsinhibit')
       
   138             repo._obsinhibit.update(c.node() for c in newinhibit)
       
   139             _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
       
   140             repo.invalidatevolatilesets()
       
   141             tr.close()
       
   142         finally:
       
   143             lockmod.release(tr, lock)
       
   144 
       
   145 def _deinhibitmarkers(repo, nodes):
       
   146     """lift obsolescence inhibition on a set of nodes
       
   147 
       
   148     This will be triggered when inhibited nodes received new obsolescence
       
   149     markers. Otherwise the new obsolescence markers would also be inhibited.
       
   150     """
       
   151     if not _inhibitenabled(repo):
       
   152         return
       
   153 
       
   154     deinhibited = repo._obsinhibit & set(nodes)
       
   155     if deinhibited:
       
   156         tr = repo.transaction('obsinhibit')
       
   157         try:
       
   158             repo._obsinhibit -= deinhibited
       
   159             _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
       
   160             repo.invalidatevolatilesets()
       
   161             tr.close()
       
   162         finally:
       
   163             tr.release()
       
   164 
       
   165 def _createmarkers(orig, repo, relations, flag=0, date=None, metadata=None):
       
   166     """wrap markers create to make sure we de-inhibit target nodes"""
       
   167     # wrapping transactio to unify the one in each function
       
   168     lock = tr = None
       
   169     try:
       
   170         lock = repo.lock()
       
   171         tr = repo.transaction('add-obsolescence-marker')
       
   172         orig(repo, relations, flag, date, metadata)
       
   173         precs = (r[0].node() for r in relations)
       
   174         _deinhibitmarkers(repo, precs)
       
   175         tr.close()
       
   176     finally:
       
   177         lockmod.release(tr, lock)
       
   178 
       
   179 def transactioncallback(orig, repo, desc, *args, **kwargs):
       
   180     """ Wrap localrepo.transaction to inhibit new obsolete changes """
       
   181     def inhibitposttransaction(transaction):
       
   182         # At the end of the transaction we catch all the new visible and
       
   183         # obsolete commit to inhibit them
       
   184         visibleobsolete = repo.revs('obsolete() - hidden()')
       
   185         ignoreset = set(getattr(repo, '_rebaseset', []))
       
   186         visibleobsolete = list(r for r in visibleobsolete if r not in ignoreset)
       
   187         if visibleobsolete:
       
   188             _inhibitmarkers(repo, [repo[r].node() for r in visibleobsolete])
       
   189     transaction = orig(repo, desc, *args, **kwargs)
       
   190     if desc != 'strip' and _inhibitenabled(repo):
       
   191         transaction.addpostclose('inhibitposttransaction', inhibitposttransaction)
       
   192     return transaction
       
   193 
       
   194 def extsetup(ui):
       
   195     # lets wrap the computation of the obsolete set
       
   196     # We apply inhibition there
       
   197     obsfunc = obsolete.cachefuncs['obsolete']
       
   198     def _computeobsoleteset(repo):
       
   199         """remove any inhibited nodes from the obsolete set
       
   200 
       
   201         This will trickle down to other part of mercurial (hidden, log, etc)"""
       
   202         obs = obsfunc(repo)
       
   203         if _inhibitenabled(repo):
       
   204             getrev = repo.changelog.nodemap.get
       
   205             for n in repo._obsinhibit:
       
   206                 obs.discard(getrev(n))
       
   207         return obs
       
   208     try:
       
   209         extensions.find('directaccess')
       
   210     except KeyError:
       
   211         errormsg = _('cannot use inhibit without the direct access extension\n')
       
   212         hint = _("(please enable it or inhibit won\'t work)\n")
       
   213         ui.warn(errormsg)
       
   214         ui.warn(hint)
       
   215         return
       
   216 
       
   217     # Wrapping this to inhibit obsolete revs resulting from a transaction
       
   218     extensions.wrapfunction(localrepo.localrepository,
       
   219                             'transaction', transactioncallback)
       
   220 
       
   221     obsolete.cachefuncs['obsolete'] = _computeobsoleteset
       
   222     # wrap create marker to make it able to lift the inhibition
       
   223     extensions.wrapfunction(obsolete, 'createmarkers', _createmarkers)
       
   224     # drop divergence computation since it is incompatible with "light revive"
       
   225     obsolete.cachefuncs['divergent'] = lambda repo: set()
       
   226     # drop bumped computation since it is incompatible with "light revive"
       
   227     obsolete.cachefuncs['bumped'] = lambda repo: set()
       
   228     # wrap update to make sure that no obsolete commit is visible after an
       
   229     # update
       
   230     extensions.wrapcommand(commands.table, 'update', _update)
       
   231     # There are two ways to save bookmark changes during a transation, we
       
   232     # wrap both to add inhibition markers.
       
   233     extensions.wrapfunction(bookmarks.bmstore, 'recordchange', _bookmarkchanged)
       
   234     extensions.wrapfunction(bookmarks.bmstore, 'write', _bookmarkchanged)
       
   235     # Add bookmark -D option
       
   236     entry = extensions.wrapcommand(commands.table, 'bookmark', _bookmark)
       
   237     entry[1].append(('D','prune',None,
       
   238                     _('delete the bookmark and prune the commits underneath')))
       
   239 
       
   240 @command('debugobsinhibit', [], '')
       
   241 def cmddebugobsinhibit(ui, repo, *revs):
       
   242     """inhibit obsolescence markers effect on a set of revs"""
       
   243     nodes = (repo[r].node() for r in scmutil.revrange(repo, revs))
       
   244     _inhibitmarkers(repo, nodes)