clonebundles.py
140 lines
| 4.8 KiB
| text/x-python
|
PythonLexer
/ hgext / clonebundles.py
Gregory Szorc
|
r26623 | # This software may be used and distributed according to the terms of the | ||
# GNU General Public License version 2 or any later version. | ||||
"""server side extension to advertise pre-generated bundles to seed clones. | ||||
The extension essentially serves the content of a .hg/clonebundles.manifest | ||||
file to clients that request it. | ||||
The clonebundles.manifest file contains a list of URLs and attributes. URLs | ||||
hold pre-generated bundles that a client fetches and applies. After applying | ||||
the pre-generated bundle, the client will connect back to the original server | ||||
and pull data not in the pre-generated bundle. | ||||
Manifest File Format: | ||||
The manifest file contains a newline (\n) delimited list of entries. | ||||
Each line in this file defines an available bundle. Lines have the format: | ||||
<URL> [<key>=<value] | ||||
That is, a URL followed by extra metadata describing it. Metadata keys and | ||||
values should be URL encoded. | ||||
This metadata is optional. It is up to server operators to populate this | ||||
metadata. | ||||
Keys in UPPERCASE are reserved for use by Mercurial. All non-uppercase keys | ||||
can be used by site installations. | ||||
The server operator is responsible for generating the bundle manifest file. | ||||
Metadata Attributes: | ||||
Gregory Szorc
|
r26644 | BUNDLESPEC | ||
A "bundle specification" string that describes the type of the bundle. | ||||
These are string values that are accepted by the "--type" argument of | ||||
`hg bundle`. | ||||
The values are parsed in strict mode, which means they must be of the | ||||
"<compression>-<type>" form. See | ||||
mercurial.exchange.parsebundlespec() for more details. | ||||
Clients will automatically filter out specifications that are unknown or | ||||
unsupported so they won't attempt to download something that likely won't | ||||
apply. | ||||
The actual value doesn't impact client behavior beyond filtering: | ||||
clients will still sniff the bundle type from the header of downloaded | ||||
files. | ||||
Gregory Szorc
|
r26645 | |||
REQUIRESNI | ||||
Whether Server Name Indication (SNI) is required to connect to the URL. | ||||
SNI allows servers to use multiple certificates on the same IP. It is | ||||
somewhat common in CDNs and other hosting providers. Older Python | ||||
versions do not support SNI. Defining this attribute enables clients | ||||
with older Python versions to filter this entry. | ||||
If this is defined, it is important to advertise a non-SNI fallback | ||||
URL or clients running old Python releases may not be able to clone | ||||
with the clonebundles facility. | ||||
Value should be "true". | ||||
Gregory Szorc
|
r26623 | """ | ||
Gregory Szorc
|
r26691 | from mercurial.i18n import _ | ||
from mercurial.node import nullid | ||||
Gregory Szorc
|
r26623 | from mercurial import ( | ||
Gregory Szorc
|
r26691 | exchange, | ||
Gregory Szorc
|
r26623 | extensions, | ||
wireproto, | ||||
) | ||||
testedwith = 'internal' | ||||
def capabilities(orig, repo, proto): | ||||
caps = orig(repo, proto) | ||||
# Only advertise if a manifest exists. This does add some I/O to requests. | ||||
# But this should be cheaper than a wasted network round trip due to | ||||
# missing file. | ||||
if repo.opener.exists('clonebundles.manifest'): | ||||
caps.append('clonebundles') | ||||
return caps | ||||
@wireproto.wireprotocommand('clonebundles', '') | ||||
def bundles(repo, proto): | ||||
"""Server command for returning info for available bundles to seed clones. | ||||
Clients will parse this response and determine what bundle to fetch. | ||||
Other extensions may wrap this command to filter or dynamically emit | ||||
data depending on the request. e.g. you could advertise URLs for | ||||
the closest data center given the client's IP address. | ||||
""" | ||||
return repo.opener.tryread('clonebundles.manifest') | ||||
Gregory Szorc
|
r26691 | @exchange.getbundle2partsgenerator('clonebundlesadvertise', 0) | ||
def advertiseclonebundlespart(bundler, repo, source, bundlecaps=None, | ||||
b2caps=None, heads=None, common=None, | ||||
cbattempted=None, **kwargs): | ||||
"""Inserts an output part to advertise clone bundles availability.""" | ||||
# Allow server operators to disable this behavior. | ||||
# # experimental config: ui.clonebundleadvertise | ||||
if not repo.ui.configbool('ui', 'clonebundleadvertise', True): | ||||
return | ||||
# Only advertise if a manifest is present. | ||||
if not repo.opener.exists('clonebundles.manifest'): | ||||
return | ||||
# And when changegroup data is requested. | ||||
if not kwargs.get('cg', True): | ||||
return | ||||
# And when the client supports clone bundles. | ||||
if cbattempted is None: | ||||
return | ||||
# And when the client didn't attempt a clone bundle as part of this pull. | ||||
if cbattempted: | ||||
return | ||||
# And when a full clone is requested. | ||||
# Note: client should not send "cbattempted" for regular pulls. This check | ||||
# is defense in depth. | ||||
if common and common != [nullid]: | ||||
return | ||||
msg = _('this server supports the experimental "clone bundles" feature ' | ||||
'that should enable faster and more reliable cloning\n' | ||||
'help test it by setting the "experimental.clonebundles" config ' | ||||
'flag to "true"') | ||||
bundler.newpart('output', data=msg) | ||||
Gregory Szorc
|
r26623 | def extsetup(ui): | ||
extensions.wrapfunction(wireproto, '_capabilities', capabilities) | ||||