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