hgext/directaccess.py
changeset 1339 0e2eb196923a
child 1347 b00c2fe51ac8
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/directaccess.py	Thu May 14 11:23:40 2015 -0700
@@ -0,0 +1,117 @@
+""" This extension provides direct access
+It is the ability to refer and access hidden sha in commands provided that you 
+know their value.
+For example hg log -r xxx where xxx is a commit has should work whether xxx is
+hidden or not as we assume that the user knows what he is doing when referring
+to xxx.
+"""
+from mercurial import extensions
+from mercurial import cmdutil
+from mercurial import repoview
+from mercurial import branchmap
+from mercurial import revset
+from mercurial import error
+from mercurial import commands
+from mercurial.i18n import _
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+# List of commands where no warning is shown for direct access
+directaccesslevel = [
+    # warning or not, extension (None if core), command name
+    (False, None, 'update'), 
+    (False, None, 'export'),
+    (True,  'rebase', 'rebase'),
+    (False,  'evolve', 'prune'),
+]
+
+def reposetup(ui, repo):
+    repo._explicitaccess = set()
+
+def _computehidden(repo):
+    hidden = repoview.computehidden(repo)
+    cl = repo.changelog
+    dynamic = hidden & repo._explicitaccess
+    if dynamic:
+        blocked = cl.ancestors(dynamic, inclusive=True)
+        hidden = frozenset(r for r in hidden if r not in blocked)
+    return hidden
+
+def setupdirectaccess():
+    """ Add two new filtername that behave like visible to provide direct access
+    and direct access with warning. Wraps the commands to setup direct access """
+    repoview.filtertable.update({'visible-directaccess-nowarn': _computehidden})
+    repoview.filtertable.update({'visible-directaccess-warn': _computehidden})
+    branchmap.subsettable['visible-directaccess-nowarn'] = 'visible'
+    branchmap.subsettable['visible-directaccess-warn'] = 'visible'
+
+    for warn, ext, cmd in directaccesslevel:
+        cmdtable = extensions.find(ext).cmdtable if ext else commands.table
+        wrapper = wrapwithwarning if warn else wrapwithoutwarning
+        try:
+            extensions.wrapcommand(cmdtable, cmd, wrapper)
+        except error.UnknownCommand:
+            pass
+
+def wrapwithoutwarning(orig, ui, repo, *args, **kwargs):
+    if repo and repo.filtername == 'visible':
+        repo = repo.filtered("visible-directaccess-nowarn")
+    return orig(ui, repo, *args, **kwargs)
+
+def wrapwithwarning(orig, ui, repo, *args, **kwargs):
+    if repo and repo.filtername == 'visible':
+        repo = repo.filtered("visible-directaccess-warn")
+    return orig(ui, repo, *args, **kwargs)
+
+def extsetup(ui):
+    extensions.wrapfunction(revset, 'posttreebuilthook', _posttreebuilthook)
+    setupdirectaccess()
+
+def gethashsymbols(tree):
+    # Returns the list of symbols of the tree that look like hashes
+    # for example for the revset 3::abe3ff it will return ('abe3ff')
+    if not tree:
+        return []
+
+    if len(tree) == 2 and tree[0] == "symbol":
+        try:
+            int(tree[1])
+            return []
+        except ValueError as e:
+            return [tree[1]]
+    elif len(tree) == 3:
+        return gethashsymbols(tree[1]) + gethashsymbols(tree[2])
+    else:
+        return []
+
+def _posttreebuilthook(orig, tree, repo):
+    # This is use to enabled direct hash access
+    # We extract the symbols that look like hashes and add them to the
+    # explicitaccess set
+    orig(tree, repo)
+    filternm = ""
+    if repo is not None:
+        filternm = repo.filtername
+    if filternm is not None and filternm.startswith('visible-directaccess'):
+        prelength = len(repo._explicitaccess)
+        accessbefore = set(repo._explicitaccess)
+        repo.symbols = gethashsymbols(tree)
+        cl = repo.unfiltered().changelog
+        for node in repo.symbols:
+            try:
+                node = cl._partialmatch(node)
+            except error.LookupError:
+                node = None
+            if node is not None:
+                rev = cl.rev(node)
+                if rev not in repo.changelog:
+                    repo._explicitaccess.add(rev)
+        if prelength != len(repo._explicitaccess):
+            if repo.filtername != 'visible-directaccess-nowarn':
+                unhiddencommits = repo._explicitaccess - accessbefore
+                repo.ui.warn( _("Warning: accessing hidden changesets %s " 
+                                "for write operation\n") % 
+                                (",".join([str(repo.unfiltered()[l]) 
+                                    for l in unhiddencommits])))
+            repo.invalidatevolatilesets()