mercurial/bundlecaches.py
branchstable
changeset 49366 288de6f5d724
parent 49332 d89bfc075289
child 49648 9be765b82a90
equal deleted inserted replaced
49364:e8ea403b1c46 49366:288de6f5d724
     1 # bundlecaches.py - utility to deal with pre-computed bundle for servers
     1 # bundlecaches.py - utility to deal with pre-computed bundle for servers
     2 #
     2 #
     3 # This software may be used and distributed according to the terms of the
     3 # This software may be used and distributed according to the terms of the
     4 # GNU General Public License version 2 or any later version.
     4 # GNU General Public License version 2 or any later version.
       
     5 
       
     6 import collections
     5 
     7 
     6 from .i18n import _
     8 from .i18n import _
     7 
     9 
     8 from .thirdparty import attr
    10 from .thirdparty import attr
     9 
    11 
    19 
    21 
    20 CB_MANIFEST_FILE = b'clonebundles.manifest'
    22 CB_MANIFEST_FILE = b'clonebundles.manifest'
    21 
    23 
    22 
    24 
    23 @attr.s
    25 @attr.s
    24 class bundlespec(object):
    26 class bundlespec:
    25     compression = attr.ib()
    27     compression = attr.ib()
    26     wirecompression = attr.ib()
    28     wirecompression = attr.ib()
    27     version = attr.ib()
    29     version = attr.ib()
    28     wireversion = attr.ib()
    30     wireversion = attr.ib()
    29     params = attr.ib()
    31     # parameters explicitly overwritten by the config or the specification
    30     contentopts = attr.ib()
    32     _explicit_params = attr.ib()
       
    33     # default parameter for the version
       
    34     #
       
    35     # Keeping it separated is useful to check what was actually overwritten.
       
    36     _default_opts = attr.ib()
       
    37 
       
    38     @property
       
    39     def params(self):
       
    40         return collections.ChainMap(self._explicit_params, self._default_opts)
       
    41 
       
    42     @property
       
    43     def contentopts(self):
       
    44         # kept for Backward Compatibility concerns.
       
    45         return self.params
       
    46 
       
    47     def set_param(self, key, value, overwrite=True):
       
    48         """Set a bundle parameter value.
       
    49 
       
    50         Will only overwrite if overwrite is true"""
       
    51         if overwrite or key not in self._explicit_params:
       
    52             self._explicit_params[key] = value
    31 
    53 
    32 
    54 
    33 # Maps bundle version human names to changegroup versions.
    55 # Maps bundle version human names to changegroup versions.
    34 _bundlespeccgversions = {
    56 _bundlespeccgversions = {
    35     b'v1': b'01',
    57     b'v1': b'01',
    54         b'obsolescence': False,
    76         b'obsolescence': False,
    55         b'phases': False,
    77         b'phases': False,
    56         b'tagsfnodescache': True,
    78         b'tagsfnodescache': True,
    57         b'revbranchcache': True,
    79         b'revbranchcache': True,
    58     },
    80     },
    59     b'packed1': {b'cg.version': b's1'},
    81     b'streamv2': {
       
    82         b'changegroup': False,
       
    83         b'cg.version': b'02',
       
    84         b'obsolescence': False,
       
    85         b'phases': False,
       
    86         b"streamv2": True,
       
    87         b'tagsfnodescache': False,
       
    88         b'revbranchcache': False,
       
    89     },
       
    90     b'packed1': {
       
    91         b'cg.version': b's1',
       
    92     },
       
    93     b'bundle2': {  # legacy
       
    94         b'cg.version': b'02',
       
    95     },
    60 }
    96 }
    61 _bundlespeccontentopts[b'bundle2'] = _bundlespeccontentopts[b'v2']
    97 _bundlespeccontentopts[b'bundle2'] = _bundlespeccontentopts[b'v2']
    62 
    98 
    63 _bundlespecvariants = {
    99 _bundlespecvariants = {b"streamv2": {}}
    64     b"streamv2": {
       
    65         b"changegroup": False,
       
    66         b"streamv2": True,
       
    67         b"tagsfnodescache": False,
       
    68         b"revbranchcache": False,
       
    69     }
       
    70 }
       
    71 
   100 
    72 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
   101 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
    73 _bundlespecv1compengines = {b'gzip', b'bzip2', b'none'}
   102 _bundlespecv1compengines = {b'gzip', b'bzip2', b'none'}
       
   103 
       
   104 
       
   105 def param_bool(key, value):
       
   106     """make a boolean out of a parameter value"""
       
   107     b = stringutil.parsebool(value)
       
   108     if b is None:
       
   109         msg = _(b"parameter %s should be a boolean ('%s')")
       
   110         msg %= (key, value)
       
   111         raise error.InvalidBundleSpecification(msg)
       
   112     return b
       
   113 
       
   114 
       
   115 # mapping of known parameter name need their value processed
       
   116 bundle_spec_param_processing = {
       
   117     b"obsolescence": param_bool,
       
   118     b"obsolescence-mandatory": param_bool,
       
   119     b"phases": param_bool,
       
   120 }
       
   121 
       
   122 
       
   123 def _parseparams(s):
       
   124     """parse bundlespec parameter section
       
   125 
       
   126     input: "comp-version;params" string
       
   127 
       
   128     return: (spec; {param_key: param_value})
       
   129     """
       
   130     if b';' not in s:
       
   131         return s, {}
       
   132 
       
   133     params = {}
       
   134     version, paramstr = s.split(b';', 1)
       
   135 
       
   136     err = _(b'invalid bundle specification: missing "=" in parameter: %s')
       
   137     for p in paramstr.split(b';'):
       
   138         if b'=' not in p:
       
   139             msg = err % p
       
   140             raise error.InvalidBundleSpecification(msg)
       
   141 
       
   142         key, value = p.split(b'=', 1)
       
   143         key = urlreq.unquote(key)
       
   144         value = urlreq.unquote(value)
       
   145         process = bundle_spec_param_processing.get(key)
       
   146         if process is not None:
       
   147             value = process(key, value)
       
   148         params[key] = value
       
   149 
       
   150     return version, params
    74 
   151 
    75 
   152 
    76 def parsebundlespec(repo, spec, strict=True):
   153 def parsebundlespec(repo, spec, strict=True):
    77     """Parse a bundle string specification into parts.
   154     """Parse a bundle string specification into parts.
    78 
   155 
   104     bundle type/version is not recognized.
   181     bundle type/version is not recognized.
   105 
   182 
   106     Note: this function will likely eventually return a more complex data
   183     Note: this function will likely eventually return a more complex data
   107     structure, including bundle2 part information.
   184     structure, including bundle2 part information.
   108     """
   185     """
   109 
       
   110     def parseparams(s):
       
   111         if b';' not in s:
       
   112             return s, {}
       
   113 
       
   114         params = {}
       
   115         version, paramstr = s.split(b';', 1)
       
   116 
       
   117         for p in paramstr.split(b';'):
       
   118             if b'=' not in p:
       
   119                 raise error.InvalidBundleSpecification(
       
   120                     _(
       
   121                         b'invalid bundle specification: '
       
   122                         b'missing "=" in parameter: %s'
       
   123                     )
       
   124                     % p
       
   125                 )
       
   126 
       
   127             key, value = p.split(b'=', 1)
       
   128             key = urlreq.unquote(key)
       
   129             value = urlreq.unquote(value)
       
   130             params[key] = value
       
   131 
       
   132         return version, params
       
   133 
       
   134     if strict and b'-' not in spec:
   186     if strict and b'-' not in spec:
   135         raise error.InvalidBundleSpecification(
   187         raise error.InvalidBundleSpecification(
   136             _(
   188             _(
   137                 b'invalid bundle specification; '
   189                 b'invalid bundle specification; '
   138                 b'must be prefixed with compression: %s'
   190                 b'must be prefixed with compression: %s'
   139             )
   191             )
   140             % spec
   192             % spec
   141         )
   193         )
   142 
   194 
   143     if b'-' in spec:
   195     pre_args = spec.split(b';', 1)[0]
       
   196     if b'-' in pre_args:
   144         compression, version = spec.split(b'-', 1)
   197         compression, version = spec.split(b'-', 1)
   145 
   198 
   146         if compression not in util.compengines.supportedbundlenames:
   199         if compression not in util.compengines.supportedbundlenames:
   147             raise error.UnsupportedBundleSpecification(
   200             raise error.UnsupportedBundleSpecification(
   148                 _(b'%s compression is not supported') % compression
   201                 _(b'%s compression is not supported') % compression
   149             )
   202             )
   150 
   203 
   151         version, params = parseparams(version)
   204         version, params = _parseparams(version)
   152 
   205 
   153         if version not in _bundlespeccgversions:
   206         if version not in _bundlespeccontentopts:
   154             raise error.UnsupportedBundleSpecification(
   207             raise error.UnsupportedBundleSpecification(
   155                 _(b'%s is not a recognized bundle version') % version
   208                 _(b'%s is not a recognized bundle version') % version
   156             )
   209             )
   157     else:
   210     else:
   158         # Value could be just the compression or just the version, in which
   211         # Value could be just the compression or just the version, in which
   159         # case some defaults are assumed (but only when not in strict mode).
   212         # case some defaults are assumed (but only when not in strict mode).
   160         assert not strict
   213         assert not strict
   161 
   214 
   162         spec, params = parseparams(spec)
   215         spec, params = _parseparams(spec)
   163 
   216 
   164         if spec in util.compengines.supportedbundlenames:
   217         if spec in util.compengines.supportedbundlenames:
   165             compression = spec
   218             compression = spec
   166             version = b'v1'
   219             version = b'v1'
   167             # Generaldelta repos require v2.
   220             # Generaldelta repos require v2.
   170             elif requirementsmod.REVLOGV2_REQUIREMENT in repo.requirements:
   223             elif requirementsmod.REVLOGV2_REQUIREMENT in repo.requirements:
   171                 version = b'v2'
   224                 version = b'v2'
   172             # Modern compression engines require v2.
   225             # Modern compression engines require v2.
   173             if compression not in _bundlespecv1compengines:
   226             if compression not in _bundlespecv1compengines:
   174                 version = b'v2'
   227                 version = b'v2'
   175         elif spec in _bundlespeccgversions:
   228         elif spec in _bundlespeccontentopts:
   176             if spec == b'packed1':
   229             if spec == b'packed1':
   177                 compression = b'none'
   230                 compression = b'none'
   178             else:
   231             else:
   179                 compression = b'bzip2'
   232                 compression = b'bzip2'
   180             version = spec
   233             version = spec
   201                 _(b'missing support for repository features: %s')
   254                 _(b'missing support for repository features: %s')
   202                 % b', '.join(sorted(missingreqs))
   255                 % b', '.join(sorted(missingreqs))
   203             )
   256             )
   204 
   257 
   205     # Compute contentopts based on the version
   258     # Compute contentopts based on the version
       
   259     if b"stream" in params and params[b"stream"] == b"v2":
       
   260         # That case is fishy as this mostly derails the version selection
       
   261         # mechanism. `stream` bundles are quite specific and used differently
       
   262         # as "normal" bundles.
       
   263         #
       
   264         # So we are pinning this to "v2", as this will likely be
       
   265         # compatible forever. (see the next conditional).
       
   266         #
       
   267         # (we should probably define a cleaner way to do this and raise a
       
   268         # warning when the old way is encounter)
       
   269         version = b"streamv2"
   206     contentopts = _bundlespeccontentopts.get(version, {}).copy()
   270     contentopts = _bundlespeccontentopts.get(version, {}).copy()
   207 
   271     if version == b"streamv2":
   208     # Process the variants
   272         # streamv2 have been reported as "v2" for a while.
   209     if b"stream" in params and params[b"stream"] == b"v2":
   273         version = b"v2"
   210         variant = _bundlespecvariants[b"streamv2"]
       
   211         contentopts.update(variant)
       
   212 
   274 
   213     engine = util.compengines.forbundlename(compression)
   275     engine = util.compengines.forbundlename(compression)
   214     compression, wirecompression = engine.bundletype()
   276     compression, wirecompression = engine.bundletype()
   215     wireversion = _bundlespeccgversions[version]
   277     wireversion = _bundlespeccontentopts[version][b'cg.version']
   216 
   278 
   217     return bundlespec(
   279     return bundlespec(
   218         compression, wirecompression, version, wireversion, params, contentopts
   280         compression, wirecompression, version, wireversion, params, contentopts
   219     )
   281     )
   220 
   282 
   341         newentries.append(entry)
   403         newentries.append(entry)
   342 
   404 
   343     return newentries
   405     return newentries
   344 
   406 
   345 
   407 
   346 class clonebundleentry(object):
   408 class clonebundleentry:
   347     """Represents an item in a clone bundles manifest.
   409     """Represents an item in a clone bundles manifest.
   348 
   410 
   349     This rich class is needed to support sorting since sorted() in Python 3
   411     This rich class is needed to support sorting since sorted() in Python 3
   350     doesn't support ``cmp`` and our comparison is complex enough that ``key=``
   412     doesn't support ``cmp`` and our comparison is complex enough that ``key=``
   351     won't work.
   413     won't work.