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