obsolete.py
changeset 51 d98e06ab8320
parent 50 19b22ad56b32
child 52 62bdc2567099
equal deleted inserted replaced
50:19b22ad56b32 51:d98e06ab8320
     1 # obsolete.py - introduce the obsolete concept in mercurial.
       
     2 #
       
     3 # Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
       
     4 #                Logilab SA        <contact@logilab.fr>
       
     5 #
       
     6 # This software may be used and distributed according to the terms of the
       
     7 # GNU General Public License version 2 or any later version.
       
     8 
       
     9 from mercurial import util
       
    10 from mercurial import context
       
    11 from mercurial import revset
       
    12 from mercurial import scmutil
       
    13 from mercurial import extensions
       
    14 from mercurial import pushkey
       
    15 from mercurial import discovery
       
    16 from mercurial import error
       
    17 from mercurial.node import hex, bin
       
    18 
       
    19 # Patch changectx
       
    20 #############################
       
    21 
       
    22 def obsolete(ctx):
       
    23     """is the changeset obsolete by other"""
       
    24     if ctx.node()is None:
       
    25         return False
       
    26     return bool(ctx._repo.obsoletedby(ctx.node()))
       
    27 
       
    28 context.changectx.obsolete = obsolete
       
    29 
       
    30 ohidden = context.changectx.hidden
       
    31 def hidden(ctx):
       
    32     # hack to fill hiddenrevs
       
    33     # compute hidden (XXX should move elsewhere)
       
    34     if not getattr(ctx._repo.changelog, 'hiddeninit', False):
       
    35         basicquery = 'obsolete() - (ancestors(not obsolete() or . or bookmark()))'
       
    36         for rev in scmutil.revrange(ctx._repo, [basicquery]):
       
    37             ctx._repo.changelog.hiddenrevs.add(rev)
       
    38         ctx._repo.changelog.hiddeninit = True
       
    39 
       
    40     return ohidden(ctx)
       
    41 context.changectx.hidden = hidden
       
    42 
       
    43 # revset
       
    44 #############################
       
    45 
       
    46 def revsetobsolete(repo, subset, x):
       
    47     args = revset.getargs(x, 0, 0, 'publicheads takes no arguments')
       
    48     return [r for r in subset if repo[r].obsolete()] # XXX slow
       
    49 
       
    50 def extsetup(ui):
       
    51     revset.symbols["obsolete"] = revsetobsolete
       
    52 
       
    53     def filterobsoleteout(orig, repo, remote, *args,**kwargs):
       
    54         common, heads = orig(repo, remote, *args, **kwargs)
       
    55 
       
    56         # filter obsolete
       
    57         heads = set(map(repo.changelog.rev, heads))
       
    58         obsoletes = set()
       
    59         for obj in repo._obsobjrels:
       
    60             try:
       
    61                 obsoletes.add(repo.changelog.rev(obj))
       
    62             except error.LookupError:
       
    63                 pass # we don't have this node locally
       
    64 
       
    65         outgoing = set(repo.changelog.ancestors(*heads))
       
    66         outgoing.update(heads)
       
    67 
       
    68         selected = outgoing - obsoletes
       
    69         heads = sorted(map(repo.changelog.node, selected))
       
    70 
       
    71         return common, heads
       
    72 
       
    73     extensions.wrapfunction(discovery, 'findcommonoutgoing', filterobsoleteout)
       
    74     
       
    75     try:
       
    76         rebase = extensions.find('rebase')
       
    77         if rebase:
       
    78             extensions.wrapfunction(rebase, 'concludenode', concludenode)
       
    79     except KeyError:
       
    80         pass # rebase not found
       
    81 
       
    82 # Pushkey mechanism for mutable
       
    83 #########################################
       
    84 
       
    85 def pushobsolete(repo, key, old, relations):
       
    86     assert key == "relations"
       
    87     w = repo.wlock()
       
    88     try:
       
    89         for sub, objs in relations.iteritems():
       
    90             for obj in objs:
       
    91                 repo.addobsolete(sub, obj)
       
    92     finally:
       
    93         w.release()
       
    94 
       
    95 def listobsolete(repo):
       
    96     return {'relations': repo._obssubrels}
       
    97 
       
    98 pushkey.register('obsolete', pushobsolete, listobsolete)
       
    99 
       
   100 # New commands
       
   101 #############################
       
   102 
       
   103 
       
   104 def cmddebugobsolete(ui, repo, subject, object):
       
   105     """Add an obsolete relation between a too node
       
   106     
       
   107     The subject is expected to be a newer version of the object"""
       
   108     sub = repo[subject]
       
   109     obj = repo[object]
       
   110     repo.addobsolete(sub.node(), obj.node())
       
   111     return 0
       
   112 
       
   113 cmdtable = {'debugobsolete': (cmddebugobsolete, [], '<subject> <object>')}
       
   114 
       
   115 def reposetup(ui, repo):
       
   116 
       
   117     if not repo.local():
       
   118         return
       
   119 
       
   120     opull = repo.pull
       
   121     opush = repo.push
       
   122 
       
   123     class obsoletingrepo(repo.__class__):
       
   124 
       
   125 
       
   126         ### Hidden revision support
       
   127         @util.propertycache
       
   128         def hiddenrevs(self):
       
   129             # It's a property because It simpler that to handle the __init__
       
   130             revs = set()
       
   131             return revs
       
   132 
       
   133         ### obsolete storage
       
   134         @util.propertycache
       
   135         def _obsobjrels(self):
       
   136             """{<old-node> -> set(<new-node>)}
       
   137 
       
   138             also compute hidden revision"""
       
   139             #reverse sub -> objs mapping
       
   140             objrels = {}
       
   141             for sub, objs in self._obssubrels.iteritems():
       
   142                 for obj in objs:
       
   143                     objrels.setdefault(obj, set()).add(sub)
       
   144             return objrels
       
   145 
       
   146         @util.propertycache
       
   147         def _obssubrels(self):
       
   148             """{<new-node> -> set(<old-node>)}"""
       
   149             return self._readobsrels()
       
   150 
       
   151 
       
   152         ### Disk IO
       
   153         def _readobsrels(self):
       
   154             """Write obsolete relation on disk"""
       
   155             # XXX handle lock
       
   156             rels = {}
       
   157             try:
       
   158                 f = self.opener('obsolete-relations')
       
   159                 try:
       
   160                     for line in f:
       
   161                         subhex, objhex = line.split()
       
   162                         rels.setdefault(bin(subhex), set()).add(bin(objhex))
       
   163                 finally:
       
   164                     f.close()
       
   165             except IOError:
       
   166                 pass
       
   167             return rels
       
   168 
       
   169         def _writeobsrels(self):
       
   170             """Write obsolete relation on disk"""
       
   171             # XXX handle lock
       
   172             f = self.opener('obsolete-relations', 'w', atomictemp=True)
       
   173             try:
       
   174                 for sub, objs in self._obssubrels.iteritems():
       
   175                     for obj in objs:
       
   176                         f.write('%s %s\n' % (hex(sub), hex(obj)))
       
   177                 f.rename()
       
   178             finally:
       
   179                 f.close()
       
   180 
       
   181         ### local clone support
       
   182 
       
   183         def cancopy(self):
       
   184             return not bool(self._obsobjrels) # you can't copy if there is obsolete
       
   185 
       
   186         ### pull // push support
       
   187 
       
   188         def pull(self, remote, *args, **kwargs):
       
   189             obskey = remote.listkeys('obsolete')
       
   190             obsrels = obskey.get('relations', {})
       
   191             result = opull(remote, *args, **kwargs)
       
   192             for sub, objs in obsrels.iteritems():
       
   193                 for obj in objs:
       
   194                     self.addobsolete(sub, obj)
       
   195             return result
       
   196 
       
   197         def push(self, remote, *args, **opts):
       
   198             obskey = remote.listkeys('obsolete')
       
   199             obssupport = 'relations' in obskey
       
   200             result = opush(remote, *args, **opts)
       
   201             if obssupport:
       
   202                 remote.pushkey('obsolete', 'relations', {}, self._obssubrels)
       
   203             return result
       
   204 
       
   205 
       
   206         ### Public method
       
   207         def obsoletedby(self, node):
       
   208             """return the set of node that make <node> obsolete (obj)"""
       
   209             return self._obsobjrels.get(node, set())
       
   210 
       
   211         def obsolete(self, node):
       
   212             """return the set of node that <node> make obsolete (sub)"""
       
   213             return self._obssubrels.get(node, set())
       
   214 
       
   215         def addobsolete(self, sub, obj):
       
   216             """Add a relation marking that node <sub> is a new version of <obj>"""
       
   217             self._obssubrels.setdefault(sub, set()).add(obj)
       
   218             self._obsobjrels.setdefault(obj, set()).add(sub)
       
   219             try:
       
   220                 self.changelog.hiddenrevs.add(repo[obj].rev())
       
   221             except error.RepoLookupError:
       
   222                 pass #unknow revision (but keep propagating the data
       
   223             self._writeobsrels()
       
   224 
       
   225     repo.__class__ = obsoletingrepo
       
   226 
       
   227 
       
   228 ### Other Extension compat
       
   229 ############################
       
   230 
       
   231 def concludenode(orig, repo, rev, *args, **kwargs):
       
   232     newrev = orig(repo, rev, *args, **kwargs)
       
   233     oldnode = repo[rev].node()
       
   234     newnode = repo[newrev].node()
       
   235     repo.addobsolete(newnode, oldnode)
       
   236     return newrev
       
   237