hgext3rd/evolve/hack/directaccess.py
changeset 3147 20a0792c1773
parent 3146 87e87881059d
child 3148 0ed75e700ad8
equal deleted inserted replaced
3146:87e87881059d 3147:20a0792c1773
     1 """ This extension provides direct access
       
     2 It is the ability to refer and access hidden sha in commands provided that you
       
     3 know their value.
       
     4 For example hg log -r xxx where xxx is a commit has should work whether xxx is
       
     5 hidden or not as we assume that the user knows what he is doing when referring
       
     6 to xxx.
       
     7 """
       
     8 from mercurial import extensions
       
     9 from mercurial import repoview
       
    10 from mercurial import branchmap
       
    11 from mercurial import registrar
       
    12 from mercurial import revset
       
    13 from mercurial import error
       
    14 from mercurial import commands
       
    15 from mercurial import hg
       
    16 from mercurial import util
       
    17 from mercurial.i18n import _
       
    18 
       
    19 cmdtable = {}
       
    20 
       
    21 if util.safehasattr(registrar, 'command'):
       
    22     command = registrar.command(cmdtable)
       
    23 else: # compat with hg < 4.3
       
    24     from mercurial import cmdutil
       
    25     command = cmdutil.command(cmdtable)
       
    26 
       
    27 if util.safehasattr(registrar, 'configitem'):
       
    28     configtable = {}
       
    29     configitem = registrar.configitem(configtable)
       
    30 
       
    31     configitem('directaccess', 'loadsafter',
       
    32                default=[],
       
    33     )
       
    34 
       
    35 # By default, all the commands have directaccess with warnings
       
    36 # List of commands that have no directaccess and directaccess with no warning
       
    37 directaccesslevel = [
       
    38     # Format:
       
    39     # ('nowarning', 'evolve', 'prune'),
       
    40     # means: no directaccess warning, for the command in evolve named prune
       
    41     #
       
    42     # ('error', None, 'serve'),
       
    43     # means: no directaccess for the command in core named serve
       
    44     #
       
    45     # The list is ordered alphabetically by command names, starting with all
       
    46     # the commands in core then all the commands in the extensions
       
    47     #
       
    48     # The general guideline is:
       
    49     # - remove directaccess warnings for read only commands
       
    50     # - no direct access for commands with consequences outside of the repo
       
    51     # - leave directaccess warnings for all the other commands
       
    52     #
       
    53     ('nowarning', None, 'annotate'),
       
    54     ('nowarning', None, 'archive'),
       
    55     ('nowarning', None, 'bisect'),
       
    56     ('nowarning', None, 'bookmarks'),
       
    57     ('nowarning', None, 'bundle'),
       
    58     ('nowarning', None, 'cat'),
       
    59     ('nowarning', None, 'diff'),
       
    60     ('nowarning', None, 'export'),
       
    61     ('nowarning', None, 'identify'),
       
    62     ('nowarning', None, 'incoming'),
       
    63     ('nowarning', None, 'log'),
       
    64     ('nowarning', None, 'manifest'),
       
    65     ('error', None, 'outgoing'), # confusing if push errors and not outgoing
       
    66     ('error', None, 'push'), # destructive
       
    67     ('nowarning', None, 'revert'),
       
    68     ('error', None, 'serve'),
       
    69     ('nowarning', None, 'tags'),
       
    70     ('nowarning', None, 'unbundle'),
       
    71     ('nowarning', None, 'update'),
       
    72 ]
       
    73 
       
    74 def reposetup(ui, repo):
       
    75     repo._explicitaccess = set()
       
    76 
       
    77 def _computehidden(repo):
       
    78     hidden = repoview.filterrevs(repo, 'visible')
       
    79     cl = repo.changelog
       
    80     dynamic = hidden & repo._explicitaccess
       
    81     if dynamic:
       
    82         blocked = cl.ancestors(dynamic, inclusive=True)
       
    83         hidden = frozenset(r for r in hidden if r not in blocked)
       
    84     return hidden
       
    85 
       
    86 def setupdirectaccess():
       
    87     """ Add two new filtername that behave like visible to provide direct access
       
    88     and direct access with warning. Wraps the commands to setup direct access
       
    89     """
       
    90     repoview.filtertable.update({'visible-directaccess-nowarn': _computehidden})
       
    91     repoview.filtertable.update({'visible-directaccess-warn': _computehidden})
       
    92     branchmap.subsettable['visible-directaccess-nowarn'] = 'visible'
       
    93     branchmap.subsettable['visible-directaccess-warn'] = 'visible'
       
    94 
       
    95     for warn, ext, cmd in directaccesslevel:
       
    96         try:
       
    97             cmdtable = extensions.find(ext).cmdtable if ext else commands.table
       
    98             wrapper = wrapwitherror if warn == 'error' else wrapwithoutwarning
       
    99             extensions.wrapcommand(cmdtable, cmd, wrapper)
       
   100         except (error.UnknownCommand, KeyError):
       
   101             pass
       
   102 
       
   103 def wrapwitherror(orig, ui, repo, *args, **kwargs):
       
   104     if repo and repo.filtername == 'visible-directaccess-warn':
       
   105         repo = repo.filtered('visible')
       
   106     return orig(ui, repo, *args, **kwargs)
       
   107 
       
   108 def wrapwithoutwarning(orig, ui, repo, *args, **kwargs):
       
   109     if repo and repo.filtername == 'visible-directaccess-warn':
       
   110         repo = repo.filtered("visible-directaccess-nowarn")
       
   111     return orig(ui, repo, *args, **kwargs)
       
   112 
       
   113 def uisetup(ui):
       
   114     """ Change ordering of extensions to ensure that directaccess extsetup comes
       
   115     after the one of the extensions in the loadsafter list """
       
   116     loadsafter = ui.configlist('directaccess', 'loadsafter')
       
   117     order = list(extensions._order)
       
   118     directaccesidx = order.index('directaccess')
       
   119 
       
   120     # The min idx for directaccess to load after all the extensions in loadafter
       
   121     minidxdirectaccess = directaccesidx
       
   122 
       
   123     for ext in loadsafter:
       
   124         try:
       
   125             minidxdirectaccess = max(minidxdirectaccess, order.index(ext))
       
   126         except ValueError:
       
   127             pass # extension not loaded
       
   128 
       
   129     if minidxdirectaccess > directaccesidx:
       
   130         order.insert(minidxdirectaccess + 1, 'directaccess')
       
   131         order.remove('directaccess')
       
   132         extensions._order = order
       
   133 
       
   134 def _repository(orig, *args, **kwargs):
       
   135     """Make visible-directaccess-warn the default filter for new repos"""
       
   136     repo = orig(*args, **kwargs)
       
   137     return repo.filtered("visible-directaccess-warn")
       
   138 
       
   139 def extsetup(ui):
       
   140     extensions.wrapfunction(revset, 'posttreebuilthook', _posttreebuilthook)
       
   141     extensions.wrapfunction(hg, 'repository', _repository)
       
   142     setupdirectaccess()
       
   143 
       
   144 hashre = util.re.compile('[0-9a-fA-F]{1,40}')
       
   145 
       
   146 _listtuple = ('symbol', '_list')
       
   147 
       
   148 def _ishashsymbol(symbol, maxrev):
       
   149     # Returns true if symbol looks like a hash
       
   150     try:
       
   151         n = int(symbol)
       
   152         if n <= maxrev:
       
   153             # It's a rev number
       
   154             return False
       
   155     except ValueError:
       
   156         pass
       
   157     return hashre.match(symbol)
       
   158 
       
   159 def gethashsymbols(tree, maxrev):
       
   160     # Returns the list of symbols of the tree that look like hashes
       
   161     # for example for the revset 3::abe3ff it will return ('abe3ff')
       
   162     if not tree:
       
   163         return []
       
   164 
       
   165     results = []
       
   166     if len(tree) in (2, 3) and tree[0] == "symbol":
       
   167         results.append(tree[1])
       
   168     elif tree[0] == "func" and tree[1] == _listtuple:
       
   169         # the optimiser will group sequence of hash request
       
   170         results += tree[2][1].split('\0')
       
   171     elif len(tree) >= 2:
       
   172         for subtree in tree[1:]:
       
   173             results += gethashsymbols(subtree, maxrev)
       
   174         # return directly, we don't need to filter symbols again
       
   175         return results
       
   176     return [s for s in results if _ishashsymbol(s, maxrev)]
       
   177 
       
   178 def _posttreebuilthook(orig, tree, repo):
       
   179     # This is use to enabled direct hash access
       
   180     # We extract the symbols that look like hashes and add them to the
       
   181     # explicitaccess set
       
   182     orig(tree, repo)
       
   183     filternm = ""
       
   184     if repo is not None:
       
   185         filternm = repo.filtername
       
   186     if filternm is not None and filternm.startswith('visible-directaccess'):
       
   187         prelength = len(repo._explicitaccess)
       
   188         accessbefore = set(repo._explicitaccess)
       
   189         cl = repo.unfiltered().changelog
       
   190         repo.symbols = gethashsymbols(tree, len(cl))
       
   191         for node in repo.symbols:
       
   192             try:
       
   193                 node = cl._partialmatch(node)
       
   194             except error.LookupError:
       
   195                 node = None
       
   196             if node is not None:
       
   197                 rev = cl.rev(node)
       
   198                 if rev not in repo.changelog:
       
   199                     repo._explicitaccess.add(rev)
       
   200         if prelength != len(repo._explicitaccess):
       
   201             if repo.filtername != 'visible-directaccess-nowarn':
       
   202                 unhiddencommits = repo._explicitaccess - accessbefore
       
   203                 repo.ui.warn(_("Warning: accessing hidden changesets %s "
       
   204                                "for write operation\n") %
       
   205                              (",".join([str(repo.unfiltered()[l])
       
   206                               for l in unhiddencommits])))
       
   207             repo.invalidatevolatilesets()