hgext/inhibit.py
branchstable
changeset 1450 5f6e78aea094
parent 1366 9c3ba42c582a
child 1465 777e5c369d99
equal deleted inserted replaced
1438:3295353b1363 1450:5f6e78aea094
       
     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.i18n import _
       
    27 
       
    28 cmdtable = {}
       
    29 command = cmdutil.command(cmdtable)
       
    30 
       
    31 def reposetup(ui, repo):
       
    32 
       
    33     class obsinhibitedrepo(repo.__class__):
       
    34 
       
    35         @localrepo.storecache('obsinhibit')
       
    36         def _obsinhibit(self):
       
    37             # XXX we should make sure it is invalidated by transaction failure
       
    38             obsinhibit = set()
       
    39             raw = self.sopener.tryread('obsinhibit')
       
    40             for i in xrange(0, len(raw), 20):
       
    41                 obsinhibit.add(raw[i:i+20])
       
    42             return obsinhibit
       
    43 
       
    44         def commit(self, *args, **kwargs):
       
    45             newnode = super(obsinhibitedrepo, self).commit(*args, **kwargs)
       
    46             if newnode is not None:
       
    47                 _inhibitmarkers(repo, [newnode])
       
    48             return newnode
       
    49 
       
    50     repo.__class__ = obsinhibitedrepo
       
    51 
       
    52 def _update(orig, ui, repo, *args, **kwargs):
       
    53     """
       
    54     When moving to a commit we want to inhibit any obsolete commit affecting
       
    55     the changeset we are updating to. In other words we don't want any visible
       
    56     commit to be obsolete.
       
    57     """
       
    58     wlock = None
       
    59     try:
       
    60         # Evolve is running a hook on lock release to display a warning message 
       
    61         # if the workind dir's parent is obsolete.
       
    62         # We take the lock here to make sure that we inhibit the parent before
       
    63         # that hook get a chance to run.
       
    64         wlock = repo.wlock()
       
    65         res = orig(ui, repo, *args, **kwargs)
       
    66         newhead = repo['.'].node()
       
    67         _inhibitmarkers(repo, [newhead])
       
    68         return res
       
    69     finally:
       
    70         lockmod.release(wlock)
       
    71 
       
    72 def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs):
       
    73     """ Add inhibition markers to every obsolete bookmarks """
       
    74     repo = bkmstoreinst._repo
       
    75     bkmstorenodes = [repo[v].node() for v in bkmstoreinst.values()]
       
    76     _inhibitmarkers(repo, bkmstorenodes)
       
    77     return orig(bkmstoreinst, *args, **kwargs)
       
    78 
       
    79 def _bookmark(orig, ui, repo, *bookmarks, **opts):
       
    80     """ Add a -D option to the bookmark command, map it to prune -B """
       
    81     haspruneopt = opts.get('prune', False)
       
    82     if not haspruneopt:
       
    83         return orig(ui, repo, *bookmarks, **opts)
       
    84 
       
    85     # Call prune -B
       
    86     evolve = extensions.find('evolve')
       
    87     optsdict = {
       
    88         'new': [],
       
    89         'succ': [],
       
    90         'rev': [],
       
    91         'bookmark': bookmarks[0],
       
    92         'keep': None,
       
    93         'biject': False,
       
    94     }
       
    95     evolve.cmdprune(ui, repo, **optsdict)
       
    96 
       
    97 # obsolescence inhibitor
       
    98 ########################
       
    99 
       
   100 def _schedulewrite(tr, obsinhibit):
       
   101     """Make sure on disk content will be updated on transaction commit"""
       
   102     def writer(fp):
       
   103         """Serialize the inhibited list to disk.
       
   104         """
       
   105         raw = ''.join(obsinhibit)
       
   106         fp.write(raw)
       
   107     tr.addfilegenerator('obsinhibit', ('obsinhibit',), writer)
       
   108     tr.hookargs['obs_inbihited'] = '1'
       
   109 
       
   110 def _filterpublic(repo, nodes):
       
   111     """filter out inhibitor on public changeset
       
   112 
       
   113     Public changesets are already immune to obsolescence"""
       
   114     getrev = repo.changelog.nodemap.get
       
   115     getphase = repo._phasecache.phase
       
   116     return (n for n in repo._obsinhibit
       
   117             if getrev(n) is not None and getphase(repo, getrev(n)))
       
   118 
       
   119 def _inhibitmarkers(repo, nodes):
       
   120     """add marker inhibitor for all obsolete revision under <nodes>
       
   121 
       
   122     Content of <nodes> and all mutable ancestors are considered. Marker for
       
   123     obsolete revision only are created.
       
   124     """
       
   125     newinhibit = repo.set('::%ln and obsolete()', nodes)
       
   126     if newinhibit:
       
   127         lock = tr = None
       
   128         try:
       
   129             lock = repo.lock()
       
   130             tr = repo.transaction('obsinhibit')
       
   131             repo._obsinhibit.update(c.node() for c in newinhibit)
       
   132             _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
       
   133             repo.invalidatevolatilesets()
       
   134             tr.close()
       
   135         finally:
       
   136             lockmod.release(tr, lock)
       
   137 
       
   138 def _deinhibitmarkers(repo, nodes):
       
   139     """lift obsolescence inhibition on a set of nodes
       
   140 
       
   141     This will be triggered when inhibited nodes received new obsolescence
       
   142     markers. Otherwise the new obsolescence markers would also be inhibited.
       
   143     """
       
   144     deinhibited = repo._obsinhibit & set(nodes)
       
   145     if deinhibited:
       
   146         tr = repo.transaction('obsinhibit')
       
   147         try:
       
   148             repo._obsinhibit -= deinhibited
       
   149             _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
       
   150             repo.invalidatevolatilesets()
       
   151             tr.close()
       
   152         finally:
       
   153             tr.release()
       
   154 
       
   155 def _createmarkers(orig, repo, relations, flag=0, date=None, metadata=None):
       
   156     """wrap markers create to make sure we de-inhibit target nodes"""
       
   157     # wrapping transactio to unify the one in each function
       
   158     tr = repo.transaction('add-obsolescence-marker')
       
   159     try:
       
   160         orig(repo, relations, flag, date, metadata)
       
   161         precs = (r[0].node() for r in relations)
       
   162         _deinhibitmarkers(repo, precs)
       
   163         tr.close()
       
   164     finally:
       
   165         tr.release()
       
   166 
       
   167 def transactioncallback(orig, repo, *args, **kwargs):
       
   168     """ Wrap localrepo.transaction to inhibit new obsolete changes """
       
   169     def inhibitposttransaction(transaction):
       
   170         # At the end of the transaction we catch all the new visible and
       
   171         # obsolete commit to inhibit them
       
   172         visibleobsolete = repo.revs('obsolete() - hidden()')
       
   173         ignoreset = set(getattr(repo, '_rebaseset', []))
       
   174         visibleobsolete = list(r for r in visibleobsolete if r not in ignoreset)
       
   175         if visibleobsolete:
       
   176             _inhibitmarkers(repo, [repo[r].node() for r in visibleobsolete])
       
   177     transaction = orig(repo, *args, **kwargs)
       
   178     transaction.addpostclose('inhibitposttransaction', inhibitposttransaction)
       
   179     return transaction
       
   180 
       
   181 def extsetup(ui):
       
   182     # lets wrap the computation of the obsolete set
       
   183     # We apply inhibition there
       
   184     obsfunc = obsolete.cachefuncs['obsolete']
       
   185     def _computeobsoleteset(repo):
       
   186         """remove any inhibited nodes from the obsolete set
       
   187 
       
   188         This will trickle down to other part of mercurial (hidden, log, etc)"""
       
   189         obs = obsfunc(repo)
       
   190         getrev = repo.changelog.nodemap.get
       
   191         for n in repo._obsinhibit:
       
   192             obs.discard(getrev(n))
       
   193         return obs
       
   194     try:
       
   195         extensions.find('directaccess')
       
   196     except KeyError:
       
   197         errormsg = _('Cannot use inhibit without the direct access extension')
       
   198         raise error.Abort(errormsg)
       
   199 
       
   200     # Wrapping this to inhibit obsolete revs resulting from a transaction
       
   201     extensions.wrapfunction(localrepo.localrepository,
       
   202                             'transaction', transactioncallback)
       
   203 
       
   204     obsolete.cachefuncs['obsolete'] = _computeobsoleteset
       
   205     # wrap create marker to make it able to lift the inhibition
       
   206     extensions.wrapfunction(obsolete, 'createmarkers', _createmarkers)
       
   207     # drop divergence computation since it is incompatible with "light revive"
       
   208     obsolete.cachefuncs['divergent'] = lambda repo: set()
       
   209     # drop bumped computation since it is incompatible with "light revive"
       
   210     obsolete.cachefuncs['bumped'] = lambda repo: set()
       
   211     # wrap update to make sure that no obsolete commit is visible after an
       
   212     # update
       
   213     extensions.wrapcommand(commands.table, 'update', _update)
       
   214     # There are two ways to save bookmark changes during a transation, we
       
   215     # wrap both to add inhibition markers.
       
   216     extensions.wrapfunction(bookmarks.bmstore, 'recordchange', _bookmarkchanged)
       
   217     extensions.wrapfunction(bookmarks.bmstore, 'write', _bookmarkchanged)
       
   218     # Add bookmark -D option
       
   219     entry = extensions.wrapcommand(commands.table, 'bookmark', _bookmark)
       
   220     entry[1].append(('D','prune',None,
       
   221                     _('delete the bookmark and prune the commits underneath')))
       
   222 
       
   223 @command('debugobsinhibit', [], '')
       
   224 def cmddebugobsinhibit(ui, repo, *revs):
       
   225     """inhibit obsolescence markers effect on a set of revs"""
       
   226     nodes = (repo[r].node() for r in scmutil.revrange(repo, revs))
       
   227     _inhibitmarkers(repo, nodes)