##// END OF EJS Templates
hgext: start building a library for simple hooks...
hgext: start building a library for simple hooks Many workflows depend on hooks to enforce certain policies, e.g. to prevent forced pushes. The Mercurial Guide includes some cases and Google can help finding others, but it can save users a lot of time if hg itself has a couple of examples for further customization. Differential Revision: https://phab.mercurial-scm.org/D6825

File last commit:

r43375:649d3ac3 default
r44897:4cabeea6 default
Show More
protocol.py
260 lines | 7.9 KiB | text/x-python | PythonLexer
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 # Copyright 2016-present Facebook. All Rights Reserved.
#
# protocol: logic for a server providing fastannotate support
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import
import contextlib
import os
from mercurial.i18n import _
Gregory Szorc
py3: manually import pycompat.open into files that need it...
r43355 from mercurial.pycompat import open
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 from mercurial import (
error,
extensions,
hg,
Gregory Szorc
py3: define and use pycompat.iteritems() for hgext/...
r43375 pycompat,
Matt Harbison
fastannotate: pconvert paths from the server for Windows...
r39287 util,
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 wireprotov1peer,
wireprotov1server,
)
from . import context
# common
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 def _getmaster(ui):
"""get the mainbranch, and enforce it is set"""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 master = ui.config(b'fastannotate', b'mainbranch')
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 if not master:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.Abort(
_(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'fastannotate.mainbranch is required '
b'for both the client and the server'
Augie Fackler
formatting: blacken the codebase...
r43346 )
)
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 return master
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 # server-side
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 def _capabilities(orig, repo, proto):
result = orig(repo, proto)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 result.append(b'getannotate')
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 return result
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 def _getannotate(repo, proto, path, lastnode):
# output:
# FILE := vfspath + '\0' + str(size) + '\0' + content
# OUTPUT := '' | FILE + OUTPUT
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 result = b''
Augie Fackler
formatting: blacken the codebase...
r43346 buildondemand = repo.ui.configbool(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'fastannotate', b'serverbuildondemand', True
Augie Fackler
formatting: blacken the codebase...
r43346 )
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 with context.annotatecontext(repo, path) as actx:
if buildondemand:
# update before responding to the client
master = _getmaster(repo.ui)
try:
if not actx.isuptodate(master):
actx.annotate(master, master)
except Exception:
# non-fast-forward move or corrupted. rebuild automically.
actx.rebuild()
try:
actx.annotate(master, master)
except Exception:
Augie Fackler
formatting: blacken the codebase...
r43346 actx.rebuild() # delete files
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 finally:
# although the "with" context will also do a close/flush, we
# need to do it early so we can send the correct respond to
# client.
actx.close()
# send back the full content of revmap and linelog, in the future we
# may want to do some rsync-like fancy updating.
# the lastnode check is not necessary if the client and the server
# agree where the main branch is.
if actx.lastnode != lastnode:
for p in [actx.revmappath, actx.linelogpath]:
if not os.path.exists(p):
continue
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 with open(p, b'rb') as f:
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 content = f.read()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 vfsbaselen = len(repo.vfs.base + b'/')
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 relpath = p[vfsbaselen:]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 result += b'%s\0%d\0%s' % (relpath, len(content), content)
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 return result
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 def _registerwireprotocommand():
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if b'getannotate' in wireprotov1server.commands:
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 return
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 wireprotov1server.wireprotocommand(b'getannotate', b'path lastnode')(
Augie Fackler
formatting: blacken the codebase...
r43346 _getannotate
)
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243
def serveruisetup(ui):
_registerwireprotocommand()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 extensions.wrapfunction(wireprotov1server, b'_capabilities', _capabilities)
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 # client-side
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 def _parseresponse(payload):
result = {}
i = 0
l = len(payload) - 1
Augie Fackler
formatting: blacken the codebase...
r43346 state = 0 # 0: vfspath, 1: size
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 vfspath = size = b''
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 while i < l:
Augie Fackler
formatting: blacken the codebase...
r43346 ch = payload[i : i + 1]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if ch == b'\0':
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 if state == 1:
Augie Fackler
formatting: blacken the codebase...
r43346 result[vfspath] = payload[i + 1 : i + 1 + int(size)]
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 i += int(size)
state = 0
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 vfspath = size = b''
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 elif state == 0:
state = 1
else:
if state == 1:
size += ch
elif state == 0:
vfspath += ch
i += 1
return result
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 def peersetup(ui, peer):
class fastannotatepeer(peer.__class__):
@wireprotov1peer.batchable
def getannotate(self, path, lastnode=None):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not self.capable(b'getannotate'):
ui.warn(_(b'remote peer cannot provide annotate cache\n'))
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 yield None, None
else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 args = {b'path': path, b'lastnode': lastnode or b''}
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 f = wireprotov1peer.future()
yield args, f
yield _parseresponse(f.value)
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 peer.__class__ = fastannotatepeer
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 @contextlib.contextmanager
def annotatepeer(repo):
ui = repo.ui
Augie Fackler
fastannotate: rip out specialized support for remotefilelog...
r39246 remotepath = ui.expandpath(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.config(b'fastannotate', b'remotepath', b'default')
Augie Fackler
formatting: blacken the codebase...
r43346 )
Augie Fackler
fastannotate: rip out specialized support for remotefilelog...
r39246 peer = hg.peer(ui, {}, remotepath)
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243
try:
yield peer
finally:
Augie Fackler
fastannotate: rip out specialized support for remotefilelog...
r39246 peer.close()
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 def clientfetch(repo, paths, lastnodemap=None, peer=None):
"""download annotate cache from the server for paths"""
if not paths:
return
if peer is None:
with annotatepeer(repo) as peer:
return clientfetch(repo, paths, lastnodemap, peer)
if lastnodemap is None:
lastnodemap = {}
ui = repo.ui
results = []
with peer.commandexecutor() as batcher:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(b'fastannotate: requesting %d files\n' % len(paths))
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 for p in paths:
Augie Fackler
formatting: blacken the codebase...
r43346 results.append(
batcher.callcommand(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'getannotate',
{b'path': p, b'lastnode': lastnodemap.get(p)},
Augie Fackler
formatting: blacken the codebase...
r43346 )
)
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243
Martin von Zweigbergk
fastannotate: process files as they arrive...
r39751 for result in results:
r = result.result()
# TODO: pconvert these paths on the server?
Gregory Szorc
py3: define and use pycompat.iteritems() for hgext/...
r43375 r = {util.pconvert(p): v for p, v in pycompat.iteritems(r)}
Martin von Zweigbergk
fastannotate: process files as they arrive...
r39751 for path in sorted(r):
# ignore malicious paths
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not path.startswith(b'fastannotate/') or b'/../' in (
path + b'/'
Augie Fackler
formatting: blacken the codebase...
r43346 ):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(
b'fastannotate: ignored malicious path %s\n' % path
)
Martin von Zweigbergk
fastannotate: process files as they arrive...
r39751 continue
content = r[path]
if ui.debugflag:
Augie Fackler
formatting: blacken the codebase...
r43346 ui.debug(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'fastannotate: writing %d bytes to %s\n'
Augie Fackler
formatting: blacken the codebase...
r43346 % (len(content), path)
)
Martin von Zweigbergk
fastannotate: process files as they arrive...
r39751 repo.vfs.makedirs(os.path.dirname(path))
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 with repo.vfs(path, b'wb') as f:
Martin von Zweigbergk
fastannotate: process files as they arrive...
r39751 f.write(content)
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 def _filterfetchpaths(repo, paths):
"""return a subset of paths whose history is long and need to fetch linelog
from the server. works with remotefilelog and non-remotefilelog repos.
"""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 threshold = repo.ui.configint(b'fastannotate', b'clientfetchthreshold', 10)
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 if threshold <= 0:
return paths
result = []
for path in paths:
try:
Augie Fackler
fastannotate: rip out specialized support for remotefilelog...
r39246 if len(repo.file(path)) >= threshold:
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 result.append(path)
Augie Fackler
formatting: blacken the codebase...
r43346 except Exception: # file not found etc.
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 result.append(path)
return result
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 def localreposetup(ui, repo):
class fastannotaterepo(repo.__class__):
def prefetchfastannotate(self, paths, peer=None):
master = _getmaster(self.ui)
needupdatepaths = []
lastnodemap = {}
try:
for path in _filterfetchpaths(self, paths):
with context.annotatecontext(self, path) as actx:
if not actx.isuptodate(master, strict=False):
needupdatepaths.append(path)
lastnodemap[path] = actx.lastnode
if needupdatepaths:
clientfetch(self, needupdatepaths, lastnodemap, peer)
except Exception as ex:
# could be directory not writable or so, not fatal
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self.ui.debug(b'fastannotate: prefetch failed: %r\n' % ex)
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 repo.__class__ = fastannotaterepo
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 def clientreposetup(ui, repo):
_registerwireprotocommand()
Gregory Szorc
fastannotate: use repo.local()...
r39580 if repo.local():
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 localreposetup(ui, repo)
Augie Fackler
fastannotate: move some global state mutation to extsetup()...
r39247 # TODO: this mutates global state, but only if at least one repo
# has the extension enabled. This is probably bad for hgweb.
Augie Fackler
fastannotate: initial import from Facebook's hg-experimental...
r39243 if peersetup not in hg.wirepeersetupfuncs:
hg.wirepeersetupfuncs.append(peersetup)