##// END OF EJS Templates
streamclone: new server config and some API changes for narrow stream clones...
Pulkit Goyal -
r40374:af62936c default
parent child Browse files
Show More
@@ -0,0 +1,39
1 Tests narrow stream clones
2
3 $ . "$TESTDIR/narrow-library.sh"
4
5 Server setup
6
7 $ hg init master
8 $ cd master
9 $ mkdir dir
10 $ mkdir dir/src
11 $ cd dir/src
12 $ for x in `$TESTDIR/seq.py 20`; do echo $x > "f$x"; hg add "f$x"; hg commit -m "Commit src $x"; done
13
14 $ cd ..
15 $ mkdir tests
16 $ cd tests
17 $ for x in `$TESTDIR/seq.py 20`; do echo $x > "f$x"; hg add "f$x"; hg commit -m "Commit src $x"; done
18 $ cd ../../..
19
20 Trying to stream clone when the server does not support it
21
22 $ hg clone --narrow ssh://user@dummy/master narrow --noupdate --include "dir/src/f10" --stream
23 streaming all changes
24 remote: abort: server does not support narrow stream clones
25 abort: pull failed on remote
26 [255]
27
28 Enable stream clone on the server
29
30 $ echo "[server]" >> master/.hg/hgrc
31 $ echo "stream-narrow-clones=True" >> master/.hg/hgrc
32
33 Cloning a specific file when stream clone is supported
34
35 $ hg clone --narrow ssh://user@dummy/master narrow --noupdate --include "dir/src/f10" --stream
36 streaming all changes
37 remote: abort: server does not support narrow stream clones
38 abort: pull failed on remote
39 [255]
@@ -1,2302 +1,2313
1 # bundle2.py - generic container format to transmit arbitrary data.
1 # bundle2.py - generic container format to transmit arbitrary data.
2 #
2 #
3 # Copyright 2013 Facebook, Inc.
3 # Copyright 2013 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """Handling of the new bundle2 format
7 """Handling of the new bundle2 format
8
8
9 The goal of bundle2 is to act as an atomically packet to transmit a set of
9 The goal of bundle2 is to act as an atomically packet to transmit a set of
10 payloads in an application agnostic way. It consist in a sequence of "parts"
10 payloads in an application agnostic way. It consist in a sequence of "parts"
11 that will be handed to and processed by the application layer.
11 that will be handed to and processed by the application layer.
12
12
13
13
14 General format architecture
14 General format architecture
15 ===========================
15 ===========================
16
16
17 The format is architectured as follow
17 The format is architectured as follow
18
18
19 - magic string
19 - magic string
20 - stream level parameters
20 - stream level parameters
21 - payload parts (any number)
21 - payload parts (any number)
22 - end of stream marker.
22 - end of stream marker.
23
23
24 the Binary format
24 the Binary format
25 ============================
25 ============================
26
26
27 All numbers are unsigned and big-endian.
27 All numbers are unsigned and big-endian.
28
28
29 stream level parameters
29 stream level parameters
30 ------------------------
30 ------------------------
31
31
32 Binary format is as follow
32 Binary format is as follow
33
33
34 :params size: int32
34 :params size: int32
35
35
36 The total number of Bytes used by the parameters
36 The total number of Bytes used by the parameters
37
37
38 :params value: arbitrary number of Bytes
38 :params value: arbitrary number of Bytes
39
39
40 A blob of `params size` containing the serialized version of all stream level
40 A blob of `params size` containing the serialized version of all stream level
41 parameters.
41 parameters.
42
42
43 The blob contains a space separated list of parameters. Parameters with value
43 The blob contains a space separated list of parameters. Parameters with value
44 are stored in the form `<name>=<value>`. Both name and value are urlquoted.
44 are stored in the form `<name>=<value>`. Both name and value are urlquoted.
45
45
46 Empty name are obviously forbidden.
46 Empty name are obviously forbidden.
47
47
48 Name MUST start with a letter. If this first letter is lower case, the
48 Name MUST start with a letter. If this first letter is lower case, the
49 parameter is advisory and can be safely ignored. However when the first
49 parameter is advisory and can be safely ignored. However when the first
50 letter is capital, the parameter is mandatory and the bundling process MUST
50 letter is capital, the parameter is mandatory and the bundling process MUST
51 stop if he is not able to proceed it.
51 stop if he is not able to proceed it.
52
52
53 Stream parameters use a simple textual format for two main reasons:
53 Stream parameters use a simple textual format for two main reasons:
54
54
55 - Stream level parameters should remain simple and we want to discourage any
55 - Stream level parameters should remain simple and we want to discourage any
56 crazy usage.
56 crazy usage.
57 - Textual data allow easy human inspection of a bundle2 header in case of
57 - Textual data allow easy human inspection of a bundle2 header in case of
58 troubles.
58 troubles.
59
59
60 Any Applicative level options MUST go into a bundle2 part instead.
60 Any Applicative level options MUST go into a bundle2 part instead.
61
61
62 Payload part
62 Payload part
63 ------------------------
63 ------------------------
64
64
65 Binary format is as follow
65 Binary format is as follow
66
66
67 :header size: int32
67 :header size: int32
68
68
69 The total number of Bytes used by the part header. When the header is empty
69 The total number of Bytes used by the part header. When the header is empty
70 (size = 0) this is interpreted as the end of stream marker.
70 (size = 0) this is interpreted as the end of stream marker.
71
71
72 :header:
72 :header:
73
73
74 The header defines how to interpret the part. It contains two piece of
74 The header defines how to interpret the part. It contains two piece of
75 data: the part type, and the part parameters.
75 data: the part type, and the part parameters.
76
76
77 The part type is used to route an application level handler, that can
77 The part type is used to route an application level handler, that can
78 interpret payload.
78 interpret payload.
79
79
80 Part parameters are passed to the application level handler. They are
80 Part parameters are passed to the application level handler. They are
81 meant to convey information that will help the application level object to
81 meant to convey information that will help the application level object to
82 interpret the part payload.
82 interpret the part payload.
83
83
84 The binary format of the header is has follow
84 The binary format of the header is has follow
85
85
86 :typesize: (one byte)
86 :typesize: (one byte)
87
87
88 :parttype: alphanumerical part name (restricted to [a-zA-Z0-9_:-]*)
88 :parttype: alphanumerical part name (restricted to [a-zA-Z0-9_:-]*)
89
89
90 :partid: A 32bits integer (unique in the bundle) that can be used to refer
90 :partid: A 32bits integer (unique in the bundle) that can be used to refer
91 to this part.
91 to this part.
92
92
93 :parameters:
93 :parameters:
94
94
95 Part's parameter may have arbitrary content, the binary structure is::
95 Part's parameter may have arbitrary content, the binary structure is::
96
96
97 <mandatory-count><advisory-count><param-sizes><param-data>
97 <mandatory-count><advisory-count><param-sizes><param-data>
98
98
99 :mandatory-count: 1 byte, number of mandatory parameters
99 :mandatory-count: 1 byte, number of mandatory parameters
100
100
101 :advisory-count: 1 byte, number of advisory parameters
101 :advisory-count: 1 byte, number of advisory parameters
102
102
103 :param-sizes:
103 :param-sizes:
104
104
105 N couple of bytes, where N is the total number of parameters. Each
105 N couple of bytes, where N is the total number of parameters. Each
106 couple contains (<size-of-key>, <size-of-value) for one parameter.
106 couple contains (<size-of-key>, <size-of-value) for one parameter.
107
107
108 :param-data:
108 :param-data:
109
109
110 A blob of bytes from which each parameter key and value can be
110 A blob of bytes from which each parameter key and value can be
111 retrieved using the list of size couples stored in the previous
111 retrieved using the list of size couples stored in the previous
112 field.
112 field.
113
113
114 Mandatory parameters comes first, then the advisory ones.
114 Mandatory parameters comes first, then the advisory ones.
115
115
116 Each parameter's key MUST be unique within the part.
116 Each parameter's key MUST be unique within the part.
117
117
118 :payload:
118 :payload:
119
119
120 payload is a series of `<chunksize><chunkdata>`.
120 payload is a series of `<chunksize><chunkdata>`.
121
121
122 `chunksize` is an int32, `chunkdata` are plain bytes (as much as
122 `chunksize` is an int32, `chunkdata` are plain bytes (as much as
123 `chunksize` says)` The payload part is concluded by a zero size chunk.
123 `chunksize` says)` The payload part is concluded by a zero size chunk.
124
124
125 The current implementation always produces either zero or one chunk.
125 The current implementation always produces either zero or one chunk.
126 This is an implementation limitation that will ultimately be lifted.
126 This is an implementation limitation that will ultimately be lifted.
127
127
128 `chunksize` can be negative to trigger special case processing. No such
128 `chunksize` can be negative to trigger special case processing. No such
129 processing is in place yet.
129 processing is in place yet.
130
130
131 Bundle processing
131 Bundle processing
132 ============================
132 ============================
133
133
134 Each part is processed in order using a "part handler". Handler are registered
134 Each part is processed in order using a "part handler". Handler are registered
135 for a certain part type.
135 for a certain part type.
136
136
137 The matching of a part to its handler is case insensitive. The case of the
137 The matching of a part to its handler is case insensitive. The case of the
138 part type is used to know if a part is mandatory or advisory. If the Part type
138 part type is used to know if a part is mandatory or advisory. If the Part type
139 contains any uppercase char it is considered mandatory. When no handler is
139 contains any uppercase char it is considered mandatory. When no handler is
140 known for a Mandatory part, the process is aborted and an exception is raised.
140 known for a Mandatory part, the process is aborted and an exception is raised.
141 If the part is advisory and no handler is known, the part is ignored. When the
141 If the part is advisory and no handler is known, the part is ignored. When the
142 process is aborted, the full bundle is still read from the stream to keep the
142 process is aborted, the full bundle is still read from the stream to keep the
143 channel usable. But none of the part read from an abort are processed. In the
143 channel usable. But none of the part read from an abort are processed. In the
144 future, dropping the stream may become an option for channel we do not care to
144 future, dropping the stream may become an option for channel we do not care to
145 preserve.
145 preserve.
146 """
146 """
147
147
148 from __future__ import absolute_import, division
148 from __future__ import absolute_import, division
149
149
150 import collections
150 import collections
151 import errno
151 import errno
152 import os
152 import os
153 import re
153 import re
154 import string
154 import string
155 import struct
155 import struct
156 import sys
156 import sys
157
157
158 from .i18n import _
158 from .i18n import _
159 from . import (
159 from . import (
160 bookmarks,
160 bookmarks,
161 changegroup,
161 changegroup,
162 encoding,
162 encoding,
163 error,
163 error,
164 node as nodemod,
164 node as nodemod,
165 obsolete,
165 obsolete,
166 phases,
166 phases,
167 pushkey,
167 pushkey,
168 pycompat,
168 pycompat,
169 streamclone,
169 streamclone,
170 tags,
170 tags,
171 url,
171 url,
172 util,
172 util,
173 )
173 )
174 from .utils import (
174 from .utils import (
175 stringutil,
175 stringutil,
176 )
176 )
177
177
178 urlerr = util.urlerr
178 urlerr = util.urlerr
179 urlreq = util.urlreq
179 urlreq = util.urlreq
180
180
181 _pack = struct.pack
181 _pack = struct.pack
182 _unpack = struct.unpack
182 _unpack = struct.unpack
183
183
184 _fstreamparamsize = '>i'
184 _fstreamparamsize = '>i'
185 _fpartheadersize = '>i'
185 _fpartheadersize = '>i'
186 _fparttypesize = '>B'
186 _fparttypesize = '>B'
187 _fpartid = '>I'
187 _fpartid = '>I'
188 _fpayloadsize = '>i'
188 _fpayloadsize = '>i'
189 _fpartparamcount = '>BB'
189 _fpartparamcount = '>BB'
190
190
191 preferedchunksize = 32768
191 preferedchunksize = 32768
192
192
193 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
193 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
194
194
195 def outdebug(ui, message):
195 def outdebug(ui, message):
196 """debug regarding output stream (bundling)"""
196 """debug regarding output stream (bundling)"""
197 if ui.configbool('devel', 'bundle2.debug'):
197 if ui.configbool('devel', 'bundle2.debug'):
198 ui.debug('bundle2-output: %s\n' % message)
198 ui.debug('bundle2-output: %s\n' % message)
199
199
200 def indebug(ui, message):
200 def indebug(ui, message):
201 """debug on input stream (unbundling)"""
201 """debug on input stream (unbundling)"""
202 if ui.configbool('devel', 'bundle2.debug'):
202 if ui.configbool('devel', 'bundle2.debug'):
203 ui.debug('bundle2-input: %s\n' % message)
203 ui.debug('bundle2-input: %s\n' % message)
204
204
205 def validateparttype(parttype):
205 def validateparttype(parttype):
206 """raise ValueError if a parttype contains invalid character"""
206 """raise ValueError if a parttype contains invalid character"""
207 if _parttypeforbidden.search(parttype):
207 if _parttypeforbidden.search(parttype):
208 raise ValueError(parttype)
208 raise ValueError(parttype)
209
209
210 def _makefpartparamsizes(nbparams):
210 def _makefpartparamsizes(nbparams):
211 """return a struct format to read part parameter sizes
211 """return a struct format to read part parameter sizes
212
212
213 The number parameters is variable so we need to build that format
213 The number parameters is variable so we need to build that format
214 dynamically.
214 dynamically.
215 """
215 """
216 return '>'+('BB'*nbparams)
216 return '>'+('BB'*nbparams)
217
217
218 parthandlermapping = {}
218 parthandlermapping = {}
219
219
220 def parthandler(parttype, params=()):
220 def parthandler(parttype, params=()):
221 """decorator that register a function as a bundle2 part handler
221 """decorator that register a function as a bundle2 part handler
222
222
223 eg::
223 eg::
224
224
225 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
225 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
226 def myparttypehandler(...):
226 def myparttypehandler(...):
227 '''process a part of type "my part".'''
227 '''process a part of type "my part".'''
228 ...
228 ...
229 """
229 """
230 validateparttype(parttype)
230 validateparttype(parttype)
231 def _decorator(func):
231 def _decorator(func):
232 lparttype = parttype.lower() # enforce lower case matching.
232 lparttype = parttype.lower() # enforce lower case matching.
233 assert lparttype not in parthandlermapping
233 assert lparttype not in parthandlermapping
234 parthandlermapping[lparttype] = func
234 parthandlermapping[lparttype] = func
235 func.params = frozenset(params)
235 func.params = frozenset(params)
236 return func
236 return func
237 return _decorator
237 return _decorator
238
238
239 class unbundlerecords(object):
239 class unbundlerecords(object):
240 """keep record of what happens during and unbundle
240 """keep record of what happens during and unbundle
241
241
242 New records are added using `records.add('cat', obj)`. Where 'cat' is a
242 New records are added using `records.add('cat', obj)`. Where 'cat' is a
243 category of record and obj is an arbitrary object.
243 category of record and obj is an arbitrary object.
244
244
245 `records['cat']` will return all entries of this category 'cat'.
245 `records['cat']` will return all entries of this category 'cat'.
246
246
247 Iterating on the object itself will yield `('category', obj)` tuples
247 Iterating on the object itself will yield `('category', obj)` tuples
248 for all entries.
248 for all entries.
249
249
250 All iterations happens in chronological order.
250 All iterations happens in chronological order.
251 """
251 """
252
252
253 def __init__(self):
253 def __init__(self):
254 self._categories = {}
254 self._categories = {}
255 self._sequences = []
255 self._sequences = []
256 self._replies = {}
256 self._replies = {}
257
257
258 def add(self, category, entry, inreplyto=None):
258 def add(self, category, entry, inreplyto=None):
259 """add a new record of a given category.
259 """add a new record of a given category.
260
260
261 The entry can then be retrieved in the list returned by
261 The entry can then be retrieved in the list returned by
262 self['category']."""
262 self['category']."""
263 self._categories.setdefault(category, []).append(entry)
263 self._categories.setdefault(category, []).append(entry)
264 self._sequences.append((category, entry))
264 self._sequences.append((category, entry))
265 if inreplyto is not None:
265 if inreplyto is not None:
266 self.getreplies(inreplyto).add(category, entry)
266 self.getreplies(inreplyto).add(category, entry)
267
267
268 def getreplies(self, partid):
268 def getreplies(self, partid):
269 """get the records that are replies to a specific part"""
269 """get the records that are replies to a specific part"""
270 return self._replies.setdefault(partid, unbundlerecords())
270 return self._replies.setdefault(partid, unbundlerecords())
271
271
272 def __getitem__(self, cat):
272 def __getitem__(self, cat):
273 return tuple(self._categories.get(cat, ()))
273 return tuple(self._categories.get(cat, ()))
274
274
275 def __iter__(self):
275 def __iter__(self):
276 return iter(self._sequences)
276 return iter(self._sequences)
277
277
278 def __len__(self):
278 def __len__(self):
279 return len(self._sequences)
279 return len(self._sequences)
280
280
281 def __nonzero__(self):
281 def __nonzero__(self):
282 return bool(self._sequences)
282 return bool(self._sequences)
283
283
284 __bool__ = __nonzero__
284 __bool__ = __nonzero__
285
285
286 class bundleoperation(object):
286 class bundleoperation(object):
287 """an object that represents a single bundling process
287 """an object that represents a single bundling process
288
288
289 Its purpose is to carry unbundle-related objects and states.
289 Its purpose is to carry unbundle-related objects and states.
290
290
291 A new object should be created at the beginning of each bundle processing.
291 A new object should be created at the beginning of each bundle processing.
292 The object is to be returned by the processing function.
292 The object is to be returned by the processing function.
293
293
294 The object has very little content now it will ultimately contain:
294 The object has very little content now it will ultimately contain:
295 * an access to the repo the bundle is applied to,
295 * an access to the repo the bundle is applied to,
296 * a ui object,
296 * a ui object,
297 * a way to retrieve a transaction to add changes to the repo,
297 * a way to retrieve a transaction to add changes to the repo,
298 * a way to record the result of processing each part,
298 * a way to record the result of processing each part,
299 * a way to construct a bundle response when applicable.
299 * a way to construct a bundle response when applicable.
300 """
300 """
301
301
302 def __init__(self, repo, transactiongetter, captureoutput=True, source=''):
302 def __init__(self, repo, transactiongetter, captureoutput=True, source=''):
303 self.repo = repo
303 self.repo = repo
304 self.ui = repo.ui
304 self.ui = repo.ui
305 self.records = unbundlerecords()
305 self.records = unbundlerecords()
306 self.reply = None
306 self.reply = None
307 self.captureoutput = captureoutput
307 self.captureoutput = captureoutput
308 self.hookargs = {}
308 self.hookargs = {}
309 self._gettransaction = transactiongetter
309 self._gettransaction = transactiongetter
310 # carries value that can modify part behavior
310 # carries value that can modify part behavior
311 self.modes = {}
311 self.modes = {}
312 self.source = source
312 self.source = source
313
313
314 def gettransaction(self):
314 def gettransaction(self):
315 transaction = self._gettransaction()
315 transaction = self._gettransaction()
316
316
317 if self.hookargs:
317 if self.hookargs:
318 # the ones added to the transaction supercede those added
318 # the ones added to the transaction supercede those added
319 # to the operation.
319 # to the operation.
320 self.hookargs.update(transaction.hookargs)
320 self.hookargs.update(transaction.hookargs)
321 transaction.hookargs = self.hookargs
321 transaction.hookargs = self.hookargs
322
322
323 # mark the hookargs as flushed. further attempts to add to
323 # mark the hookargs as flushed. further attempts to add to
324 # hookargs will result in an abort.
324 # hookargs will result in an abort.
325 self.hookargs = None
325 self.hookargs = None
326
326
327 return transaction
327 return transaction
328
328
329 def addhookargs(self, hookargs):
329 def addhookargs(self, hookargs):
330 if self.hookargs is None:
330 if self.hookargs is None:
331 raise error.ProgrammingError('attempted to add hookargs to '
331 raise error.ProgrammingError('attempted to add hookargs to '
332 'operation after transaction started')
332 'operation after transaction started')
333 self.hookargs.update(hookargs)
333 self.hookargs.update(hookargs)
334
334
335 class TransactionUnavailable(RuntimeError):
335 class TransactionUnavailable(RuntimeError):
336 pass
336 pass
337
337
338 def _notransaction():
338 def _notransaction():
339 """default method to get a transaction while processing a bundle
339 """default method to get a transaction while processing a bundle
340
340
341 Raise an exception to highlight the fact that no transaction was expected
341 Raise an exception to highlight the fact that no transaction was expected
342 to be created"""
342 to be created"""
343 raise TransactionUnavailable()
343 raise TransactionUnavailable()
344
344
345 def applybundle(repo, unbundler, tr, source, url=None, **kwargs):
345 def applybundle(repo, unbundler, tr, source, url=None, **kwargs):
346 # transform me into unbundler.apply() as soon as the freeze is lifted
346 # transform me into unbundler.apply() as soon as the freeze is lifted
347 if isinstance(unbundler, unbundle20):
347 if isinstance(unbundler, unbundle20):
348 tr.hookargs['bundle2'] = '1'
348 tr.hookargs['bundle2'] = '1'
349 if source is not None and 'source' not in tr.hookargs:
349 if source is not None and 'source' not in tr.hookargs:
350 tr.hookargs['source'] = source
350 tr.hookargs['source'] = source
351 if url is not None and 'url' not in tr.hookargs:
351 if url is not None and 'url' not in tr.hookargs:
352 tr.hookargs['url'] = url
352 tr.hookargs['url'] = url
353 return processbundle(repo, unbundler, lambda: tr, source=source)
353 return processbundle(repo, unbundler, lambda: tr, source=source)
354 else:
354 else:
355 # the transactiongetter won't be used, but we might as well set it
355 # the transactiongetter won't be used, but we might as well set it
356 op = bundleoperation(repo, lambda: tr, source=source)
356 op = bundleoperation(repo, lambda: tr, source=source)
357 _processchangegroup(op, unbundler, tr, source, url, **kwargs)
357 _processchangegroup(op, unbundler, tr, source, url, **kwargs)
358 return op
358 return op
359
359
360 class partiterator(object):
360 class partiterator(object):
361 def __init__(self, repo, op, unbundler):
361 def __init__(self, repo, op, unbundler):
362 self.repo = repo
362 self.repo = repo
363 self.op = op
363 self.op = op
364 self.unbundler = unbundler
364 self.unbundler = unbundler
365 self.iterator = None
365 self.iterator = None
366 self.count = 0
366 self.count = 0
367 self.current = None
367 self.current = None
368
368
369 def __enter__(self):
369 def __enter__(self):
370 def func():
370 def func():
371 itr = enumerate(self.unbundler.iterparts())
371 itr = enumerate(self.unbundler.iterparts())
372 for count, p in itr:
372 for count, p in itr:
373 self.count = count
373 self.count = count
374 self.current = p
374 self.current = p
375 yield p
375 yield p
376 p.consume()
376 p.consume()
377 self.current = None
377 self.current = None
378 self.iterator = func()
378 self.iterator = func()
379 return self.iterator
379 return self.iterator
380
380
381 def __exit__(self, type, exc, tb):
381 def __exit__(self, type, exc, tb):
382 if not self.iterator:
382 if not self.iterator:
383 return
383 return
384
384
385 # Only gracefully abort in a normal exception situation. User aborts
385 # Only gracefully abort in a normal exception situation. User aborts
386 # like Ctrl+C throw a KeyboardInterrupt which is not a base Exception,
386 # like Ctrl+C throw a KeyboardInterrupt which is not a base Exception,
387 # and should not gracefully cleanup.
387 # and should not gracefully cleanup.
388 if isinstance(exc, Exception):
388 if isinstance(exc, Exception):
389 # Any exceptions seeking to the end of the bundle at this point are
389 # Any exceptions seeking to the end of the bundle at this point are
390 # almost certainly related to the underlying stream being bad.
390 # almost certainly related to the underlying stream being bad.
391 # And, chances are that the exception we're handling is related to
391 # And, chances are that the exception we're handling is related to
392 # getting in that bad state. So, we swallow the seeking error and
392 # getting in that bad state. So, we swallow the seeking error and
393 # re-raise the original error.
393 # re-raise the original error.
394 seekerror = False
394 seekerror = False
395 try:
395 try:
396 if self.current:
396 if self.current:
397 # consume the part content to not corrupt the stream.
397 # consume the part content to not corrupt the stream.
398 self.current.consume()
398 self.current.consume()
399
399
400 for part in self.iterator:
400 for part in self.iterator:
401 # consume the bundle content
401 # consume the bundle content
402 part.consume()
402 part.consume()
403 except Exception:
403 except Exception:
404 seekerror = True
404 seekerror = True
405
405
406 # Small hack to let caller code distinguish exceptions from bundle2
406 # Small hack to let caller code distinguish exceptions from bundle2
407 # processing from processing the old format. This is mostly needed
407 # processing from processing the old format. This is mostly needed
408 # to handle different return codes to unbundle according to the type
408 # to handle different return codes to unbundle according to the type
409 # of bundle. We should probably clean up or drop this return code
409 # of bundle. We should probably clean up or drop this return code
410 # craziness in a future version.
410 # craziness in a future version.
411 exc.duringunbundle2 = True
411 exc.duringunbundle2 = True
412 salvaged = []
412 salvaged = []
413 replycaps = None
413 replycaps = None
414 if self.op.reply is not None:
414 if self.op.reply is not None:
415 salvaged = self.op.reply.salvageoutput()
415 salvaged = self.op.reply.salvageoutput()
416 replycaps = self.op.reply.capabilities
416 replycaps = self.op.reply.capabilities
417 exc._replycaps = replycaps
417 exc._replycaps = replycaps
418 exc._bundle2salvagedoutput = salvaged
418 exc._bundle2salvagedoutput = salvaged
419
419
420 # Re-raising from a variable loses the original stack. So only use
420 # Re-raising from a variable loses the original stack. So only use
421 # that form if we need to.
421 # that form if we need to.
422 if seekerror:
422 if seekerror:
423 raise exc
423 raise exc
424
424
425 self.repo.ui.debug('bundle2-input-bundle: %i parts total\n' %
425 self.repo.ui.debug('bundle2-input-bundle: %i parts total\n' %
426 self.count)
426 self.count)
427
427
428 def processbundle(repo, unbundler, transactiongetter=None, op=None, source=''):
428 def processbundle(repo, unbundler, transactiongetter=None, op=None, source=''):
429 """This function process a bundle, apply effect to/from a repo
429 """This function process a bundle, apply effect to/from a repo
430
430
431 It iterates over each part then searches for and uses the proper handling
431 It iterates over each part then searches for and uses the proper handling
432 code to process the part. Parts are processed in order.
432 code to process the part. Parts are processed in order.
433
433
434 Unknown Mandatory part will abort the process.
434 Unknown Mandatory part will abort the process.
435
435
436 It is temporarily possible to provide a prebuilt bundleoperation to the
436 It is temporarily possible to provide a prebuilt bundleoperation to the
437 function. This is used to ensure output is properly propagated in case of
437 function. This is used to ensure output is properly propagated in case of
438 an error during the unbundling. This output capturing part will likely be
438 an error during the unbundling. This output capturing part will likely be
439 reworked and this ability will probably go away in the process.
439 reworked and this ability will probably go away in the process.
440 """
440 """
441 if op is None:
441 if op is None:
442 if transactiongetter is None:
442 if transactiongetter is None:
443 transactiongetter = _notransaction
443 transactiongetter = _notransaction
444 op = bundleoperation(repo, transactiongetter, source=source)
444 op = bundleoperation(repo, transactiongetter, source=source)
445 # todo:
445 # todo:
446 # - replace this is a init function soon.
446 # - replace this is a init function soon.
447 # - exception catching
447 # - exception catching
448 unbundler.params
448 unbundler.params
449 if repo.ui.debugflag:
449 if repo.ui.debugflag:
450 msg = ['bundle2-input-bundle:']
450 msg = ['bundle2-input-bundle:']
451 if unbundler.params:
451 if unbundler.params:
452 msg.append(' %i params' % len(unbundler.params))
452 msg.append(' %i params' % len(unbundler.params))
453 if op._gettransaction is None or op._gettransaction is _notransaction:
453 if op._gettransaction is None or op._gettransaction is _notransaction:
454 msg.append(' no-transaction')
454 msg.append(' no-transaction')
455 else:
455 else:
456 msg.append(' with-transaction')
456 msg.append(' with-transaction')
457 msg.append('\n')
457 msg.append('\n')
458 repo.ui.debug(''.join(msg))
458 repo.ui.debug(''.join(msg))
459
459
460 processparts(repo, op, unbundler)
460 processparts(repo, op, unbundler)
461
461
462 return op
462 return op
463
463
464 def processparts(repo, op, unbundler):
464 def processparts(repo, op, unbundler):
465 with partiterator(repo, op, unbundler) as parts:
465 with partiterator(repo, op, unbundler) as parts:
466 for part in parts:
466 for part in parts:
467 _processpart(op, part)
467 _processpart(op, part)
468
468
469 def _processchangegroup(op, cg, tr, source, url, **kwargs):
469 def _processchangegroup(op, cg, tr, source, url, **kwargs):
470 ret = cg.apply(op.repo, tr, source, url, **kwargs)
470 ret = cg.apply(op.repo, tr, source, url, **kwargs)
471 op.records.add('changegroup', {
471 op.records.add('changegroup', {
472 'return': ret,
472 'return': ret,
473 })
473 })
474 return ret
474 return ret
475
475
476 def _gethandler(op, part):
476 def _gethandler(op, part):
477 status = 'unknown' # used by debug output
477 status = 'unknown' # used by debug output
478 try:
478 try:
479 handler = parthandlermapping.get(part.type)
479 handler = parthandlermapping.get(part.type)
480 if handler is None:
480 if handler is None:
481 status = 'unsupported-type'
481 status = 'unsupported-type'
482 raise error.BundleUnknownFeatureError(parttype=part.type)
482 raise error.BundleUnknownFeatureError(parttype=part.type)
483 indebug(op.ui, 'found a handler for part %s' % part.type)
483 indebug(op.ui, 'found a handler for part %s' % part.type)
484 unknownparams = part.mandatorykeys - handler.params
484 unknownparams = part.mandatorykeys - handler.params
485 if unknownparams:
485 if unknownparams:
486 unknownparams = list(unknownparams)
486 unknownparams = list(unknownparams)
487 unknownparams.sort()
487 unknownparams.sort()
488 status = 'unsupported-params (%s)' % ', '.join(unknownparams)
488 status = 'unsupported-params (%s)' % ', '.join(unknownparams)
489 raise error.BundleUnknownFeatureError(parttype=part.type,
489 raise error.BundleUnknownFeatureError(parttype=part.type,
490 params=unknownparams)
490 params=unknownparams)
491 status = 'supported'
491 status = 'supported'
492 except error.BundleUnknownFeatureError as exc:
492 except error.BundleUnknownFeatureError as exc:
493 if part.mandatory: # mandatory parts
493 if part.mandatory: # mandatory parts
494 raise
494 raise
495 indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
495 indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
496 return # skip to part processing
496 return # skip to part processing
497 finally:
497 finally:
498 if op.ui.debugflag:
498 if op.ui.debugflag:
499 msg = ['bundle2-input-part: "%s"' % part.type]
499 msg = ['bundle2-input-part: "%s"' % part.type]
500 if not part.mandatory:
500 if not part.mandatory:
501 msg.append(' (advisory)')
501 msg.append(' (advisory)')
502 nbmp = len(part.mandatorykeys)
502 nbmp = len(part.mandatorykeys)
503 nbap = len(part.params) - nbmp
503 nbap = len(part.params) - nbmp
504 if nbmp or nbap:
504 if nbmp or nbap:
505 msg.append(' (params:')
505 msg.append(' (params:')
506 if nbmp:
506 if nbmp:
507 msg.append(' %i mandatory' % nbmp)
507 msg.append(' %i mandatory' % nbmp)
508 if nbap:
508 if nbap:
509 msg.append(' %i advisory' % nbmp)
509 msg.append(' %i advisory' % nbmp)
510 msg.append(')')
510 msg.append(')')
511 msg.append(' %s\n' % status)
511 msg.append(' %s\n' % status)
512 op.ui.debug(''.join(msg))
512 op.ui.debug(''.join(msg))
513
513
514 return handler
514 return handler
515
515
516 def _processpart(op, part):
516 def _processpart(op, part):
517 """process a single part from a bundle
517 """process a single part from a bundle
518
518
519 The part is guaranteed to have been fully consumed when the function exits
519 The part is guaranteed to have been fully consumed when the function exits
520 (even if an exception is raised)."""
520 (even if an exception is raised)."""
521 handler = _gethandler(op, part)
521 handler = _gethandler(op, part)
522 if handler is None:
522 if handler is None:
523 return
523 return
524
524
525 # handler is called outside the above try block so that we don't
525 # handler is called outside the above try block so that we don't
526 # risk catching KeyErrors from anything other than the
526 # risk catching KeyErrors from anything other than the
527 # parthandlermapping lookup (any KeyError raised by handler()
527 # parthandlermapping lookup (any KeyError raised by handler()
528 # itself represents a defect of a different variety).
528 # itself represents a defect of a different variety).
529 output = None
529 output = None
530 if op.captureoutput and op.reply is not None:
530 if op.captureoutput and op.reply is not None:
531 op.ui.pushbuffer(error=True, subproc=True)
531 op.ui.pushbuffer(error=True, subproc=True)
532 output = ''
532 output = ''
533 try:
533 try:
534 handler(op, part)
534 handler(op, part)
535 finally:
535 finally:
536 if output is not None:
536 if output is not None:
537 output = op.ui.popbuffer()
537 output = op.ui.popbuffer()
538 if output:
538 if output:
539 outpart = op.reply.newpart('output', data=output,
539 outpart = op.reply.newpart('output', data=output,
540 mandatory=False)
540 mandatory=False)
541 outpart.addparam(
541 outpart.addparam(
542 'in-reply-to', pycompat.bytestr(part.id), mandatory=False)
542 'in-reply-to', pycompat.bytestr(part.id), mandatory=False)
543
543
544 def decodecaps(blob):
544 def decodecaps(blob):
545 """decode a bundle2 caps bytes blob into a dictionary
545 """decode a bundle2 caps bytes blob into a dictionary
546
546
547 The blob is a list of capabilities (one per line)
547 The blob is a list of capabilities (one per line)
548 Capabilities may have values using a line of the form::
548 Capabilities may have values using a line of the form::
549
549
550 capability=value1,value2,value3
550 capability=value1,value2,value3
551
551
552 The values are always a list."""
552 The values are always a list."""
553 caps = {}
553 caps = {}
554 for line in blob.splitlines():
554 for line in blob.splitlines():
555 if not line:
555 if not line:
556 continue
556 continue
557 if '=' not in line:
557 if '=' not in line:
558 key, vals = line, ()
558 key, vals = line, ()
559 else:
559 else:
560 key, vals = line.split('=', 1)
560 key, vals = line.split('=', 1)
561 vals = vals.split(',')
561 vals = vals.split(',')
562 key = urlreq.unquote(key)
562 key = urlreq.unquote(key)
563 vals = [urlreq.unquote(v) for v in vals]
563 vals = [urlreq.unquote(v) for v in vals]
564 caps[key] = vals
564 caps[key] = vals
565 return caps
565 return caps
566
566
567 def encodecaps(caps):
567 def encodecaps(caps):
568 """encode a bundle2 caps dictionary into a bytes blob"""
568 """encode a bundle2 caps dictionary into a bytes blob"""
569 chunks = []
569 chunks = []
570 for ca in sorted(caps):
570 for ca in sorted(caps):
571 vals = caps[ca]
571 vals = caps[ca]
572 ca = urlreq.quote(ca)
572 ca = urlreq.quote(ca)
573 vals = [urlreq.quote(v) for v in vals]
573 vals = [urlreq.quote(v) for v in vals]
574 if vals:
574 if vals:
575 ca = "%s=%s" % (ca, ','.join(vals))
575 ca = "%s=%s" % (ca, ','.join(vals))
576 chunks.append(ca)
576 chunks.append(ca)
577 return '\n'.join(chunks)
577 return '\n'.join(chunks)
578
578
579 bundletypes = {
579 bundletypes = {
580 "": ("", 'UN'), # only when using unbundle on ssh and old http servers
580 "": ("", 'UN'), # only when using unbundle on ssh and old http servers
581 # since the unification ssh accepts a header but there
581 # since the unification ssh accepts a header but there
582 # is no capability signaling it.
582 # is no capability signaling it.
583 "HG20": (), # special-cased below
583 "HG20": (), # special-cased below
584 "HG10UN": ("HG10UN", 'UN'),
584 "HG10UN": ("HG10UN", 'UN'),
585 "HG10BZ": ("HG10", 'BZ'),
585 "HG10BZ": ("HG10", 'BZ'),
586 "HG10GZ": ("HG10GZ", 'GZ'),
586 "HG10GZ": ("HG10GZ", 'GZ'),
587 }
587 }
588
588
589 # hgweb uses this list to communicate its preferred type
589 # hgweb uses this list to communicate its preferred type
590 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
590 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
591
591
592 class bundle20(object):
592 class bundle20(object):
593 """represent an outgoing bundle2 container
593 """represent an outgoing bundle2 container
594
594
595 Use the `addparam` method to add stream level parameter. and `newpart` to
595 Use the `addparam` method to add stream level parameter. and `newpart` to
596 populate it. Then call `getchunks` to retrieve all the binary chunks of
596 populate it. Then call `getchunks` to retrieve all the binary chunks of
597 data that compose the bundle2 container."""
597 data that compose the bundle2 container."""
598
598
599 _magicstring = 'HG20'
599 _magicstring = 'HG20'
600
600
601 def __init__(self, ui, capabilities=()):
601 def __init__(self, ui, capabilities=()):
602 self.ui = ui
602 self.ui = ui
603 self._params = []
603 self._params = []
604 self._parts = []
604 self._parts = []
605 self.capabilities = dict(capabilities)
605 self.capabilities = dict(capabilities)
606 self._compengine = util.compengines.forbundletype('UN')
606 self._compengine = util.compengines.forbundletype('UN')
607 self._compopts = None
607 self._compopts = None
608 # If compression is being handled by a consumer of the raw
608 # If compression is being handled by a consumer of the raw
609 # data (e.g. the wire protocol), unsetting this flag tells
609 # data (e.g. the wire protocol), unsetting this flag tells
610 # consumers that the bundle is best left uncompressed.
610 # consumers that the bundle is best left uncompressed.
611 self.prefercompressed = True
611 self.prefercompressed = True
612
612
613 def setcompression(self, alg, compopts=None):
613 def setcompression(self, alg, compopts=None):
614 """setup core part compression to <alg>"""
614 """setup core part compression to <alg>"""
615 if alg in (None, 'UN'):
615 if alg in (None, 'UN'):
616 return
616 return
617 assert not any(n.lower() == 'compression' for n, v in self._params)
617 assert not any(n.lower() == 'compression' for n, v in self._params)
618 self.addparam('Compression', alg)
618 self.addparam('Compression', alg)
619 self._compengine = util.compengines.forbundletype(alg)
619 self._compengine = util.compengines.forbundletype(alg)
620 self._compopts = compopts
620 self._compopts = compopts
621
621
622 @property
622 @property
623 def nbparts(self):
623 def nbparts(self):
624 """total number of parts added to the bundler"""
624 """total number of parts added to the bundler"""
625 return len(self._parts)
625 return len(self._parts)
626
626
627 # methods used to defines the bundle2 content
627 # methods used to defines the bundle2 content
628 def addparam(self, name, value=None):
628 def addparam(self, name, value=None):
629 """add a stream level parameter"""
629 """add a stream level parameter"""
630 if not name:
630 if not name:
631 raise error.ProgrammingError(b'empty parameter name')
631 raise error.ProgrammingError(b'empty parameter name')
632 if name[0:1] not in pycompat.bytestr(string.ascii_letters):
632 if name[0:1] not in pycompat.bytestr(string.ascii_letters):
633 raise error.ProgrammingError(b'non letter first character: %s'
633 raise error.ProgrammingError(b'non letter first character: %s'
634 % name)
634 % name)
635 self._params.append((name, value))
635 self._params.append((name, value))
636
636
637 def addpart(self, part):
637 def addpart(self, part):
638 """add a new part to the bundle2 container
638 """add a new part to the bundle2 container
639
639
640 Parts contains the actual applicative payload."""
640 Parts contains the actual applicative payload."""
641 assert part.id is None
641 assert part.id is None
642 part.id = len(self._parts) # very cheap counter
642 part.id = len(self._parts) # very cheap counter
643 self._parts.append(part)
643 self._parts.append(part)
644
644
645 def newpart(self, typeid, *args, **kwargs):
645 def newpart(self, typeid, *args, **kwargs):
646 """create a new part and add it to the containers
646 """create a new part and add it to the containers
647
647
648 As the part is directly added to the containers. For now, this means
648 As the part is directly added to the containers. For now, this means
649 that any failure to properly initialize the part after calling
649 that any failure to properly initialize the part after calling
650 ``newpart`` should result in a failure of the whole bundling process.
650 ``newpart`` should result in a failure of the whole bundling process.
651
651
652 You can still fall back to manually create and add if you need better
652 You can still fall back to manually create and add if you need better
653 control."""
653 control."""
654 part = bundlepart(typeid, *args, **kwargs)
654 part = bundlepart(typeid, *args, **kwargs)
655 self.addpart(part)
655 self.addpart(part)
656 return part
656 return part
657
657
658 # methods used to generate the bundle2 stream
658 # methods used to generate the bundle2 stream
659 def getchunks(self):
659 def getchunks(self):
660 if self.ui.debugflag:
660 if self.ui.debugflag:
661 msg = ['bundle2-output-bundle: "%s",' % self._magicstring]
661 msg = ['bundle2-output-bundle: "%s",' % self._magicstring]
662 if self._params:
662 if self._params:
663 msg.append(' (%i params)' % len(self._params))
663 msg.append(' (%i params)' % len(self._params))
664 msg.append(' %i parts total\n' % len(self._parts))
664 msg.append(' %i parts total\n' % len(self._parts))
665 self.ui.debug(''.join(msg))
665 self.ui.debug(''.join(msg))
666 outdebug(self.ui, 'start emission of %s stream' % self._magicstring)
666 outdebug(self.ui, 'start emission of %s stream' % self._magicstring)
667 yield self._magicstring
667 yield self._magicstring
668 param = self._paramchunk()
668 param = self._paramchunk()
669 outdebug(self.ui, 'bundle parameter: %s' % param)
669 outdebug(self.ui, 'bundle parameter: %s' % param)
670 yield _pack(_fstreamparamsize, len(param))
670 yield _pack(_fstreamparamsize, len(param))
671 if param:
671 if param:
672 yield param
672 yield param
673 for chunk in self._compengine.compressstream(self._getcorechunk(),
673 for chunk in self._compengine.compressstream(self._getcorechunk(),
674 self._compopts):
674 self._compopts):
675 yield chunk
675 yield chunk
676
676
677 def _paramchunk(self):
677 def _paramchunk(self):
678 """return a encoded version of all stream parameters"""
678 """return a encoded version of all stream parameters"""
679 blocks = []
679 blocks = []
680 for par, value in self._params:
680 for par, value in self._params:
681 par = urlreq.quote(par)
681 par = urlreq.quote(par)
682 if value is not None:
682 if value is not None:
683 value = urlreq.quote(value)
683 value = urlreq.quote(value)
684 par = '%s=%s' % (par, value)
684 par = '%s=%s' % (par, value)
685 blocks.append(par)
685 blocks.append(par)
686 return ' '.join(blocks)
686 return ' '.join(blocks)
687
687
688 def _getcorechunk(self):
688 def _getcorechunk(self):
689 """yield chunk for the core part of the bundle
689 """yield chunk for the core part of the bundle
690
690
691 (all but headers and parameters)"""
691 (all but headers and parameters)"""
692 outdebug(self.ui, 'start of parts')
692 outdebug(self.ui, 'start of parts')
693 for part in self._parts:
693 for part in self._parts:
694 outdebug(self.ui, 'bundle part: "%s"' % part.type)
694 outdebug(self.ui, 'bundle part: "%s"' % part.type)
695 for chunk in part.getchunks(ui=self.ui):
695 for chunk in part.getchunks(ui=self.ui):
696 yield chunk
696 yield chunk
697 outdebug(self.ui, 'end of bundle')
697 outdebug(self.ui, 'end of bundle')
698 yield _pack(_fpartheadersize, 0)
698 yield _pack(_fpartheadersize, 0)
699
699
700
700
701 def salvageoutput(self):
701 def salvageoutput(self):
702 """return a list with a copy of all output parts in the bundle
702 """return a list with a copy of all output parts in the bundle
703
703
704 This is meant to be used during error handling to make sure we preserve
704 This is meant to be used during error handling to make sure we preserve
705 server output"""
705 server output"""
706 salvaged = []
706 salvaged = []
707 for part in self._parts:
707 for part in self._parts:
708 if part.type.startswith('output'):
708 if part.type.startswith('output'):
709 salvaged.append(part.copy())
709 salvaged.append(part.copy())
710 return salvaged
710 return salvaged
711
711
712
712
713 class unpackermixin(object):
713 class unpackermixin(object):
714 """A mixin to extract bytes and struct data from a stream"""
714 """A mixin to extract bytes and struct data from a stream"""
715
715
716 def __init__(self, fp):
716 def __init__(self, fp):
717 self._fp = fp
717 self._fp = fp
718
718
719 def _unpack(self, format):
719 def _unpack(self, format):
720 """unpack this struct format from the stream
720 """unpack this struct format from the stream
721
721
722 This method is meant for internal usage by the bundle2 protocol only.
722 This method is meant for internal usage by the bundle2 protocol only.
723 They directly manipulate the low level stream including bundle2 level
723 They directly manipulate the low level stream including bundle2 level
724 instruction.
724 instruction.
725
725
726 Do not use it to implement higher-level logic or methods."""
726 Do not use it to implement higher-level logic or methods."""
727 data = self._readexact(struct.calcsize(format))
727 data = self._readexact(struct.calcsize(format))
728 return _unpack(format, data)
728 return _unpack(format, data)
729
729
730 def _readexact(self, size):
730 def _readexact(self, size):
731 """read exactly <size> bytes from the stream
731 """read exactly <size> bytes from the stream
732
732
733 This method is meant for internal usage by the bundle2 protocol only.
733 This method is meant for internal usage by the bundle2 protocol only.
734 They directly manipulate the low level stream including bundle2 level
734 They directly manipulate the low level stream including bundle2 level
735 instruction.
735 instruction.
736
736
737 Do not use it to implement higher-level logic or methods."""
737 Do not use it to implement higher-level logic or methods."""
738 return changegroup.readexactly(self._fp, size)
738 return changegroup.readexactly(self._fp, size)
739
739
740 def getunbundler(ui, fp, magicstring=None):
740 def getunbundler(ui, fp, magicstring=None):
741 """return a valid unbundler object for a given magicstring"""
741 """return a valid unbundler object for a given magicstring"""
742 if magicstring is None:
742 if magicstring is None:
743 magicstring = changegroup.readexactly(fp, 4)
743 magicstring = changegroup.readexactly(fp, 4)
744 magic, version = magicstring[0:2], magicstring[2:4]
744 magic, version = magicstring[0:2], magicstring[2:4]
745 if magic != 'HG':
745 if magic != 'HG':
746 ui.debug(
746 ui.debug(
747 "error: invalid magic: %r (version %r), should be 'HG'\n"
747 "error: invalid magic: %r (version %r), should be 'HG'\n"
748 % (magic, version))
748 % (magic, version))
749 raise error.Abort(_('not a Mercurial bundle'))
749 raise error.Abort(_('not a Mercurial bundle'))
750 unbundlerclass = formatmap.get(version)
750 unbundlerclass = formatmap.get(version)
751 if unbundlerclass is None:
751 if unbundlerclass is None:
752 raise error.Abort(_('unknown bundle version %s') % version)
752 raise error.Abort(_('unknown bundle version %s') % version)
753 unbundler = unbundlerclass(ui, fp)
753 unbundler = unbundlerclass(ui, fp)
754 indebug(ui, 'start processing of %s stream' % magicstring)
754 indebug(ui, 'start processing of %s stream' % magicstring)
755 return unbundler
755 return unbundler
756
756
757 class unbundle20(unpackermixin):
757 class unbundle20(unpackermixin):
758 """interpret a bundle2 stream
758 """interpret a bundle2 stream
759
759
760 This class is fed with a binary stream and yields parts through its
760 This class is fed with a binary stream and yields parts through its
761 `iterparts` methods."""
761 `iterparts` methods."""
762
762
763 _magicstring = 'HG20'
763 _magicstring = 'HG20'
764
764
765 def __init__(self, ui, fp):
765 def __init__(self, ui, fp):
766 """If header is specified, we do not read it out of the stream."""
766 """If header is specified, we do not read it out of the stream."""
767 self.ui = ui
767 self.ui = ui
768 self._compengine = util.compengines.forbundletype('UN')
768 self._compengine = util.compengines.forbundletype('UN')
769 self._compressed = None
769 self._compressed = None
770 super(unbundle20, self).__init__(fp)
770 super(unbundle20, self).__init__(fp)
771
771
772 @util.propertycache
772 @util.propertycache
773 def params(self):
773 def params(self):
774 """dictionary of stream level parameters"""
774 """dictionary of stream level parameters"""
775 indebug(self.ui, 'reading bundle2 stream parameters')
775 indebug(self.ui, 'reading bundle2 stream parameters')
776 params = {}
776 params = {}
777 paramssize = self._unpack(_fstreamparamsize)[0]
777 paramssize = self._unpack(_fstreamparamsize)[0]
778 if paramssize < 0:
778 if paramssize < 0:
779 raise error.BundleValueError('negative bundle param size: %i'
779 raise error.BundleValueError('negative bundle param size: %i'
780 % paramssize)
780 % paramssize)
781 if paramssize:
781 if paramssize:
782 params = self._readexact(paramssize)
782 params = self._readexact(paramssize)
783 params = self._processallparams(params)
783 params = self._processallparams(params)
784 return params
784 return params
785
785
786 def _processallparams(self, paramsblock):
786 def _processallparams(self, paramsblock):
787 """"""
787 """"""
788 params = util.sortdict()
788 params = util.sortdict()
789 for p in paramsblock.split(' '):
789 for p in paramsblock.split(' '):
790 p = p.split('=', 1)
790 p = p.split('=', 1)
791 p = [urlreq.unquote(i) for i in p]
791 p = [urlreq.unquote(i) for i in p]
792 if len(p) < 2:
792 if len(p) < 2:
793 p.append(None)
793 p.append(None)
794 self._processparam(*p)
794 self._processparam(*p)
795 params[p[0]] = p[1]
795 params[p[0]] = p[1]
796 return params
796 return params
797
797
798
798
799 def _processparam(self, name, value):
799 def _processparam(self, name, value):
800 """process a parameter, applying its effect if needed
800 """process a parameter, applying its effect if needed
801
801
802 Parameter starting with a lower case letter are advisory and will be
802 Parameter starting with a lower case letter are advisory and will be
803 ignored when unknown. Those starting with an upper case letter are
803 ignored when unknown. Those starting with an upper case letter are
804 mandatory and will this function will raise a KeyError when unknown.
804 mandatory and will this function will raise a KeyError when unknown.
805
805
806 Note: no option are currently supported. Any input will be either
806 Note: no option are currently supported. Any input will be either
807 ignored or failing.
807 ignored or failing.
808 """
808 """
809 if not name:
809 if not name:
810 raise ValueError(r'empty parameter name')
810 raise ValueError(r'empty parameter name')
811 if name[0:1] not in pycompat.bytestr(string.ascii_letters):
811 if name[0:1] not in pycompat.bytestr(string.ascii_letters):
812 raise ValueError(r'non letter first character: %s' % name)
812 raise ValueError(r'non letter first character: %s' % name)
813 try:
813 try:
814 handler = b2streamparamsmap[name.lower()]
814 handler = b2streamparamsmap[name.lower()]
815 except KeyError:
815 except KeyError:
816 if name[0:1].islower():
816 if name[0:1].islower():
817 indebug(self.ui, "ignoring unknown parameter %s" % name)
817 indebug(self.ui, "ignoring unknown parameter %s" % name)
818 else:
818 else:
819 raise error.BundleUnknownFeatureError(params=(name,))
819 raise error.BundleUnknownFeatureError(params=(name,))
820 else:
820 else:
821 handler(self, name, value)
821 handler(self, name, value)
822
822
823 def _forwardchunks(self):
823 def _forwardchunks(self):
824 """utility to transfer a bundle2 as binary
824 """utility to transfer a bundle2 as binary
825
825
826 This is made necessary by the fact the 'getbundle' command over 'ssh'
826 This is made necessary by the fact the 'getbundle' command over 'ssh'
827 have no way to know then the reply end, relying on the bundle to be
827 have no way to know then the reply end, relying on the bundle to be
828 interpreted to know its end. This is terrible and we are sorry, but we
828 interpreted to know its end. This is terrible and we are sorry, but we
829 needed to move forward to get general delta enabled.
829 needed to move forward to get general delta enabled.
830 """
830 """
831 yield self._magicstring
831 yield self._magicstring
832 assert 'params' not in vars(self)
832 assert 'params' not in vars(self)
833 paramssize = self._unpack(_fstreamparamsize)[0]
833 paramssize = self._unpack(_fstreamparamsize)[0]
834 if paramssize < 0:
834 if paramssize < 0:
835 raise error.BundleValueError('negative bundle param size: %i'
835 raise error.BundleValueError('negative bundle param size: %i'
836 % paramssize)
836 % paramssize)
837 yield _pack(_fstreamparamsize, paramssize)
837 yield _pack(_fstreamparamsize, paramssize)
838 if paramssize:
838 if paramssize:
839 params = self._readexact(paramssize)
839 params = self._readexact(paramssize)
840 self._processallparams(params)
840 self._processallparams(params)
841 yield params
841 yield params
842 assert self._compengine.bundletype == 'UN'
842 assert self._compengine.bundletype == 'UN'
843 # From there, payload might need to be decompressed
843 # From there, payload might need to be decompressed
844 self._fp = self._compengine.decompressorreader(self._fp)
844 self._fp = self._compengine.decompressorreader(self._fp)
845 emptycount = 0
845 emptycount = 0
846 while emptycount < 2:
846 while emptycount < 2:
847 # so we can brainlessly loop
847 # so we can brainlessly loop
848 assert _fpartheadersize == _fpayloadsize
848 assert _fpartheadersize == _fpayloadsize
849 size = self._unpack(_fpartheadersize)[0]
849 size = self._unpack(_fpartheadersize)[0]
850 yield _pack(_fpartheadersize, size)
850 yield _pack(_fpartheadersize, size)
851 if size:
851 if size:
852 emptycount = 0
852 emptycount = 0
853 else:
853 else:
854 emptycount += 1
854 emptycount += 1
855 continue
855 continue
856 if size == flaginterrupt:
856 if size == flaginterrupt:
857 continue
857 continue
858 elif size < 0:
858 elif size < 0:
859 raise error.BundleValueError('negative chunk size: %i')
859 raise error.BundleValueError('negative chunk size: %i')
860 yield self._readexact(size)
860 yield self._readexact(size)
861
861
862
862
863 def iterparts(self, seekable=False):
863 def iterparts(self, seekable=False):
864 """yield all parts contained in the stream"""
864 """yield all parts contained in the stream"""
865 cls = seekableunbundlepart if seekable else unbundlepart
865 cls = seekableunbundlepart if seekable else unbundlepart
866 # make sure param have been loaded
866 # make sure param have been loaded
867 self.params
867 self.params
868 # From there, payload need to be decompressed
868 # From there, payload need to be decompressed
869 self._fp = self._compengine.decompressorreader(self._fp)
869 self._fp = self._compengine.decompressorreader(self._fp)
870 indebug(self.ui, 'start extraction of bundle2 parts')
870 indebug(self.ui, 'start extraction of bundle2 parts')
871 headerblock = self._readpartheader()
871 headerblock = self._readpartheader()
872 while headerblock is not None:
872 while headerblock is not None:
873 part = cls(self.ui, headerblock, self._fp)
873 part = cls(self.ui, headerblock, self._fp)
874 yield part
874 yield part
875 # Ensure part is fully consumed so we can start reading the next
875 # Ensure part is fully consumed so we can start reading the next
876 # part.
876 # part.
877 part.consume()
877 part.consume()
878
878
879 headerblock = self._readpartheader()
879 headerblock = self._readpartheader()
880 indebug(self.ui, 'end of bundle2 stream')
880 indebug(self.ui, 'end of bundle2 stream')
881
881
882 def _readpartheader(self):
882 def _readpartheader(self):
883 """reads a part header size and return the bytes blob
883 """reads a part header size and return the bytes blob
884
884
885 returns None if empty"""
885 returns None if empty"""
886 headersize = self._unpack(_fpartheadersize)[0]
886 headersize = self._unpack(_fpartheadersize)[0]
887 if headersize < 0:
887 if headersize < 0:
888 raise error.BundleValueError('negative part header size: %i'
888 raise error.BundleValueError('negative part header size: %i'
889 % headersize)
889 % headersize)
890 indebug(self.ui, 'part header size: %i' % headersize)
890 indebug(self.ui, 'part header size: %i' % headersize)
891 if headersize:
891 if headersize:
892 return self._readexact(headersize)
892 return self._readexact(headersize)
893 return None
893 return None
894
894
895 def compressed(self):
895 def compressed(self):
896 self.params # load params
896 self.params # load params
897 return self._compressed
897 return self._compressed
898
898
899 def close(self):
899 def close(self):
900 """close underlying file"""
900 """close underlying file"""
901 if util.safehasattr(self._fp, 'close'):
901 if util.safehasattr(self._fp, 'close'):
902 return self._fp.close()
902 return self._fp.close()
903
903
904 formatmap = {'20': unbundle20}
904 formatmap = {'20': unbundle20}
905
905
906 b2streamparamsmap = {}
906 b2streamparamsmap = {}
907
907
908 def b2streamparamhandler(name):
908 def b2streamparamhandler(name):
909 """register a handler for a stream level parameter"""
909 """register a handler for a stream level parameter"""
910 def decorator(func):
910 def decorator(func):
911 assert name not in formatmap
911 assert name not in formatmap
912 b2streamparamsmap[name] = func
912 b2streamparamsmap[name] = func
913 return func
913 return func
914 return decorator
914 return decorator
915
915
916 @b2streamparamhandler('compression')
916 @b2streamparamhandler('compression')
917 def processcompression(unbundler, param, value):
917 def processcompression(unbundler, param, value):
918 """read compression parameter and install payload decompression"""
918 """read compression parameter and install payload decompression"""
919 if value not in util.compengines.supportedbundletypes:
919 if value not in util.compengines.supportedbundletypes:
920 raise error.BundleUnknownFeatureError(params=(param,),
920 raise error.BundleUnknownFeatureError(params=(param,),
921 values=(value,))
921 values=(value,))
922 unbundler._compengine = util.compengines.forbundletype(value)
922 unbundler._compengine = util.compengines.forbundletype(value)
923 if value is not None:
923 if value is not None:
924 unbundler._compressed = True
924 unbundler._compressed = True
925
925
926 class bundlepart(object):
926 class bundlepart(object):
927 """A bundle2 part contains application level payload
927 """A bundle2 part contains application level payload
928
928
929 The part `type` is used to route the part to the application level
929 The part `type` is used to route the part to the application level
930 handler.
930 handler.
931
931
932 The part payload is contained in ``part.data``. It could be raw bytes or a
932 The part payload is contained in ``part.data``. It could be raw bytes or a
933 generator of byte chunks.
933 generator of byte chunks.
934
934
935 You can add parameters to the part using the ``addparam`` method.
935 You can add parameters to the part using the ``addparam`` method.
936 Parameters can be either mandatory (default) or advisory. Remote side
936 Parameters can be either mandatory (default) or advisory. Remote side
937 should be able to safely ignore the advisory ones.
937 should be able to safely ignore the advisory ones.
938
938
939 Both data and parameters cannot be modified after the generation has begun.
939 Both data and parameters cannot be modified after the generation has begun.
940 """
940 """
941
941
942 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
942 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
943 data='', mandatory=True):
943 data='', mandatory=True):
944 validateparttype(parttype)
944 validateparttype(parttype)
945 self.id = None
945 self.id = None
946 self.type = parttype
946 self.type = parttype
947 self._data = data
947 self._data = data
948 self._mandatoryparams = list(mandatoryparams)
948 self._mandatoryparams = list(mandatoryparams)
949 self._advisoryparams = list(advisoryparams)
949 self._advisoryparams = list(advisoryparams)
950 # checking for duplicated entries
950 # checking for duplicated entries
951 self._seenparams = set()
951 self._seenparams = set()
952 for pname, __ in self._mandatoryparams + self._advisoryparams:
952 for pname, __ in self._mandatoryparams + self._advisoryparams:
953 if pname in self._seenparams:
953 if pname in self._seenparams:
954 raise error.ProgrammingError('duplicated params: %s' % pname)
954 raise error.ProgrammingError('duplicated params: %s' % pname)
955 self._seenparams.add(pname)
955 self._seenparams.add(pname)
956 # status of the part's generation:
956 # status of the part's generation:
957 # - None: not started,
957 # - None: not started,
958 # - False: currently generated,
958 # - False: currently generated,
959 # - True: generation done.
959 # - True: generation done.
960 self._generated = None
960 self._generated = None
961 self.mandatory = mandatory
961 self.mandatory = mandatory
962
962
963 def __repr__(self):
963 def __repr__(self):
964 cls = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
964 cls = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
965 return ('<%s object at %x; id: %s; type: %s; mandatory: %s>'
965 return ('<%s object at %x; id: %s; type: %s; mandatory: %s>'
966 % (cls, id(self), self.id, self.type, self.mandatory))
966 % (cls, id(self), self.id, self.type, self.mandatory))
967
967
968 def copy(self):
968 def copy(self):
969 """return a copy of the part
969 """return a copy of the part
970
970
971 The new part have the very same content but no partid assigned yet.
971 The new part have the very same content but no partid assigned yet.
972 Parts with generated data cannot be copied."""
972 Parts with generated data cannot be copied."""
973 assert not util.safehasattr(self.data, 'next')
973 assert not util.safehasattr(self.data, 'next')
974 return self.__class__(self.type, self._mandatoryparams,
974 return self.__class__(self.type, self._mandatoryparams,
975 self._advisoryparams, self._data, self.mandatory)
975 self._advisoryparams, self._data, self.mandatory)
976
976
977 # methods used to defines the part content
977 # methods used to defines the part content
978 @property
978 @property
979 def data(self):
979 def data(self):
980 return self._data
980 return self._data
981
981
982 @data.setter
982 @data.setter
983 def data(self, data):
983 def data(self, data):
984 if self._generated is not None:
984 if self._generated is not None:
985 raise error.ReadOnlyPartError('part is being generated')
985 raise error.ReadOnlyPartError('part is being generated')
986 self._data = data
986 self._data = data
987
987
988 @property
988 @property
989 def mandatoryparams(self):
989 def mandatoryparams(self):
990 # make it an immutable tuple to force people through ``addparam``
990 # make it an immutable tuple to force people through ``addparam``
991 return tuple(self._mandatoryparams)
991 return tuple(self._mandatoryparams)
992
992
993 @property
993 @property
994 def advisoryparams(self):
994 def advisoryparams(self):
995 # make it an immutable tuple to force people through ``addparam``
995 # make it an immutable tuple to force people through ``addparam``
996 return tuple(self._advisoryparams)
996 return tuple(self._advisoryparams)
997
997
998 def addparam(self, name, value='', mandatory=True):
998 def addparam(self, name, value='', mandatory=True):
999 """add a parameter to the part
999 """add a parameter to the part
1000
1000
1001 If 'mandatory' is set to True, the remote handler must claim support
1001 If 'mandatory' is set to True, the remote handler must claim support
1002 for this parameter or the unbundling will be aborted.
1002 for this parameter or the unbundling will be aborted.
1003
1003
1004 The 'name' and 'value' cannot exceed 255 bytes each.
1004 The 'name' and 'value' cannot exceed 255 bytes each.
1005 """
1005 """
1006 if self._generated is not None:
1006 if self._generated is not None:
1007 raise error.ReadOnlyPartError('part is being generated')
1007 raise error.ReadOnlyPartError('part is being generated')
1008 if name in self._seenparams:
1008 if name in self._seenparams:
1009 raise ValueError('duplicated params: %s' % name)
1009 raise ValueError('duplicated params: %s' % name)
1010 self._seenparams.add(name)
1010 self._seenparams.add(name)
1011 params = self._advisoryparams
1011 params = self._advisoryparams
1012 if mandatory:
1012 if mandatory:
1013 params = self._mandatoryparams
1013 params = self._mandatoryparams
1014 params.append((name, value))
1014 params.append((name, value))
1015
1015
1016 # methods used to generates the bundle2 stream
1016 # methods used to generates the bundle2 stream
1017 def getchunks(self, ui):
1017 def getchunks(self, ui):
1018 if self._generated is not None:
1018 if self._generated is not None:
1019 raise error.ProgrammingError('part can only be consumed once')
1019 raise error.ProgrammingError('part can only be consumed once')
1020 self._generated = False
1020 self._generated = False
1021
1021
1022 if ui.debugflag:
1022 if ui.debugflag:
1023 msg = ['bundle2-output-part: "%s"' % self.type]
1023 msg = ['bundle2-output-part: "%s"' % self.type]
1024 if not self.mandatory:
1024 if not self.mandatory:
1025 msg.append(' (advisory)')
1025 msg.append(' (advisory)')
1026 nbmp = len(self.mandatoryparams)
1026 nbmp = len(self.mandatoryparams)
1027 nbap = len(self.advisoryparams)
1027 nbap = len(self.advisoryparams)
1028 if nbmp or nbap:
1028 if nbmp or nbap:
1029 msg.append(' (params:')
1029 msg.append(' (params:')
1030 if nbmp:
1030 if nbmp:
1031 msg.append(' %i mandatory' % nbmp)
1031 msg.append(' %i mandatory' % nbmp)
1032 if nbap:
1032 if nbap:
1033 msg.append(' %i advisory' % nbmp)
1033 msg.append(' %i advisory' % nbmp)
1034 msg.append(')')
1034 msg.append(')')
1035 if not self.data:
1035 if not self.data:
1036 msg.append(' empty payload')
1036 msg.append(' empty payload')
1037 elif (util.safehasattr(self.data, 'next')
1037 elif (util.safehasattr(self.data, 'next')
1038 or util.safehasattr(self.data, '__next__')):
1038 or util.safehasattr(self.data, '__next__')):
1039 msg.append(' streamed payload')
1039 msg.append(' streamed payload')
1040 else:
1040 else:
1041 msg.append(' %i bytes payload' % len(self.data))
1041 msg.append(' %i bytes payload' % len(self.data))
1042 msg.append('\n')
1042 msg.append('\n')
1043 ui.debug(''.join(msg))
1043 ui.debug(''.join(msg))
1044
1044
1045 #### header
1045 #### header
1046 if self.mandatory:
1046 if self.mandatory:
1047 parttype = self.type.upper()
1047 parttype = self.type.upper()
1048 else:
1048 else:
1049 parttype = self.type.lower()
1049 parttype = self.type.lower()
1050 outdebug(ui, 'part %s: "%s"' % (pycompat.bytestr(self.id), parttype))
1050 outdebug(ui, 'part %s: "%s"' % (pycompat.bytestr(self.id), parttype))
1051 ## parttype
1051 ## parttype
1052 header = [_pack(_fparttypesize, len(parttype)),
1052 header = [_pack(_fparttypesize, len(parttype)),
1053 parttype, _pack(_fpartid, self.id),
1053 parttype, _pack(_fpartid, self.id),
1054 ]
1054 ]
1055 ## parameters
1055 ## parameters
1056 # count
1056 # count
1057 manpar = self.mandatoryparams
1057 manpar = self.mandatoryparams
1058 advpar = self.advisoryparams
1058 advpar = self.advisoryparams
1059 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
1059 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
1060 # size
1060 # size
1061 parsizes = []
1061 parsizes = []
1062 for key, value in manpar:
1062 for key, value in manpar:
1063 parsizes.append(len(key))
1063 parsizes.append(len(key))
1064 parsizes.append(len(value))
1064 parsizes.append(len(value))
1065 for key, value in advpar:
1065 for key, value in advpar:
1066 parsizes.append(len(key))
1066 parsizes.append(len(key))
1067 parsizes.append(len(value))
1067 parsizes.append(len(value))
1068 paramsizes = _pack(_makefpartparamsizes(len(parsizes) // 2), *parsizes)
1068 paramsizes = _pack(_makefpartparamsizes(len(parsizes) // 2), *parsizes)
1069 header.append(paramsizes)
1069 header.append(paramsizes)
1070 # key, value
1070 # key, value
1071 for key, value in manpar:
1071 for key, value in manpar:
1072 header.append(key)
1072 header.append(key)
1073 header.append(value)
1073 header.append(value)
1074 for key, value in advpar:
1074 for key, value in advpar:
1075 header.append(key)
1075 header.append(key)
1076 header.append(value)
1076 header.append(value)
1077 ## finalize header
1077 ## finalize header
1078 try:
1078 try:
1079 headerchunk = ''.join(header)
1079 headerchunk = ''.join(header)
1080 except TypeError:
1080 except TypeError:
1081 raise TypeError(r'Found a non-bytes trying to '
1081 raise TypeError(r'Found a non-bytes trying to '
1082 r'build bundle part header: %r' % header)
1082 r'build bundle part header: %r' % header)
1083 outdebug(ui, 'header chunk size: %i' % len(headerchunk))
1083 outdebug(ui, 'header chunk size: %i' % len(headerchunk))
1084 yield _pack(_fpartheadersize, len(headerchunk))
1084 yield _pack(_fpartheadersize, len(headerchunk))
1085 yield headerchunk
1085 yield headerchunk
1086 ## payload
1086 ## payload
1087 try:
1087 try:
1088 for chunk in self._payloadchunks():
1088 for chunk in self._payloadchunks():
1089 outdebug(ui, 'payload chunk size: %i' % len(chunk))
1089 outdebug(ui, 'payload chunk size: %i' % len(chunk))
1090 yield _pack(_fpayloadsize, len(chunk))
1090 yield _pack(_fpayloadsize, len(chunk))
1091 yield chunk
1091 yield chunk
1092 except GeneratorExit:
1092 except GeneratorExit:
1093 # GeneratorExit means that nobody is listening for our
1093 # GeneratorExit means that nobody is listening for our
1094 # results anyway, so just bail quickly rather than trying
1094 # results anyway, so just bail quickly rather than trying
1095 # to produce an error part.
1095 # to produce an error part.
1096 ui.debug('bundle2-generatorexit\n')
1096 ui.debug('bundle2-generatorexit\n')
1097 raise
1097 raise
1098 except BaseException as exc:
1098 except BaseException as exc:
1099 bexc = stringutil.forcebytestr(exc)
1099 bexc = stringutil.forcebytestr(exc)
1100 # backup exception data for later
1100 # backup exception data for later
1101 ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
1101 ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
1102 % bexc)
1102 % bexc)
1103 tb = sys.exc_info()[2]
1103 tb = sys.exc_info()[2]
1104 msg = 'unexpected error: %s' % bexc
1104 msg = 'unexpected error: %s' % bexc
1105 interpart = bundlepart('error:abort', [('message', msg)],
1105 interpart = bundlepart('error:abort', [('message', msg)],
1106 mandatory=False)
1106 mandatory=False)
1107 interpart.id = 0
1107 interpart.id = 0
1108 yield _pack(_fpayloadsize, -1)
1108 yield _pack(_fpayloadsize, -1)
1109 for chunk in interpart.getchunks(ui=ui):
1109 for chunk in interpart.getchunks(ui=ui):
1110 yield chunk
1110 yield chunk
1111 outdebug(ui, 'closing payload chunk')
1111 outdebug(ui, 'closing payload chunk')
1112 # abort current part payload
1112 # abort current part payload
1113 yield _pack(_fpayloadsize, 0)
1113 yield _pack(_fpayloadsize, 0)
1114 pycompat.raisewithtb(exc, tb)
1114 pycompat.raisewithtb(exc, tb)
1115 # end of payload
1115 # end of payload
1116 outdebug(ui, 'closing payload chunk')
1116 outdebug(ui, 'closing payload chunk')
1117 yield _pack(_fpayloadsize, 0)
1117 yield _pack(_fpayloadsize, 0)
1118 self._generated = True
1118 self._generated = True
1119
1119
1120 def _payloadchunks(self):
1120 def _payloadchunks(self):
1121 """yield chunks of a the part payload
1121 """yield chunks of a the part payload
1122
1122
1123 Exists to handle the different methods to provide data to a part."""
1123 Exists to handle the different methods to provide data to a part."""
1124 # we only support fixed size data now.
1124 # we only support fixed size data now.
1125 # This will be improved in the future.
1125 # This will be improved in the future.
1126 if (util.safehasattr(self.data, 'next')
1126 if (util.safehasattr(self.data, 'next')
1127 or util.safehasattr(self.data, '__next__')):
1127 or util.safehasattr(self.data, '__next__')):
1128 buff = util.chunkbuffer(self.data)
1128 buff = util.chunkbuffer(self.data)
1129 chunk = buff.read(preferedchunksize)
1129 chunk = buff.read(preferedchunksize)
1130 while chunk:
1130 while chunk:
1131 yield chunk
1131 yield chunk
1132 chunk = buff.read(preferedchunksize)
1132 chunk = buff.read(preferedchunksize)
1133 elif len(self.data):
1133 elif len(self.data):
1134 yield self.data
1134 yield self.data
1135
1135
1136
1136
1137 flaginterrupt = -1
1137 flaginterrupt = -1
1138
1138
1139 class interrupthandler(unpackermixin):
1139 class interrupthandler(unpackermixin):
1140 """read one part and process it with restricted capability
1140 """read one part and process it with restricted capability
1141
1141
1142 This allows to transmit exception raised on the producer size during part
1142 This allows to transmit exception raised on the producer size during part
1143 iteration while the consumer is reading a part.
1143 iteration while the consumer is reading a part.
1144
1144
1145 Part processed in this manner only have access to a ui object,"""
1145 Part processed in this manner only have access to a ui object,"""
1146
1146
1147 def __init__(self, ui, fp):
1147 def __init__(self, ui, fp):
1148 super(interrupthandler, self).__init__(fp)
1148 super(interrupthandler, self).__init__(fp)
1149 self.ui = ui
1149 self.ui = ui
1150
1150
1151 def _readpartheader(self):
1151 def _readpartheader(self):
1152 """reads a part header size and return the bytes blob
1152 """reads a part header size and return the bytes blob
1153
1153
1154 returns None if empty"""
1154 returns None if empty"""
1155 headersize = self._unpack(_fpartheadersize)[0]
1155 headersize = self._unpack(_fpartheadersize)[0]
1156 if headersize < 0:
1156 if headersize < 0:
1157 raise error.BundleValueError('negative part header size: %i'
1157 raise error.BundleValueError('negative part header size: %i'
1158 % headersize)
1158 % headersize)
1159 indebug(self.ui, 'part header size: %i\n' % headersize)
1159 indebug(self.ui, 'part header size: %i\n' % headersize)
1160 if headersize:
1160 if headersize:
1161 return self._readexact(headersize)
1161 return self._readexact(headersize)
1162 return None
1162 return None
1163
1163
1164 def __call__(self):
1164 def __call__(self):
1165
1165
1166 self.ui.debug('bundle2-input-stream-interrupt:'
1166 self.ui.debug('bundle2-input-stream-interrupt:'
1167 ' opening out of band context\n')
1167 ' opening out of band context\n')
1168 indebug(self.ui, 'bundle2 stream interruption, looking for a part.')
1168 indebug(self.ui, 'bundle2 stream interruption, looking for a part.')
1169 headerblock = self._readpartheader()
1169 headerblock = self._readpartheader()
1170 if headerblock is None:
1170 if headerblock is None:
1171 indebug(self.ui, 'no part found during interruption.')
1171 indebug(self.ui, 'no part found during interruption.')
1172 return
1172 return
1173 part = unbundlepart(self.ui, headerblock, self._fp)
1173 part = unbundlepart(self.ui, headerblock, self._fp)
1174 op = interruptoperation(self.ui)
1174 op = interruptoperation(self.ui)
1175 hardabort = False
1175 hardabort = False
1176 try:
1176 try:
1177 _processpart(op, part)
1177 _processpart(op, part)
1178 except (SystemExit, KeyboardInterrupt):
1178 except (SystemExit, KeyboardInterrupt):
1179 hardabort = True
1179 hardabort = True
1180 raise
1180 raise
1181 finally:
1181 finally:
1182 if not hardabort:
1182 if not hardabort:
1183 part.consume()
1183 part.consume()
1184 self.ui.debug('bundle2-input-stream-interrupt:'
1184 self.ui.debug('bundle2-input-stream-interrupt:'
1185 ' closing out of band context\n')
1185 ' closing out of band context\n')
1186
1186
1187 class interruptoperation(object):
1187 class interruptoperation(object):
1188 """A limited operation to be use by part handler during interruption
1188 """A limited operation to be use by part handler during interruption
1189
1189
1190 It only have access to an ui object.
1190 It only have access to an ui object.
1191 """
1191 """
1192
1192
1193 def __init__(self, ui):
1193 def __init__(self, ui):
1194 self.ui = ui
1194 self.ui = ui
1195 self.reply = None
1195 self.reply = None
1196 self.captureoutput = False
1196 self.captureoutput = False
1197
1197
1198 @property
1198 @property
1199 def repo(self):
1199 def repo(self):
1200 raise error.ProgrammingError('no repo access from stream interruption')
1200 raise error.ProgrammingError('no repo access from stream interruption')
1201
1201
1202 def gettransaction(self):
1202 def gettransaction(self):
1203 raise TransactionUnavailable('no repo access from stream interruption')
1203 raise TransactionUnavailable('no repo access from stream interruption')
1204
1204
1205 def decodepayloadchunks(ui, fh):
1205 def decodepayloadchunks(ui, fh):
1206 """Reads bundle2 part payload data into chunks.
1206 """Reads bundle2 part payload data into chunks.
1207
1207
1208 Part payload data consists of framed chunks. This function takes
1208 Part payload data consists of framed chunks. This function takes
1209 a file handle and emits those chunks.
1209 a file handle and emits those chunks.
1210 """
1210 """
1211 dolog = ui.configbool('devel', 'bundle2.debug')
1211 dolog = ui.configbool('devel', 'bundle2.debug')
1212 debug = ui.debug
1212 debug = ui.debug
1213
1213
1214 headerstruct = struct.Struct(_fpayloadsize)
1214 headerstruct = struct.Struct(_fpayloadsize)
1215 headersize = headerstruct.size
1215 headersize = headerstruct.size
1216 unpack = headerstruct.unpack
1216 unpack = headerstruct.unpack
1217
1217
1218 readexactly = changegroup.readexactly
1218 readexactly = changegroup.readexactly
1219 read = fh.read
1219 read = fh.read
1220
1220
1221 chunksize = unpack(readexactly(fh, headersize))[0]
1221 chunksize = unpack(readexactly(fh, headersize))[0]
1222 indebug(ui, 'payload chunk size: %i' % chunksize)
1222 indebug(ui, 'payload chunk size: %i' % chunksize)
1223
1223
1224 # changegroup.readexactly() is inlined below for performance.
1224 # changegroup.readexactly() is inlined below for performance.
1225 while chunksize:
1225 while chunksize:
1226 if chunksize >= 0:
1226 if chunksize >= 0:
1227 s = read(chunksize)
1227 s = read(chunksize)
1228 if len(s) < chunksize:
1228 if len(s) < chunksize:
1229 raise error.Abort(_('stream ended unexpectedly '
1229 raise error.Abort(_('stream ended unexpectedly '
1230 ' (got %d bytes, expected %d)') %
1230 ' (got %d bytes, expected %d)') %
1231 (len(s), chunksize))
1231 (len(s), chunksize))
1232
1232
1233 yield s
1233 yield s
1234 elif chunksize == flaginterrupt:
1234 elif chunksize == flaginterrupt:
1235 # Interrupt "signal" detected. The regular stream is interrupted
1235 # Interrupt "signal" detected. The regular stream is interrupted
1236 # and a bundle2 part follows. Consume it.
1236 # and a bundle2 part follows. Consume it.
1237 interrupthandler(ui, fh)()
1237 interrupthandler(ui, fh)()
1238 else:
1238 else:
1239 raise error.BundleValueError(
1239 raise error.BundleValueError(
1240 'negative payload chunk size: %s' % chunksize)
1240 'negative payload chunk size: %s' % chunksize)
1241
1241
1242 s = read(headersize)
1242 s = read(headersize)
1243 if len(s) < headersize:
1243 if len(s) < headersize:
1244 raise error.Abort(_('stream ended unexpectedly '
1244 raise error.Abort(_('stream ended unexpectedly '
1245 ' (got %d bytes, expected %d)') %
1245 ' (got %d bytes, expected %d)') %
1246 (len(s), chunksize))
1246 (len(s), chunksize))
1247
1247
1248 chunksize = unpack(s)[0]
1248 chunksize = unpack(s)[0]
1249
1249
1250 # indebug() inlined for performance.
1250 # indebug() inlined for performance.
1251 if dolog:
1251 if dolog:
1252 debug('bundle2-input: payload chunk size: %i\n' % chunksize)
1252 debug('bundle2-input: payload chunk size: %i\n' % chunksize)
1253
1253
1254 class unbundlepart(unpackermixin):
1254 class unbundlepart(unpackermixin):
1255 """a bundle part read from a bundle"""
1255 """a bundle part read from a bundle"""
1256
1256
1257 def __init__(self, ui, header, fp):
1257 def __init__(self, ui, header, fp):
1258 super(unbundlepart, self).__init__(fp)
1258 super(unbundlepart, self).__init__(fp)
1259 self._seekable = (util.safehasattr(fp, 'seek') and
1259 self._seekable = (util.safehasattr(fp, 'seek') and
1260 util.safehasattr(fp, 'tell'))
1260 util.safehasattr(fp, 'tell'))
1261 self.ui = ui
1261 self.ui = ui
1262 # unbundle state attr
1262 # unbundle state attr
1263 self._headerdata = header
1263 self._headerdata = header
1264 self._headeroffset = 0
1264 self._headeroffset = 0
1265 self._initialized = False
1265 self._initialized = False
1266 self.consumed = False
1266 self.consumed = False
1267 # part data
1267 # part data
1268 self.id = None
1268 self.id = None
1269 self.type = None
1269 self.type = None
1270 self.mandatoryparams = None
1270 self.mandatoryparams = None
1271 self.advisoryparams = None
1271 self.advisoryparams = None
1272 self.params = None
1272 self.params = None
1273 self.mandatorykeys = ()
1273 self.mandatorykeys = ()
1274 self._readheader()
1274 self._readheader()
1275 self._mandatory = None
1275 self._mandatory = None
1276 self._pos = 0
1276 self._pos = 0
1277
1277
1278 def _fromheader(self, size):
1278 def _fromheader(self, size):
1279 """return the next <size> byte from the header"""
1279 """return the next <size> byte from the header"""
1280 offset = self._headeroffset
1280 offset = self._headeroffset
1281 data = self._headerdata[offset:(offset + size)]
1281 data = self._headerdata[offset:(offset + size)]
1282 self._headeroffset = offset + size
1282 self._headeroffset = offset + size
1283 return data
1283 return data
1284
1284
1285 def _unpackheader(self, format):
1285 def _unpackheader(self, format):
1286 """read given format from header
1286 """read given format from header
1287
1287
1288 This automatically compute the size of the format to read."""
1288 This automatically compute the size of the format to read."""
1289 data = self._fromheader(struct.calcsize(format))
1289 data = self._fromheader(struct.calcsize(format))
1290 return _unpack(format, data)
1290 return _unpack(format, data)
1291
1291
1292 def _initparams(self, mandatoryparams, advisoryparams):
1292 def _initparams(self, mandatoryparams, advisoryparams):
1293 """internal function to setup all logic related parameters"""
1293 """internal function to setup all logic related parameters"""
1294 # make it read only to prevent people touching it by mistake.
1294 # make it read only to prevent people touching it by mistake.
1295 self.mandatoryparams = tuple(mandatoryparams)
1295 self.mandatoryparams = tuple(mandatoryparams)
1296 self.advisoryparams = tuple(advisoryparams)
1296 self.advisoryparams = tuple(advisoryparams)
1297 # user friendly UI
1297 # user friendly UI
1298 self.params = util.sortdict(self.mandatoryparams)
1298 self.params = util.sortdict(self.mandatoryparams)
1299 self.params.update(self.advisoryparams)
1299 self.params.update(self.advisoryparams)
1300 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
1300 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
1301
1301
1302 def _readheader(self):
1302 def _readheader(self):
1303 """read the header and setup the object"""
1303 """read the header and setup the object"""
1304 typesize = self._unpackheader(_fparttypesize)[0]
1304 typesize = self._unpackheader(_fparttypesize)[0]
1305 self.type = self._fromheader(typesize)
1305 self.type = self._fromheader(typesize)
1306 indebug(self.ui, 'part type: "%s"' % self.type)
1306 indebug(self.ui, 'part type: "%s"' % self.type)
1307 self.id = self._unpackheader(_fpartid)[0]
1307 self.id = self._unpackheader(_fpartid)[0]
1308 indebug(self.ui, 'part id: "%s"' % pycompat.bytestr(self.id))
1308 indebug(self.ui, 'part id: "%s"' % pycompat.bytestr(self.id))
1309 # extract mandatory bit from type
1309 # extract mandatory bit from type
1310 self.mandatory = (self.type != self.type.lower())
1310 self.mandatory = (self.type != self.type.lower())
1311 self.type = self.type.lower()
1311 self.type = self.type.lower()
1312 ## reading parameters
1312 ## reading parameters
1313 # param count
1313 # param count
1314 mancount, advcount = self._unpackheader(_fpartparamcount)
1314 mancount, advcount = self._unpackheader(_fpartparamcount)
1315 indebug(self.ui, 'part parameters: %i' % (mancount + advcount))
1315 indebug(self.ui, 'part parameters: %i' % (mancount + advcount))
1316 # param size
1316 # param size
1317 fparamsizes = _makefpartparamsizes(mancount + advcount)
1317 fparamsizes = _makefpartparamsizes(mancount + advcount)
1318 paramsizes = self._unpackheader(fparamsizes)
1318 paramsizes = self._unpackheader(fparamsizes)
1319 # make it a list of couple again
1319 # make it a list of couple again
1320 paramsizes = list(zip(paramsizes[::2], paramsizes[1::2]))
1320 paramsizes = list(zip(paramsizes[::2], paramsizes[1::2]))
1321 # split mandatory from advisory
1321 # split mandatory from advisory
1322 mansizes = paramsizes[:mancount]
1322 mansizes = paramsizes[:mancount]
1323 advsizes = paramsizes[mancount:]
1323 advsizes = paramsizes[mancount:]
1324 # retrieve param value
1324 # retrieve param value
1325 manparams = []
1325 manparams = []
1326 for key, value in mansizes:
1326 for key, value in mansizes:
1327 manparams.append((self._fromheader(key), self._fromheader(value)))
1327 manparams.append((self._fromheader(key), self._fromheader(value)))
1328 advparams = []
1328 advparams = []
1329 for key, value in advsizes:
1329 for key, value in advsizes:
1330 advparams.append((self._fromheader(key), self._fromheader(value)))
1330 advparams.append((self._fromheader(key), self._fromheader(value)))
1331 self._initparams(manparams, advparams)
1331 self._initparams(manparams, advparams)
1332 ## part payload
1332 ## part payload
1333 self._payloadstream = util.chunkbuffer(self._payloadchunks())
1333 self._payloadstream = util.chunkbuffer(self._payloadchunks())
1334 # we read the data, tell it
1334 # we read the data, tell it
1335 self._initialized = True
1335 self._initialized = True
1336
1336
1337 def _payloadchunks(self):
1337 def _payloadchunks(self):
1338 """Generator of decoded chunks in the payload."""
1338 """Generator of decoded chunks in the payload."""
1339 return decodepayloadchunks(self.ui, self._fp)
1339 return decodepayloadchunks(self.ui, self._fp)
1340
1340
1341 def consume(self):
1341 def consume(self):
1342 """Read the part payload until completion.
1342 """Read the part payload until completion.
1343
1343
1344 By consuming the part data, the underlying stream read offset will
1344 By consuming the part data, the underlying stream read offset will
1345 be advanced to the next part (or end of stream).
1345 be advanced to the next part (or end of stream).
1346 """
1346 """
1347 if self.consumed:
1347 if self.consumed:
1348 return
1348 return
1349
1349
1350 chunk = self.read(32768)
1350 chunk = self.read(32768)
1351 while chunk:
1351 while chunk:
1352 self._pos += len(chunk)
1352 self._pos += len(chunk)
1353 chunk = self.read(32768)
1353 chunk = self.read(32768)
1354
1354
1355 def read(self, size=None):
1355 def read(self, size=None):
1356 """read payload data"""
1356 """read payload data"""
1357 if not self._initialized:
1357 if not self._initialized:
1358 self._readheader()
1358 self._readheader()
1359 if size is None:
1359 if size is None:
1360 data = self._payloadstream.read()
1360 data = self._payloadstream.read()
1361 else:
1361 else:
1362 data = self._payloadstream.read(size)
1362 data = self._payloadstream.read(size)
1363 self._pos += len(data)
1363 self._pos += len(data)
1364 if size is None or len(data) < size:
1364 if size is None or len(data) < size:
1365 if not self.consumed and self._pos:
1365 if not self.consumed and self._pos:
1366 self.ui.debug('bundle2-input-part: total payload size %i\n'
1366 self.ui.debug('bundle2-input-part: total payload size %i\n'
1367 % self._pos)
1367 % self._pos)
1368 self.consumed = True
1368 self.consumed = True
1369 return data
1369 return data
1370
1370
1371 class seekableunbundlepart(unbundlepart):
1371 class seekableunbundlepart(unbundlepart):
1372 """A bundle2 part in a bundle that is seekable.
1372 """A bundle2 part in a bundle that is seekable.
1373
1373
1374 Regular ``unbundlepart`` instances can only be read once. This class
1374 Regular ``unbundlepart`` instances can only be read once. This class
1375 extends ``unbundlepart`` to enable bi-directional seeking within the
1375 extends ``unbundlepart`` to enable bi-directional seeking within the
1376 part.
1376 part.
1377
1377
1378 Bundle2 part data consists of framed chunks. Offsets when seeking
1378 Bundle2 part data consists of framed chunks. Offsets when seeking
1379 refer to the decoded data, not the offsets in the underlying bundle2
1379 refer to the decoded data, not the offsets in the underlying bundle2
1380 stream.
1380 stream.
1381
1381
1382 To facilitate quickly seeking within the decoded data, instances of this
1382 To facilitate quickly seeking within the decoded data, instances of this
1383 class maintain a mapping between offsets in the underlying stream and
1383 class maintain a mapping between offsets in the underlying stream and
1384 the decoded payload. This mapping will consume memory in proportion
1384 the decoded payload. This mapping will consume memory in proportion
1385 to the number of chunks within the payload (which almost certainly
1385 to the number of chunks within the payload (which almost certainly
1386 increases in proportion with the size of the part).
1386 increases in proportion with the size of the part).
1387 """
1387 """
1388 def __init__(self, ui, header, fp):
1388 def __init__(self, ui, header, fp):
1389 # (payload, file) offsets for chunk starts.
1389 # (payload, file) offsets for chunk starts.
1390 self._chunkindex = []
1390 self._chunkindex = []
1391
1391
1392 super(seekableunbundlepart, self).__init__(ui, header, fp)
1392 super(seekableunbundlepart, self).__init__(ui, header, fp)
1393
1393
1394 def _payloadchunks(self, chunknum=0):
1394 def _payloadchunks(self, chunknum=0):
1395 '''seek to specified chunk and start yielding data'''
1395 '''seek to specified chunk and start yielding data'''
1396 if len(self._chunkindex) == 0:
1396 if len(self._chunkindex) == 0:
1397 assert chunknum == 0, 'Must start with chunk 0'
1397 assert chunknum == 0, 'Must start with chunk 0'
1398 self._chunkindex.append((0, self._tellfp()))
1398 self._chunkindex.append((0, self._tellfp()))
1399 else:
1399 else:
1400 assert chunknum < len(self._chunkindex), \
1400 assert chunknum < len(self._chunkindex), \
1401 'Unknown chunk %d' % chunknum
1401 'Unknown chunk %d' % chunknum
1402 self._seekfp(self._chunkindex[chunknum][1])
1402 self._seekfp(self._chunkindex[chunknum][1])
1403
1403
1404 pos = self._chunkindex[chunknum][0]
1404 pos = self._chunkindex[chunknum][0]
1405
1405
1406 for chunk in decodepayloadchunks(self.ui, self._fp):
1406 for chunk in decodepayloadchunks(self.ui, self._fp):
1407 chunknum += 1
1407 chunknum += 1
1408 pos += len(chunk)
1408 pos += len(chunk)
1409 if chunknum == len(self._chunkindex):
1409 if chunknum == len(self._chunkindex):
1410 self._chunkindex.append((pos, self._tellfp()))
1410 self._chunkindex.append((pos, self._tellfp()))
1411
1411
1412 yield chunk
1412 yield chunk
1413
1413
1414 def _findchunk(self, pos):
1414 def _findchunk(self, pos):
1415 '''for a given payload position, return a chunk number and offset'''
1415 '''for a given payload position, return a chunk number and offset'''
1416 for chunk, (ppos, fpos) in enumerate(self._chunkindex):
1416 for chunk, (ppos, fpos) in enumerate(self._chunkindex):
1417 if ppos == pos:
1417 if ppos == pos:
1418 return chunk, 0
1418 return chunk, 0
1419 elif ppos > pos:
1419 elif ppos > pos:
1420 return chunk - 1, pos - self._chunkindex[chunk - 1][0]
1420 return chunk - 1, pos - self._chunkindex[chunk - 1][0]
1421 raise ValueError('Unknown chunk')
1421 raise ValueError('Unknown chunk')
1422
1422
1423 def tell(self):
1423 def tell(self):
1424 return self._pos
1424 return self._pos
1425
1425
1426 def seek(self, offset, whence=os.SEEK_SET):
1426 def seek(self, offset, whence=os.SEEK_SET):
1427 if whence == os.SEEK_SET:
1427 if whence == os.SEEK_SET:
1428 newpos = offset
1428 newpos = offset
1429 elif whence == os.SEEK_CUR:
1429 elif whence == os.SEEK_CUR:
1430 newpos = self._pos + offset
1430 newpos = self._pos + offset
1431 elif whence == os.SEEK_END:
1431 elif whence == os.SEEK_END:
1432 if not self.consumed:
1432 if not self.consumed:
1433 # Can't use self.consume() here because it advances self._pos.
1433 # Can't use self.consume() here because it advances self._pos.
1434 chunk = self.read(32768)
1434 chunk = self.read(32768)
1435 while chunk:
1435 while chunk:
1436 chunk = self.read(32768)
1436 chunk = self.read(32768)
1437 newpos = self._chunkindex[-1][0] - offset
1437 newpos = self._chunkindex[-1][0] - offset
1438 else:
1438 else:
1439 raise ValueError('Unknown whence value: %r' % (whence,))
1439 raise ValueError('Unknown whence value: %r' % (whence,))
1440
1440
1441 if newpos > self._chunkindex[-1][0] and not self.consumed:
1441 if newpos > self._chunkindex[-1][0] and not self.consumed:
1442 # Can't use self.consume() here because it advances self._pos.
1442 # Can't use self.consume() here because it advances self._pos.
1443 chunk = self.read(32768)
1443 chunk = self.read(32768)
1444 while chunk:
1444 while chunk:
1445 chunk = self.read(32668)
1445 chunk = self.read(32668)
1446
1446
1447 if not 0 <= newpos <= self._chunkindex[-1][0]:
1447 if not 0 <= newpos <= self._chunkindex[-1][0]:
1448 raise ValueError('Offset out of range')
1448 raise ValueError('Offset out of range')
1449
1449
1450 if self._pos != newpos:
1450 if self._pos != newpos:
1451 chunk, internaloffset = self._findchunk(newpos)
1451 chunk, internaloffset = self._findchunk(newpos)
1452 self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
1452 self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
1453 adjust = self.read(internaloffset)
1453 adjust = self.read(internaloffset)
1454 if len(adjust) != internaloffset:
1454 if len(adjust) != internaloffset:
1455 raise error.Abort(_('Seek failed\n'))
1455 raise error.Abort(_('Seek failed\n'))
1456 self._pos = newpos
1456 self._pos = newpos
1457
1457
1458 def _seekfp(self, offset, whence=0):
1458 def _seekfp(self, offset, whence=0):
1459 """move the underlying file pointer
1459 """move the underlying file pointer
1460
1460
1461 This method is meant for internal usage by the bundle2 protocol only.
1461 This method is meant for internal usage by the bundle2 protocol only.
1462 They directly manipulate the low level stream including bundle2 level
1462 They directly manipulate the low level stream including bundle2 level
1463 instruction.
1463 instruction.
1464
1464
1465 Do not use it to implement higher-level logic or methods."""
1465 Do not use it to implement higher-level logic or methods."""
1466 if self._seekable:
1466 if self._seekable:
1467 return self._fp.seek(offset, whence)
1467 return self._fp.seek(offset, whence)
1468 else:
1468 else:
1469 raise NotImplementedError(_('File pointer is not seekable'))
1469 raise NotImplementedError(_('File pointer is not seekable'))
1470
1470
1471 def _tellfp(self):
1471 def _tellfp(self):
1472 """return the file offset, or None if file is not seekable
1472 """return the file offset, or None if file is not seekable
1473
1473
1474 This method is meant for internal usage by the bundle2 protocol only.
1474 This method is meant for internal usage by the bundle2 protocol only.
1475 They directly manipulate the low level stream including bundle2 level
1475 They directly manipulate the low level stream including bundle2 level
1476 instruction.
1476 instruction.
1477
1477
1478 Do not use it to implement higher-level logic or methods."""
1478 Do not use it to implement higher-level logic or methods."""
1479 if self._seekable:
1479 if self._seekable:
1480 try:
1480 try:
1481 return self._fp.tell()
1481 return self._fp.tell()
1482 except IOError as e:
1482 except IOError as e:
1483 if e.errno == errno.ESPIPE:
1483 if e.errno == errno.ESPIPE:
1484 self._seekable = False
1484 self._seekable = False
1485 else:
1485 else:
1486 raise
1486 raise
1487 return None
1487 return None
1488
1488
1489 # These are only the static capabilities.
1489 # These are only the static capabilities.
1490 # Check the 'getrepocaps' function for the rest.
1490 # Check the 'getrepocaps' function for the rest.
1491 capabilities = {'HG20': (),
1491 capabilities = {'HG20': (),
1492 'bookmarks': (),
1492 'bookmarks': (),
1493 'error': ('abort', 'unsupportedcontent', 'pushraced',
1493 'error': ('abort', 'unsupportedcontent', 'pushraced',
1494 'pushkey'),
1494 'pushkey'),
1495 'listkeys': (),
1495 'listkeys': (),
1496 'pushkey': (),
1496 'pushkey': (),
1497 'digests': tuple(sorted(util.DIGESTS.keys())),
1497 'digests': tuple(sorted(util.DIGESTS.keys())),
1498 'remote-changegroup': ('http', 'https'),
1498 'remote-changegroup': ('http', 'https'),
1499 'hgtagsfnodes': (),
1499 'hgtagsfnodes': (),
1500 'rev-branch-cache': (),
1500 'rev-branch-cache': (),
1501 'phases': ('heads',),
1501 'phases': ('heads',),
1502 'stream': ('v2',),
1502 'stream': ('v2',),
1503 }
1503 }
1504
1504
1505 def getrepocaps(repo, allowpushback=False, role=None):
1505 def getrepocaps(repo, allowpushback=False, role=None):
1506 """return the bundle2 capabilities for a given repo
1506 """return the bundle2 capabilities for a given repo
1507
1507
1508 Exists to allow extensions (like evolution) to mutate the capabilities.
1508 Exists to allow extensions (like evolution) to mutate the capabilities.
1509
1509
1510 The returned value is used for servers advertising their capabilities as
1510 The returned value is used for servers advertising their capabilities as
1511 well as clients advertising their capabilities to servers as part of
1511 well as clients advertising their capabilities to servers as part of
1512 bundle2 requests. The ``role`` argument specifies which is which.
1512 bundle2 requests. The ``role`` argument specifies which is which.
1513 """
1513 """
1514 if role not in ('client', 'server'):
1514 if role not in ('client', 'server'):
1515 raise error.ProgrammingError('role argument must be client or server')
1515 raise error.ProgrammingError('role argument must be client or server')
1516
1516
1517 caps = capabilities.copy()
1517 caps = capabilities.copy()
1518 caps['changegroup'] = tuple(sorted(
1518 caps['changegroup'] = tuple(sorted(
1519 changegroup.supportedincomingversions(repo)))
1519 changegroup.supportedincomingversions(repo)))
1520 if obsolete.isenabled(repo, obsolete.exchangeopt):
1520 if obsolete.isenabled(repo, obsolete.exchangeopt):
1521 supportedformat = tuple('V%i' % v for v in obsolete.formats)
1521 supportedformat = tuple('V%i' % v for v in obsolete.formats)
1522 caps['obsmarkers'] = supportedformat
1522 caps['obsmarkers'] = supportedformat
1523 if allowpushback:
1523 if allowpushback:
1524 caps['pushback'] = ()
1524 caps['pushback'] = ()
1525 cpmode = repo.ui.config('server', 'concurrent-push-mode')
1525 cpmode = repo.ui.config('server', 'concurrent-push-mode')
1526 if cpmode == 'check-related':
1526 if cpmode == 'check-related':
1527 caps['checkheads'] = ('related',)
1527 caps['checkheads'] = ('related',)
1528 if 'phases' in repo.ui.configlist('devel', 'legacy.exchange'):
1528 if 'phases' in repo.ui.configlist('devel', 'legacy.exchange'):
1529 caps.pop('phases')
1529 caps.pop('phases')
1530
1530
1531 # Don't advertise stream clone support in server mode if not configured.
1531 # Don't advertise stream clone support in server mode if not configured.
1532 if role == 'server':
1532 if role == 'server':
1533 streamsupported = repo.ui.configbool('server', 'uncompressed',
1533 streamsupported = repo.ui.configbool('server', 'uncompressed',
1534 untrusted=True)
1534 untrusted=True)
1535 featuresupported = repo.ui.configbool('server', 'bundle2.stream')
1535 featuresupported = repo.ui.configbool('server', 'bundle2.stream')
1536
1536
1537 if not streamsupported or not featuresupported:
1537 if not streamsupported or not featuresupported:
1538 caps.pop('stream')
1538 caps.pop('stream')
1539 # Else always advertise support on client, because payload support
1539 # Else always advertise support on client, because payload support
1540 # should always be advertised.
1540 # should always be advertised.
1541
1541
1542 return caps
1542 return caps
1543
1543
1544 def bundle2caps(remote):
1544 def bundle2caps(remote):
1545 """return the bundle capabilities of a peer as dict"""
1545 """return the bundle capabilities of a peer as dict"""
1546 raw = remote.capable('bundle2')
1546 raw = remote.capable('bundle2')
1547 if not raw and raw != '':
1547 if not raw and raw != '':
1548 return {}
1548 return {}
1549 capsblob = urlreq.unquote(remote.capable('bundle2'))
1549 capsblob = urlreq.unquote(remote.capable('bundle2'))
1550 return decodecaps(capsblob)
1550 return decodecaps(capsblob)
1551
1551
1552 def obsmarkersversion(caps):
1552 def obsmarkersversion(caps):
1553 """extract the list of supported obsmarkers versions from a bundle2caps dict
1553 """extract the list of supported obsmarkers versions from a bundle2caps dict
1554 """
1554 """
1555 obscaps = caps.get('obsmarkers', ())
1555 obscaps = caps.get('obsmarkers', ())
1556 return [int(c[1:]) for c in obscaps if c.startswith('V')]
1556 return [int(c[1:]) for c in obscaps if c.startswith('V')]
1557
1557
1558 def writenewbundle(ui, repo, source, filename, bundletype, outgoing, opts,
1558 def writenewbundle(ui, repo, source, filename, bundletype, outgoing, opts,
1559 vfs=None, compression=None, compopts=None):
1559 vfs=None, compression=None, compopts=None):
1560 if bundletype.startswith('HG10'):
1560 if bundletype.startswith('HG10'):
1561 cg = changegroup.makechangegroup(repo, outgoing, '01', source)
1561 cg = changegroup.makechangegroup(repo, outgoing, '01', source)
1562 return writebundle(ui, cg, filename, bundletype, vfs=vfs,
1562 return writebundle(ui, cg, filename, bundletype, vfs=vfs,
1563 compression=compression, compopts=compopts)
1563 compression=compression, compopts=compopts)
1564 elif not bundletype.startswith('HG20'):
1564 elif not bundletype.startswith('HG20'):
1565 raise error.ProgrammingError('unknown bundle type: %s' % bundletype)
1565 raise error.ProgrammingError('unknown bundle type: %s' % bundletype)
1566
1566
1567 caps = {}
1567 caps = {}
1568 if 'obsolescence' in opts:
1568 if 'obsolescence' in opts:
1569 caps['obsmarkers'] = ('V1',)
1569 caps['obsmarkers'] = ('V1',)
1570 bundle = bundle20(ui, caps)
1570 bundle = bundle20(ui, caps)
1571 bundle.setcompression(compression, compopts)
1571 bundle.setcompression(compression, compopts)
1572 _addpartsfromopts(ui, repo, bundle, source, outgoing, opts)
1572 _addpartsfromopts(ui, repo, bundle, source, outgoing, opts)
1573 chunkiter = bundle.getchunks()
1573 chunkiter = bundle.getchunks()
1574
1574
1575 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1575 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1576
1576
1577 def _addpartsfromopts(ui, repo, bundler, source, outgoing, opts):
1577 def _addpartsfromopts(ui, repo, bundler, source, outgoing, opts):
1578 # We should eventually reconcile this logic with the one behind
1578 # We should eventually reconcile this logic with the one behind
1579 # 'exchange.getbundle2partsgenerator'.
1579 # 'exchange.getbundle2partsgenerator'.
1580 #
1580 #
1581 # The type of input from 'getbundle' and 'writenewbundle' are a bit
1581 # The type of input from 'getbundle' and 'writenewbundle' are a bit
1582 # different right now. So we keep them separated for now for the sake of
1582 # different right now. So we keep them separated for now for the sake of
1583 # simplicity.
1583 # simplicity.
1584
1584
1585 # we might not always want a changegroup in such bundle, for example in
1585 # we might not always want a changegroup in such bundle, for example in
1586 # stream bundles
1586 # stream bundles
1587 if opts.get('changegroup', True):
1587 if opts.get('changegroup', True):
1588 cgversion = opts.get('cg.version')
1588 cgversion = opts.get('cg.version')
1589 if cgversion is None:
1589 if cgversion is None:
1590 cgversion = changegroup.safeversion(repo)
1590 cgversion = changegroup.safeversion(repo)
1591 cg = changegroup.makechangegroup(repo, outgoing, cgversion, source)
1591 cg = changegroup.makechangegroup(repo, outgoing, cgversion, source)
1592 part = bundler.newpart('changegroup', data=cg.getchunks())
1592 part = bundler.newpart('changegroup', data=cg.getchunks())
1593 part.addparam('version', cg.version)
1593 part.addparam('version', cg.version)
1594 if 'clcount' in cg.extras:
1594 if 'clcount' in cg.extras:
1595 part.addparam('nbchanges', '%d' % cg.extras['clcount'],
1595 part.addparam('nbchanges', '%d' % cg.extras['clcount'],
1596 mandatory=False)
1596 mandatory=False)
1597 if opts.get('phases') and repo.revs('%ln and secret()',
1597 if opts.get('phases') and repo.revs('%ln and secret()',
1598 outgoing.missingheads):
1598 outgoing.missingheads):
1599 part.addparam('targetphase', '%d' % phases.secret, mandatory=False)
1599 part.addparam('targetphase', '%d' % phases.secret, mandatory=False)
1600
1600
1601 if opts.get('streamv2', False):
1601 if opts.get('streamv2', False):
1602 addpartbundlestream2(bundler, repo, stream=True)
1602 addpartbundlestream2(bundler, repo, stream=True)
1603
1603
1604 if opts.get('tagsfnodescache', True):
1604 if opts.get('tagsfnodescache', True):
1605 addparttagsfnodescache(repo, bundler, outgoing)
1605 addparttagsfnodescache(repo, bundler, outgoing)
1606
1606
1607 if opts.get('revbranchcache', True):
1607 if opts.get('revbranchcache', True):
1608 addpartrevbranchcache(repo, bundler, outgoing)
1608 addpartrevbranchcache(repo, bundler, outgoing)
1609
1609
1610 if opts.get('obsolescence', False):
1610 if opts.get('obsolescence', False):
1611 obsmarkers = repo.obsstore.relevantmarkers(outgoing.missing)
1611 obsmarkers = repo.obsstore.relevantmarkers(outgoing.missing)
1612 buildobsmarkerspart(bundler, obsmarkers)
1612 buildobsmarkerspart(bundler, obsmarkers)
1613
1613
1614 if opts.get('phases', False):
1614 if opts.get('phases', False):
1615 headsbyphase = phases.subsetphaseheads(repo, outgoing.missing)
1615 headsbyphase = phases.subsetphaseheads(repo, outgoing.missing)
1616 phasedata = phases.binaryencode(headsbyphase)
1616 phasedata = phases.binaryencode(headsbyphase)
1617 bundler.newpart('phase-heads', data=phasedata)
1617 bundler.newpart('phase-heads', data=phasedata)
1618
1618
1619 def addparttagsfnodescache(repo, bundler, outgoing):
1619 def addparttagsfnodescache(repo, bundler, outgoing):
1620 # we include the tags fnode cache for the bundle changeset
1620 # we include the tags fnode cache for the bundle changeset
1621 # (as an optional parts)
1621 # (as an optional parts)
1622 cache = tags.hgtagsfnodescache(repo.unfiltered())
1622 cache = tags.hgtagsfnodescache(repo.unfiltered())
1623 chunks = []
1623 chunks = []
1624
1624
1625 # .hgtags fnodes are only relevant for head changesets. While we could
1625 # .hgtags fnodes are only relevant for head changesets. While we could
1626 # transfer values for all known nodes, there will likely be little to
1626 # transfer values for all known nodes, there will likely be little to
1627 # no benefit.
1627 # no benefit.
1628 #
1628 #
1629 # We don't bother using a generator to produce output data because
1629 # We don't bother using a generator to produce output data because
1630 # a) we only have 40 bytes per head and even esoteric numbers of heads
1630 # a) we only have 40 bytes per head and even esoteric numbers of heads
1631 # consume little memory (1M heads is 40MB) b) we don't want to send the
1631 # consume little memory (1M heads is 40MB) b) we don't want to send the
1632 # part if we don't have entries and knowing if we have entries requires
1632 # part if we don't have entries and knowing if we have entries requires
1633 # cache lookups.
1633 # cache lookups.
1634 for node in outgoing.missingheads:
1634 for node in outgoing.missingheads:
1635 # Don't compute missing, as this may slow down serving.
1635 # Don't compute missing, as this may slow down serving.
1636 fnode = cache.getfnode(node, computemissing=False)
1636 fnode = cache.getfnode(node, computemissing=False)
1637 if fnode is not None:
1637 if fnode is not None:
1638 chunks.extend([node, fnode])
1638 chunks.extend([node, fnode])
1639
1639
1640 if chunks:
1640 if chunks:
1641 bundler.newpart('hgtagsfnodes', data=''.join(chunks))
1641 bundler.newpart('hgtagsfnodes', data=''.join(chunks))
1642
1642
1643 def addpartrevbranchcache(repo, bundler, outgoing):
1643 def addpartrevbranchcache(repo, bundler, outgoing):
1644 # we include the rev branch cache for the bundle changeset
1644 # we include the rev branch cache for the bundle changeset
1645 # (as an optional parts)
1645 # (as an optional parts)
1646 cache = repo.revbranchcache()
1646 cache = repo.revbranchcache()
1647 cl = repo.unfiltered().changelog
1647 cl = repo.unfiltered().changelog
1648 branchesdata = collections.defaultdict(lambda: (set(), set()))
1648 branchesdata = collections.defaultdict(lambda: (set(), set()))
1649 for node in outgoing.missing:
1649 for node in outgoing.missing:
1650 branch, close = cache.branchinfo(cl.rev(node))
1650 branch, close = cache.branchinfo(cl.rev(node))
1651 branchesdata[branch][close].add(node)
1651 branchesdata[branch][close].add(node)
1652
1652
1653 def generate():
1653 def generate():
1654 for branch, (nodes, closed) in sorted(branchesdata.items()):
1654 for branch, (nodes, closed) in sorted(branchesdata.items()):
1655 utf8branch = encoding.fromlocal(branch)
1655 utf8branch = encoding.fromlocal(branch)
1656 yield rbcstruct.pack(len(utf8branch), len(nodes), len(closed))
1656 yield rbcstruct.pack(len(utf8branch), len(nodes), len(closed))
1657 yield utf8branch
1657 yield utf8branch
1658 for n in sorted(nodes):
1658 for n in sorted(nodes):
1659 yield n
1659 yield n
1660 for n in sorted(closed):
1660 for n in sorted(closed):
1661 yield n
1661 yield n
1662
1662
1663 bundler.newpart('cache:rev-branch-cache', data=generate(),
1663 bundler.newpart('cache:rev-branch-cache', data=generate(),
1664 mandatory=False)
1664 mandatory=False)
1665
1665
1666 def _formatrequirementsspec(requirements):
1666 def _formatrequirementsspec(requirements):
1667 return urlreq.quote(','.join(sorted(requirements)))
1667 return urlreq.quote(','.join(sorted(requirements)))
1668
1668
1669 def _formatrequirementsparams(requirements):
1669 def _formatrequirementsparams(requirements):
1670 requirements = _formatrequirementsspec(requirements)
1670 requirements = _formatrequirementsspec(requirements)
1671 params = "%s%s" % (urlreq.quote("requirements="), requirements)
1671 params = "%s%s" % (urlreq.quote("requirements="), requirements)
1672 return params
1672 return params
1673
1673
1674 def addpartbundlestream2(bundler, repo, **kwargs):
1674 def addpartbundlestream2(bundler, repo, **kwargs):
1675 if not kwargs.get(r'stream', False):
1675 if not kwargs.get(r'stream', False):
1676 return
1676 return
1677
1677
1678 if not streamclone.allowservergeneration(repo):
1678 if not streamclone.allowservergeneration(repo):
1679 raise error.Abort(_('stream data requested but server does not allow '
1679 raise error.Abort(_('stream data requested but server does not allow '
1680 'this feature'),
1680 'this feature'),
1681 hint=_('well-behaved clients should not be '
1681 hint=_('well-behaved clients should not be '
1682 'requesting stream data from servers not '
1682 'requesting stream data from servers not '
1683 'advertising it; the client may be buggy'))
1683 'advertising it; the client may be buggy'))
1684
1684
1685 # Stream clones don't compress well. And compression undermines a
1685 # Stream clones don't compress well. And compression undermines a
1686 # goal of stream clones, which is to be fast. Communicate the desire
1686 # goal of stream clones, which is to be fast. Communicate the desire
1687 # to avoid compression to consumers of the bundle.
1687 # to avoid compression to consumers of the bundle.
1688 bundler.prefercompressed = False
1688 bundler.prefercompressed = False
1689
1689
1690 filecount, bytecount, it = streamclone.generatev2(repo)
1690 # get the includes and excludes
1691 includepats = kwargs.get(r'includepats')
1692 excludepats = kwargs.get(r'excludepats')
1693
1694 narrowstream = repo.ui.configbool('experimental.server',
1695 'stream-narrow-clones')
1696
1697 if (includepats or excludepats) and not narrowstream:
1698 raise error.Abort(_('server does not support narrow stream clones'))
1699
1700 filecount, bytecount, it = streamclone.generatev2(repo, includepats,
1701 excludepats)
1691 requirements = _formatrequirementsspec(repo.requirements)
1702 requirements = _formatrequirementsspec(repo.requirements)
1692 part = bundler.newpart('stream2', data=it)
1703 part = bundler.newpart('stream2', data=it)
1693 part.addparam('bytecount', '%d' % bytecount, mandatory=True)
1704 part.addparam('bytecount', '%d' % bytecount, mandatory=True)
1694 part.addparam('filecount', '%d' % filecount, mandatory=True)
1705 part.addparam('filecount', '%d' % filecount, mandatory=True)
1695 part.addparam('requirements', requirements, mandatory=True)
1706 part.addparam('requirements', requirements, mandatory=True)
1696
1707
1697 def buildobsmarkerspart(bundler, markers):
1708 def buildobsmarkerspart(bundler, markers):
1698 """add an obsmarker part to the bundler with <markers>
1709 """add an obsmarker part to the bundler with <markers>
1699
1710
1700 No part is created if markers is empty.
1711 No part is created if markers is empty.
1701 Raises ValueError if the bundler doesn't support any known obsmarker format.
1712 Raises ValueError if the bundler doesn't support any known obsmarker format.
1702 """
1713 """
1703 if not markers:
1714 if not markers:
1704 return None
1715 return None
1705
1716
1706 remoteversions = obsmarkersversion(bundler.capabilities)
1717 remoteversions = obsmarkersversion(bundler.capabilities)
1707 version = obsolete.commonversion(remoteversions)
1718 version = obsolete.commonversion(remoteversions)
1708 if version is None:
1719 if version is None:
1709 raise ValueError('bundler does not support common obsmarker format')
1720 raise ValueError('bundler does not support common obsmarker format')
1710 stream = obsolete.encodemarkers(markers, True, version=version)
1721 stream = obsolete.encodemarkers(markers, True, version=version)
1711 return bundler.newpart('obsmarkers', data=stream)
1722 return bundler.newpart('obsmarkers', data=stream)
1712
1723
1713 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None,
1724 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None,
1714 compopts=None):
1725 compopts=None):
1715 """Write a bundle file and return its filename.
1726 """Write a bundle file and return its filename.
1716
1727
1717 Existing files will not be overwritten.
1728 Existing files will not be overwritten.
1718 If no filename is specified, a temporary file is created.
1729 If no filename is specified, a temporary file is created.
1719 bz2 compression can be turned off.
1730 bz2 compression can be turned off.
1720 The bundle file will be deleted in case of errors.
1731 The bundle file will be deleted in case of errors.
1721 """
1732 """
1722
1733
1723 if bundletype == "HG20":
1734 if bundletype == "HG20":
1724 bundle = bundle20(ui)
1735 bundle = bundle20(ui)
1725 bundle.setcompression(compression, compopts)
1736 bundle.setcompression(compression, compopts)
1726 part = bundle.newpart('changegroup', data=cg.getchunks())
1737 part = bundle.newpart('changegroup', data=cg.getchunks())
1727 part.addparam('version', cg.version)
1738 part.addparam('version', cg.version)
1728 if 'clcount' in cg.extras:
1739 if 'clcount' in cg.extras:
1729 part.addparam('nbchanges', '%d' % cg.extras['clcount'],
1740 part.addparam('nbchanges', '%d' % cg.extras['clcount'],
1730 mandatory=False)
1741 mandatory=False)
1731 chunkiter = bundle.getchunks()
1742 chunkiter = bundle.getchunks()
1732 else:
1743 else:
1733 # compression argument is only for the bundle2 case
1744 # compression argument is only for the bundle2 case
1734 assert compression is None
1745 assert compression is None
1735 if cg.version != '01':
1746 if cg.version != '01':
1736 raise error.Abort(_('old bundle types only supports v1 '
1747 raise error.Abort(_('old bundle types only supports v1 '
1737 'changegroups'))
1748 'changegroups'))
1738 header, comp = bundletypes[bundletype]
1749 header, comp = bundletypes[bundletype]
1739 if comp not in util.compengines.supportedbundletypes:
1750 if comp not in util.compengines.supportedbundletypes:
1740 raise error.Abort(_('unknown stream compression type: %s')
1751 raise error.Abort(_('unknown stream compression type: %s')
1741 % comp)
1752 % comp)
1742 compengine = util.compengines.forbundletype(comp)
1753 compengine = util.compengines.forbundletype(comp)
1743 def chunkiter():
1754 def chunkiter():
1744 yield header
1755 yield header
1745 for chunk in compengine.compressstream(cg.getchunks(), compopts):
1756 for chunk in compengine.compressstream(cg.getchunks(), compopts):
1746 yield chunk
1757 yield chunk
1747 chunkiter = chunkiter()
1758 chunkiter = chunkiter()
1748
1759
1749 # parse the changegroup data, otherwise we will block
1760 # parse the changegroup data, otherwise we will block
1750 # in case of sshrepo because we don't know the end of the stream
1761 # in case of sshrepo because we don't know the end of the stream
1751 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1762 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1752
1763
1753 def combinechangegroupresults(op):
1764 def combinechangegroupresults(op):
1754 """logic to combine 0 or more addchangegroup results into one"""
1765 """logic to combine 0 or more addchangegroup results into one"""
1755 results = [r.get('return', 0)
1766 results = [r.get('return', 0)
1756 for r in op.records['changegroup']]
1767 for r in op.records['changegroup']]
1757 changedheads = 0
1768 changedheads = 0
1758 result = 1
1769 result = 1
1759 for ret in results:
1770 for ret in results:
1760 # If any changegroup result is 0, return 0
1771 # If any changegroup result is 0, return 0
1761 if ret == 0:
1772 if ret == 0:
1762 result = 0
1773 result = 0
1763 break
1774 break
1764 if ret < -1:
1775 if ret < -1:
1765 changedheads += ret + 1
1776 changedheads += ret + 1
1766 elif ret > 1:
1777 elif ret > 1:
1767 changedheads += ret - 1
1778 changedheads += ret - 1
1768 if changedheads > 0:
1779 if changedheads > 0:
1769 result = 1 + changedheads
1780 result = 1 + changedheads
1770 elif changedheads < 0:
1781 elif changedheads < 0:
1771 result = -1 + changedheads
1782 result = -1 + changedheads
1772 return result
1783 return result
1773
1784
1774 @parthandler('changegroup', ('version', 'nbchanges', 'treemanifest',
1785 @parthandler('changegroup', ('version', 'nbchanges', 'treemanifest',
1775 'targetphase'))
1786 'targetphase'))
1776 def handlechangegroup(op, inpart):
1787 def handlechangegroup(op, inpart):
1777 """apply a changegroup part on the repo
1788 """apply a changegroup part on the repo
1778
1789
1779 This is a very early implementation that will massive rework before being
1790 This is a very early implementation that will massive rework before being
1780 inflicted to any end-user.
1791 inflicted to any end-user.
1781 """
1792 """
1782 from . import localrepo
1793 from . import localrepo
1783
1794
1784 tr = op.gettransaction()
1795 tr = op.gettransaction()
1785 unpackerversion = inpart.params.get('version', '01')
1796 unpackerversion = inpart.params.get('version', '01')
1786 # We should raise an appropriate exception here
1797 # We should raise an appropriate exception here
1787 cg = changegroup.getunbundler(unpackerversion, inpart, None)
1798 cg = changegroup.getunbundler(unpackerversion, inpart, None)
1788 # the source and url passed here are overwritten by the one contained in
1799 # the source and url passed here are overwritten by the one contained in
1789 # the transaction.hookargs argument. So 'bundle2' is a placeholder
1800 # the transaction.hookargs argument. So 'bundle2' is a placeholder
1790 nbchangesets = None
1801 nbchangesets = None
1791 if 'nbchanges' in inpart.params:
1802 if 'nbchanges' in inpart.params:
1792 nbchangesets = int(inpart.params.get('nbchanges'))
1803 nbchangesets = int(inpart.params.get('nbchanges'))
1793 if ('treemanifest' in inpart.params and
1804 if ('treemanifest' in inpart.params and
1794 'treemanifest' not in op.repo.requirements):
1805 'treemanifest' not in op.repo.requirements):
1795 if len(op.repo.changelog) != 0:
1806 if len(op.repo.changelog) != 0:
1796 raise error.Abort(_(
1807 raise error.Abort(_(
1797 "bundle contains tree manifests, but local repo is "
1808 "bundle contains tree manifests, but local repo is "
1798 "non-empty and does not use tree manifests"))
1809 "non-empty and does not use tree manifests"))
1799 op.repo.requirements.add('treemanifest')
1810 op.repo.requirements.add('treemanifest')
1800 op.repo.svfs.options = localrepo.resolvestorevfsoptions(
1811 op.repo.svfs.options = localrepo.resolvestorevfsoptions(
1801 op.repo.ui, op.repo.requirements, op.repo.features)
1812 op.repo.ui, op.repo.requirements, op.repo.features)
1802 op.repo._writerequirements()
1813 op.repo._writerequirements()
1803 extrakwargs = {}
1814 extrakwargs = {}
1804 targetphase = inpart.params.get('targetphase')
1815 targetphase = inpart.params.get('targetphase')
1805 if targetphase is not None:
1816 if targetphase is not None:
1806 extrakwargs[r'targetphase'] = int(targetphase)
1817 extrakwargs[r'targetphase'] = int(targetphase)
1807 ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2',
1818 ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2',
1808 expectedtotal=nbchangesets, **extrakwargs)
1819 expectedtotal=nbchangesets, **extrakwargs)
1809 if op.reply is not None:
1820 if op.reply is not None:
1810 # This is definitely not the final form of this
1821 # This is definitely not the final form of this
1811 # return. But one need to start somewhere.
1822 # return. But one need to start somewhere.
1812 part = op.reply.newpart('reply:changegroup', mandatory=False)
1823 part = op.reply.newpart('reply:changegroup', mandatory=False)
1813 part.addparam(
1824 part.addparam(
1814 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
1825 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
1815 part.addparam('return', '%i' % ret, mandatory=False)
1826 part.addparam('return', '%i' % ret, mandatory=False)
1816 assert not inpart.read()
1827 assert not inpart.read()
1817
1828
1818 _remotechangegroupparams = tuple(['url', 'size', 'digests'] +
1829 _remotechangegroupparams = tuple(['url', 'size', 'digests'] +
1819 ['digest:%s' % k for k in util.DIGESTS.keys()])
1830 ['digest:%s' % k for k in util.DIGESTS.keys()])
1820 @parthandler('remote-changegroup', _remotechangegroupparams)
1831 @parthandler('remote-changegroup', _remotechangegroupparams)
1821 def handleremotechangegroup(op, inpart):
1832 def handleremotechangegroup(op, inpart):
1822 """apply a bundle10 on the repo, given an url and validation information
1833 """apply a bundle10 on the repo, given an url and validation information
1823
1834
1824 All the information about the remote bundle to import are given as
1835 All the information about the remote bundle to import are given as
1825 parameters. The parameters include:
1836 parameters. The parameters include:
1826 - url: the url to the bundle10.
1837 - url: the url to the bundle10.
1827 - size: the bundle10 file size. It is used to validate what was
1838 - size: the bundle10 file size. It is used to validate what was
1828 retrieved by the client matches the server knowledge about the bundle.
1839 retrieved by the client matches the server knowledge about the bundle.
1829 - digests: a space separated list of the digest types provided as
1840 - digests: a space separated list of the digest types provided as
1830 parameters.
1841 parameters.
1831 - digest:<digest-type>: the hexadecimal representation of the digest with
1842 - digest:<digest-type>: the hexadecimal representation of the digest with
1832 that name. Like the size, it is used to validate what was retrieved by
1843 that name. Like the size, it is used to validate what was retrieved by
1833 the client matches what the server knows about the bundle.
1844 the client matches what the server knows about the bundle.
1834
1845
1835 When multiple digest types are given, all of them are checked.
1846 When multiple digest types are given, all of them are checked.
1836 """
1847 """
1837 try:
1848 try:
1838 raw_url = inpart.params['url']
1849 raw_url = inpart.params['url']
1839 except KeyError:
1850 except KeyError:
1840 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'url')
1851 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'url')
1841 parsed_url = util.url(raw_url)
1852 parsed_url = util.url(raw_url)
1842 if parsed_url.scheme not in capabilities['remote-changegroup']:
1853 if parsed_url.scheme not in capabilities['remote-changegroup']:
1843 raise error.Abort(_('remote-changegroup does not support %s urls') %
1854 raise error.Abort(_('remote-changegroup does not support %s urls') %
1844 parsed_url.scheme)
1855 parsed_url.scheme)
1845
1856
1846 try:
1857 try:
1847 size = int(inpart.params['size'])
1858 size = int(inpart.params['size'])
1848 except ValueError:
1859 except ValueError:
1849 raise error.Abort(_('remote-changegroup: invalid value for param "%s"')
1860 raise error.Abort(_('remote-changegroup: invalid value for param "%s"')
1850 % 'size')
1861 % 'size')
1851 except KeyError:
1862 except KeyError:
1852 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'size')
1863 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'size')
1853
1864
1854 digests = {}
1865 digests = {}
1855 for typ in inpart.params.get('digests', '').split():
1866 for typ in inpart.params.get('digests', '').split():
1856 param = 'digest:%s' % typ
1867 param = 'digest:%s' % typ
1857 try:
1868 try:
1858 value = inpart.params[param]
1869 value = inpart.params[param]
1859 except KeyError:
1870 except KeyError:
1860 raise error.Abort(_('remote-changegroup: missing "%s" param') %
1871 raise error.Abort(_('remote-changegroup: missing "%s" param') %
1861 param)
1872 param)
1862 digests[typ] = value
1873 digests[typ] = value
1863
1874
1864 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
1875 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
1865
1876
1866 tr = op.gettransaction()
1877 tr = op.gettransaction()
1867 from . import exchange
1878 from . import exchange
1868 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
1879 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
1869 if not isinstance(cg, changegroup.cg1unpacker):
1880 if not isinstance(cg, changegroup.cg1unpacker):
1870 raise error.Abort(_('%s: not a bundle version 1.0') %
1881 raise error.Abort(_('%s: not a bundle version 1.0') %
1871 util.hidepassword(raw_url))
1882 util.hidepassword(raw_url))
1872 ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2')
1883 ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2')
1873 if op.reply is not None:
1884 if op.reply is not None:
1874 # This is definitely not the final form of this
1885 # This is definitely not the final form of this
1875 # return. But one need to start somewhere.
1886 # return. But one need to start somewhere.
1876 part = op.reply.newpart('reply:changegroup')
1887 part = op.reply.newpart('reply:changegroup')
1877 part.addparam(
1888 part.addparam(
1878 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
1889 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
1879 part.addparam('return', '%i' % ret, mandatory=False)
1890 part.addparam('return', '%i' % ret, mandatory=False)
1880 try:
1891 try:
1881 real_part.validate()
1892 real_part.validate()
1882 except error.Abort as e:
1893 except error.Abort as e:
1883 raise error.Abort(_('bundle at %s is corrupted:\n%s') %
1894 raise error.Abort(_('bundle at %s is corrupted:\n%s') %
1884 (util.hidepassword(raw_url), bytes(e)))
1895 (util.hidepassword(raw_url), bytes(e)))
1885 assert not inpart.read()
1896 assert not inpart.read()
1886
1897
1887 @parthandler('reply:changegroup', ('return', 'in-reply-to'))
1898 @parthandler('reply:changegroup', ('return', 'in-reply-to'))
1888 def handlereplychangegroup(op, inpart):
1899 def handlereplychangegroup(op, inpart):
1889 ret = int(inpart.params['return'])
1900 ret = int(inpart.params['return'])
1890 replyto = int(inpart.params['in-reply-to'])
1901 replyto = int(inpart.params['in-reply-to'])
1891 op.records.add('changegroup', {'return': ret}, replyto)
1902 op.records.add('changegroup', {'return': ret}, replyto)
1892
1903
1893 @parthandler('check:bookmarks')
1904 @parthandler('check:bookmarks')
1894 def handlecheckbookmarks(op, inpart):
1905 def handlecheckbookmarks(op, inpart):
1895 """check location of bookmarks
1906 """check location of bookmarks
1896
1907
1897 This part is to be used to detect push race regarding bookmark, it
1908 This part is to be used to detect push race regarding bookmark, it
1898 contains binary encoded (bookmark, node) tuple. If the local state does
1909 contains binary encoded (bookmark, node) tuple. If the local state does
1899 not marks the one in the part, a PushRaced exception is raised
1910 not marks the one in the part, a PushRaced exception is raised
1900 """
1911 """
1901 bookdata = bookmarks.binarydecode(inpart)
1912 bookdata = bookmarks.binarydecode(inpart)
1902
1913
1903 msgstandard = ('remote repository changed while pushing - please try again '
1914 msgstandard = ('remote repository changed while pushing - please try again '
1904 '(bookmark "%s" move from %s to %s)')
1915 '(bookmark "%s" move from %s to %s)')
1905 msgmissing = ('remote repository changed while pushing - please try again '
1916 msgmissing = ('remote repository changed while pushing - please try again '
1906 '(bookmark "%s" is missing, expected %s)')
1917 '(bookmark "%s" is missing, expected %s)')
1907 msgexist = ('remote repository changed while pushing - please try again '
1918 msgexist = ('remote repository changed while pushing - please try again '
1908 '(bookmark "%s" set on %s, expected missing)')
1919 '(bookmark "%s" set on %s, expected missing)')
1909 for book, node in bookdata:
1920 for book, node in bookdata:
1910 currentnode = op.repo._bookmarks.get(book)
1921 currentnode = op.repo._bookmarks.get(book)
1911 if currentnode != node:
1922 if currentnode != node:
1912 if node is None:
1923 if node is None:
1913 finalmsg = msgexist % (book, nodemod.short(currentnode))
1924 finalmsg = msgexist % (book, nodemod.short(currentnode))
1914 elif currentnode is None:
1925 elif currentnode is None:
1915 finalmsg = msgmissing % (book, nodemod.short(node))
1926 finalmsg = msgmissing % (book, nodemod.short(node))
1916 else:
1927 else:
1917 finalmsg = msgstandard % (book, nodemod.short(node),
1928 finalmsg = msgstandard % (book, nodemod.short(node),
1918 nodemod.short(currentnode))
1929 nodemod.short(currentnode))
1919 raise error.PushRaced(finalmsg)
1930 raise error.PushRaced(finalmsg)
1920
1931
1921 @parthandler('check:heads')
1932 @parthandler('check:heads')
1922 def handlecheckheads(op, inpart):
1933 def handlecheckheads(op, inpart):
1923 """check that head of the repo did not change
1934 """check that head of the repo did not change
1924
1935
1925 This is used to detect a push race when using unbundle.
1936 This is used to detect a push race when using unbundle.
1926 This replaces the "heads" argument of unbundle."""
1937 This replaces the "heads" argument of unbundle."""
1927 h = inpart.read(20)
1938 h = inpart.read(20)
1928 heads = []
1939 heads = []
1929 while len(h) == 20:
1940 while len(h) == 20:
1930 heads.append(h)
1941 heads.append(h)
1931 h = inpart.read(20)
1942 h = inpart.read(20)
1932 assert not h
1943 assert not h
1933 # Trigger a transaction so that we are guaranteed to have the lock now.
1944 # Trigger a transaction so that we are guaranteed to have the lock now.
1934 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1945 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1935 op.gettransaction()
1946 op.gettransaction()
1936 if sorted(heads) != sorted(op.repo.heads()):
1947 if sorted(heads) != sorted(op.repo.heads()):
1937 raise error.PushRaced('remote repository changed while pushing - '
1948 raise error.PushRaced('remote repository changed while pushing - '
1938 'please try again')
1949 'please try again')
1939
1950
1940 @parthandler('check:updated-heads')
1951 @parthandler('check:updated-heads')
1941 def handlecheckupdatedheads(op, inpart):
1952 def handlecheckupdatedheads(op, inpart):
1942 """check for race on the heads touched by a push
1953 """check for race on the heads touched by a push
1943
1954
1944 This is similar to 'check:heads' but focus on the heads actually updated
1955 This is similar to 'check:heads' but focus on the heads actually updated
1945 during the push. If other activities happen on unrelated heads, it is
1956 during the push. If other activities happen on unrelated heads, it is
1946 ignored.
1957 ignored.
1947
1958
1948 This allow server with high traffic to avoid push contention as long as
1959 This allow server with high traffic to avoid push contention as long as
1949 unrelated parts of the graph are involved."""
1960 unrelated parts of the graph are involved."""
1950 h = inpart.read(20)
1961 h = inpart.read(20)
1951 heads = []
1962 heads = []
1952 while len(h) == 20:
1963 while len(h) == 20:
1953 heads.append(h)
1964 heads.append(h)
1954 h = inpart.read(20)
1965 h = inpart.read(20)
1955 assert not h
1966 assert not h
1956 # trigger a transaction so that we are guaranteed to have the lock now.
1967 # trigger a transaction so that we are guaranteed to have the lock now.
1957 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1968 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1958 op.gettransaction()
1969 op.gettransaction()
1959
1970
1960 currentheads = set()
1971 currentheads = set()
1961 for ls in op.repo.branchmap().itervalues():
1972 for ls in op.repo.branchmap().itervalues():
1962 currentheads.update(ls)
1973 currentheads.update(ls)
1963
1974
1964 for h in heads:
1975 for h in heads:
1965 if h not in currentheads:
1976 if h not in currentheads:
1966 raise error.PushRaced('remote repository changed while pushing - '
1977 raise error.PushRaced('remote repository changed while pushing - '
1967 'please try again')
1978 'please try again')
1968
1979
1969 @parthandler('check:phases')
1980 @parthandler('check:phases')
1970 def handlecheckphases(op, inpart):
1981 def handlecheckphases(op, inpart):
1971 """check that phase boundaries of the repository did not change
1982 """check that phase boundaries of the repository did not change
1972
1983
1973 This is used to detect a push race.
1984 This is used to detect a push race.
1974 """
1985 """
1975 phasetonodes = phases.binarydecode(inpart)
1986 phasetonodes = phases.binarydecode(inpart)
1976 unfi = op.repo.unfiltered()
1987 unfi = op.repo.unfiltered()
1977 cl = unfi.changelog
1988 cl = unfi.changelog
1978 phasecache = unfi._phasecache
1989 phasecache = unfi._phasecache
1979 msg = ('remote repository changed while pushing - please try again '
1990 msg = ('remote repository changed while pushing - please try again '
1980 '(%s is %s expected %s)')
1991 '(%s is %s expected %s)')
1981 for expectedphase, nodes in enumerate(phasetonodes):
1992 for expectedphase, nodes in enumerate(phasetonodes):
1982 for n in nodes:
1993 for n in nodes:
1983 actualphase = phasecache.phase(unfi, cl.rev(n))
1994 actualphase = phasecache.phase(unfi, cl.rev(n))
1984 if actualphase != expectedphase:
1995 if actualphase != expectedphase:
1985 finalmsg = msg % (nodemod.short(n),
1996 finalmsg = msg % (nodemod.short(n),
1986 phases.phasenames[actualphase],
1997 phases.phasenames[actualphase],
1987 phases.phasenames[expectedphase])
1998 phases.phasenames[expectedphase])
1988 raise error.PushRaced(finalmsg)
1999 raise error.PushRaced(finalmsg)
1989
2000
1990 @parthandler('output')
2001 @parthandler('output')
1991 def handleoutput(op, inpart):
2002 def handleoutput(op, inpart):
1992 """forward output captured on the server to the client"""
2003 """forward output captured on the server to the client"""
1993 for line in inpart.read().splitlines():
2004 for line in inpart.read().splitlines():
1994 op.ui.status(_('remote: %s\n') % line)
2005 op.ui.status(_('remote: %s\n') % line)
1995
2006
1996 @parthandler('replycaps')
2007 @parthandler('replycaps')
1997 def handlereplycaps(op, inpart):
2008 def handlereplycaps(op, inpart):
1998 """Notify that a reply bundle should be created
2009 """Notify that a reply bundle should be created
1999
2010
2000 The payload contains the capabilities information for the reply"""
2011 The payload contains the capabilities information for the reply"""
2001 caps = decodecaps(inpart.read())
2012 caps = decodecaps(inpart.read())
2002 if op.reply is None:
2013 if op.reply is None:
2003 op.reply = bundle20(op.ui, caps)
2014 op.reply = bundle20(op.ui, caps)
2004
2015
2005 class AbortFromPart(error.Abort):
2016 class AbortFromPart(error.Abort):
2006 """Sub-class of Abort that denotes an error from a bundle2 part."""
2017 """Sub-class of Abort that denotes an error from a bundle2 part."""
2007
2018
2008 @parthandler('error:abort', ('message', 'hint'))
2019 @parthandler('error:abort', ('message', 'hint'))
2009 def handleerrorabort(op, inpart):
2020 def handleerrorabort(op, inpart):
2010 """Used to transmit abort error over the wire"""
2021 """Used to transmit abort error over the wire"""
2011 raise AbortFromPart(inpart.params['message'],
2022 raise AbortFromPart(inpart.params['message'],
2012 hint=inpart.params.get('hint'))
2023 hint=inpart.params.get('hint'))
2013
2024
2014 @parthandler('error:pushkey', ('namespace', 'key', 'new', 'old', 'ret',
2025 @parthandler('error:pushkey', ('namespace', 'key', 'new', 'old', 'ret',
2015 'in-reply-to'))
2026 'in-reply-to'))
2016 def handleerrorpushkey(op, inpart):
2027 def handleerrorpushkey(op, inpart):
2017 """Used to transmit failure of a mandatory pushkey over the wire"""
2028 """Used to transmit failure of a mandatory pushkey over the wire"""
2018 kwargs = {}
2029 kwargs = {}
2019 for name in ('namespace', 'key', 'new', 'old', 'ret'):
2030 for name in ('namespace', 'key', 'new', 'old', 'ret'):
2020 value = inpart.params.get(name)
2031 value = inpart.params.get(name)
2021 if value is not None:
2032 if value is not None:
2022 kwargs[name] = value
2033 kwargs[name] = value
2023 raise error.PushkeyFailed(inpart.params['in-reply-to'],
2034 raise error.PushkeyFailed(inpart.params['in-reply-to'],
2024 **pycompat.strkwargs(kwargs))
2035 **pycompat.strkwargs(kwargs))
2025
2036
2026 @parthandler('error:unsupportedcontent', ('parttype', 'params'))
2037 @parthandler('error:unsupportedcontent', ('parttype', 'params'))
2027 def handleerrorunsupportedcontent(op, inpart):
2038 def handleerrorunsupportedcontent(op, inpart):
2028 """Used to transmit unknown content error over the wire"""
2039 """Used to transmit unknown content error over the wire"""
2029 kwargs = {}
2040 kwargs = {}
2030 parttype = inpart.params.get('parttype')
2041 parttype = inpart.params.get('parttype')
2031 if parttype is not None:
2042 if parttype is not None:
2032 kwargs['parttype'] = parttype
2043 kwargs['parttype'] = parttype
2033 params = inpart.params.get('params')
2044 params = inpart.params.get('params')
2034 if params is not None:
2045 if params is not None:
2035 kwargs['params'] = params.split('\0')
2046 kwargs['params'] = params.split('\0')
2036
2047
2037 raise error.BundleUnknownFeatureError(**pycompat.strkwargs(kwargs))
2048 raise error.BundleUnknownFeatureError(**pycompat.strkwargs(kwargs))
2038
2049
2039 @parthandler('error:pushraced', ('message',))
2050 @parthandler('error:pushraced', ('message',))
2040 def handleerrorpushraced(op, inpart):
2051 def handleerrorpushraced(op, inpart):
2041 """Used to transmit push race error over the wire"""
2052 """Used to transmit push race error over the wire"""
2042 raise error.ResponseError(_('push failed:'), inpart.params['message'])
2053 raise error.ResponseError(_('push failed:'), inpart.params['message'])
2043
2054
2044 @parthandler('listkeys', ('namespace',))
2055 @parthandler('listkeys', ('namespace',))
2045 def handlelistkeys(op, inpart):
2056 def handlelistkeys(op, inpart):
2046 """retrieve pushkey namespace content stored in a bundle2"""
2057 """retrieve pushkey namespace content stored in a bundle2"""
2047 namespace = inpart.params['namespace']
2058 namespace = inpart.params['namespace']
2048 r = pushkey.decodekeys(inpart.read())
2059 r = pushkey.decodekeys(inpart.read())
2049 op.records.add('listkeys', (namespace, r))
2060 op.records.add('listkeys', (namespace, r))
2050
2061
2051 @parthandler('pushkey', ('namespace', 'key', 'old', 'new'))
2062 @parthandler('pushkey', ('namespace', 'key', 'old', 'new'))
2052 def handlepushkey(op, inpart):
2063 def handlepushkey(op, inpart):
2053 """process a pushkey request"""
2064 """process a pushkey request"""
2054 dec = pushkey.decode
2065 dec = pushkey.decode
2055 namespace = dec(inpart.params['namespace'])
2066 namespace = dec(inpart.params['namespace'])
2056 key = dec(inpart.params['key'])
2067 key = dec(inpart.params['key'])
2057 old = dec(inpart.params['old'])
2068 old = dec(inpart.params['old'])
2058 new = dec(inpart.params['new'])
2069 new = dec(inpart.params['new'])
2059 # Grab the transaction to ensure that we have the lock before performing the
2070 # Grab the transaction to ensure that we have the lock before performing the
2060 # pushkey.
2071 # pushkey.
2061 if op.ui.configbool('experimental', 'bundle2lazylocking'):
2072 if op.ui.configbool('experimental', 'bundle2lazylocking'):
2062 op.gettransaction()
2073 op.gettransaction()
2063 ret = op.repo.pushkey(namespace, key, old, new)
2074 ret = op.repo.pushkey(namespace, key, old, new)
2064 record = {'namespace': namespace,
2075 record = {'namespace': namespace,
2065 'key': key,
2076 'key': key,
2066 'old': old,
2077 'old': old,
2067 'new': new}
2078 'new': new}
2068 op.records.add('pushkey', record)
2079 op.records.add('pushkey', record)
2069 if op.reply is not None:
2080 if op.reply is not None:
2070 rpart = op.reply.newpart('reply:pushkey')
2081 rpart = op.reply.newpart('reply:pushkey')
2071 rpart.addparam(
2082 rpart.addparam(
2072 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
2083 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
2073 rpart.addparam('return', '%i' % ret, mandatory=False)
2084 rpart.addparam('return', '%i' % ret, mandatory=False)
2074 if inpart.mandatory and not ret:
2085 if inpart.mandatory and not ret:
2075 kwargs = {}
2086 kwargs = {}
2076 for key in ('namespace', 'key', 'new', 'old', 'ret'):
2087 for key in ('namespace', 'key', 'new', 'old', 'ret'):
2077 if key in inpart.params:
2088 if key in inpart.params:
2078 kwargs[key] = inpart.params[key]
2089 kwargs[key] = inpart.params[key]
2079 raise error.PushkeyFailed(partid='%d' % inpart.id,
2090 raise error.PushkeyFailed(partid='%d' % inpart.id,
2080 **pycompat.strkwargs(kwargs))
2091 **pycompat.strkwargs(kwargs))
2081
2092
2082 @parthandler('bookmarks')
2093 @parthandler('bookmarks')
2083 def handlebookmark(op, inpart):
2094 def handlebookmark(op, inpart):
2084 """transmit bookmark information
2095 """transmit bookmark information
2085
2096
2086 The part contains binary encoded bookmark information.
2097 The part contains binary encoded bookmark information.
2087
2098
2088 The exact behavior of this part can be controlled by the 'bookmarks' mode
2099 The exact behavior of this part can be controlled by the 'bookmarks' mode
2089 on the bundle operation.
2100 on the bundle operation.
2090
2101
2091 When mode is 'apply' (the default) the bookmark information is applied as
2102 When mode is 'apply' (the default) the bookmark information is applied as
2092 is to the unbundling repository. Make sure a 'check:bookmarks' part is
2103 is to the unbundling repository. Make sure a 'check:bookmarks' part is
2093 issued earlier to check for push races in such update. This behavior is
2104 issued earlier to check for push races in such update. This behavior is
2094 suitable for pushing.
2105 suitable for pushing.
2095
2106
2096 When mode is 'records', the information is recorded into the 'bookmarks'
2107 When mode is 'records', the information is recorded into the 'bookmarks'
2097 records of the bundle operation. This behavior is suitable for pulling.
2108 records of the bundle operation. This behavior is suitable for pulling.
2098 """
2109 """
2099 changes = bookmarks.binarydecode(inpart)
2110 changes = bookmarks.binarydecode(inpart)
2100
2111
2101 pushkeycompat = op.repo.ui.configbool('server', 'bookmarks-pushkey-compat')
2112 pushkeycompat = op.repo.ui.configbool('server', 'bookmarks-pushkey-compat')
2102 bookmarksmode = op.modes.get('bookmarks', 'apply')
2113 bookmarksmode = op.modes.get('bookmarks', 'apply')
2103
2114
2104 if bookmarksmode == 'apply':
2115 if bookmarksmode == 'apply':
2105 tr = op.gettransaction()
2116 tr = op.gettransaction()
2106 bookstore = op.repo._bookmarks
2117 bookstore = op.repo._bookmarks
2107 if pushkeycompat:
2118 if pushkeycompat:
2108 allhooks = []
2119 allhooks = []
2109 for book, node in changes:
2120 for book, node in changes:
2110 hookargs = tr.hookargs.copy()
2121 hookargs = tr.hookargs.copy()
2111 hookargs['pushkeycompat'] = '1'
2122 hookargs['pushkeycompat'] = '1'
2112 hookargs['namespace'] = 'bookmarks'
2123 hookargs['namespace'] = 'bookmarks'
2113 hookargs['key'] = book
2124 hookargs['key'] = book
2114 hookargs['old'] = nodemod.hex(bookstore.get(book, ''))
2125 hookargs['old'] = nodemod.hex(bookstore.get(book, ''))
2115 hookargs['new'] = nodemod.hex(node if node is not None else '')
2126 hookargs['new'] = nodemod.hex(node if node is not None else '')
2116 allhooks.append(hookargs)
2127 allhooks.append(hookargs)
2117
2128
2118 for hookargs in allhooks:
2129 for hookargs in allhooks:
2119 op.repo.hook('prepushkey', throw=True,
2130 op.repo.hook('prepushkey', throw=True,
2120 **pycompat.strkwargs(hookargs))
2131 **pycompat.strkwargs(hookargs))
2121
2132
2122 bookstore.applychanges(op.repo, op.gettransaction(), changes)
2133 bookstore.applychanges(op.repo, op.gettransaction(), changes)
2123
2134
2124 if pushkeycompat:
2135 if pushkeycompat:
2125 def runhook():
2136 def runhook():
2126 for hookargs in allhooks:
2137 for hookargs in allhooks:
2127 op.repo.hook('pushkey', **pycompat.strkwargs(hookargs))
2138 op.repo.hook('pushkey', **pycompat.strkwargs(hookargs))
2128 op.repo._afterlock(runhook)
2139 op.repo._afterlock(runhook)
2129
2140
2130 elif bookmarksmode == 'records':
2141 elif bookmarksmode == 'records':
2131 for book, node in changes:
2142 for book, node in changes:
2132 record = {'bookmark': book, 'node': node}
2143 record = {'bookmark': book, 'node': node}
2133 op.records.add('bookmarks', record)
2144 op.records.add('bookmarks', record)
2134 else:
2145 else:
2135 raise error.ProgrammingError('unkown bookmark mode: %s' % bookmarksmode)
2146 raise error.ProgrammingError('unkown bookmark mode: %s' % bookmarksmode)
2136
2147
2137 @parthandler('phase-heads')
2148 @parthandler('phase-heads')
2138 def handlephases(op, inpart):
2149 def handlephases(op, inpart):
2139 """apply phases from bundle part to repo"""
2150 """apply phases from bundle part to repo"""
2140 headsbyphase = phases.binarydecode(inpart)
2151 headsbyphase = phases.binarydecode(inpart)
2141 phases.updatephases(op.repo.unfiltered(), op.gettransaction, headsbyphase)
2152 phases.updatephases(op.repo.unfiltered(), op.gettransaction, headsbyphase)
2142
2153
2143 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
2154 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
2144 def handlepushkeyreply(op, inpart):
2155 def handlepushkeyreply(op, inpart):
2145 """retrieve the result of a pushkey request"""
2156 """retrieve the result of a pushkey request"""
2146 ret = int(inpart.params['return'])
2157 ret = int(inpart.params['return'])
2147 partid = int(inpart.params['in-reply-to'])
2158 partid = int(inpart.params['in-reply-to'])
2148 op.records.add('pushkey', {'return': ret}, partid)
2159 op.records.add('pushkey', {'return': ret}, partid)
2149
2160
2150 @parthandler('obsmarkers')
2161 @parthandler('obsmarkers')
2151 def handleobsmarker(op, inpart):
2162 def handleobsmarker(op, inpart):
2152 """add a stream of obsmarkers to the repo"""
2163 """add a stream of obsmarkers to the repo"""
2153 tr = op.gettransaction()
2164 tr = op.gettransaction()
2154 markerdata = inpart.read()
2165 markerdata = inpart.read()
2155 if op.ui.config('experimental', 'obsmarkers-exchange-debug'):
2166 if op.ui.config('experimental', 'obsmarkers-exchange-debug'):
2156 op.ui.write(('obsmarker-exchange: %i bytes received\n')
2167 op.ui.write(('obsmarker-exchange: %i bytes received\n')
2157 % len(markerdata))
2168 % len(markerdata))
2158 # The mergemarkers call will crash if marker creation is not enabled.
2169 # The mergemarkers call will crash if marker creation is not enabled.
2159 # we want to avoid this if the part is advisory.
2170 # we want to avoid this if the part is advisory.
2160 if not inpart.mandatory and op.repo.obsstore.readonly:
2171 if not inpart.mandatory and op.repo.obsstore.readonly:
2161 op.repo.ui.debug('ignoring obsolescence markers, feature not enabled\n')
2172 op.repo.ui.debug('ignoring obsolescence markers, feature not enabled\n')
2162 return
2173 return
2163 new = op.repo.obsstore.mergemarkers(tr, markerdata)
2174 new = op.repo.obsstore.mergemarkers(tr, markerdata)
2164 op.repo.invalidatevolatilesets()
2175 op.repo.invalidatevolatilesets()
2165 if new:
2176 if new:
2166 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
2177 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
2167 op.records.add('obsmarkers', {'new': new})
2178 op.records.add('obsmarkers', {'new': new})
2168 if op.reply is not None:
2179 if op.reply is not None:
2169 rpart = op.reply.newpart('reply:obsmarkers')
2180 rpart = op.reply.newpart('reply:obsmarkers')
2170 rpart.addparam(
2181 rpart.addparam(
2171 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
2182 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
2172 rpart.addparam('new', '%i' % new, mandatory=False)
2183 rpart.addparam('new', '%i' % new, mandatory=False)
2173
2184
2174
2185
2175 @parthandler('reply:obsmarkers', ('new', 'in-reply-to'))
2186 @parthandler('reply:obsmarkers', ('new', 'in-reply-to'))
2176 def handleobsmarkerreply(op, inpart):
2187 def handleobsmarkerreply(op, inpart):
2177 """retrieve the result of a pushkey request"""
2188 """retrieve the result of a pushkey request"""
2178 ret = int(inpart.params['new'])
2189 ret = int(inpart.params['new'])
2179 partid = int(inpart.params['in-reply-to'])
2190 partid = int(inpart.params['in-reply-to'])
2180 op.records.add('obsmarkers', {'new': ret}, partid)
2191 op.records.add('obsmarkers', {'new': ret}, partid)
2181
2192
2182 @parthandler('hgtagsfnodes')
2193 @parthandler('hgtagsfnodes')
2183 def handlehgtagsfnodes(op, inpart):
2194 def handlehgtagsfnodes(op, inpart):
2184 """Applies .hgtags fnodes cache entries to the local repo.
2195 """Applies .hgtags fnodes cache entries to the local repo.
2185
2196
2186 Payload is pairs of 20 byte changeset nodes and filenodes.
2197 Payload is pairs of 20 byte changeset nodes and filenodes.
2187 """
2198 """
2188 # Grab the transaction so we ensure that we have the lock at this point.
2199 # Grab the transaction so we ensure that we have the lock at this point.
2189 if op.ui.configbool('experimental', 'bundle2lazylocking'):
2200 if op.ui.configbool('experimental', 'bundle2lazylocking'):
2190 op.gettransaction()
2201 op.gettransaction()
2191 cache = tags.hgtagsfnodescache(op.repo.unfiltered())
2202 cache = tags.hgtagsfnodescache(op.repo.unfiltered())
2192
2203
2193 count = 0
2204 count = 0
2194 while True:
2205 while True:
2195 node = inpart.read(20)
2206 node = inpart.read(20)
2196 fnode = inpart.read(20)
2207 fnode = inpart.read(20)
2197 if len(node) < 20 or len(fnode) < 20:
2208 if len(node) < 20 or len(fnode) < 20:
2198 op.ui.debug('ignoring incomplete received .hgtags fnodes data\n')
2209 op.ui.debug('ignoring incomplete received .hgtags fnodes data\n')
2199 break
2210 break
2200 cache.setfnode(node, fnode)
2211 cache.setfnode(node, fnode)
2201 count += 1
2212 count += 1
2202
2213
2203 cache.write()
2214 cache.write()
2204 op.ui.debug('applied %i hgtags fnodes cache entries\n' % count)
2215 op.ui.debug('applied %i hgtags fnodes cache entries\n' % count)
2205
2216
2206 rbcstruct = struct.Struct('>III')
2217 rbcstruct = struct.Struct('>III')
2207
2218
2208 @parthandler('cache:rev-branch-cache')
2219 @parthandler('cache:rev-branch-cache')
2209 def handlerbc(op, inpart):
2220 def handlerbc(op, inpart):
2210 """receive a rev-branch-cache payload and update the local cache
2221 """receive a rev-branch-cache payload and update the local cache
2211
2222
2212 The payload is a series of data related to each branch
2223 The payload is a series of data related to each branch
2213
2224
2214 1) branch name length
2225 1) branch name length
2215 2) number of open heads
2226 2) number of open heads
2216 3) number of closed heads
2227 3) number of closed heads
2217 4) open heads nodes
2228 4) open heads nodes
2218 5) closed heads nodes
2229 5) closed heads nodes
2219 """
2230 """
2220 total = 0
2231 total = 0
2221 rawheader = inpart.read(rbcstruct.size)
2232 rawheader = inpart.read(rbcstruct.size)
2222 cache = op.repo.revbranchcache()
2233 cache = op.repo.revbranchcache()
2223 cl = op.repo.unfiltered().changelog
2234 cl = op.repo.unfiltered().changelog
2224 while rawheader:
2235 while rawheader:
2225 header = rbcstruct.unpack(rawheader)
2236 header = rbcstruct.unpack(rawheader)
2226 total += header[1] + header[2]
2237 total += header[1] + header[2]
2227 utf8branch = inpart.read(header[0])
2238 utf8branch = inpart.read(header[0])
2228 branch = encoding.tolocal(utf8branch)
2239 branch = encoding.tolocal(utf8branch)
2229 for x in pycompat.xrange(header[1]):
2240 for x in pycompat.xrange(header[1]):
2230 node = inpart.read(20)
2241 node = inpart.read(20)
2231 rev = cl.rev(node)
2242 rev = cl.rev(node)
2232 cache.setdata(branch, rev, node, False)
2243 cache.setdata(branch, rev, node, False)
2233 for x in pycompat.xrange(header[2]):
2244 for x in pycompat.xrange(header[2]):
2234 node = inpart.read(20)
2245 node = inpart.read(20)
2235 rev = cl.rev(node)
2246 rev = cl.rev(node)
2236 cache.setdata(branch, rev, node, True)
2247 cache.setdata(branch, rev, node, True)
2237 rawheader = inpart.read(rbcstruct.size)
2248 rawheader = inpart.read(rbcstruct.size)
2238 cache.write()
2249 cache.write()
2239
2250
2240 @parthandler('pushvars')
2251 @parthandler('pushvars')
2241 def bundle2getvars(op, part):
2252 def bundle2getvars(op, part):
2242 '''unbundle a bundle2 containing shellvars on the server'''
2253 '''unbundle a bundle2 containing shellvars on the server'''
2243 # An option to disable unbundling on server-side for security reasons
2254 # An option to disable unbundling on server-side for security reasons
2244 if op.ui.configbool('push', 'pushvars.server'):
2255 if op.ui.configbool('push', 'pushvars.server'):
2245 hookargs = {}
2256 hookargs = {}
2246 for key, value in part.advisoryparams:
2257 for key, value in part.advisoryparams:
2247 key = key.upper()
2258 key = key.upper()
2248 # We want pushed variables to have USERVAR_ prepended so we know
2259 # We want pushed variables to have USERVAR_ prepended so we know
2249 # they came from the --pushvar flag.
2260 # they came from the --pushvar flag.
2250 key = "USERVAR_" + key
2261 key = "USERVAR_" + key
2251 hookargs[key] = value
2262 hookargs[key] = value
2252 op.addhookargs(hookargs)
2263 op.addhookargs(hookargs)
2253
2264
2254 @parthandler('stream2', ('requirements', 'filecount', 'bytecount'))
2265 @parthandler('stream2', ('requirements', 'filecount', 'bytecount'))
2255 def handlestreamv2bundle(op, part):
2266 def handlestreamv2bundle(op, part):
2256
2267
2257 requirements = urlreq.unquote(part.params['requirements']).split(',')
2268 requirements = urlreq.unquote(part.params['requirements']).split(',')
2258 filecount = int(part.params['filecount'])
2269 filecount = int(part.params['filecount'])
2259 bytecount = int(part.params['bytecount'])
2270 bytecount = int(part.params['bytecount'])
2260
2271
2261 repo = op.repo
2272 repo = op.repo
2262 if len(repo):
2273 if len(repo):
2263 msg = _('cannot apply stream clone to non empty repository')
2274 msg = _('cannot apply stream clone to non empty repository')
2264 raise error.Abort(msg)
2275 raise error.Abort(msg)
2265
2276
2266 repo.ui.debug('applying stream bundle\n')
2277 repo.ui.debug('applying stream bundle\n')
2267 streamclone.applybundlev2(repo, part, filecount, bytecount,
2278 streamclone.applybundlev2(repo, part, filecount, bytecount,
2268 requirements)
2279 requirements)
2269
2280
2270 def widen_bundle(repo, diffmatcher, common, known, cgversion, ellipses):
2281 def widen_bundle(repo, diffmatcher, common, known, cgversion, ellipses):
2271 """generates bundle2 for widening a narrow clone
2282 """generates bundle2 for widening a narrow clone
2272
2283
2273 repo is the localrepository instance
2284 repo is the localrepository instance
2274 diffmatcher is a differencemacther of '(newincludes, newexcludes) -
2285 diffmatcher is a differencemacther of '(newincludes, newexcludes) -
2275 (oldincludes, oldexcludes)'
2286 (oldincludes, oldexcludes)'
2276 common is set of common heads between server and client
2287 common is set of common heads between server and client
2277 known is a set of revs known on the client side (used in ellipses)
2288 known is a set of revs known on the client side (used in ellipses)
2278 cgversion is the changegroup version to send
2289 cgversion is the changegroup version to send
2279 ellipses is boolean value telling whether to send ellipses data or not
2290 ellipses is boolean value telling whether to send ellipses data or not
2280
2291
2281 returns bundle2 of the data required for extending
2292 returns bundle2 of the data required for extending
2282 """
2293 """
2283 bundler = bundle20(repo.ui)
2294 bundler = bundle20(repo.ui)
2284 commonnodes = set()
2295 commonnodes = set()
2285 cl = repo.changelog
2296 cl = repo.changelog
2286 for r in repo.revs("::%ln", common):
2297 for r in repo.revs("::%ln", common):
2287 commonnodes.add(cl.node(r))
2298 commonnodes.add(cl.node(r))
2288 if commonnodes:
2299 if commonnodes:
2289 # XXX: we should only send the filelogs (and treemanifest). user
2300 # XXX: we should only send the filelogs (and treemanifest). user
2290 # already has the changelog and manifest
2301 # already has the changelog and manifest
2291 packer = changegroup.getbundler(cgversion, repo,
2302 packer = changegroup.getbundler(cgversion, repo,
2292 filematcher=diffmatcher,
2303 filematcher=diffmatcher,
2293 fullnodes=commonnodes)
2304 fullnodes=commonnodes)
2294 cgdata = packer.generate(set([nodemod.nullid]), list(commonnodes),
2305 cgdata = packer.generate(set([nodemod.nullid]), list(commonnodes),
2295 False, 'narrow_widen', changelog=False)
2306 False, 'narrow_widen', changelog=False)
2296
2307
2297 part = bundler.newpart('changegroup', data=cgdata)
2308 part = bundler.newpart('changegroup', data=cgdata)
2298 part.addparam('version', cgversion)
2309 part.addparam('version', cgversion)
2299 if 'treemanifest' in repo.requirements:
2310 if 'treemanifest' in repo.requirements:
2300 part.addparam('treemanifest', '1')
2311 part.addparam('treemanifest', '1')
2301
2312
2302 return bundler
2313 return bundler
@@ -1,1424 +1,1427
1 # configitems.py - centralized declaration of configuration option
1 # configitems.py - centralized declaration of configuration option
2 #
2 #
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import functools
10 import functools
11 import re
11 import re
12
12
13 from . import (
13 from . import (
14 encoding,
14 encoding,
15 error,
15 error,
16 )
16 )
17
17
18 def loadconfigtable(ui, extname, configtable):
18 def loadconfigtable(ui, extname, configtable):
19 """update config item known to the ui with the extension ones"""
19 """update config item known to the ui with the extension ones"""
20 for section, items in sorted(configtable.items()):
20 for section, items in sorted(configtable.items()):
21 knownitems = ui._knownconfig.setdefault(section, itemregister())
21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 knownkeys = set(knownitems)
22 knownkeys = set(knownitems)
23 newkeys = set(items)
23 newkeys = set(items)
24 for key in sorted(knownkeys & newkeys):
24 for key in sorted(knownkeys & newkeys):
25 msg = "extension '%s' overwrite config item '%s.%s'"
25 msg = "extension '%s' overwrite config item '%s.%s'"
26 msg %= (extname, section, key)
26 msg %= (extname, section, key)
27 ui.develwarn(msg, config='warn-config')
27 ui.develwarn(msg, config='warn-config')
28
28
29 knownitems.update(items)
29 knownitems.update(items)
30
30
31 class configitem(object):
31 class configitem(object):
32 """represent a known config item
32 """represent a known config item
33
33
34 :section: the official config section where to find this item,
34 :section: the official config section where to find this item,
35 :name: the official name within the section,
35 :name: the official name within the section,
36 :default: default value for this item,
36 :default: default value for this item,
37 :alias: optional list of tuples as alternatives,
37 :alias: optional list of tuples as alternatives,
38 :generic: this is a generic definition, match name using regular expression.
38 :generic: this is a generic definition, match name using regular expression.
39 """
39 """
40
40
41 def __init__(self, section, name, default=None, alias=(),
41 def __init__(self, section, name, default=None, alias=(),
42 generic=False, priority=0):
42 generic=False, priority=0):
43 self.section = section
43 self.section = section
44 self.name = name
44 self.name = name
45 self.default = default
45 self.default = default
46 self.alias = list(alias)
46 self.alias = list(alias)
47 self.generic = generic
47 self.generic = generic
48 self.priority = priority
48 self.priority = priority
49 self._re = None
49 self._re = None
50 if generic:
50 if generic:
51 self._re = re.compile(self.name)
51 self._re = re.compile(self.name)
52
52
53 class itemregister(dict):
53 class itemregister(dict):
54 """A specialized dictionary that can handle wild-card selection"""
54 """A specialized dictionary that can handle wild-card selection"""
55
55
56 def __init__(self):
56 def __init__(self):
57 super(itemregister, self).__init__()
57 super(itemregister, self).__init__()
58 self._generics = set()
58 self._generics = set()
59
59
60 def update(self, other):
60 def update(self, other):
61 super(itemregister, self).update(other)
61 super(itemregister, self).update(other)
62 self._generics.update(other._generics)
62 self._generics.update(other._generics)
63
63
64 def __setitem__(self, key, item):
64 def __setitem__(self, key, item):
65 super(itemregister, self).__setitem__(key, item)
65 super(itemregister, self).__setitem__(key, item)
66 if item.generic:
66 if item.generic:
67 self._generics.add(item)
67 self._generics.add(item)
68
68
69 def get(self, key):
69 def get(self, key):
70 baseitem = super(itemregister, self).get(key)
70 baseitem = super(itemregister, self).get(key)
71 if baseitem is not None and not baseitem.generic:
71 if baseitem is not None and not baseitem.generic:
72 return baseitem
72 return baseitem
73
73
74 # search for a matching generic item
74 # search for a matching generic item
75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
76 for item in generics:
76 for item in generics:
77 # we use 'match' instead of 'search' to make the matching simpler
77 # we use 'match' instead of 'search' to make the matching simpler
78 # for people unfamiliar with regular expression. Having the match
78 # for people unfamiliar with regular expression. Having the match
79 # rooted to the start of the string will produce less surprising
79 # rooted to the start of the string will produce less surprising
80 # result for user writing simple regex for sub-attribute.
80 # result for user writing simple regex for sub-attribute.
81 #
81 #
82 # For example using "color\..*" match produces an unsurprising
82 # For example using "color\..*" match produces an unsurprising
83 # result, while using search could suddenly match apparently
83 # result, while using search could suddenly match apparently
84 # unrelated configuration that happens to contains "color."
84 # unrelated configuration that happens to contains "color."
85 # anywhere. This is a tradeoff where we favor requiring ".*" on
85 # anywhere. This is a tradeoff where we favor requiring ".*" on
86 # some match to avoid the need to prefix most pattern with "^".
86 # some match to avoid the need to prefix most pattern with "^".
87 # The "^" seems more error prone.
87 # The "^" seems more error prone.
88 if item._re.match(key):
88 if item._re.match(key):
89 return item
89 return item
90
90
91 return None
91 return None
92
92
93 coreitems = {}
93 coreitems = {}
94
94
95 def _register(configtable, *args, **kwargs):
95 def _register(configtable, *args, **kwargs):
96 item = configitem(*args, **kwargs)
96 item = configitem(*args, **kwargs)
97 section = configtable.setdefault(item.section, itemregister())
97 section = configtable.setdefault(item.section, itemregister())
98 if item.name in section:
98 if item.name in section:
99 msg = "duplicated config item registration for '%s.%s'"
99 msg = "duplicated config item registration for '%s.%s'"
100 raise error.ProgrammingError(msg % (item.section, item.name))
100 raise error.ProgrammingError(msg % (item.section, item.name))
101 section[item.name] = item
101 section[item.name] = item
102
102
103 # special value for case where the default is derived from other values
103 # special value for case where the default is derived from other values
104 dynamicdefault = object()
104 dynamicdefault = object()
105
105
106 # Registering actual config items
106 # Registering actual config items
107
107
108 def getitemregister(configtable):
108 def getitemregister(configtable):
109 f = functools.partial(_register, configtable)
109 f = functools.partial(_register, configtable)
110 # export pseudo enum as configitem.*
110 # export pseudo enum as configitem.*
111 f.dynamicdefault = dynamicdefault
111 f.dynamicdefault = dynamicdefault
112 return f
112 return f
113
113
114 coreconfigitem = getitemregister(coreitems)
114 coreconfigitem = getitemregister(coreitems)
115
115
116 coreconfigitem('alias', '.*',
116 coreconfigitem('alias', '.*',
117 default=dynamicdefault,
117 default=dynamicdefault,
118 generic=True,
118 generic=True,
119 )
119 )
120 coreconfigitem('annotate', 'nodates',
120 coreconfigitem('annotate', 'nodates',
121 default=False,
121 default=False,
122 )
122 )
123 coreconfigitem('annotate', 'showfunc',
123 coreconfigitem('annotate', 'showfunc',
124 default=False,
124 default=False,
125 )
125 )
126 coreconfigitem('annotate', 'unified',
126 coreconfigitem('annotate', 'unified',
127 default=None,
127 default=None,
128 )
128 )
129 coreconfigitem('annotate', 'git',
129 coreconfigitem('annotate', 'git',
130 default=False,
130 default=False,
131 )
131 )
132 coreconfigitem('annotate', 'ignorews',
132 coreconfigitem('annotate', 'ignorews',
133 default=False,
133 default=False,
134 )
134 )
135 coreconfigitem('annotate', 'ignorewsamount',
135 coreconfigitem('annotate', 'ignorewsamount',
136 default=False,
136 default=False,
137 )
137 )
138 coreconfigitem('annotate', 'ignoreblanklines',
138 coreconfigitem('annotate', 'ignoreblanklines',
139 default=False,
139 default=False,
140 )
140 )
141 coreconfigitem('annotate', 'ignorewseol',
141 coreconfigitem('annotate', 'ignorewseol',
142 default=False,
142 default=False,
143 )
143 )
144 coreconfigitem('annotate', 'nobinary',
144 coreconfigitem('annotate', 'nobinary',
145 default=False,
145 default=False,
146 )
146 )
147 coreconfigitem('annotate', 'noprefix',
147 coreconfigitem('annotate', 'noprefix',
148 default=False,
148 default=False,
149 )
149 )
150 coreconfigitem('annotate', 'word-diff',
150 coreconfigitem('annotate', 'word-diff',
151 default=False,
151 default=False,
152 )
152 )
153 coreconfigitem('auth', 'cookiefile',
153 coreconfigitem('auth', 'cookiefile',
154 default=None,
154 default=None,
155 )
155 )
156 # bookmarks.pushing: internal hack for discovery
156 # bookmarks.pushing: internal hack for discovery
157 coreconfigitem('bookmarks', 'pushing',
157 coreconfigitem('bookmarks', 'pushing',
158 default=list,
158 default=list,
159 )
159 )
160 # bundle.mainreporoot: internal hack for bundlerepo
160 # bundle.mainreporoot: internal hack for bundlerepo
161 coreconfigitem('bundle', 'mainreporoot',
161 coreconfigitem('bundle', 'mainreporoot',
162 default='',
162 default='',
163 )
163 )
164 coreconfigitem('censor', 'policy',
164 coreconfigitem('censor', 'policy',
165 default='abort',
165 default='abort',
166 )
166 )
167 coreconfigitem('chgserver', 'idletimeout',
167 coreconfigitem('chgserver', 'idletimeout',
168 default=3600,
168 default=3600,
169 )
169 )
170 coreconfigitem('chgserver', 'skiphash',
170 coreconfigitem('chgserver', 'skiphash',
171 default=False,
171 default=False,
172 )
172 )
173 coreconfigitem('cmdserver', 'log',
173 coreconfigitem('cmdserver', 'log',
174 default=None,
174 default=None,
175 )
175 )
176 coreconfigitem('color', '.*',
176 coreconfigitem('color', '.*',
177 default=None,
177 default=None,
178 generic=True,
178 generic=True,
179 )
179 )
180 coreconfigitem('color', 'mode',
180 coreconfigitem('color', 'mode',
181 default='auto',
181 default='auto',
182 )
182 )
183 coreconfigitem('color', 'pagermode',
183 coreconfigitem('color', 'pagermode',
184 default=dynamicdefault,
184 default=dynamicdefault,
185 )
185 )
186 coreconfigitem('commands', 'grep.all-files',
186 coreconfigitem('commands', 'grep.all-files',
187 default=False,
187 default=False,
188 )
188 )
189 coreconfigitem('commands', 'resolve.confirm',
189 coreconfigitem('commands', 'resolve.confirm',
190 default=False,
190 default=False,
191 )
191 )
192 coreconfigitem('commands', 'resolve.explicit-re-merge',
192 coreconfigitem('commands', 'resolve.explicit-re-merge',
193 default=False,
193 default=False,
194 )
194 )
195 coreconfigitem('commands', 'resolve.mark-check',
195 coreconfigitem('commands', 'resolve.mark-check',
196 default='none',
196 default='none',
197 )
197 )
198 coreconfigitem('commands', 'show.aliasprefix',
198 coreconfigitem('commands', 'show.aliasprefix',
199 default=list,
199 default=list,
200 )
200 )
201 coreconfigitem('commands', 'status.relative',
201 coreconfigitem('commands', 'status.relative',
202 default=False,
202 default=False,
203 )
203 )
204 coreconfigitem('commands', 'status.skipstates',
204 coreconfigitem('commands', 'status.skipstates',
205 default=[],
205 default=[],
206 )
206 )
207 coreconfigitem('commands', 'status.terse',
207 coreconfigitem('commands', 'status.terse',
208 default='',
208 default='',
209 )
209 )
210 coreconfigitem('commands', 'status.verbose',
210 coreconfigitem('commands', 'status.verbose',
211 default=False,
211 default=False,
212 )
212 )
213 coreconfigitem('commands', 'update.check',
213 coreconfigitem('commands', 'update.check',
214 default=None,
214 default=None,
215 )
215 )
216 coreconfigitem('commands', 'update.requiredest',
216 coreconfigitem('commands', 'update.requiredest',
217 default=False,
217 default=False,
218 )
218 )
219 coreconfigitem('committemplate', '.*',
219 coreconfigitem('committemplate', '.*',
220 default=None,
220 default=None,
221 generic=True,
221 generic=True,
222 )
222 )
223 coreconfigitem('convert', 'bzr.saverev',
223 coreconfigitem('convert', 'bzr.saverev',
224 default=True,
224 default=True,
225 )
225 )
226 coreconfigitem('convert', 'cvsps.cache',
226 coreconfigitem('convert', 'cvsps.cache',
227 default=True,
227 default=True,
228 )
228 )
229 coreconfigitem('convert', 'cvsps.fuzz',
229 coreconfigitem('convert', 'cvsps.fuzz',
230 default=60,
230 default=60,
231 )
231 )
232 coreconfigitem('convert', 'cvsps.logencoding',
232 coreconfigitem('convert', 'cvsps.logencoding',
233 default=None,
233 default=None,
234 )
234 )
235 coreconfigitem('convert', 'cvsps.mergefrom',
235 coreconfigitem('convert', 'cvsps.mergefrom',
236 default=None,
236 default=None,
237 )
237 )
238 coreconfigitem('convert', 'cvsps.mergeto',
238 coreconfigitem('convert', 'cvsps.mergeto',
239 default=None,
239 default=None,
240 )
240 )
241 coreconfigitem('convert', 'git.committeractions',
241 coreconfigitem('convert', 'git.committeractions',
242 default=lambda: ['messagedifferent'],
242 default=lambda: ['messagedifferent'],
243 )
243 )
244 coreconfigitem('convert', 'git.extrakeys',
244 coreconfigitem('convert', 'git.extrakeys',
245 default=list,
245 default=list,
246 )
246 )
247 coreconfigitem('convert', 'git.findcopiesharder',
247 coreconfigitem('convert', 'git.findcopiesharder',
248 default=False,
248 default=False,
249 )
249 )
250 coreconfigitem('convert', 'git.remoteprefix',
250 coreconfigitem('convert', 'git.remoteprefix',
251 default='remote',
251 default='remote',
252 )
252 )
253 coreconfigitem('convert', 'git.renamelimit',
253 coreconfigitem('convert', 'git.renamelimit',
254 default=400,
254 default=400,
255 )
255 )
256 coreconfigitem('convert', 'git.saverev',
256 coreconfigitem('convert', 'git.saverev',
257 default=True,
257 default=True,
258 )
258 )
259 coreconfigitem('convert', 'git.similarity',
259 coreconfigitem('convert', 'git.similarity',
260 default=50,
260 default=50,
261 )
261 )
262 coreconfigitem('convert', 'git.skipsubmodules',
262 coreconfigitem('convert', 'git.skipsubmodules',
263 default=False,
263 default=False,
264 )
264 )
265 coreconfigitem('convert', 'hg.clonebranches',
265 coreconfigitem('convert', 'hg.clonebranches',
266 default=False,
266 default=False,
267 )
267 )
268 coreconfigitem('convert', 'hg.ignoreerrors',
268 coreconfigitem('convert', 'hg.ignoreerrors',
269 default=False,
269 default=False,
270 )
270 )
271 coreconfigitem('convert', 'hg.revs',
271 coreconfigitem('convert', 'hg.revs',
272 default=None,
272 default=None,
273 )
273 )
274 coreconfigitem('convert', 'hg.saverev',
274 coreconfigitem('convert', 'hg.saverev',
275 default=False,
275 default=False,
276 )
276 )
277 coreconfigitem('convert', 'hg.sourcename',
277 coreconfigitem('convert', 'hg.sourcename',
278 default=None,
278 default=None,
279 )
279 )
280 coreconfigitem('convert', 'hg.startrev',
280 coreconfigitem('convert', 'hg.startrev',
281 default=None,
281 default=None,
282 )
282 )
283 coreconfigitem('convert', 'hg.tagsbranch',
283 coreconfigitem('convert', 'hg.tagsbranch',
284 default='default',
284 default='default',
285 )
285 )
286 coreconfigitem('convert', 'hg.usebranchnames',
286 coreconfigitem('convert', 'hg.usebranchnames',
287 default=True,
287 default=True,
288 )
288 )
289 coreconfigitem('convert', 'ignoreancestorcheck',
289 coreconfigitem('convert', 'ignoreancestorcheck',
290 default=False,
290 default=False,
291 )
291 )
292 coreconfigitem('convert', 'localtimezone',
292 coreconfigitem('convert', 'localtimezone',
293 default=False,
293 default=False,
294 )
294 )
295 coreconfigitem('convert', 'p4.encoding',
295 coreconfigitem('convert', 'p4.encoding',
296 default=dynamicdefault,
296 default=dynamicdefault,
297 )
297 )
298 coreconfigitem('convert', 'p4.startrev',
298 coreconfigitem('convert', 'p4.startrev',
299 default=0,
299 default=0,
300 )
300 )
301 coreconfigitem('convert', 'skiptags',
301 coreconfigitem('convert', 'skiptags',
302 default=False,
302 default=False,
303 )
303 )
304 coreconfigitem('convert', 'svn.debugsvnlog',
304 coreconfigitem('convert', 'svn.debugsvnlog',
305 default=True,
305 default=True,
306 )
306 )
307 coreconfigitem('convert', 'svn.trunk',
307 coreconfigitem('convert', 'svn.trunk',
308 default=None,
308 default=None,
309 )
309 )
310 coreconfigitem('convert', 'svn.tags',
310 coreconfigitem('convert', 'svn.tags',
311 default=None,
311 default=None,
312 )
312 )
313 coreconfigitem('convert', 'svn.branches',
313 coreconfigitem('convert', 'svn.branches',
314 default=None,
314 default=None,
315 )
315 )
316 coreconfigitem('convert', 'svn.startrev',
316 coreconfigitem('convert', 'svn.startrev',
317 default=0,
317 default=0,
318 )
318 )
319 coreconfigitem('debug', 'dirstate.delaywrite',
319 coreconfigitem('debug', 'dirstate.delaywrite',
320 default=0,
320 default=0,
321 )
321 )
322 coreconfigitem('defaults', '.*',
322 coreconfigitem('defaults', '.*',
323 default=None,
323 default=None,
324 generic=True,
324 generic=True,
325 )
325 )
326 coreconfigitem('devel', 'all-warnings',
326 coreconfigitem('devel', 'all-warnings',
327 default=False,
327 default=False,
328 )
328 )
329 coreconfigitem('devel', 'bundle2.debug',
329 coreconfigitem('devel', 'bundle2.debug',
330 default=False,
330 default=False,
331 )
331 )
332 coreconfigitem('devel', 'cache-vfs',
332 coreconfigitem('devel', 'cache-vfs',
333 default=None,
333 default=None,
334 )
334 )
335 coreconfigitem('devel', 'check-locks',
335 coreconfigitem('devel', 'check-locks',
336 default=False,
336 default=False,
337 )
337 )
338 coreconfigitem('devel', 'check-relroot',
338 coreconfigitem('devel', 'check-relroot',
339 default=False,
339 default=False,
340 )
340 )
341 coreconfigitem('devel', 'default-date',
341 coreconfigitem('devel', 'default-date',
342 default=None,
342 default=None,
343 )
343 )
344 coreconfigitem('devel', 'deprec-warn',
344 coreconfigitem('devel', 'deprec-warn',
345 default=False,
345 default=False,
346 )
346 )
347 coreconfigitem('devel', 'disableloaddefaultcerts',
347 coreconfigitem('devel', 'disableloaddefaultcerts',
348 default=False,
348 default=False,
349 )
349 )
350 coreconfigitem('devel', 'warn-empty-changegroup',
350 coreconfigitem('devel', 'warn-empty-changegroup',
351 default=False,
351 default=False,
352 )
352 )
353 coreconfigitem('devel', 'legacy.exchange',
353 coreconfigitem('devel', 'legacy.exchange',
354 default=list,
354 default=list,
355 )
355 )
356 coreconfigitem('devel', 'servercafile',
356 coreconfigitem('devel', 'servercafile',
357 default='',
357 default='',
358 )
358 )
359 coreconfigitem('devel', 'serverexactprotocol',
359 coreconfigitem('devel', 'serverexactprotocol',
360 default='',
360 default='',
361 )
361 )
362 coreconfigitem('devel', 'serverrequirecert',
362 coreconfigitem('devel', 'serverrequirecert',
363 default=False,
363 default=False,
364 )
364 )
365 coreconfigitem('devel', 'strip-obsmarkers',
365 coreconfigitem('devel', 'strip-obsmarkers',
366 default=True,
366 default=True,
367 )
367 )
368 coreconfigitem('devel', 'warn-config',
368 coreconfigitem('devel', 'warn-config',
369 default=None,
369 default=None,
370 )
370 )
371 coreconfigitem('devel', 'warn-config-default',
371 coreconfigitem('devel', 'warn-config-default',
372 default=None,
372 default=None,
373 )
373 )
374 coreconfigitem('devel', 'user.obsmarker',
374 coreconfigitem('devel', 'user.obsmarker',
375 default=None,
375 default=None,
376 )
376 )
377 coreconfigitem('devel', 'warn-config-unknown',
377 coreconfigitem('devel', 'warn-config-unknown',
378 default=None,
378 default=None,
379 )
379 )
380 coreconfigitem('devel', 'debug.copies',
380 coreconfigitem('devel', 'debug.copies',
381 default=False,
381 default=False,
382 )
382 )
383 coreconfigitem('devel', 'debug.extensions',
383 coreconfigitem('devel', 'debug.extensions',
384 default=False,
384 default=False,
385 )
385 )
386 coreconfigitem('devel', 'debug.peer-request',
386 coreconfigitem('devel', 'debug.peer-request',
387 default=False,
387 default=False,
388 )
388 )
389 coreconfigitem('diff', 'nodates',
389 coreconfigitem('diff', 'nodates',
390 default=False,
390 default=False,
391 )
391 )
392 coreconfigitem('diff', 'showfunc',
392 coreconfigitem('diff', 'showfunc',
393 default=False,
393 default=False,
394 )
394 )
395 coreconfigitem('diff', 'unified',
395 coreconfigitem('diff', 'unified',
396 default=None,
396 default=None,
397 )
397 )
398 coreconfigitem('diff', 'git',
398 coreconfigitem('diff', 'git',
399 default=False,
399 default=False,
400 )
400 )
401 coreconfigitem('diff', 'ignorews',
401 coreconfigitem('diff', 'ignorews',
402 default=False,
402 default=False,
403 )
403 )
404 coreconfigitem('diff', 'ignorewsamount',
404 coreconfigitem('diff', 'ignorewsamount',
405 default=False,
405 default=False,
406 )
406 )
407 coreconfigitem('diff', 'ignoreblanklines',
407 coreconfigitem('diff', 'ignoreblanklines',
408 default=False,
408 default=False,
409 )
409 )
410 coreconfigitem('diff', 'ignorewseol',
410 coreconfigitem('diff', 'ignorewseol',
411 default=False,
411 default=False,
412 )
412 )
413 coreconfigitem('diff', 'nobinary',
413 coreconfigitem('diff', 'nobinary',
414 default=False,
414 default=False,
415 )
415 )
416 coreconfigitem('diff', 'noprefix',
416 coreconfigitem('diff', 'noprefix',
417 default=False,
417 default=False,
418 )
418 )
419 coreconfigitem('diff', 'word-diff',
419 coreconfigitem('diff', 'word-diff',
420 default=False,
420 default=False,
421 )
421 )
422 coreconfigitem('email', 'bcc',
422 coreconfigitem('email', 'bcc',
423 default=None,
423 default=None,
424 )
424 )
425 coreconfigitem('email', 'cc',
425 coreconfigitem('email', 'cc',
426 default=None,
426 default=None,
427 )
427 )
428 coreconfigitem('email', 'charsets',
428 coreconfigitem('email', 'charsets',
429 default=list,
429 default=list,
430 )
430 )
431 coreconfigitem('email', 'from',
431 coreconfigitem('email', 'from',
432 default=None,
432 default=None,
433 )
433 )
434 coreconfigitem('email', 'method',
434 coreconfigitem('email', 'method',
435 default='smtp',
435 default='smtp',
436 )
436 )
437 coreconfigitem('email', 'reply-to',
437 coreconfigitem('email', 'reply-to',
438 default=None,
438 default=None,
439 )
439 )
440 coreconfigitem('email', 'to',
440 coreconfigitem('email', 'to',
441 default=None,
441 default=None,
442 )
442 )
443 coreconfigitem('experimental', 'archivemetatemplate',
443 coreconfigitem('experimental', 'archivemetatemplate',
444 default=dynamicdefault,
444 default=dynamicdefault,
445 )
445 )
446 coreconfigitem('experimental', 'bundle-phases',
446 coreconfigitem('experimental', 'bundle-phases',
447 default=False,
447 default=False,
448 )
448 )
449 coreconfigitem('experimental', 'bundle2-advertise',
449 coreconfigitem('experimental', 'bundle2-advertise',
450 default=True,
450 default=True,
451 )
451 )
452 coreconfigitem('experimental', 'bundle2-output-capture',
452 coreconfigitem('experimental', 'bundle2-output-capture',
453 default=False,
453 default=False,
454 )
454 )
455 coreconfigitem('experimental', 'bundle2.pushback',
455 coreconfigitem('experimental', 'bundle2.pushback',
456 default=False,
456 default=False,
457 )
457 )
458 coreconfigitem('experimental', 'bundle2lazylocking',
458 coreconfigitem('experimental', 'bundle2lazylocking',
459 default=False,
459 default=False,
460 )
460 )
461 coreconfigitem('experimental', 'bundlecomplevel',
461 coreconfigitem('experimental', 'bundlecomplevel',
462 default=None,
462 default=None,
463 )
463 )
464 coreconfigitem('experimental', 'bundlecomplevel.bzip2',
464 coreconfigitem('experimental', 'bundlecomplevel.bzip2',
465 default=None,
465 default=None,
466 )
466 )
467 coreconfigitem('experimental', 'bundlecomplevel.gzip',
467 coreconfigitem('experimental', 'bundlecomplevel.gzip',
468 default=None,
468 default=None,
469 )
469 )
470 coreconfigitem('experimental', 'bundlecomplevel.none',
470 coreconfigitem('experimental', 'bundlecomplevel.none',
471 default=None,
471 default=None,
472 )
472 )
473 coreconfigitem('experimental', 'bundlecomplevel.zstd',
473 coreconfigitem('experimental', 'bundlecomplevel.zstd',
474 default=None,
474 default=None,
475 )
475 )
476 coreconfigitem('experimental', 'changegroup3',
476 coreconfigitem('experimental', 'changegroup3',
477 default=False,
477 default=False,
478 )
478 )
479 coreconfigitem('experimental', 'clientcompressionengines',
479 coreconfigitem('experimental', 'clientcompressionengines',
480 default=list,
480 default=list,
481 )
481 )
482 coreconfigitem('experimental', 'copytrace',
482 coreconfigitem('experimental', 'copytrace',
483 default='on',
483 default='on',
484 )
484 )
485 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
485 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
486 default=100,
486 default=100,
487 )
487 )
488 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
488 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
489 default=100,
489 default=100,
490 )
490 )
491 coreconfigitem('experimental', 'crecordtest',
491 coreconfigitem('experimental', 'crecordtest',
492 default=None,
492 default=None,
493 )
493 )
494 coreconfigitem('experimental', 'directaccess',
494 coreconfigitem('experimental', 'directaccess',
495 default=False,
495 default=False,
496 )
496 )
497 coreconfigitem('experimental', 'directaccess.revnums',
497 coreconfigitem('experimental', 'directaccess.revnums',
498 default=False,
498 default=False,
499 )
499 )
500 coreconfigitem('experimental', 'editortmpinhg',
500 coreconfigitem('experimental', 'editortmpinhg',
501 default=False,
501 default=False,
502 )
502 )
503 coreconfigitem('experimental', 'evolution',
503 coreconfigitem('experimental', 'evolution',
504 default=list,
504 default=list,
505 )
505 )
506 coreconfigitem('experimental', 'evolution.allowdivergence',
506 coreconfigitem('experimental', 'evolution.allowdivergence',
507 default=False,
507 default=False,
508 alias=[('experimental', 'allowdivergence')]
508 alias=[('experimental', 'allowdivergence')]
509 )
509 )
510 coreconfigitem('experimental', 'evolution.allowunstable',
510 coreconfigitem('experimental', 'evolution.allowunstable',
511 default=None,
511 default=None,
512 )
512 )
513 coreconfigitem('experimental', 'evolution.createmarkers',
513 coreconfigitem('experimental', 'evolution.createmarkers',
514 default=None,
514 default=None,
515 )
515 )
516 coreconfigitem('experimental', 'evolution.effect-flags',
516 coreconfigitem('experimental', 'evolution.effect-flags',
517 default=True,
517 default=True,
518 alias=[('experimental', 'effect-flags')]
518 alias=[('experimental', 'effect-flags')]
519 )
519 )
520 coreconfigitem('experimental', 'evolution.exchange',
520 coreconfigitem('experimental', 'evolution.exchange',
521 default=None,
521 default=None,
522 )
522 )
523 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
523 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
524 default=False,
524 default=False,
525 )
525 )
526 coreconfigitem('experimental', 'evolution.report-instabilities',
526 coreconfigitem('experimental', 'evolution.report-instabilities',
527 default=True,
527 default=True,
528 )
528 )
529 coreconfigitem('experimental', 'evolution.track-operation',
529 coreconfigitem('experimental', 'evolution.track-operation',
530 default=True,
530 default=True,
531 )
531 )
532 coreconfigitem('experimental', 'maxdeltachainspan',
532 coreconfigitem('experimental', 'maxdeltachainspan',
533 default=-1,
533 default=-1,
534 )
534 )
535 coreconfigitem('experimental', 'mergetempdirprefix',
535 coreconfigitem('experimental', 'mergetempdirprefix',
536 default=None,
536 default=None,
537 )
537 )
538 coreconfigitem('experimental', 'mmapindexthreshold',
538 coreconfigitem('experimental', 'mmapindexthreshold',
539 default=None,
539 default=None,
540 )
540 )
541 coreconfigitem('experimental', 'narrow',
541 coreconfigitem('experimental', 'narrow',
542 default=False,
542 default=False,
543 )
543 )
544 coreconfigitem('experimental', 'nonnormalparanoidcheck',
544 coreconfigitem('experimental', 'nonnormalparanoidcheck',
545 default=False,
545 default=False,
546 )
546 )
547 coreconfigitem('experimental', 'exportableenviron',
547 coreconfigitem('experimental', 'exportableenviron',
548 default=list,
548 default=list,
549 )
549 )
550 coreconfigitem('experimental', 'extendedheader.index',
550 coreconfigitem('experimental', 'extendedheader.index',
551 default=None,
551 default=None,
552 )
552 )
553 coreconfigitem('experimental', 'extendedheader.similarity',
553 coreconfigitem('experimental', 'extendedheader.similarity',
554 default=False,
554 default=False,
555 )
555 )
556 coreconfigitem('experimental', 'format.compression',
556 coreconfigitem('experimental', 'format.compression',
557 default='zlib',
557 default='zlib',
558 )
558 )
559 coreconfigitem('experimental', 'graphshorten',
559 coreconfigitem('experimental', 'graphshorten',
560 default=False,
560 default=False,
561 )
561 )
562 coreconfigitem('experimental', 'graphstyle.parent',
562 coreconfigitem('experimental', 'graphstyle.parent',
563 default=dynamicdefault,
563 default=dynamicdefault,
564 )
564 )
565 coreconfigitem('experimental', 'graphstyle.missing',
565 coreconfigitem('experimental', 'graphstyle.missing',
566 default=dynamicdefault,
566 default=dynamicdefault,
567 )
567 )
568 coreconfigitem('experimental', 'graphstyle.grandparent',
568 coreconfigitem('experimental', 'graphstyle.grandparent',
569 default=dynamicdefault,
569 default=dynamicdefault,
570 )
570 )
571 coreconfigitem('experimental', 'hook-track-tags',
571 coreconfigitem('experimental', 'hook-track-tags',
572 default=False,
572 default=False,
573 )
573 )
574 coreconfigitem('experimental', 'httppeer.advertise-v2',
574 coreconfigitem('experimental', 'httppeer.advertise-v2',
575 default=False,
575 default=False,
576 )
576 )
577 coreconfigitem('experimental', 'httppeer.v2-encoder-order',
577 coreconfigitem('experimental', 'httppeer.v2-encoder-order',
578 default=None,
578 default=None,
579 )
579 )
580 coreconfigitem('experimental', 'httppostargs',
580 coreconfigitem('experimental', 'httppostargs',
581 default=False,
581 default=False,
582 )
582 )
583 coreconfigitem('experimental', 'mergedriver',
583 coreconfigitem('experimental', 'mergedriver',
584 default=None,
584 default=None,
585 )
585 )
586 coreconfigitem('experimental', 'nointerrupt', default=False)
586 coreconfigitem('experimental', 'nointerrupt', default=False)
587 coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
587 coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
588
588
589 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
589 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
590 default=False,
590 default=False,
591 )
591 )
592 coreconfigitem('experimental', 'remotenames',
592 coreconfigitem('experimental', 'remotenames',
593 default=False,
593 default=False,
594 )
594 )
595 coreconfigitem('experimental', 'removeemptydirs',
595 coreconfigitem('experimental', 'removeemptydirs',
596 default=True,
596 default=True,
597 )
597 )
598 coreconfigitem('experimental', 'revisions.prefixhexnode',
598 coreconfigitem('experimental', 'revisions.prefixhexnode',
599 default=False,
599 default=False,
600 )
600 )
601 coreconfigitem('experimental', 'revlogv2',
601 coreconfigitem('experimental', 'revlogv2',
602 default=None,
602 default=None,
603 )
603 )
604 coreconfigitem('experimental', 'revisions.disambiguatewithin',
604 coreconfigitem('experimental', 'revisions.disambiguatewithin',
605 default=None,
605 default=None,
606 )
606 )
607 coreconfigitem('experimental', 'server.filesdata.recommended-batch-size',
607 coreconfigitem('experimental', 'server.filesdata.recommended-batch-size',
608 default=50000,
608 default=50000,
609 )
609 )
610 coreconfigitem('experimental', 'server.manifestdata.recommended-batch-size',
610 coreconfigitem('experimental', 'server.manifestdata.recommended-batch-size',
611 default=100000,
611 default=100000,
612 )
612 )
613 coreconfigitem('experimental.server', 'stream-narrow-clones',
614 default=False,
615 )
613 coreconfigitem('experimental', 'single-head-per-branch',
616 coreconfigitem('experimental', 'single-head-per-branch',
614 default=False,
617 default=False,
615 )
618 )
616 coreconfigitem('experimental', 'sshserver.support-v2',
619 coreconfigitem('experimental', 'sshserver.support-v2',
617 default=False,
620 default=False,
618 )
621 )
619 coreconfigitem('experimental', 'sparse-read',
622 coreconfigitem('experimental', 'sparse-read',
620 default=False,
623 default=False,
621 )
624 )
622 coreconfigitem('experimental', 'sparse-read.density-threshold',
625 coreconfigitem('experimental', 'sparse-read.density-threshold',
623 default=0.50,
626 default=0.50,
624 )
627 )
625 coreconfigitem('experimental', 'sparse-read.min-gap-size',
628 coreconfigitem('experimental', 'sparse-read.min-gap-size',
626 default='65K',
629 default='65K',
627 )
630 )
628 coreconfigitem('experimental', 'treemanifest',
631 coreconfigitem('experimental', 'treemanifest',
629 default=False,
632 default=False,
630 )
633 )
631 coreconfigitem('experimental', 'update.atomic-file',
634 coreconfigitem('experimental', 'update.atomic-file',
632 default=False,
635 default=False,
633 )
636 )
634 coreconfigitem('experimental', 'sshpeer.advertise-v2',
637 coreconfigitem('experimental', 'sshpeer.advertise-v2',
635 default=False,
638 default=False,
636 )
639 )
637 coreconfigitem('experimental', 'web.apiserver',
640 coreconfigitem('experimental', 'web.apiserver',
638 default=False,
641 default=False,
639 )
642 )
640 coreconfigitem('experimental', 'web.api.http-v2',
643 coreconfigitem('experimental', 'web.api.http-v2',
641 default=False,
644 default=False,
642 )
645 )
643 coreconfigitem('experimental', 'web.api.debugreflect',
646 coreconfigitem('experimental', 'web.api.debugreflect',
644 default=False,
647 default=False,
645 )
648 )
646 coreconfigitem('experimental', 'worker.wdir-get-thread-safe',
649 coreconfigitem('experimental', 'worker.wdir-get-thread-safe',
647 default=False,
650 default=False,
648 )
651 )
649 coreconfigitem('experimental', 'xdiff',
652 coreconfigitem('experimental', 'xdiff',
650 default=False,
653 default=False,
651 )
654 )
652 coreconfigitem('extensions', '.*',
655 coreconfigitem('extensions', '.*',
653 default=None,
656 default=None,
654 generic=True,
657 generic=True,
655 )
658 )
656 coreconfigitem('extdata', '.*',
659 coreconfigitem('extdata', '.*',
657 default=None,
660 default=None,
658 generic=True,
661 generic=True,
659 )
662 )
660 coreconfigitem('format', 'chunkcachesize',
663 coreconfigitem('format', 'chunkcachesize',
661 default=None,
664 default=None,
662 )
665 )
663 coreconfigitem('format', 'dotencode',
666 coreconfigitem('format', 'dotencode',
664 default=True,
667 default=True,
665 )
668 )
666 coreconfigitem('format', 'generaldelta',
669 coreconfigitem('format', 'generaldelta',
667 default=False,
670 default=False,
668 )
671 )
669 coreconfigitem('format', 'manifestcachesize',
672 coreconfigitem('format', 'manifestcachesize',
670 default=None,
673 default=None,
671 )
674 )
672 coreconfigitem('format', 'maxchainlen',
675 coreconfigitem('format', 'maxchainlen',
673 default=dynamicdefault,
676 default=dynamicdefault,
674 )
677 )
675 coreconfigitem('format', 'obsstore-version',
678 coreconfigitem('format', 'obsstore-version',
676 default=None,
679 default=None,
677 )
680 )
678 coreconfigitem('format', 'sparse-revlog',
681 coreconfigitem('format', 'sparse-revlog',
679 default=False,
682 default=False,
680 )
683 )
681 coreconfigitem('format', 'usefncache',
684 coreconfigitem('format', 'usefncache',
682 default=True,
685 default=True,
683 )
686 )
684 coreconfigitem('format', 'usegeneraldelta',
687 coreconfigitem('format', 'usegeneraldelta',
685 default=True,
688 default=True,
686 )
689 )
687 coreconfigitem('format', 'usestore',
690 coreconfigitem('format', 'usestore',
688 default=True,
691 default=True,
689 )
692 )
690 coreconfigitem('format', 'internal-phase',
693 coreconfigitem('format', 'internal-phase',
691 default=False,
694 default=False,
692 )
695 )
693 coreconfigitem('fsmonitor', 'warn_when_unused',
696 coreconfigitem('fsmonitor', 'warn_when_unused',
694 default=True,
697 default=True,
695 )
698 )
696 coreconfigitem('fsmonitor', 'warn_update_file_count',
699 coreconfigitem('fsmonitor', 'warn_update_file_count',
697 default=50000,
700 default=50000,
698 )
701 )
699 coreconfigitem('hooks', '.*',
702 coreconfigitem('hooks', '.*',
700 default=dynamicdefault,
703 default=dynamicdefault,
701 generic=True,
704 generic=True,
702 )
705 )
703 coreconfigitem('hgweb-paths', '.*',
706 coreconfigitem('hgweb-paths', '.*',
704 default=list,
707 default=list,
705 generic=True,
708 generic=True,
706 )
709 )
707 coreconfigitem('hostfingerprints', '.*',
710 coreconfigitem('hostfingerprints', '.*',
708 default=list,
711 default=list,
709 generic=True,
712 generic=True,
710 )
713 )
711 coreconfigitem('hostsecurity', 'ciphers',
714 coreconfigitem('hostsecurity', 'ciphers',
712 default=None,
715 default=None,
713 )
716 )
714 coreconfigitem('hostsecurity', 'disabletls10warning',
717 coreconfigitem('hostsecurity', 'disabletls10warning',
715 default=False,
718 default=False,
716 )
719 )
717 coreconfigitem('hostsecurity', 'minimumprotocol',
720 coreconfigitem('hostsecurity', 'minimumprotocol',
718 default=dynamicdefault,
721 default=dynamicdefault,
719 )
722 )
720 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
723 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
721 default=dynamicdefault,
724 default=dynamicdefault,
722 generic=True,
725 generic=True,
723 )
726 )
724 coreconfigitem('hostsecurity', '.*:ciphers$',
727 coreconfigitem('hostsecurity', '.*:ciphers$',
725 default=dynamicdefault,
728 default=dynamicdefault,
726 generic=True,
729 generic=True,
727 )
730 )
728 coreconfigitem('hostsecurity', '.*:fingerprints$',
731 coreconfigitem('hostsecurity', '.*:fingerprints$',
729 default=list,
732 default=list,
730 generic=True,
733 generic=True,
731 )
734 )
732 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
735 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
733 default=None,
736 default=None,
734 generic=True,
737 generic=True,
735 )
738 )
736
739
737 coreconfigitem('http_proxy', 'always',
740 coreconfigitem('http_proxy', 'always',
738 default=False,
741 default=False,
739 )
742 )
740 coreconfigitem('http_proxy', 'host',
743 coreconfigitem('http_proxy', 'host',
741 default=None,
744 default=None,
742 )
745 )
743 coreconfigitem('http_proxy', 'no',
746 coreconfigitem('http_proxy', 'no',
744 default=list,
747 default=list,
745 )
748 )
746 coreconfigitem('http_proxy', 'passwd',
749 coreconfigitem('http_proxy', 'passwd',
747 default=None,
750 default=None,
748 )
751 )
749 coreconfigitem('http_proxy', 'user',
752 coreconfigitem('http_proxy', 'user',
750 default=None,
753 default=None,
751 )
754 )
752
755
753 coreconfigitem('http', 'timeout',
756 coreconfigitem('http', 'timeout',
754 default=None,
757 default=None,
755 )
758 )
756
759
757 coreconfigitem('logtoprocess', 'commandexception',
760 coreconfigitem('logtoprocess', 'commandexception',
758 default=None,
761 default=None,
759 )
762 )
760 coreconfigitem('logtoprocess', 'commandfinish',
763 coreconfigitem('logtoprocess', 'commandfinish',
761 default=None,
764 default=None,
762 )
765 )
763 coreconfigitem('logtoprocess', 'command',
766 coreconfigitem('logtoprocess', 'command',
764 default=None,
767 default=None,
765 )
768 )
766 coreconfigitem('logtoprocess', 'develwarn',
769 coreconfigitem('logtoprocess', 'develwarn',
767 default=None,
770 default=None,
768 )
771 )
769 coreconfigitem('logtoprocess', 'uiblocked',
772 coreconfigitem('logtoprocess', 'uiblocked',
770 default=None,
773 default=None,
771 )
774 )
772 coreconfigitem('merge', 'checkunknown',
775 coreconfigitem('merge', 'checkunknown',
773 default='abort',
776 default='abort',
774 )
777 )
775 coreconfigitem('merge', 'checkignored',
778 coreconfigitem('merge', 'checkignored',
776 default='abort',
779 default='abort',
777 )
780 )
778 coreconfigitem('experimental', 'merge.checkpathconflicts',
781 coreconfigitem('experimental', 'merge.checkpathconflicts',
779 default=False,
782 default=False,
780 )
783 )
781 coreconfigitem('merge', 'followcopies',
784 coreconfigitem('merge', 'followcopies',
782 default=True,
785 default=True,
783 )
786 )
784 coreconfigitem('merge', 'on-failure',
787 coreconfigitem('merge', 'on-failure',
785 default='continue',
788 default='continue',
786 )
789 )
787 coreconfigitem('merge', 'preferancestor',
790 coreconfigitem('merge', 'preferancestor',
788 default=lambda: ['*'],
791 default=lambda: ['*'],
789 )
792 )
790 coreconfigitem('merge', 'strict-capability-check',
793 coreconfigitem('merge', 'strict-capability-check',
791 default=False,
794 default=False,
792 )
795 )
793 coreconfigitem('merge-tools', '.*',
796 coreconfigitem('merge-tools', '.*',
794 default=None,
797 default=None,
795 generic=True,
798 generic=True,
796 )
799 )
797 coreconfigitem('merge-tools', br'.*\.args$',
800 coreconfigitem('merge-tools', br'.*\.args$',
798 default="$local $base $other",
801 default="$local $base $other",
799 generic=True,
802 generic=True,
800 priority=-1,
803 priority=-1,
801 )
804 )
802 coreconfigitem('merge-tools', br'.*\.binary$',
805 coreconfigitem('merge-tools', br'.*\.binary$',
803 default=False,
806 default=False,
804 generic=True,
807 generic=True,
805 priority=-1,
808 priority=-1,
806 )
809 )
807 coreconfigitem('merge-tools', br'.*\.check$',
810 coreconfigitem('merge-tools', br'.*\.check$',
808 default=list,
811 default=list,
809 generic=True,
812 generic=True,
810 priority=-1,
813 priority=-1,
811 )
814 )
812 coreconfigitem('merge-tools', br'.*\.checkchanged$',
815 coreconfigitem('merge-tools', br'.*\.checkchanged$',
813 default=False,
816 default=False,
814 generic=True,
817 generic=True,
815 priority=-1,
818 priority=-1,
816 )
819 )
817 coreconfigitem('merge-tools', br'.*\.executable$',
820 coreconfigitem('merge-tools', br'.*\.executable$',
818 default=dynamicdefault,
821 default=dynamicdefault,
819 generic=True,
822 generic=True,
820 priority=-1,
823 priority=-1,
821 )
824 )
822 coreconfigitem('merge-tools', br'.*\.fixeol$',
825 coreconfigitem('merge-tools', br'.*\.fixeol$',
823 default=False,
826 default=False,
824 generic=True,
827 generic=True,
825 priority=-1,
828 priority=-1,
826 )
829 )
827 coreconfigitem('merge-tools', br'.*\.gui$',
830 coreconfigitem('merge-tools', br'.*\.gui$',
828 default=False,
831 default=False,
829 generic=True,
832 generic=True,
830 priority=-1,
833 priority=-1,
831 )
834 )
832 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
835 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
833 default='basic',
836 default='basic',
834 generic=True,
837 generic=True,
835 priority=-1,
838 priority=-1,
836 )
839 )
837 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
840 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
838 default=dynamicdefault, # take from ui.mergemarkertemplate
841 default=dynamicdefault, # take from ui.mergemarkertemplate
839 generic=True,
842 generic=True,
840 priority=-1,
843 priority=-1,
841 )
844 )
842 coreconfigitem('merge-tools', br'.*\.priority$',
845 coreconfigitem('merge-tools', br'.*\.priority$',
843 default=0,
846 default=0,
844 generic=True,
847 generic=True,
845 priority=-1,
848 priority=-1,
846 )
849 )
847 coreconfigitem('merge-tools', br'.*\.premerge$',
850 coreconfigitem('merge-tools', br'.*\.premerge$',
848 default=dynamicdefault,
851 default=dynamicdefault,
849 generic=True,
852 generic=True,
850 priority=-1,
853 priority=-1,
851 )
854 )
852 coreconfigitem('merge-tools', br'.*\.symlink$',
855 coreconfigitem('merge-tools', br'.*\.symlink$',
853 default=False,
856 default=False,
854 generic=True,
857 generic=True,
855 priority=-1,
858 priority=-1,
856 )
859 )
857 coreconfigitem('pager', 'attend-.*',
860 coreconfigitem('pager', 'attend-.*',
858 default=dynamicdefault,
861 default=dynamicdefault,
859 generic=True,
862 generic=True,
860 )
863 )
861 coreconfigitem('pager', 'ignore',
864 coreconfigitem('pager', 'ignore',
862 default=list,
865 default=list,
863 )
866 )
864 coreconfigitem('pager', 'pager',
867 coreconfigitem('pager', 'pager',
865 default=dynamicdefault,
868 default=dynamicdefault,
866 )
869 )
867 coreconfigitem('patch', 'eol',
870 coreconfigitem('patch', 'eol',
868 default='strict',
871 default='strict',
869 )
872 )
870 coreconfigitem('patch', 'fuzz',
873 coreconfigitem('patch', 'fuzz',
871 default=2,
874 default=2,
872 )
875 )
873 coreconfigitem('paths', 'default',
876 coreconfigitem('paths', 'default',
874 default=None,
877 default=None,
875 )
878 )
876 coreconfigitem('paths', 'default-push',
879 coreconfigitem('paths', 'default-push',
877 default=None,
880 default=None,
878 )
881 )
879 coreconfigitem('paths', '.*',
882 coreconfigitem('paths', '.*',
880 default=None,
883 default=None,
881 generic=True,
884 generic=True,
882 )
885 )
883 coreconfigitem('phases', 'checksubrepos',
886 coreconfigitem('phases', 'checksubrepos',
884 default='follow',
887 default='follow',
885 )
888 )
886 coreconfigitem('phases', 'new-commit',
889 coreconfigitem('phases', 'new-commit',
887 default='draft',
890 default='draft',
888 )
891 )
889 coreconfigitem('phases', 'publish',
892 coreconfigitem('phases', 'publish',
890 default=True,
893 default=True,
891 )
894 )
892 coreconfigitem('profiling', 'enabled',
895 coreconfigitem('profiling', 'enabled',
893 default=False,
896 default=False,
894 )
897 )
895 coreconfigitem('profiling', 'format',
898 coreconfigitem('profiling', 'format',
896 default='text',
899 default='text',
897 )
900 )
898 coreconfigitem('profiling', 'freq',
901 coreconfigitem('profiling', 'freq',
899 default=1000,
902 default=1000,
900 )
903 )
901 coreconfigitem('profiling', 'limit',
904 coreconfigitem('profiling', 'limit',
902 default=30,
905 default=30,
903 )
906 )
904 coreconfigitem('profiling', 'nested',
907 coreconfigitem('profiling', 'nested',
905 default=0,
908 default=0,
906 )
909 )
907 coreconfigitem('profiling', 'output',
910 coreconfigitem('profiling', 'output',
908 default=None,
911 default=None,
909 )
912 )
910 coreconfigitem('profiling', 'showmax',
913 coreconfigitem('profiling', 'showmax',
911 default=0.999,
914 default=0.999,
912 )
915 )
913 coreconfigitem('profiling', 'showmin',
916 coreconfigitem('profiling', 'showmin',
914 default=dynamicdefault,
917 default=dynamicdefault,
915 )
918 )
916 coreconfigitem('profiling', 'sort',
919 coreconfigitem('profiling', 'sort',
917 default='inlinetime',
920 default='inlinetime',
918 )
921 )
919 coreconfigitem('profiling', 'statformat',
922 coreconfigitem('profiling', 'statformat',
920 default='hotpath',
923 default='hotpath',
921 )
924 )
922 coreconfigitem('profiling', 'time-track',
925 coreconfigitem('profiling', 'time-track',
923 default='cpu',
926 default='cpu',
924 )
927 )
925 coreconfigitem('profiling', 'type',
928 coreconfigitem('profiling', 'type',
926 default='stat',
929 default='stat',
927 )
930 )
928 coreconfigitem('progress', 'assume-tty',
931 coreconfigitem('progress', 'assume-tty',
929 default=False,
932 default=False,
930 )
933 )
931 coreconfigitem('progress', 'changedelay',
934 coreconfigitem('progress', 'changedelay',
932 default=1,
935 default=1,
933 )
936 )
934 coreconfigitem('progress', 'clear-complete',
937 coreconfigitem('progress', 'clear-complete',
935 default=True,
938 default=True,
936 )
939 )
937 coreconfigitem('progress', 'debug',
940 coreconfigitem('progress', 'debug',
938 default=False,
941 default=False,
939 )
942 )
940 coreconfigitem('progress', 'delay',
943 coreconfigitem('progress', 'delay',
941 default=3,
944 default=3,
942 )
945 )
943 coreconfigitem('progress', 'disable',
946 coreconfigitem('progress', 'disable',
944 default=False,
947 default=False,
945 )
948 )
946 coreconfigitem('progress', 'estimateinterval',
949 coreconfigitem('progress', 'estimateinterval',
947 default=60.0,
950 default=60.0,
948 )
951 )
949 coreconfigitem('progress', 'format',
952 coreconfigitem('progress', 'format',
950 default=lambda: ['topic', 'bar', 'number', 'estimate'],
953 default=lambda: ['topic', 'bar', 'number', 'estimate'],
951 )
954 )
952 coreconfigitem('progress', 'refresh',
955 coreconfigitem('progress', 'refresh',
953 default=0.1,
956 default=0.1,
954 )
957 )
955 coreconfigitem('progress', 'width',
958 coreconfigitem('progress', 'width',
956 default=dynamicdefault,
959 default=dynamicdefault,
957 )
960 )
958 coreconfigitem('push', 'pushvars.server',
961 coreconfigitem('push', 'pushvars.server',
959 default=False,
962 default=False,
960 )
963 )
961 coreconfigitem('storage', 'new-repo-backend',
964 coreconfigitem('storage', 'new-repo-backend',
962 default='revlogv1',
965 default='revlogv1',
963 )
966 )
964 coreconfigitem('storage', 'revlog.optimize-delta-parent-choice',
967 coreconfigitem('storage', 'revlog.optimize-delta-parent-choice',
965 default=True,
968 default=True,
966 alias=[('format', 'aggressivemergedeltas')],
969 alias=[('format', 'aggressivemergedeltas')],
967 )
970 )
968 coreconfigitem('server', 'bookmarks-pushkey-compat',
971 coreconfigitem('server', 'bookmarks-pushkey-compat',
969 default=True,
972 default=True,
970 )
973 )
971 coreconfigitem('server', 'bundle1',
974 coreconfigitem('server', 'bundle1',
972 default=True,
975 default=True,
973 )
976 )
974 coreconfigitem('server', 'bundle1gd',
977 coreconfigitem('server', 'bundle1gd',
975 default=None,
978 default=None,
976 )
979 )
977 coreconfigitem('server', 'bundle1.pull',
980 coreconfigitem('server', 'bundle1.pull',
978 default=None,
981 default=None,
979 )
982 )
980 coreconfigitem('server', 'bundle1gd.pull',
983 coreconfigitem('server', 'bundle1gd.pull',
981 default=None,
984 default=None,
982 )
985 )
983 coreconfigitem('server', 'bundle1.push',
986 coreconfigitem('server', 'bundle1.push',
984 default=None,
987 default=None,
985 )
988 )
986 coreconfigitem('server', 'bundle1gd.push',
989 coreconfigitem('server', 'bundle1gd.push',
987 default=None,
990 default=None,
988 )
991 )
989 coreconfigitem('server', 'bundle2.stream',
992 coreconfigitem('server', 'bundle2.stream',
990 default=True,
993 default=True,
991 alias=[('experimental', 'bundle2.stream')]
994 alias=[('experimental', 'bundle2.stream')]
992 )
995 )
993 coreconfigitem('server', 'compressionengines',
996 coreconfigitem('server', 'compressionengines',
994 default=list,
997 default=list,
995 )
998 )
996 coreconfigitem('server', 'concurrent-push-mode',
999 coreconfigitem('server', 'concurrent-push-mode',
997 default='strict',
1000 default='strict',
998 )
1001 )
999 coreconfigitem('server', 'disablefullbundle',
1002 coreconfigitem('server', 'disablefullbundle',
1000 default=False,
1003 default=False,
1001 )
1004 )
1002 coreconfigitem('server', 'maxhttpheaderlen',
1005 coreconfigitem('server', 'maxhttpheaderlen',
1003 default=1024,
1006 default=1024,
1004 )
1007 )
1005 coreconfigitem('server', 'pullbundle',
1008 coreconfigitem('server', 'pullbundle',
1006 default=False,
1009 default=False,
1007 )
1010 )
1008 coreconfigitem('server', 'preferuncompressed',
1011 coreconfigitem('server', 'preferuncompressed',
1009 default=False,
1012 default=False,
1010 )
1013 )
1011 coreconfigitem('server', 'streamunbundle',
1014 coreconfigitem('server', 'streamunbundle',
1012 default=False,
1015 default=False,
1013 )
1016 )
1014 coreconfigitem('server', 'uncompressed',
1017 coreconfigitem('server', 'uncompressed',
1015 default=True,
1018 default=True,
1016 )
1019 )
1017 coreconfigitem('server', 'uncompressedallowsecret',
1020 coreconfigitem('server', 'uncompressedallowsecret',
1018 default=False,
1021 default=False,
1019 )
1022 )
1020 coreconfigitem('server', 'validate',
1023 coreconfigitem('server', 'validate',
1021 default=False,
1024 default=False,
1022 )
1025 )
1023 coreconfigitem('server', 'zliblevel',
1026 coreconfigitem('server', 'zliblevel',
1024 default=-1,
1027 default=-1,
1025 )
1028 )
1026 coreconfigitem('server', 'zstdlevel',
1029 coreconfigitem('server', 'zstdlevel',
1027 default=3,
1030 default=3,
1028 )
1031 )
1029 coreconfigitem('share', 'pool',
1032 coreconfigitem('share', 'pool',
1030 default=None,
1033 default=None,
1031 )
1034 )
1032 coreconfigitem('share', 'poolnaming',
1035 coreconfigitem('share', 'poolnaming',
1033 default='identity',
1036 default='identity',
1034 )
1037 )
1035 coreconfigitem('smtp', 'host',
1038 coreconfigitem('smtp', 'host',
1036 default=None,
1039 default=None,
1037 )
1040 )
1038 coreconfigitem('smtp', 'local_hostname',
1041 coreconfigitem('smtp', 'local_hostname',
1039 default=None,
1042 default=None,
1040 )
1043 )
1041 coreconfigitem('smtp', 'password',
1044 coreconfigitem('smtp', 'password',
1042 default=None,
1045 default=None,
1043 )
1046 )
1044 coreconfigitem('smtp', 'port',
1047 coreconfigitem('smtp', 'port',
1045 default=dynamicdefault,
1048 default=dynamicdefault,
1046 )
1049 )
1047 coreconfigitem('smtp', 'tls',
1050 coreconfigitem('smtp', 'tls',
1048 default='none',
1051 default='none',
1049 )
1052 )
1050 coreconfigitem('smtp', 'username',
1053 coreconfigitem('smtp', 'username',
1051 default=None,
1054 default=None,
1052 )
1055 )
1053 coreconfigitem('sparse', 'missingwarning',
1056 coreconfigitem('sparse', 'missingwarning',
1054 default=True,
1057 default=True,
1055 )
1058 )
1056 coreconfigitem('subrepos', 'allowed',
1059 coreconfigitem('subrepos', 'allowed',
1057 default=dynamicdefault, # to make backporting simpler
1060 default=dynamicdefault, # to make backporting simpler
1058 )
1061 )
1059 coreconfigitem('subrepos', 'hg:allowed',
1062 coreconfigitem('subrepos', 'hg:allowed',
1060 default=dynamicdefault,
1063 default=dynamicdefault,
1061 )
1064 )
1062 coreconfigitem('subrepos', 'git:allowed',
1065 coreconfigitem('subrepos', 'git:allowed',
1063 default=dynamicdefault,
1066 default=dynamicdefault,
1064 )
1067 )
1065 coreconfigitem('subrepos', 'svn:allowed',
1068 coreconfigitem('subrepos', 'svn:allowed',
1066 default=dynamicdefault,
1069 default=dynamicdefault,
1067 )
1070 )
1068 coreconfigitem('templates', '.*',
1071 coreconfigitem('templates', '.*',
1069 default=None,
1072 default=None,
1070 generic=True,
1073 generic=True,
1071 )
1074 )
1072 coreconfigitem('trusted', 'groups',
1075 coreconfigitem('trusted', 'groups',
1073 default=list,
1076 default=list,
1074 )
1077 )
1075 coreconfigitem('trusted', 'users',
1078 coreconfigitem('trusted', 'users',
1076 default=list,
1079 default=list,
1077 )
1080 )
1078 coreconfigitem('ui', '_usedassubrepo',
1081 coreconfigitem('ui', '_usedassubrepo',
1079 default=False,
1082 default=False,
1080 )
1083 )
1081 coreconfigitem('ui', 'allowemptycommit',
1084 coreconfigitem('ui', 'allowemptycommit',
1082 default=False,
1085 default=False,
1083 )
1086 )
1084 coreconfigitem('ui', 'archivemeta',
1087 coreconfigitem('ui', 'archivemeta',
1085 default=True,
1088 default=True,
1086 )
1089 )
1087 coreconfigitem('ui', 'askusername',
1090 coreconfigitem('ui', 'askusername',
1088 default=False,
1091 default=False,
1089 )
1092 )
1090 coreconfigitem('ui', 'clonebundlefallback',
1093 coreconfigitem('ui', 'clonebundlefallback',
1091 default=False,
1094 default=False,
1092 )
1095 )
1093 coreconfigitem('ui', 'clonebundleprefers',
1096 coreconfigitem('ui', 'clonebundleprefers',
1094 default=list,
1097 default=list,
1095 )
1098 )
1096 coreconfigitem('ui', 'clonebundles',
1099 coreconfigitem('ui', 'clonebundles',
1097 default=True,
1100 default=True,
1098 )
1101 )
1099 coreconfigitem('ui', 'color',
1102 coreconfigitem('ui', 'color',
1100 default='auto',
1103 default='auto',
1101 )
1104 )
1102 coreconfigitem('ui', 'commitsubrepos',
1105 coreconfigitem('ui', 'commitsubrepos',
1103 default=False,
1106 default=False,
1104 )
1107 )
1105 coreconfigitem('ui', 'debug',
1108 coreconfigitem('ui', 'debug',
1106 default=False,
1109 default=False,
1107 )
1110 )
1108 coreconfigitem('ui', 'debugger',
1111 coreconfigitem('ui', 'debugger',
1109 default=None,
1112 default=None,
1110 )
1113 )
1111 coreconfigitem('ui', 'editor',
1114 coreconfigitem('ui', 'editor',
1112 default=dynamicdefault,
1115 default=dynamicdefault,
1113 )
1116 )
1114 coreconfigitem('ui', 'fallbackencoding',
1117 coreconfigitem('ui', 'fallbackencoding',
1115 default=None,
1118 default=None,
1116 )
1119 )
1117 coreconfigitem('ui', 'forcecwd',
1120 coreconfigitem('ui', 'forcecwd',
1118 default=None,
1121 default=None,
1119 )
1122 )
1120 coreconfigitem('ui', 'forcemerge',
1123 coreconfigitem('ui', 'forcemerge',
1121 default=None,
1124 default=None,
1122 )
1125 )
1123 coreconfigitem('ui', 'formatdebug',
1126 coreconfigitem('ui', 'formatdebug',
1124 default=False,
1127 default=False,
1125 )
1128 )
1126 coreconfigitem('ui', 'formatjson',
1129 coreconfigitem('ui', 'formatjson',
1127 default=False,
1130 default=False,
1128 )
1131 )
1129 coreconfigitem('ui', 'formatted',
1132 coreconfigitem('ui', 'formatted',
1130 default=None,
1133 default=None,
1131 )
1134 )
1132 coreconfigitem('ui', 'graphnodetemplate',
1135 coreconfigitem('ui', 'graphnodetemplate',
1133 default=None,
1136 default=None,
1134 )
1137 )
1135 coreconfigitem('ui', 'history-editing-backup',
1138 coreconfigitem('ui', 'history-editing-backup',
1136 default=True,
1139 default=True,
1137 )
1140 )
1138 coreconfigitem('ui', 'interactive',
1141 coreconfigitem('ui', 'interactive',
1139 default=None,
1142 default=None,
1140 )
1143 )
1141 coreconfigitem('ui', 'interface',
1144 coreconfigitem('ui', 'interface',
1142 default=None,
1145 default=None,
1143 )
1146 )
1144 coreconfigitem('ui', 'interface.chunkselector',
1147 coreconfigitem('ui', 'interface.chunkselector',
1145 default=None,
1148 default=None,
1146 )
1149 )
1147 coreconfigitem('ui', 'large-file-limit',
1150 coreconfigitem('ui', 'large-file-limit',
1148 default=10000000,
1151 default=10000000,
1149 )
1152 )
1150 coreconfigitem('ui', 'logblockedtimes',
1153 coreconfigitem('ui', 'logblockedtimes',
1151 default=False,
1154 default=False,
1152 )
1155 )
1153 coreconfigitem('ui', 'logtemplate',
1156 coreconfigitem('ui', 'logtemplate',
1154 default=None,
1157 default=None,
1155 )
1158 )
1156 coreconfigitem('ui', 'merge',
1159 coreconfigitem('ui', 'merge',
1157 default=None,
1160 default=None,
1158 )
1161 )
1159 coreconfigitem('ui', 'mergemarkers',
1162 coreconfigitem('ui', 'mergemarkers',
1160 default='basic',
1163 default='basic',
1161 )
1164 )
1162 coreconfigitem('ui', 'mergemarkertemplate',
1165 coreconfigitem('ui', 'mergemarkertemplate',
1163 default=('{node|short} '
1166 default=('{node|short} '
1164 '{ifeq(tags, "tip", "", '
1167 '{ifeq(tags, "tip", "", '
1165 'ifeq(tags, "", "", "{tags} "))}'
1168 'ifeq(tags, "", "", "{tags} "))}'
1166 '{if(bookmarks, "{bookmarks} ")}'
1169 '{if(bookmarks, "{bookmarks} ")}'
1167 '{ifeq(branch, "default", "", "{branch} ")}'
1170 '{ifeq(branch, "default", "", "{branch} ")}'
1168 '- {author|user}: {desc|firstline}')
1171 '- {author|user}: {desc|firstline}')
1169 )
1172 )
1170 coreconfigitem('ui', 'nontty',
1173 coreconfigitem('ui', 'nontty',
1171 default=False,
1174 default=False,
1172 )
1175 )
1173 coreconfigitem('ui', 'origbackuppath',
1176 coreconfigitem('ui', 'origbackuppath',
1174 default=None,
1177 default=None,
1175 )
1178 )
1176 coreconfigitem('ui', 'paginate',
1179 coreconfigitem('ui', 'paginate',
1177 default=True,
1180 default=True,
1178 )
1181 )
1179 coreconfigitem('ui', 'patch',
1182 coreconfigitem('ui', 'patch',
1180 default=None,
1183 default=None,
1181 )
1184 )
1182 coreconfigitem('ui', 'portablefilenames',
1185 coreconfigitem('ui', 'portablefilenames',
1183 default='warn',
1186 default='warn',
1184 )
1187 )
1185 coreconfigitem('ui', 'promptecho',
1188 coreconfigitem('ui', 'promptecho',
1186 default=False,
1189 default=False,
1187 )
1190 )
1188 coreconfigitem('ui', 'quiet',
1191 coreconfigitem('ui', 'quiet',
1189 default=False,
1192 default=False,
1190 )
1193 )
1191 coreconfigitem('ui', 'quietbookmarkmove',
1194 coreconfigitem('ui', 'quietbookmarkmove',
1192 default=False,
1195 default=False,
1193 )
1196 )
1194 coreconfigitem('ui', 'remotecmd',
1197 coreconfigitem('ui', 'remotecmd',
1195 default='hg',
1198 default='hg',
1196 )
1199 )
1197 coreconfigitem('ui', 'report_untrusted',
1200 coreconfigitem('ui', 'report_untrusted',
1198 default=True,
1201 default=True,
1199 )
1202 )
1200 coreconfigitem('ui', 'rollback',
1203 coreconfigitem('ui', 'rollback',
1201 default=True,
1204 default=True,
1202 )
1205 )
1203 coreconfigitem('ui', 'signal-safe-lock',
1206 coreconfigitem('ui', 'signal-safe-lock',
1204 default=True,
1207 default=True,
1205 )
1208 )
1206 coreconfigitem('ui', 'slash',
1209 coreconfigitem('ui', 'slash',
1207 default=False,
1210 default=False,
1208 )
1211 )
1209 coreconfigitem('ui', 'ssh',
1212 coreconfigitem('ui', 'ssh',
1210 default='ssh',
1213 default='ssh',
1211 )
1214 )
1212 coreconfigitem('ui', 'ssherrorhint',
1215 coreconfigitem('ui', 'ssherrorhint',
1213 default=None,
1216 default=None,
1214 )
1217 )
1215 coreconfigitem('ui', 'statuscopies',
1218 coreconfigitem('ui', 'statuscopies',
1216 default=False,
1219 default=False,
1217 )
1220 )
1218 coreconfigitem('ui', 'strict',
1221 coreconfigitem('ui', 'strict',
1219 default=False,
1222 default=False,
1220 )
1223 )
1221 coreconfigitem('ui', 'style',
1224 coreconfigitem('ui', 'style',
1222 default='',
1225 default='',
1223 )
1226 )
1224 coreconfigitem('ui', 'supportcontact',
1227 coreconfigitem('ui', 'supportcontact',
1225 default=None,
1228 default=None,
1226 )
1229 )
1227 coreconfigitem('ui', 'textwidth',
1230 coreconfigitem('ui', 'textwidth',
1228 default=78,
1231 default=78,
1229 )
1232 )
1230 coreconfigitem('ui', 'timeout',
1233 coreconfigitem('ui', 'timeout',
1231 default='600',
1234 default='600',
1232 )
1235 )
1233 coreconfigitem('ui', 'timeout.warn',
1236 coreconfigitem('ui', 'timeout.warn',
1234 default=0,
1237 default=0,
1235 )
1238 )
1236 coreconfigitem('ui', 'traceback',
1239 coreconfigitem('ui', 'traceback',
1237 default=False,
1240 default=False,
1238 )
1241 )
1239 coreconfigitem('ui', 'tweakdefaults',
1242 coreconfigitem('ui', 'tweakdefaults',
1240 default=False,
1243 default=False,
1241 )
1244 )
1242 coreconfigitem('ui', 'username',
1245 coreconfigitem('ui', 'username',
1243 alias=[('ui', 'user')]
1246 alias=[('ui', 'user')]
1244 )
1247 )
1245 coreconfigitem('ui', 'verbose',
1248 coreconfigitem('ui', 'verbose',
1246 default=False,
1249 default=False,
1247 )
1250 )
1248 coreconfigitem('verify', 'skipflags',
1251 coreconfigitem('verify', 'skipflags',
1249 default=None,
1252 default=None,
1250 )
1253 )
1251 coreconfigitem('web', 'allowbz2',
1254 coreconfigitem('web', 'allowbz2',
1252 default=False,
1255 default=False,
1253 )
1256 )
1254 coreconfigitem('web', 'allowgz',
1257 coreconfigitem('web', 'allowgz',
1255 default=False,
1258 default=False,
1256 )
1259 )
1257 coreconfigitem('web', 'allow-pull',
1260 coreconfigitem('web', 'allow-pull',
1258 alias=[('web', 'allowpull')],
1261 alias=[('web', 'allowpull')],
1259 default=True,
1262 default=True,
1260 )
1263 )
1261 coreconfigitem('web', 'allow-push',
1264 coreconfigitem('web', 'allow-push',
1262 alias=[('web', 'allow_push')],
1265 alias=[('web', 'allow_push')],
1263 default=list,
1266 default=list,
1264 )
1267 )
1265 coreconfigitem('web', 'allowzip',
1268 coreconfigitem('web', 'allowzip',
1266 default=False,
1269 default=False,
1267 )
1270 )
1268 coreconfigitem('web', 'archivesubrepos',
1271 coreconfigitem('web', 'archivesubrepos',
1269 default=False,
1272 default=False,
1270 )
1273 )
1271 coreconfigitem('web', 'cache',
1274 coreconfigitem('web', 'cache',
1272 default=True,
1275 default=True,
1273 )
1276 )
1274 coreconfigitem('web', 'contact',
1277 coreconfigitem('web', 'contact',
1275 default=None,
1278 default=None,
1276 )
1279 )
1277 coreconfigitem('web', 'deny_push',
1280 coreconfigitem('web', 'deny_push',
1278 default=list,
1281 default=list,
1279 )
1282 )
1280 coreconfigitem('web', 'guessmime',
1283 coreconfigitem('web', 'guessmime',
1281 default=False,
1284 default=False,
1282 )
1285 )
1283 coreconfigitem('web', 'hidden',
1286 coreconfigitem('web', 'hidden',
1284 default=False,
1287 default=False,
1285 )
1288 )
1286 coreconfigitem('web', 'labels',
1289 coreconfigitem('web', 'labels',
1287 default=list,
1290 default=list,
1288 )
1291 )
1289 coreconfigitem('web', 'logoimg',
1292 coreconfigitem('web', 'logoimg',
1290 default='hglogo.png',
1293 default='hglogo.png',
1291 )
1294 )
1292 coreconfigitem('web', 'logourl',
1295 coreconfigitem('web', 'logourl',
1293 default='https://mercurial-scm.org/',
1296 default='https://mercurial-scm.org/',
1294 )
1297 )
1295 coreconfigitem('web', 'accesslog',
1298 coreconfigitem('web', 'accesslog',
1296 default='-',
1299 default='-',
1297 )
1300 )
1298 coreconfigitem('web', 'address',
1301 coreconfigitem('web', 'address',
1299 default='',
1302 default='',
1300 )
1303 )
1301 coreconfigitem('web', 'allow-archive',
1304 coreconfigitem('web', 'allow-archive',
1302 alias=[('web', 'allow_archive')],
1305 alias=[('web', 'allow_archive')],
1303 default=list,
1306 default=list,
1304 )
1307 )
1305 coreconfigitem('web', 'allow_read',
1308 coreconfigitem('web', 'allow_read',
1306 default=list,
1309 default=list,
1307 )
1310 )
1308 coreconfigitem('web', 'baseurl',
1311 coreconfigitem('web', 'baseurl',
1309 default=None,
1312 default=None,
1310 )
1313 )
1311 coreconfigitem('web', 'cacerts',
1314 coreconfigitem('web', 'cacerts',
1312 default=None,
1315 default=None,
1313 )
1316 )
1314 coreconfigitem('web', 'certificate',
1317 coreconfigitem('web', 'certificate',
1315 default=None,
1318 default=None,
1316 )
1319 )
1317 coreconfigitem('web', 'collapse',
1320 coreconfigitem('web', 'collapse',
1318 default=False,
1321 default=False,
1319 )
1322 )
1320 coreconfigitem('web', 'csp',
1323 coreconfigitem('web', 'csp',
1321 default=None,
1324 default=None,
1322 )
1325 )
1323 coreconfigitem('web', 'deny_read',
1326 coreconfigitem('web', 'deny_read',
1324 default=list,
1327 default=list,
1325 )
1328 )
1326 coreconfigitem('web', 'descend',
1329 coreconfigitem('web', 'descend',
1327 default=True,
1330 default=True,
1328 )
1331 )
1329 coreconfigitem('web', 'description',
1332 coreconfigitem('web', 'description',
1330 default="",
1333 default="",
1331 )
1334 )
1332 coreconfigitem('web', 'encoding',
1335 coreconfigitem('web', 'encoding',
1333 default=lambda: encoding.encoding,
1336 default=lambda: encoding.encoding,
1334 )
1337 )
1335 coreconfigitem('web', 'errorlog',
1338 coreconfigitem('web', 'errorlog',
1336 default='-',
1339 default='-',
1337 )
1340 )
1338 coreconfigitem('web', 'ipv6',
1341 coreconfigitem('web', 'ipv6',
1339 default=False,
1342 default=False,
1340 )
1343 )
1341 coreconfigitem('web', 'maxchanges',
1344 coreconfigitem('web', 'maxchanges',
1342 default=10,
1345 default=10,
1343 )
1346 )
1344 coreconfigitem('web', 'maxfiles',
1347 coreconfigitem('web', 'maxfiles',
1345 default=10,
1348 default=10,
1346 )
1349 )
1347 coreconfigitem('web', 'maxshortchanges',
1350 coreconfigitem('web', 'maxshortchanges',
1348 default=60,
1351 default=60,
1349 )
1352 )
1350 coreconfigitem('web', 'motd',
1353 coreconfigitem('web', 'motd',
1351 default='',
1354 default='',
1352 )
1355 )
1353 coreconfigitem('web', 'name',
1356 coreconfigitem('web', 'name',
1354 default=dynamicdefault,
1357 default=dynamicdefault,
1355 )
1358 )
1356 coreconfigitem('web', 'port',
1359 coreconfigitem('web', 'port',
1357 default=8000,
1360 default=8000,
1358 )
1361 )
1359 coreconfigitem('web', 'prefix',
1362 coreconfigitem('web', 'prefix',
1360 default='',
1363 default='',
1361 )
1364 )
1362 coreconfigitem('web', 'push_ssl',
1365 coreconfigitem('web', 'push_ssl',
1363 default=True,
1366 default=True,
1364 )
1367 )
1365 coreconfigitem('web', 'refreshinterval',
1368 coreconfigitem('web', 'refreshinterval',
1366 default=20,
1369 default=20,
1367 )
1370 )
1368 coreconfigitem('web', 'server-header',
1371 coreconfigitem('web', 'server-header',
1369 default=None,
1372 default=None,
1370 )
1373 )
1371 coreconfigitem('web', 'static',
1374 coreconfigitem('web', 'static',
1372 default=None,
1375 default=None,
1373 )
1376 )
1374 coreconfigitem('web', 'staticurl',
1377 coreconfigitem('web', 'staticurl',
1375 default=None,
1378 default=None,
1376 )
1379 )
1377 coreconfigitem('web', 'stripes',
1380 coreconfigitem('web', 'stripes',
1378 default=1,
1381 default=1,
1379 )
1382 )
1380 coreconfigitem('web', 'style',
1383 coreconfigitem('web', 'style',
1381 default='paper',
1384 default='paper',
1382 )
1385 )
1383 coreconfigitem('web', 'templates',
1386 coreconfigitem('web', 'templates',
1384 default=None,
1387 default=None,
1385 )
1388 )
1386 coreconfigitem('web', 'view',
1389 coreconfigitem('web', 'view',
1387 default='served',
1390 default='served',
1388 )
1391 )
1389 coreconfigitem('worker', 'backgroundclose',
1392 coreconfigitem('worker', 'backgroundclose',
1390 default=dynamicdefault,
1393 default=dynamicdefault,
1391 )
1394 )
1392 # Windows defaults to a limit of 512 open files. A buffer of 128
1395 # Windows defaults to a limit of 512 open files. A buffer of 128
1393 # should give us enough headway.
1396 # should give us enough headway.
1394 coreconfigitem('worker', 'backgroundclosemaxqueue',
1397 coreconfigitem('worker', 'backgroundclosemaxqueue',
1395 default=384,
1398 default=384,
1396 )
1399 )
1397 coreconfigitem('worker', 'backgroundcloseminfilecount',
1400 coreconfigitem('worker', 'backgroundcloseminfilecount',
1398 default=2048,
1401 default=2048,
1399 )
1402 )
1400 coreconfigitem('worker', 'backgroundclosethreadcount',
1403 coreconfigitem('worker', 'backgroundclosethreadcount',
1401 default=4,
1404 default=4,
1402 )
1405 )
1403 coreconfigitem('worker', 'enabled',
1406 coreconfigitem('worker', 'enabled',
1404 default=True,
1407 default=True,
1405 )
1408 )
1406 coreconfigitem('worker', 'numcpus',
1409 coreconfigitem('worker', 'numcpus',
1407 default=None,
1410 default=None,
1408 )
1411 )
1409
1412
1410 # Rebase related configuration moved to core because other extension are doing
1413 # Rebase related configuration moved to core because other extension are doing
1411 # strange things. For example, shelve import the extensions to reuse some bit
1414 # strange things. For example, shelve import the extensions to reuse some bit
1412 # without formally loading it.
1415 # without formally loading it.
1413 coreconfigitem('commands', 'rebase.requiredest',
1416 coreconfigitem('commands', 'rebase.requiredest',
1414 default=False,
1417 default=False,
1415 )
1418 )
1416 coreconfigitem('experimental', 'rebaseskipobsolete',
1419 coreconfigitem('experimental', 'rebaseskipobsolete',
1417 default=True,
1420 default=True,
1418 )
1421 )
1419 coreconfigitem('rebase', 'singletransaction',
1422 coreconfigitem('rebase', 'singletransaction',
1420 default=False,
1423 default=False,
1421 )
1424 )
1422 coreconfigitem('rebase', 'experimental.inmemory',
1425 coreconfigitem('rebase', 'experimental.inmemory',
1423 default=False,
1426 default=False,
1424 )
1427 )
@@ -1,651 +1,655
1 # streamclone.py - producing and consuming streaming repository data
1 # streamclone.py - producing and consuming streaming repository data
2 #
2 #
3 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import contextlib
10 import contextlib
11 import os
11 import os
12 import struct
12 import struct
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 branchmap,
16 branchmap,
17 cacheutil,
17 cacheutil,
18 error,
18 error,
19 phases,
19 phases,
20 pycompat,
20 pycompat,
21 repository,
21 repository,
22 store,
22 store,
23 util,
23 util,
24 )
24 )
25
25
26 def canperformstreamclone(pullop, bundle2=False):
26 def canperformstreamclone(pullop, bundle2=False):
27 """Whether it is possible to perform a streaming clone as part of pull.
27 """Whether it is possible to perform a streaming clone as part of pull.
28
28
29 ``bundle2`` will cause the function to consider stream clone through
29 ``bundle2`` will cause the function to consider stream clone through
30 bundle2 and only through bundle2.
30 bundle2 and only through bundle2.
31
31
32 Returns a tuple of (supported, requirements). ``supported`` is True if
32 Returns a tuple of (supported, requirements). ``supported`` is True if
33 streaming clone is supported and False otherwise. ``requirements`` is
33 streaming clone is supported and False otherwise. ``requirements`` is
34 a set of repo requirements from the remote, or ``None`` if stream clone
34 a set of repo requirements from the remote, or ``None`` if stream clone
35 isn't supported.
35 isn't supported.
36 """
36 """
37 repo = pullop.repo
37 repo = pullop.repo
38 remote = pullop.remote
38 remote = pullop.remote
39
39
40 bundle2supported = False
40 bundle2supported = False
41 if pullop.canusebundle2:
41 if pullop.canusebundle2:
42 if 'v2' in pullop.remotebundle2caps.get('stream', []):
42 if 'v2' in pullop.remotebundle2caps.get('stream', []):
43 bundle2supported = True
43 bundle2supported = True
44 # else
44 # else
45 # Server doesn't support bundle2 stream clone or doesn't support
45 # Server doesn't support bundle2 stream clone or doesn't support
46 # the versions we support. Fall back and possibly allow legacy.
46 # the versions we support. Fall back and possibly allow legacy.
47
47
48 # Ensures legacy code path uses available bundle2.
48 # Ensures legacy code path uses available bundle2.
49 if bundle2supported and not bundle2:
49 if bundle2supported and not bundle2:
50 return False, None
50 return False, None
51 # Ensures bundle2 doesn't try to do a stream clone if it isn't supported.
51 # Ensures bundle2 doesn't try to do a stream clone if it isn't supported.
52 elif bundle2 and not bundle2supported:
52 elif bundle2 and not bundle2supported:
53 return False, None
53 return False, None
54
54
55 # Streaming clone only works on empty repositories.
55 # Streaming clone only works on empty repositories.
56 if len(repo):
56 if len(repo):
57 return False, None
57 return False, None
58
58
59 # Streaming clone only works if all data is being requested.
59 # Streaming clone only works if all data is being requested.
60 if pullop.heads:
60 if pullop.heads:
61 return False, None
61 return False, None
62
62
63 streamrequested = pullop.streamclonerequested
63 streamrequested = pullop.streamclonerequested
64
64
65 # If we don't have a preference, let the server decide for us. This
65 # If we don't have a preference, let the server decide for us. This
66 # likely only comes into play in LANs.
66 # likely only comes into play in LANs.
67 if streamrequested is None:
67 if streamrequested is None:
68 # The server can advertise whether to prefer streaming clone.
68 # The server can advertise whether to prefer streaming clone.
69 streamrequested = remote.capable('stream-preferred')
69 streamrequested = remote.capable('stream-preferred')
70
70
71 if not streamrequested:
71 if not streamrequested:
72 return False, None
72 return False, None
73
73
74 # In order for stream clone to work, the client has to support all the
74 # In order for stream clone to work, the client has to support all the
75 # requirements advertised by the server.
75 # requirements advertised by the server.
76 #
76 #
77 # The server advertises its requirements via the "stream" and "streamreqs"
77 # The server advertises its requirements via the "stream" and "streamreqs"
78 # capability. "stream" (a value-less capability) is advertised if and only
78 # capability. "stream" (a value-less capability) is advertised if and only
79 # if the only requirement is "revlogv1." Else, the "streamreqs" capability
79 # if the only requirement is "revlogv1." Else, the "streamreqs" capability
80 # is advertised and contains a comma-delimited list of requirements.
80 # is advertised and contains a comma-delimited list of requirements.
81 requirements = set()
81 requirements = set()
82 if remote.capable('stream'):
82 if remote.capable('stream'):
83 requirements.add('revlogv1')
83 requirements.add('revlogv1')
84 else:
84 else:
85 streamreqs = remote.capable('streamreqs')
85 streamreqs = remote.capable('streamreqs')
86 # This is weird and shouldn't happen with modern servers.
86 # This is weird and shouldn't happen with modern servers.
87 if not streamreqs:
87 if not streamreqs:
88 pullop.repo.ui.warn(_(
88 pullop.repo.ui.warn(_(
89 'warning: stream clone requested but server has them '
89 'warning: stream clone requested but server has them '
90 'disabled\n'))
90 'disabled\n'))
91 return False, None
91 return False, None
92
92
93 streamreqs = set(streamreqs.split(','))
93 streamreqs = set(streamreqs.split(','))
94 # Server requires something we don't support. Bail.
94 # Server requires something we don't support. Bail.
95 missingreqs = streamreqs - repo.supportedformats
95 missingreqs = streamreqs - repo.supportedformats
96 if missingreqs:
96 if missingreqs:
97 pullop.repo.ui.warn(_(
97 pullop.repo.ui.warn(_(
98 'warning: stream clone requested but client is missing '
98 'warning: stream clone requested but client is missing '
99 'requirements: %s\n') % ', '.join(sorted(missingreqs)))
99 'requirements: %s\n') % ', '.join(sorted(missingreqs)))
100 pullop.repo.ui.warn(
100 pullop.repo.ui.warn(
101 _('(see https://www.mercurial-scm.org/wiki/MissingRequirement '
101 _('(see https://www.mercurial-scm.org/wiki/MissingRequirement '
102 'for more information)\n'))
102 'for more information)\n'))
103 return False, None
103 return False, None
104 requirements = streamreqs
104 requirements = streamreqs
105
105
106 return True, requirements
106 return True, requirements
107
107
108 def maybeperformlegacystreamclone(pullop):
108 def maybeperformlegacystreamclone(pullop):
109 """Possibly perform a legacy stream clone operation.
109 """Possibly perform a legacy stream clone operation.
110
110
111 Legacy stream clones are performed as part of pull but before all other
111 Legacy stream clones are performed as part of pull but before all other
112 operations.
112 operations.
113
113
114 A legacy stream clone will not be performed if a bundle2 stream clone is
114 A legacy stream clone will not be performed if a bundle2 stream clone is
115 supported.
115 supported.
116 """
116 """
117 from . import localrepo
117 from . import localrepo
118
118
119 supported, requirements = canperformstreamclone(pullop)
119 supported, requirements = canperformstreamclone(pullop)
120
120
121 if not supported:
121 if not supported:
122 return
122 return
123
123
124 repo = pullop.repo
124 repo = pullop.repo
125 remote = pullop.remote
125 remote = pullop.remote
126
126
127 # Save remote branchmap. We will use it later to speed up branchcache
127 # Save remote branchmap. We will use it later to speed up branchcache
128 # creation.
128 # creation.
129 rbranchmap = None
129 rbranchmap = None
130 if remote.capable('branchmap'):
130 if remote.capable('branchmap'):
131 with remote.commandexecutor() as e:
131 with remote.commandexecutor() as e:
132 rbranchmap = e.callcommand('branchmap', {}).result()
132 rbranchmap = e.callcommand('branchmap', {}).result()
133
133
134 repo.ui.status(_('streaming all changes\n'))
134 repo.ui.status(_('streaming all changes\n'))
135
135
136 with remote.commandexecutor() as e:
136 with remote.commandexecutor() as e:
137 fp = e.callcommand('stream_out', {}).result()
137 fp = e.callcommand('stream_out', {}).result()
138
138
139 # TODO strictly speaking, this code should all be inside the context
139 # TODO strictly speaking, this code should all be inside the context
140 # manager because the context manager is supposed to ensure all wire state
140 # manager because the context manager is supposed to ensure all wire state
141 # is flushed when exiting. But the legacy peers don't do this, so it
141 # is flushed when exiting. But the legacy peers don't do this, so it
142 # doesn't matter.
142 # doesn't matter.
143 l = fp.readline()
143 l = fp.readline()
144 try:
144 try:
145 resp = int(l)
145 resp = int(l)
146 except ValueError:
146 except ValueError:
147 raise error.ResponseError(
147 raise error.ResponseError(
148 _('unexpected response from remote server:'), l)
148 _('unexpected response from remote server:'), l)
149 if resp == 1:
149 if resp == 1:
150 raise error.Abort(_('operation forbidden by server'))
150 raise error.Abort(_('operation forbidden by server'))
151 elif resp == 2:
151 elif resp == 2:
152 raise error.Abort(_('locking the remote repository failed'))
152 raise error.Abort(_('locking the remote repository failed'))
153 elif resp != 0:
153 elif resp != 0:
154 raise error.Abort(_('the server sent an unknown error code'))
154 raise error.Abort(_('the server sent an unknown error code'))
155
155
156 l = fp.readline()
156 l = fp.readline()
157 try:
157 try:
158 filecount, bytecount = map(int, l.split(' ', 1))
158 filecount, bytecount = map(int, l.split(' ', 1))
159 except (ValueError, TypeError):
159 except (ValueError, TypeError):
160 raise error.ResponseError(
160 raise error.ResponseError(
161 _('unexpected response from remote server:'), l)
161 _('unexpected response from remote server:'), l)
162
162
163 with repo.lock():
163 with repo.lock():
164 consumev1(repo, fp, filecount, bytecount)
164 consumev1(repo, fp, filecount, bytecount)
165
165
166 # new requirements = old non-format requirements +
166 # new requirements = old non-format requirements +
167 # new format-related remote requirements
167 # new format-related remote requirements
168 # requirements from the streamed-in repository
168 # requirements from the streamed-in repository
169 repo.requirements = requirements | (
169 repo.requirements = requirements | (
170 repo.requirements - repo.supportedformats)
170 repo.requirements - repo.supportedformats)
171 repo.svfs.options = localrepo.resolvestorevfsoptions(
171 repo.svfs.options = localrepo.resolvestorevfsoptions(
172 repo.ui, repo.requirements, repo.features)
172 repo.ui, repo.requirements, repo.features)
173 repo._writerequirements()
173 repo._writerequirements()
174
174
175 if rbranchmap:
175 if rbranchmap:
176 branchmap.replacecache(repo, rbranchmap)
176 branchmap.replacecache(repo, rbranchmap)
177
177
178 repo.invalidate()
178 repo.invalidate()
179
179
180 def allowservergeneration(repo):
180 def allowservergeneration(repo):
181 """Whether streaming clones are allowed from the server."""
181 """Whether streaming clones are allowed from the server."""
182 if repository.REPO_FEATURE_STREAM_CLONE not in repo.features:
182 if repository.REPO_FEATURE_STREAM_CLONE not in repo.features:
183 return False
183 return False
184
184
185 if not repo.ui.configbool('server', 'uncompressed', untrusted=True):
185 if not repo.ui.configbool('server', 'uncompressed', untrusted=True):
186 return False
186 return False
187
187
188 # The way stream clone works makes it impossible to hide secret changesets.
188 # The way stream clone works makes it impossible to hide secret changesets.
189 # So don't allow this by default.
189 # So don't allow this by default.
190 secret = phases.hassecret(repo)
190 secret = phases.hassecret(repo)
191 if secret:
191 if secret:
192 return repo.ui.configbool('server', 'uncompressedallowsecret')
192 return repo.ui.configbool('server', 'uncompressedallowsecret')
193
193
194 return True
194 return True
195
195
196 # This is it's own function so extensions can override it.
196 # This is it's own function so extensions can override it.
197 def _walkstreamfiles(repo):
197 def _walkstreamfiles(repo):
198 return repo.store.walk()
198 return repo.store.walk()
199
199
200 def generatev1(repo):
200 def generatev1(repo):
201 """Emit content for version 1 of a streaming clone.
201 """Emit content for version 1 of a streaming clone.
202
202
203 This returns a 3-tuple of (file count, byte size, data iterator).
203 This returns a 3-tuple of (file count, byte size, data iterator).
204
204
205 The data iterator consists of N entries for each file being transferred.
205 The data iterator consists of N entries for each file being transferred.
206 Each file entry starts as a line with the file name and integer size
206 Each file entry starts as a line with the file name and integer size
207 delimited by a null byte.
207 delimited by a null byte.
208
208
209 The raw file data follows. Following the raw file data is the next file
209 The raw file data follows. Following the raw file data is the next file
210 entry, or EOF.
210 entry, or EOF.
211
211
212 When used on the wire protocol, an additional line indicating protocol
212 When used on the wire protocol, an additional line indicating protocol
213 success will be prepended to the stream. This function is not responsible
213 success will be prepended to the stream. This function is not responsible
214 for adding it.
214 for adding it.
215
215
216 This function will obtain a repository lock to ensure a consistent view of
216 This function will obtain a repository lock to ensure a consistent view of
217 the store is captured. It therefore may raise LockError.
217 the store is captured. It therefore may raise LockError.
218 """
218 """
219 entries = []
219 entries = []
220 total_bytes = 0
220 total_bytes = 0
221 # Get consistent snapshot of repo, lock during scan.
221 # Get consistent snapshot of repo, lock during scan.
222 with repo.lock():
222 with repo.lock():
223 repo.ui.debug('scanning\n')
223 repo.ui.debug('scanning\n')
224 for name, ename, size in _walkstreamfiles(repo):
224 for name, ename, size in _walkstreamfiles(repo):
225 if size:
225 if size:
226 entries.append((name, size))
226 entries.append((name, size))
227 total_bytes += size
227 total_bytes += size
228
228
229 repo.ui.debug('%d files, %d bytes to transfer\n' %
229 repo.ui.debug('%d files, %d bytes to transfer\n' %
230 (len(entries), total_bytes))
230 (len(entries), total_bytes))
231
231
232 svfs = repo.svfs
232 svfs = repo.svfs
233 debugflag = repo.ui.debugflag
233 debugflag = repo.ui.debugflag
234
234
235 def emitrevlogdata():
235 def emitrevlogdata():
236 for name, size in entries:
236 for name, size in entries:
237 if debugflag:
237 if debugflag:
238 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
238 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
239 # partially encode name over the wire for backwards compat
239 # partially encode name over the wire for backwards compat
240 yield '%s\0%d\n' % (store.encodedir(name), size)
240 yield '%s\0%d\n' % (store.encodedir(name), size)
241 # auditing at this stage is both pointless (paths are already
241 # auditing at this stage is both pointless (paths are already
242 # trusted by the local repo) and expensive
242 # trusted by the local repo) and expensive
243 with svfs(name, 'rb', auditpath=False) as fp:
243 with svfs(name, 'rb', auditpath=False) as fp:
244 if size <= 65536:
244 if size <= 65536:
245 yield fp.read(size)
245 yield fp.read(size)
246 else:
246 else:
247 for chunk in util.filechunkiter(fp, limit=size):
247 for chunk in util.filechunkiter(fp, limit=size):
248 yield chunk
248 yield chunk
249
249
250 return len(entries), total_bytes, emitrevlogdata()
250 return len(entries), total_bytes, emitrevlogdata()
251
251
252 def generatev1wireproto(repo):
252 def generatev1wireproto(repo):
253 """Emit content for version 1 of streaming clone suitable for the wire.
253 """Emit content for version 1 of streaming clone suitable for the wire.
254
254
255 This is the data output from ``generatev1()`` with 2 header lines. The
255 This is the data output from ``generatev1()`` with 2 header lines. The
256 first line indicates overall success. The 2nd contains the file count and
256 first line indicates overall success. The 2nd contains the file count and
257 byte size of payload.
257 byte size of payload.
258
258
259 The success line contains "0" for success, "1" for stream generation not
259 The success line contains "0" for success, "1" for stream generation not
260 allowed, and "2" for error locking the repository (possibly indicating
260 allowed, and "2" for error locking the repository (possibly indicating
261 a permissions error for the server process).
261 a permissions error for the server process).
262 """
262 """
263 if not allowservergeneration(repo):
263 if not allowservergeneration(repo):
264 yield '1\n'
264 yield '1\n'
265 return
265 return
266
266
267 try:
267 try:
268 filecount, bytecount, it = generatev1(repo)
268 filecount, bytecount, it = generatev1(repo)
269 except error.LockError:
269 except error.LockError:
270 yield '2\n'
270 yield '2\n'
271 return
271 return
272
272
273 # Indicates successful response.
273 # Indicates successful response.
274 yield '0\n'
274 yield '0\n'
275 yield '%d %d\n' % (filecount, bytecount)
275 yield '%d %d\n' % (filecount, bytecount)
276 for chunk in it:
276 for chunk in it:
277 yield chunk
277 yield chunk
278
278
279 def generatebundlev1(repo, compression='UN'):
279 def generatebundlev1(repo, compression='UN'):
280 """Emit content for version 1 of a stream clone bundle.
280 """Emit content for version 1 of a stream clone bundle.
281
281
282 The first 4 bytes of the output ("HGS1") denote this as stream clone
282 The first 4 bytes of the output ("HGS1") denote this as stream clone
283 bundle version 1.
283 bundle version 1.
284
284
285 The next 2 bytes indicate the compression type. Only "UN" is currently
285 The next 2 bytes indicate the compression type. Only "UN" is currently
286 supported.
286 supported.
287
287
288 The next 16 bytes are two 64-bit big endian unsigned integers indicating
288 The next 16 bytes are two 64-bit big endian unsigned integers indicating
289 file count and byte count, respectively.
289 file count and byte count, respectively.
290
290
291 The next 2 bytes is a 16-bit big endian unsigned short declaring the length
291 The next 2 bytes is a 16-bit big endian unsigned short declaring the length
292 of the requirements string, including a trailing \0. The following N bytes
292 of the requirements string, including a trailing \0. The following N bytes
293 are the requirements string, which is ASCII containing a comma-delimited
293 are the requirements string, which is ASCII containing a comma-delimited
294 list of repo requirements that are needed to support the data.
294 list of repo requirements that are needed to support the data.
295
295
296 The remaining content is the output of ``generatev1()`` (which may be
296 The remaining content is the output of ``generatev1()`` (which may be
297 compressed in the future).
297 compressed in the future).
298
298
299 Returns a tuple of (requirements, data generator).
299 Returns a tuple of (requirements, data generator).
300 """
300 """
301 if compression != 'UN':
301 if compression != 'UN':
302 raise ValueError('we do not support the compression argument yet')
302 raise ValueError('we do not support the compression argument yet')
303
303
304 requirements = repo.requirements & repo.supportedformats
304 requirements = repo.requirements & repo.supportedformats
305 requires = ','.join(sorted(requirements))
305 requires = ','.join(sorted(requirements))
306
306
307 def gen():
307 def gen():
308 yield 'HGS1'
308 yield 'HGS1'
309 yield compression
309 yield compression
310
310
311 filecount, bytecount, it = generatev1(repo)
311 filecount, bytecount, it = generatev1(repo)
312 repo.ui.status(_('writing %d bytes for %d files\n') %
312 repo.ui.status(_('writing %d bytes for %d files\n') %
313 (bytecount, filecount))
313 (bytecount, filecount))
314
314
315 yield struct.pack('>QQ', filecount, bytecount)
315 yield struct.pack('>QQ', filecount, bytecount)
316 yield struct.pack('>H', len(requires) + 1)
316 yield struct.pack('>H', len(requires) + 1)
317 yield requires + '\0'
317 yield requires + '\0'
318
318
319 # This is where we'll add compression in the future.
319 # This is where we'll add compression in the future.
320 assert compression == 'UN'
320 assert compression == 'UN'
321
321
322 progress = repo.ui.makeprogress(_('bundle'), total=bytecount,
322 progress = repo.ui.makeprogress(_('bundle'), total=bytecount,
323 unit=_('bytes'))
323 unit=_('bytes'))
324 progress.update(0)
324 progress.update(0)
325
325
326 for chunk in it:
326 for chunk in it:
327 progress.increment(step=len(chunk))
327 progress.increment(step=len(chunk))
328 yield chunk
328 yield chunk
329
329
330 progress.complete()
330 progress.complete()
331
331
332 return requirements, gen()
332 return requirements, gen()
333
333
334 def consumev1(repo, fp, filecount, bytecount):
334 def consumev1(repo, fp, filecount, bytecount):
335 """Apply the contents from version 1 of a streaming clone file handle.
335 """Apply the contents from version 1 of a streaming clone file handle.
336
336
337 This takes the output from "stream_out" and applies it to the specified
337 This takes the output from "stream_out" and applies it to the specified
338 repository.
338 repository.
339
339
340 Like "stream_out," the status line added by the wire protocol is not
340 Like "stream_out," the status line added by the wire protocol is not
341 handled by this function.
341 handled by this function.
342 """
342 """
343 with repo.lock():
343 with repo.lock():
344 repo.ui.status(_('%d files to transfer, %s of data\n') %
344 repo.ui.status(_('%d files to transfer, %s of data\n') %
345 (filecount, util.bytecount(bytecount)))
345 (filecount, util.bytecount(bytecount)))
346 progress = repo.ui.makeprogress(_('clone'), total=bytecount,
346 progress = repo.ui.makeprogress(_('clone'), total=bytecount,
347 unit=_('bytes'))
347 unit=_('bytes'))
348 progress.update(0)
348 progress.update(0)
349 start = util.timer()
349 start = util.timer()
350
350
351 # TODO: get rid of (potential) inconsistency
351 # TODO: get rid of (potential) inconsistency
352 #
352 #
353 # If transaction is started and any @filecache property is
353 # If transaction is started and any @filecache property is
354 # changed at this point, it causes inconsistency between
354 # changed at this point, it causes inconsistency between
355 # in-memory cached property and streamclone-ed file on the
355 # in-memory cached property and streamclone-ed file on the
356 # disk. Nested transaction prevents transaction scope "clone"
356 # disk. Nested transaction prevents transaction scope "clone"
357 # below from writing in-memory changes out at the end of it,
357 # below from writing in-memory changes out at the end of it,
358 # even though in-memory changes are discarded at the end of it
358 # even though in-memory changes are discarded at the end of it
359 # regardless of transaction nesting.
359 # regardless of transaction nesting.
360 #
360 #
361 # But transaction nesting can't be simply prohibited, because
361 # But transaction nesting can't be simply prohibited, because
362 # nesting occurs also in ordinary case (e.g. enabling
362 # nesting occurs also in ordinary case (e.g. enabling
363 # clonebundles).
363 # clonebundles).
364
364
365 with repo.transaction('clone'):
365 with repo.transaction('clone'):
366 with repo.svfs.backgroundclosing(repo.ui, expectedcount=filecount):
366 with repo.svfs.backgroundclosing(repo.ui, expectedcount=filecount):
367 for i in pycompat.xrange(filecount):
367 for i in pycompat.xrange(filecount):
368 # XXX doesn't support '\n' or '\r' in filenames
368 # XXX doesn't support '\n' or '\r' in filenames
369 l = fp.readline()
369 l = fp.readline()
370 try:
370 try:
371 name, size = l.split('\0', 1)
371 name, size = l.split('\0', 1)
372 size = int(size)
372 size = int(size)
373 except (ValueError, TypeError):
373 except (ValueError, TypeError):
374 raise error.ResponseError(
374 raise error.ResponseError(
375 _('unexpected response from remote server:'), l)
375 _('unexpected response from remote server:'), l)
376 if repo.ui.debugflag:
376 if repo.ui.debugflag:
377 repo.ui.debug('adding %s (%s)\n' %
377 repo.ui.debug('adding %s (%s)\n' %
378 (name, util.bytecount(size)))
378 (name, util.bytecount(size)))
379 # for backwards compat, name was partially encoded
379 # for backwards compat, name was partially encoded
380 path = store.decodedir(name)
380 path = store.decodedir(name)
381 with repo.svfs(path, 'w', backgroundclose=True) as ofp:
381 with repo.svfs(path, 'w', backgroundclose=True) as ofp:
382 for chunk in util.filechunkiter(fp, limit=size):
382 for chunk in util.filechunkiter(fp, limit=size):
383 progress.increment(step=len(chunk))
383 progress.increment(step=len(chunk))
384 ofp.write(chunk)
384 ofp.write(chunk)
385
385
386 # force @filecache properties to be reloaded from
386 # force @filecache properties to be reloaded from
387 # streamclone-ed file at next access
387 # streamclone-ed file at next access
388 repo.invalidate(clearfilecache=True)
388 repo.invalidate(clearfilecache=True)
389
389
390 elapsed = util.timer() - start
390 elapsed = util.timer() - start
391 if elapsed <= 0:
391 if elapsed <= 0:
392 elapsed = 0.001
392 elapsed = 0.001
393 progress.complete()
393 progress.complete()
394 repo.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
394 repo.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
395 (util.bytecount(bytecount), elapsed,
395 (util.bytecount(bytecount), elapsed,
396 util.bytecount(bytecount / elapsed)))
396 util.bytecount(bytecount / elapsed)))
397
397
398 def readbundle1header(fp):
398 def readbundle1header(fp):
399 compression = fp.read(2)
399 compression = fp.read(2)
400 if compression != 'UN':
400 if compression != 'UN':
401 raise error.Abort(_('only uncompressed stream clone bundles are '
401 raise error.Abort(_('only uncompressed stream clone bundles are '
402 'supported; got %s') % compression)
402 'supported; got %s') % compression)
403
403
404 filecount, bytecount = struct.unpack('>QQ', fp.read(16))
404 filecount, bytecount = struct.unpack('>QQ', fp.read(16))
405 requireslen = struct.unpack('>H', fp.read(2))[0]
405 requireslen = struct.unpack('>H', fp.read(2))[0]
406 requires = fp.read(requireslen)
406 requires = fp.read(requireslen)
407
407
408 if not requires.endswith('\0'):
408 if not requires.endswith('\0'):
409 raise error.Abort(_('malformed stream clone bundle: '
409 raise error.Abort(_('malformed stream clone bundle: '
410 'requirements not properly encoded'))
410 'requirements not properly encoded'))
411
411
412 requirements = set(requires.rstrip('\0').split(','))
412 requirements = set(requires.rstrip('\0').split(','))
413
413
414 return filecount, bytecount, requirements
414 return filecount, bytecount, requirements
415
415
416 def applybundlev1(repo, fp):
416 def applybundlev1(repo, fp):
417 """Apply the content from a stream clone bundle version 1.
417 """Apply the content from a stream clone bundle version 1.
418
418
419 We assume the 4 byte header has been read and validated and the file handle
419 We assume the 4 byte header has been read and validated and the file handle
420 is at the 2 byte compression identifier.
420 is at the 2 byte compression identifier.
421 """
421 """
422 if len(repo):
422 if len(repo):
423 raise error.Abort(_('cannot apply stream clone bundle on non-empty '
423 raise error.Abort(_('cannot apply stream clone bundle on non-empty '
424 'repo'))
424 'repo'))
425
425
426 filecount, bytecount, requirements = readbundle1header(fp)
426 filecount, bytecount, requirements = readbundle1header(fp)
427 missingreqs = requirements - repo.supportedformats
427 missingreqs = requirements - repo.supportedformats
428 if missingreqs:
428 if missingreqs:
429 raise error.Abort(_('unable to apply stream clone: '
429 raise error.Abort(_('unable to apply stream clone: '
430 'unsupported format: %s') %
430 'unsupported format: %s') %
431 ', '.join(sorted(missingreqs)))
431 ', '.join(sorted(missingreqs)))
432
432
433 consumev1(repo, fp, filecount, bytecount)
433 consumev1(repo, fp, filecount, bytecount)
434
434
435 class streamcloneapplier(object):
435 class streamcloneapplier(object):
436 """Class to manage applying streaming clone bundles.
436 """Class to manage applying streaming clone bundles.
437
437
438 We need to wrap ``applybundlev1()`` in a dedicated type to enable bundle
438 We need to wrap ``applybundlev1()`` in a dedicated type to enable bundle
439 readers to perform bundle type-specific functionality.
439 readers to perform bundle type-specific functionality.
440 """
440 """
441 def __init__(self, fh):
441 def __init__(self, fh):
442 self._fh = fh
442 self._fh = fh
443
443
444 def apply(self, repo):
444 def apply(self, repo):
445 return applybundlev1(repo, self._fh)
445 return applybundlev1(repo, self._fh)
446
446
447 # type of file to stream
447 # type of file to stream
448 _fileappend = 0 # append only file
448 _fileappend = 0 # append only file
449 _filefull = 1 # full snapshot file
449 _filefull = 1 # full snapshot file
450
450
451 # Source of the file
451 # Source of the file
452 _srcstore = 's' # store (svfs)
452 _srcstore = 's' # store (svfs)
453 _srccache = 'c' # cache (cache)
453 _srccache = 'c' # cache (cache)
454
454
455 # This is it's own function so extensions can override it.
455 # This is it's own function so extensions can override it.
456 def _walkstreamfullstorefiles(repo):
456 def _walkstreamfullstorefiles(repo):
457 """list snapshot file from the store"""
457 """list snapshot file from the store"""
458 fnames = []
458 fnames = []
459 if not repo.publishing():
459 if not repo.publishing():
460 fnames.append('phaseroots')
460 fnames.append('phaseroots')
461 return fnames
461 return fnames
462
462
463 def _filterfull(entry, copy, vfsmap):
463 def _filterfull(entry, copy, vfsmap):
464 """actually copy the snapshot files"""
464 """actually copy the snapshot files"""
465 src, name, ftype, data = entry
465 src, name, ftype, data = entry
466 if ftype != _filefull:
466 if ftype != _filefull:
467 return entry
467 return entry
468 return (src, name, ftype, copy(vfsmap[src].join(name)))
468 return (src, name, ftype, copy(vfsmap[src].join(name)))
469
469
470 @contextlib.contextmanager
470 @contextlib.contextmanager
471 def maketempcopies():
471 def maketempcopies():
472 """return a function to temporary copy file"""
472 """return a function to temporary copy file"""
473 files = []
473 files = []
474 try:
474 try:
475 def copy(src):
475 def copy(src):
476 fd, dst = pycompat.mkstemp()
476 fd, dst = pycompat.mkstemp()
477 os.close(fd)
477 os.close(fd)
478 files.append(dst)
478 files.append(dst)
479 util.copyfiles(src, dst, hardlink=True)
479 util.copyfiles(src, dst, hardlink=True)
480 return dst
480 return dst
481 yield copy
481 yield copy
482 finally:
482 finally:
483 for tmp in files:
483 for tmp in files:
484 util.tryunlink(tmp)
484 util.tryunlink(tmp)
485
485
486 def _makemap(repo):
486 def _makemap(repo):
487 """make a (src -> vfs) map for the repo"""
487 """make a (src -> vfs) map for the repo"""
488 vfsmap = {
488 vfsmap = {
489 _srcstore: repo.svfs,
489 _srcstore: repo.svfs,
490 _srccache: repo.cachevfs,
490 _srccache: repo.cachevfs,
491 }
491 }
492 # we keep repo.vfs out of the on purpose, ther are too many danger there
492 # we keep repo.vfs out of the on purpose, ther are too many danger there
493 # (eg: .hg/hgrc)
493 # (eg: .hg/hgrc)
494 assert repo.vfs not in vfsmap.values()
494 assert repo.vfs not in vfsmap.values()
495
495
496 return vfsmap
496 return vfsmap
497
497
498 def _emit2(repo, entries, totalfilesize):
498 def _emit2(repo, entries, totalfilesize):
499 """actually emit the stream bundle"""
499 """actually emit the stream bundle"""
500 vfsmap = _makemap(repo)
500 vfsmap = _makemap(repo)
501 progress = repo.ui.makeprogress(_('bundle'), total=totalfilesize,
501 progress = repo.ui.makeprogress(_('bundle'), total=totalfilesize,
502 unit=_('bytes'))
502 unit=_('bytes'))
503 progress.update(0)
503 progress.update(0)
504 with maketempcopies() as copy, progress:
504 with maketempcopies() as copy, progress:
505 # copy is delayed until we are in the try
505 # copy is delayed until we are in the try
506 entries = [_filterfull(e, copy, vfsmap) for e in entries]
506 entries = [_filterfull(e, copy, vfsmap) for e in entries]
507 yield None # this release the lock on the repository
507 yield None # this release the lock on the repository
508 seen = 0
508 seen = 0
509
509
510 for src, name, ftype, data in entries:
510 for src, name, ftype, data in entries:
511 vfs = vfsmap[src]
511 vfs = vfsmap[src]
512 yield src
512 yield src
513 yield util.uvarintencode(len(name))
513 yield util.uvarintencode(len(name))
514 if ftype == _fileappend:
514 if ftype == _fileappend:
515 fp = vfs(name)
515 fp = vfs(name)
516 size = data
516 size = data
517 elif ftype == _filefull:
517 elif ftype == _filefull:
518 fp = open(data, 'rb')
518 fp = open(data, 'rb')
519 size = util.fstat(fp).st_size
519 size = util.fstat(fp).st_size
520 try:
520 try:
521 yield util.uvarintencode(size)
521 yield util.uvarintencode(size)
522 yield name
522 yield name
523 if size <= 65536:
523 if size <= 65536:
524 chunks = (fp.read(size),)
524 chunks = (fp.read(size),)
525 else:
525 else:
526 chunks = util.filechunkiter(fp, limit=size)
526 chunks = util.filechunkiter(fp, limit=size)
527 for chunk in chunks:
527 for chunk in chunks:
528 seen += len(chunk)
528 seen += len(chunk)
529 progress.update(seen)
529 progress.update(seen)
530 yield chunk
530 yield chunk
531 finally:
531 finally:
532 fp.close()
532 fp.close()
533
533
534 def generatev2(repo):
534 def generatev2(repo, includes, excludes):
535 """Emit content for version 2 of a streaming clone.
535 """Emit content for version 2 of a streaming clone.
536
536
537 the data stream consists the following entries:
537 the data stream consists the following entries:
538 1) A char representing the file destination (eg: store or cache)
538 1) A char representing the file destination (eg: store or cache)
539 2) A varint containing the length of the filename
539 2) A varint containing the length of the filename
540 3) A varint containing the length of file data
540 3) A varint containing the length of file data
541 4) N bytes containing the filename (the internal, store-agnostic form)
541 4) N bytes containing the filename (the internal, store-agnostic form)
542 5) N bytes containing the file data
542 5) N bytes containing the file data
543
543
544 Returns a 3-tuple of (file count, file size, data iterator).
544 Returns a 3-tuple of (file count, file size, data iterator).
545 """
545 """
546
546
547 # temporarily raise error until we add storage level logic
548 if includes or excludes:
549 raise error.Abort(_("server does not support narrow stream clones"))
550
547 with repo.lock():
551 with repo.lock():
548
552
549 entries = []
553 entries = []
550 totalfilesize = 0
554 totalfilesize = 0
551
555
552 repo.ui.debug('scanning\n')
556 repo.ui.debug('scanning\n')
553 for name, ename, size in _walkstreamfiles(repo):
557 for name, ename, size in _walkstreamfiles(repo):
554 if size:
558 if size:
555 entries.append((_srcstore, name, _fileappend, size))
559 entries.append((_srcstore, name, _fileappend, size))
556 totalfilesize += size
560 totalfilesize += size
557 for name in _walkstreamfullstorefiles(repo):
561 for name in _walkstreamfullstorefiles(repo):
558 if repo.svfs.exists(name):
562 if repo.svfs.exists(name):
559 totalfilesize += repo.svfs.lstat(name).st_size
563 totalfilesize += repo.svfs.lstat(name).st_size
560 entries.append((_srcstore, name, _filefull, None))
564 entries.append((_srcstore, name, _filefull, None))
561 for name in cacheutil.cachetocopy(repo):
565 for name in cacheutil.cachetocopy(repo):
562 if repo.cachevfs.exists(name):
566 if repo.cachevfs.exists(name):
563 totalfilesize += repo.cachevfs.lstat(name).st_size
567 totalfilesize += repo.cachevfs.lstat(name).st_size
564 entries.append((_srccache, name, _filefull, None))
568 entries.append((_srccache, name, _filefull, None))
565
569
566 chunks = _emit2(repo, entries, totalfilesize)
570 chunks = _emit2(repo, entries, totalfilesize)
567 first = next(chunks)
571 first = next(chunks)
568 assert first is None
572 assert first is None
569
573
570 return len(entries), totalfilesize, chunks
574 return len(entries), totalfilesize, chunks
571
575
572 @contextlib.contextmanager
576 @contextlib.contextmanager
573 def nested(*ctxs):
577 def nested(*ctxs):
574 this = ctxs[0]
578 this = ctxs[0]
575 rest = ctxs[1:]
579 rest = ctxs[1:]
576 with this:
580 with this:
577 if rest:
581 if rest:
578 with nested(*rest):
582 with nested(*rest):
579 yield
583 yield
580 else:
584 else:
581 yield
585 yield
582
586
583 def consumev2(repo, fp, filecount, filesize):
587 def consumev2(repo, fp, filecount, filesize):
584 """Apply the contents from a version 2 streaming clone.
588 """Apply the contents from a version 2 streaming clone.
585
589
586 Data is read from an object that only needs to provide a ``read(size)``
590 Data is read from an object that only needs to provide a ``read(size)``
587 method.
591 method.
588 """
592 """
589 with repo.lock():
593 with repo.lock():
590 repo.ui.status(_('%d files to transfer, %s of data\n') %
594 repo.ui.status(_('%d files to transfer, %s of data\n') %
591 (filecount, util.bytecount(filesize)))
595 (filecount, util.bytecount(filesize)))
592
596
593 start = util.timer()
597 start = util.timer()
594 progress = repo.ui.makeprogress(_('clone'), total=filesize,
598 progress = repo.ui.makeprogress(_('clone'), total=filesize,
595 unit=_('bytes'))
599 unit=_('bytes'))
596 progress.update(0)
600 progress.update(0)
597
601
598 vfsmap = _makemap(repo)
602 vfsmap = _makemap(repo)
599
603
600 with repo.transaction('clone'):
604 with repo.transaction('clone'):
601 ctxs = (vfs.backgroundclosing(repo.ui)
605 ctxs = (vfs.backgroundclosing(repo.ui)
602 for vfs in vfsmap.values())
606 for vfs in vfsmap.values())
603 with nested(*ctxs):
607 with nested(*ctxs):
604 for i in range(filecount):
608 for i in range(filecount):
605 src = util.readexactly(fp, 1)
609 src = util.readexactly(fp, 1)
606 vfs = vfsmap[src]
610 vfs = vfsmap[src]
607 namelen = util.uvarintdecodestream(fp)
611 namelen = util.uvarintdecodestream(fp)
608 datalen = util.uvarintdecodestream(fp)
612 datalen = util.uvarintdecodestream(fp)
609
613
610 name = util.readexactly(fp, namelen)
614 name = util.readexactly(fp, namelen)
611
615
612 if repo.ui.debugflag:
616 if repo.ui.debugflag:
613 repo.ui.debug('adding [%s] %s (%s)\n' %
617 repo.ui.debug('adding [%s] %s (%s)\n' %
614 (src, name, util.bytecount(datalen)))
618 (src, name, util.bytecount(datalen)))
615
619
616 with vfs(name, 'w') as ofp:
620 with vfs(name, 'w') as ofp:
617 for chunk in util.filechunkiter(fp, limit=datalen):
621 for chunk in util.filechunkiter(fp, limit=datalen):
618 progress.increment(step=len(chunk))
622 progress.increment(step=len(chunk))
619 ofp.write(chunk)
623 ofp.write(chunk)
620
624
621 # force @filecache properties to be reloaded from
625 # force @filecache properties to be reloaded from
622 # streamclone-ed file at next access
626 # streamclone-ed file at next access
623 repo.invalidate(clearfilecache=True)
627 repo.invalidate(clearfilecache=True)
624
628
625 elapsed = util.timer() - start
629 elapsed = util.timer() - start
626 if elapsed <= 0:
630 if elapsed <= 0:
627 elapsed = 0.001
631 elapsed = 0.001
628 repo.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
632 repo.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
629 (util.bytecount(progress.pos), elapsed,
633 (util.bytecount(progress.pos), elapsed,
630 util.bytecount(progress.pos / elapsed)))
634 util.bytecount(progress.pos / elapsed)))
631 progress.complete()
635 progress.complete()
632
636
633 def applybundlev2(repo, fp, filecount, filesize, requirements):
637 def applybundlev2(repo, fp, filecount, filesize, requirements):
634 from . import localrepo
638 from . import localrepo
635
639
636 missingreqs = [r for r in requirements if r not in repo.supported]
640 missingreqs = [r for r in requirements if r not in repo.supported]
637 if missingreqs:
641 if missingreqs:
638 raise error.Abort(_('unable to apply stream clone: '
642 raise error.Abort(_('unable to apply stream clone: '
639 'unsupported format: %s') %
643 'unsupported format: %s') %
640 ', '.join(sorted(missingreqs)))
644 ', '.join(sorted(missingreqs)))
641
645
642 consumev2(repo, fp, filecount, filesize)
646 consumev2(repo, fp, filecount, filesize)
643
647
644 # new requirements = old non-format requirements +
648 # new requirements = old non-format requirements +
645 # new format-related remote requirements
649 # new format-related remote requirements
646 # requirements from the streamed-in repository
650 # requirements from the streamed-in repository
647 repo.requirements = set(requirements) | (
651 repo.requirements = set(requirements) | (
648 repo.requirements - repo.supportedformats)
652 repo.requirements - repo.supportedformats)
649 repo.svfs.options = localrepo.resolvestorevfsoptions(
653 repo.svfs.options = localrepo.resolvestorevfsoptions(
650 repo.ui, repo.requirements, repo.features)
654 repo.ui, repo.requirements, repo.features)
651 repo._writerequirements()
655 repo._writerequirements()
General Comments 0
You need to be logged in to leave comments. Login now