hgext/directaccess.py
branchmercurial-3.4
changeset 1607 3c7f98753e37
parent 1599 dcf145d0ce21
child 1608 e359d33856c3
equal deleted inserted replaced
1599:dcf145d0ce21 1607:3c7f98753e37
     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     repoview.filtertable.update({'visible-directaccess-nowarn': _computehidden})
       
    77     repoview.filtertable.update({'visible-directaccess-warn': _computehidden})
       
    78     branchmap.subsettable['visible-directaccess-nowarn'] = 'visible'
       
    79     branchmap.subsettable['visible-directaccess-warn'] = 'visible'
       
    80 
       
    81     for warn, ext, cmd in directaccesslevel:
       
    82         try:
       
    83             cmdtable = extensions.find(ext).cmdtable if ext else commands.table
       
    84             wrapper = wrapwitherror if warn == 'error' else wrapwithoutwarning
       
    85             extensions.wrapcommand(cmdtable, cmd, wrapper)
       
    86         except (error.UnknownCommand, KeyError):
       
    87             pass
       
    88 
       
    89 def wrapwitherror(orig, ui, repo, *args, **kwargs):
       
    90     if repo and repo.filtername == 'visible-directaccess-warn':
       
    91         repo = repo.filtered('visible')
       
    92     return orig(ui, repo, *args, **kwargs)
       
    93 
       
    94 def wrapwithoutwarning(orig, ui, repo, *args, **kwargs):
       
    95     if repo and repo.filtername == 'visible-directaccess-warn':
       
    96         repo = repo.filtered("visible-directaccess-nowarn")
       
    97     return orig(ui, repo, *args, **kwargs)
       
    98 
       
    99 def uisetup(ui):
       
   100     """ Change ordering of extensions to ensure that directaccess extsetup comes
       
   101     after the one of the extensions in the loadsafter list """
       
   102     loadsafter = ui.configlist('directaccess','loadsafter')
       
   103     order = list(extensions._order)
       
   104     directaccesidx = order.index('directaccess')
       
   105 
       
   106     # The min idx for directaccess to load after all the extensions in loadafter
       
   107     minidxdirectaccess = directaccesidx
       
   108 
       
   109     for ext in loadsafter:
       
   110         try:
       
   111             minidxdirectaccess = max(minidxdirectaccess, order.index(ext))
       
   112         except ValueError:
       
   113             pass # extension not loaded
       
   114 
       
   115     if minidxdirectaccess > directaccesidx:
       
   116         order.insert(minidxdirectaccess + 1, 'directaccess')
       
   117         order.remove('directaccess')
       
   118         extensions._order = order
       
   119 
       
   120 def _repository(orig, *args, **kwargs):
       
   121     """Make visible-directaccess-warn the default filter for new repos"""
       
   122     repo = orig(*args, **kwargs)
       
   123     return repo.filtered("visible-directaccess-warn")
       
   124 
       
   125 def extsetup(ui):
       
   126     extensions.wrapfunction(revset, 'posttreebuilthook', _posttreebuilthook)
       
   127     extensions.wrapfunction(hg, 'repository', _repository)
       
   128     setupdirectaccess()
       
   129 
       
   130 hashre = util.re.compile('[0-9a-fA-F]{1,40}')
       
   131 
       
   132 _listtuple = ('symbol', '_list')
       
   133 
       
   134 def gethashsymbols(tree):
       
   135     # Returns the list of symbols of the tree that look like hashes
       
   136     # for example for the revset 3::abe3ff it will return ('abe3ff')
       
   137     if not tree:
       
   138         return []
       
   139 
       
   140     if len(tree) == 2 and tree[0] == "symbol":
       
   141         try:
       
   142             int(tree[1])
       
   143             return []
       
   144         except ValueError as e:
       
   145             if hashre.match(tree[1]):
       
   146                 return [tree[1]]
       
   147             return []
       
   148     elif tree[0] == "func" and tree[1] == _listtuple:
       
   149         # the optimiser will group sequence of hash request
       
   150         result = []
       
   151         for entry in tree[2][1].split('\0'):
       
   152             if hashre.match(entry):
       
   153                 result.append(entry)
       
   154         return result
       
   155     elif len(tree) >= 3:
       
   156         results = []
       
   157         for subtree in tree[1:]:
       
   158             results += gethashsymbols(subtree)
       
   159         return results
       
   160     else:
       
   161         return []
       
   162 
       
   163 def _posttreebuilthook(orig, tree, repo):
       
   164     # This is use to enabled direct hash access
       
   165     # We extract the symbols that look like hashes and add them to the
       
   166     # explicitaccess set
       
   167     orig(tree, repo)
       
   168     filternm = ""
       
   169     if repo is not None:
       
   170         filternm = repo.filtername
       
   171     if filternm is not None and filternm.startswith('visible-directaccess'):
       
   172         prelength = len(repo._explicitaccess)
       
   173         accessbefore = set(repo._explicitaccess)
       
   174         repo.symbols = gethashsymbols(tree)
       
   175         cl = repo.unfiltered().changelog
       
   176         for node in repo.symbols:
       
   177             try:
       
   178                 node = cl._partialmatch(node)
       
   179             except error.LookupError:
       
   180                 node = None
       
   181             if node is not None:
       
   182                 rev = cl.rev(node)
       
   183                 if rev not in repo.changelog:
       
   184                     repo._explicitaccess.add(rev)
       
   185         if prelength != len(repo._explicitaccess):
       
   186             if repo.filtername != 'visible-directaccess-nowarn':
       
   187                 unhiddencommits = repo._explicitaccess - accessbefore
       
   188                 repo.ui.warn( _("Warning: accessing hidden changesets %s " 
       
   189                                 "for write operation\n") % 
       
   190                                 (",".join([str(repo.unfiltered()[l]) 
       
   191                                     for l in unhiddencommits])))
       
   192             repo.invalidatevolatilesets()