##// END OF EJS Templates
stream-clone: introduce the notion of an experimental "v3" version...
marmoute -
r51417:58adcabc default
parent child Browse files
Show More
@@ -1,1261 +1,1259
1 1 # __init__.py - remotefilelog extension
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """remotefilelog causes Mercurial to lazilly fetch file contents (EXPERIMENTAL)
8 8
9 9 This extension is HIGHLY EXPERIMENTAL. There are NO BACKWARDS COMPATIBILITY
10 10 GUARANTEES. This means that repositories created with this extension may
11 11 only be usable with the exact version of this extension/Mercurial that was
12 12 used. The extension attempts to enforce this in order to prevent repository
13 13 corruption.
14 14
15 15 remotefilelog works by fetching file contents lazily and storing them
16 16 in a cache on the client rather than in revlogs. This allows enormous
17 17 histories to be transferred only partially, making them easier to
18 18 operate on.
19 19
20 20 Configs:
21 21
22 22 ``packs.maxchainlen`` specifies the maximum delta chain length in pack files
23 23
24 24 ``packs.maxpacksize`` specifies the maximum pack file size
25 25
26 26 ``packs.maxpackfilecount`` specifies the maximum number of packs in the
27 27 shared cache (trees only for now)
28 28
29 29 ``remotefilelog.backgroundprefetch`` runs prefetch in background when True
30 30
31 31 ``remotefilelog.bgprefetchrevs`` specifies revisions to fetch on commit and
32 32 update, and on other commands that use them. Different from pullprefetch.
33 33
34 34 ``remotefilelog.gcrepack`` does garbage collection during repack when True
35 35
36 36 ``remotefilelog.nodettl`` specifies maximum TTL of a node in seconds before
37 37 it is garbage collected
38 38
39 39 ``remotefilelog.repackonhggc`` runs repack on hg gc when True
40 40
41 41 ``remotefilelog.prefetchdays`` specifies the maximum age of a commit in
42 42 days after which it is no longer prefetched.
43 43
44 44 ``remotefilelog.prefetchdelay`` specifies delay between background
45 45 prefetches in seconds after operations that change the working copy parent
46 46
47 47 ``remotefilelog.data.gencountlimit`` constraints the minimum number of data
48 48 pack files required to be considered part of a generation. In particular,
49 49 minimum number of packs files > gencountlimit.
50 50
51 51 ``remotefilelog.data.generations`` list for specifying the lower bound of
52 52 each generation of the data pack files. For example, list ['100MB','1MB']
53 53 or ['1MB', '100MB'] will lead to three generations: [0, 1MB), [
54 54 1MB, 100MB) and [100MB, infinity).
55 55
56 56 ``remotefilelog.data.maxrepackpacks`` the maximum number of pack files to
57 57 include in an incremental data repack.
58 58
59 59 ``remotefilelog.data.repackmaxpacksize`` the maximum size of a pack file for
60 60 it to be considered for an incremental data repack.
61 61
62 62 ``remotefilelog.data.repacksizelimit`` the maximum total size of pack files
63 63 to include in an incremental data repack.
64 64
65 65 ``remotefilelog.history.gencountlimit`` constraints the minimum number of
66 66 history pack files required to be considered part of a generation. In
67 67 particular, minimum number of packs files > gencountlimit.
68 68
69 69 ``remotefilelog.history.generations`` list for specifying the lower bound of
70 70 each generation of the history pack files. For example, list [
71 71 '100MB', '1MB'] or ['1MB', '100MB'] will lead to three generations: [
72 72 0, 1MB), [1MB, 100MB) and [100MB, infinity).
73 73
74 74 ``remotefilelog.history.maxrepackpacks`` the maximum number of pack files to
75 75 include in an incremental history repack.
76 76
77 77 ``remotefilelog.history.repackmaxpacksize`` the maximum size of a pack file
78 78 for it to be considered for an incremental history repack.
79 79
80 80 ``remotefilelog.history.repacksizelimit`` the maximum total size of pack
81 81 files to include in an incremental history repack.
82 82
83 83 ``remotefilelog.backgroundrepack`` automatically consolidate packs in the
84 84 background
85 85
86 86 ``remotefilelog.cachepath`` path to cache
87 87
88 88 ``remotefilelog.cachegroup`` if set, make cache directory sgid to this
89 89 group
90 90
91 91 ``remotefilelog.cacheprocess`` binary to invoke for fetching file data
92 92
93 93 ``remotefilelog.debug`` turn on remotefilelog-specific debug output
94 94
95 95 ``remotefilelog.excludepattern`` pattern of files to exclude from pulls
96 96
97 97 ``remotefilelog.includepattern`` pattern of files to include in pulls
98 98
99 99 ``remotefilelog.fetchwarning``: message to print when too many
100 100 single-file fetches occur
101 101
102 102 ``remotefilelog.getfilesstep`` number of files to request in a single RPC
103 103
104 104 ``remotefilelog.getfilestype`` if set to 'threaded' use threads to fetch
105 105 files, otherwise use optimistic fetching
106 106
107 107 ``remotefilelog.pullprefetch`` revset for selecting files that should be
108 108 eagerly downloaded rather than lazily
109 109
110 110 ``remotefilelog.reponame`` name of the repo. If set, used to partition
111 111 data from other repos in a shared store.
112 112
113 113 ``remotefilelog.server`` if true, enable server-side functionality
114 114
115 115 ``remotefilelog.servercachepath`` path for caching blobs on the server
116 116
117 117 ``remotefilelog.serverexpiration`` number of days to keep cached server
118 118 blobs
119 119
120 120 ``remotefilelog.validatecache`` if set, check cache entries for corruption
121 121 before returning blobs
122 122
123 123 ``remotefilelog.validatecachelog`` if set, check cache entries for
124 124 corruption before returning metadata
125 125
126 126 """
127 127
128 128 import os
129 129 import time
130 130 import traceback
131 131
132 132 from mercurial.node import (
133 133 hex,
134 134 wdirrev,
135 135 )
136 136 from mercurial.i18n import _
137 137 from mercurial.pycompat import open
138 138 from mercurial import (
139 139 changegroup,
140 140 changelog,
141 141 commands,
142 142 configitems,
143 143 context,
144 144 copies,
145 145 debugcommands as hgdebugcommands,
146 146 dispatch,
147 147 error,
148 148 exchange,
149 149 extensions,
150 150 hg,
151 151 localrepo,
152 152 match as matchmod,
153 153 merge,
154 154 mergestate as mergestatemod,
155 155 patch,
156 156 pycompat,
157 157 registrar,
158 158 repair,
159 159 repoview,
160 160 revset,
161 161 scmutil,
162 162 smartset,
163 163 streamclone,
164 164 util,
165 165 )
166 166 from . import (
167 167 constants,
168 168 debugcommands,
169 169 fileserverclient,
170 170 remotefilectx,
171 171 remotefilelog,
172 172 remotefilelogserver,
173 173 repack as repackmod,
174 174 shallowbundle,
175 175 shallowrepo,
176 176 shallowstore,
177 177 shallowutil,
178 178 shallowverifier,
179 179 )
180 180
181 181 # ensures debug commands are registered
182 182 hgdebugcommands.command
183 183
184 184 cmdtable = {}
185 185 command = registrar.command(cmdtable)
186 186
187 187 configtable = {}
188 188 configitem = registrar.configitem(configtable)
189 189
190 190 configitem(b'remotefilelog', b'debug', default=False)
191 191
192 192 configitem(b'remotefilelog', b'reponame', default=b'')
193 193 configitem(b'remotefilelog', b'cachepath', default=None)
194 194 configitem(b'remotefilelog', b'cachegroup', default=None)
195 195 configitem(b'remotefilelog', b'cacheprocess', default=None)
196 196 configitem(b'remotefilelog', b'cacheprocess.includepath', default=None)
197 197 configitem(b"remotefilelog", b"cachelimit", default=b"1000 GB")
198 198
199 199 configitem(
200 200 b'remotefilelog',
201 201 b'fallbackpath',
202 202 default=configitems.dynamicdefault,
203 203 alias=[(b'remotefilelog', b'fallbackrepo')],
204 204 )
205 205
206 206 configitem(b'remotefilelog', b'validatecachelog', default=None)
207 207 configitem(b'remotefilelog', b'validatecache', default=b'on')
208 208 configitem(b'remotefilelog', b'server', default=None)
209 209 configitem(b'remotefilelog', b'servercachepath', default=None)
210 210 configitem(b"remotefilelog", b"serverexpiration", default=30)
211 211 configitem(b'remotefilelog', b'backgroundrepack', default=False)
212 212 configitem(b'remotefilelog', b'bgprefetchrevs', default=None)
213 213 configitem(b'remotefilelog', b'pullprefetch', default=None)
214 214 configitem(b'remotefilelog', b'backgroundprefetch', default=False)
215 215 configitem(b'remotefilelog', b'prefetchdelay', default=120)
216 216 configitem(b'remotefilelog', b'prefetchdays', default=14)
217 217 # Other values include 'local' or 'none'. Any unrecognized value is 'all'.
218 218 configitem(b'remotefilelog', b'strip.includefiles', default='all')
219 219
220 220 configitem(b'remotefilelog', b'getfilesstep', default=10000)
221 221 configitem(b'remotefilelog', b'getfilestype', default=b'optimistic')
222 222 configitem(b'remotefilelog', b'batchsize', configitems.dynamicdefault)
223 223 configitem(b'remotefilelog', b'fetchwarning', default=b'')
224 224
225 225 configitem(b'remotefilelog', b'includepattern', default=None)
226 226 configitem(b'remotefilelog', b'excludepattern', default=None)
227 227
228 228 configitem(b'remotefilelog', b'gcrepack', default=False)
229 229 configitem(b'remotefilelog', b'repackonhggc', default=False)
230 230 configitem(b'repack', b'chainorphansbysize', default=True, experimental=True)
231 231
232 232 configitem(b'packs', b'maxpacksize', default=0)
233 233 configitem(b'packs', b'maxchainlen', default=1000)
234 234
235 235 configitem(b'devel', b'remotefilelog.bg-wait', default=False)
236 236
237 237 # default TTL limit is 30 days
238 238 _defaultlimit = 60 * 60 * 24 * 30
239 239 configitem(b'remotefilelog', b'nodettl', default=_defaultlimit)
240 240
241 241 configitem(b'remotefilelog', b'data.gencountlimit', default=2),
242 242 configitem(
243 243 b'remotefilelog', b'data.generations', default=[b'1GB', b'100MB', b'1MB']
244 244 )
245 245 configitem(b'remotefilelog', b'data.maxrepackpacks', default=50)
246 246 configitem(b'remotefilelog', b'data.repackmaxpacksize', default=b'4GB')
247 247 configitem(b'remotefilelog', b'data.repacksizelimit', default=b'100MB')
248 248
249 249 configitem(b'remotefilelog', b'history.gencountlimit', default=2),
250 250 configitem(b'remotefilelog', b'history.generations', default=[b'100MB'])
251 251 configitem(b'remotefilelog', b'history.maxrepackpacks', default=50)
252 252 configitem(b'remotefilelog', b'history.repackmaxpacksize', default=b'400MB')
253 253 configitem(b'remotefilelog', b'history.repacksizelimit', default=b'100MB')
254 254
255 255 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
256 256 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
257 257 # be specifying the version(s) of Mercurial they are tested with, or
258 258 # leave the attribute unspecified.
259 259 testedwith = b'ships-with-hg-core'
260 260
261 261 repoclass = localrepo.localrepository
262 262 repoclass._basesupported.add(constants.SHALLOWREPO_REQUIREMENT)
263 263
264 264 isenabled = shallowutil.isenabled
265 265
266 266
267 267 def uisetup(ui):
268 268 """Wraps user facing Mercurial commands to swap them out with shallow
269 269 versions.
270 270 """
271 271 hg.wirepeersetupfuncs.append(fileserverclient.peersetup)
272 272
273 273 entry = extensions.wrapcommand(commands.table, b'clone', cloneshallow)
274 274 entry[1].append(
275 275 (
276 276 b'',
277 277 b'shallow',
278 278 None,
279 279 _(b"create a shallow clone which uses remote file history"),
280 280 )
281 281 )
282 282
283 283 extensions.wrapcommand(
284 284 commands.table, b'debugindex', debugcommands.debugindex
285 285 )
286 286 extensions.wrapcommand(
287 287 commands.table, b'debugindexdot', debugcommands.debugindexdot
288 288 )
289 289 extensions.wrapcommand(commands.table, b'log', log)
290 290 extensions.wrapcommand(commands.table, b'pull', pull)
291 291
292 292 # Prevent 'hg manifest --all'
293 293 def _manifest(orig, ui, repo, *args, **opts):
294 294 if isenabled(repo) and opts.get('all'):
295 295 raise error.Abort(_(b"--all is not supported in a shallow repo"))
296 296
297 297 return orig(ui, repo, *args, **opts)
298 298
299 299 extensions.wrapcommand(commands.table, b"manifest", _manifest)
300 300
301 301 # Wrap remotefilelog with lfs code
302 302 def _lfsloaded(loaded=False):
303 303 lfsmod = None
304 304 try:
305 305 lfsmod = extensions.find(b'lfs')
306 306 except KeyError:
307 307 pass
308 308 if lfsmod:
309 309 lfsmod.wrapfilelog(remotefilelog.remotefilelog)
310 310 fileserverclient._lfsmod = lfsmod
311 311
312 312 extensions.afterloaded(b'lfs', _lfsloaded)
313 313
314 314 # debugdata needs remotefilelog.len to work
315 315 extensions.wrapcommand(commands.table, b'debugdata', debugdatashallow)
316 316
317 317 changegroup.cgpacker = shallowbundle.shallowcg1packer
318 318
319 319 extensions.wrapfunction(
320 320 changegroup, b'_addchangegroupfiles', shallowbundle.addchangegroupfiles
321 321 )
322 322 extensions.wrapfunction(
323 323 changegroup, b'makechangegroup', shallowbundle.makechangegroup
324 324 )
325 325 extensions.wrapfunction(localrepo, b'makestore', storewrapper)
326 326 extensions.wrapfunction(exchange, b'pull', exchangepull)
327 327 extensions.wrapfunction(merge, b'applyupdates', applyupdates)
328 328 extensions.wrapfunction(merge, b'_checkunknownfiles', checkunknownfiles)
329 329 extensions.wrapfunction(context.workingctx, b'_checklookup', checklookup)
330 330 extensions.wrapfunction(scmutil, b'_findrenames', findrenames)
331 331 extensions.wrapfunction(
332 332 copies, b'_computeforwardmissing', computeforwardmissing
333 333 )
334 334 extensions.wrapfunction(dispatch, b'runcommand', runcommand)
335 335 extensions.wrapfunction(repair, b'_collectbrokencsets', _collectbrokencsets)
336 336 extensions.wrapfunction(context.changectx, b'filectx', filectx)
337 337 extensions.wrapfunction(context.workingctx, b'filectx', workingfilectx)
338 338 extensions.wrapfunction(patch, b'trydiff', trydiff)
339 339 extensions.wrapfunction(hg, b'verify', _verify)
340 340 scmutil.fileprefetchhooks.add(b'remotefilelog', _fileprefetchhook)
341 341
342 342 # disappointing hacks below
343 343 extensions.wrapfunction(scmutil, b'getrenamedfn', getrenamedfn)
344 344 extensions.wrapfunction(revset, b'filelog', filelogrevset)
345 345 revset.symbols[b'filelog'] = revset.filelog
346 346
347 347
348 348 def cloneshallow(orig, ui, repo, *args, **opts):
349 349 if opts.get('shallow'):
350 350 repos = []
351 351
352 352 def pull_shallow(orig, self, *args, **kwargs):
353 353 if not isenabled(self):
354 354 repos.append(self.unfiltered())
355 355 # set up the client hooks so the post-clone update works
356 356 setupclient(self.ui, self.unfiltered())
357 357
358 358 # setupclient fixed the class on the repo itself
359 359 # but we also need to fix it on the repoview
360 360 if isinstance(self, repoview.repoview):
361 361 self.__class__.__bases__ = (
362 362 self.__class__.__bases__[0],
363 363 self.unfiltered().__class__,
364 364 )
365 365 self.requirements.add(constants.SHALLOWREPO_REQUIREMENT)
366 366 with self.lock():
367 367 # acquire store lock before writing requirements as some
368 368 # requirements might be written to .hg/store/requires
369 369 scmutil.writereporequirements(self)
370 370
371 371 # Since setupclient hadn't been called, exchange.pull was not
372 372 # wrapped. So we need to manually invoke our version of it.
373 373 return exchangepull(orig, self, *args, **kwargs)
374 374 else:
375 375 return orig(self, *args, **kwargs)
376 376
377 377 extensions.wrapfunction(exchange, b'pull', pull_shallow)
378 378
379 379 # Wrap the stream logic to add requirements and to pass include/exclude
380 380 # patterns around.
381 381 def setup_streamout(repo, remote):
382 382 # Replace remote.stream_out with a version that sends file
383 383 # patterns.
384 384 def stream_out_shallow(orig):
385 385 caps = remote.capabilities()
386 386 if constants.NETWORK_CAP_LEGACY_SSH_GETFILES in caps:
387 387 opts = {}
388 388 if repo.includepattern:
389 389 opts['includepattern'] = b'\0'.join(repo.includepattern)
390 390 if repo.excludepattern:
391 391 opts['excludepattern'] = b'\0'.join(repo.excludepattern)
392 392 return remote._callstream(b'stream_out_shallow', **opts)
393 393 else:
394 394 return orig()
395 395
396 396 extensions.wrapfunction(remote, b'stream_out', stream_out_shallow)
397 397
398 398 def stream_wrap(orig, op):
399 399 setup_streamout(op.repo, op.remote)
400 400 return orig(op)
401 401
402 402 extensions.wrapfunction(
403 403 streamclone, b'maybeperformlegacystreamclone', stream_wrap
404 404 )
405 405
406 406 def canperformstreamclone(orig, pullop, bundle2=False):
407 407 # remotefilelog is currently incompatible with the
408 408 # bundle2 flavor of streamclones, so force us to use
409 409 # v1 instead.
410 410 if b'v2' in pullop.remotebundle2caps.get(b'stream', []):
411 pullop.remotebundle2caps[b'stream'] = [
412 c for c in pullop.remotebundle2caps[b'stream'] if c != b'v2'
413 ]
411 pullop.remotebundle2caps[b'stream'] = []
414 412 if bundle2:
415 413 return False, None
416 414 supported, requirements = orig(pullop, bundle2=bundle2)
417 415 if requirements is not None:
418 416 requirements.add(constants.SHALLOWREPO_REQUIREMENT)
419 417 return supported, requirements
420 418
421 419 extensions.wrapfunction(
422 420 streamclone, b'canperformstreamclone', canperformstreamclone
423 421 )
424 422
425 423 try:
426 424 orig(ui, repo, *args, **opts)
427 425 finally:
428 426 if opts.get('shallow'):
429 427 for r in repos:
430 428 if util.safehasattr(r, b'fileservice'):
431 429 r.fileservice.close()
432 430
433 431
434 432 def debugdatashallow(orig, *args, **kwds):
435 433 oldlen = remotefilelog.remotefilelog.__len__
436 434 try:
437 435 remotefilelog.remotefilelog.__len__ = lambda x: 1
438 436 return orig(*args, **kwds)
439 437 finally:
440 438 remotefilelog.remotefilelog.__len__ = oldlen
441 439
442 440
443 441 def reposetup(ui, repo):
444 442 if not repo.local():
445 443 return
446 444
447 445 # put here intentionally bc doesnt work in uisetup
448 446 ui.setconfig(b'hooks', b'update.prefetch', wcpprefetch)
449 447 ui.setconfig(b'hooks', b'commit.prefetch', wcpprefetch)
450 448
451 449 isserverenabled = ui.configbool(b'remotefilelog', b'server')
452 450 isshallowclient = isenabled(repo)
453 451
454 452 if isserverenabled and isshallowclient:
455 453 raise RuntimeError(b"Cannot be both a server and shallow client.")
456 454
457 455 if isshallowclient:
458 456 setupclient(ui, repo)
459 457
460 458 if isserverenabled:
461 459 remotefilelogserver.setupserver(ui, repo)
462 460
463 461
464 462 def setupclient(ui, repo):
465 463 if not isinstance(repo, localrepo.localrepository):
466 464 return
467 465
468 466 # Even clients get the server setup since they need to have the
469 467 # wireprotocol endpoints registered.
470 468 remotefilelogserver.onetimesetup(ui)
471 469 onetimeclientsetup(ui)
472 470
473 471 shallowrepo.wraprepo(repo)
474 472 repo.store = shallowstore.wrapstore(repo.store)
475 473
476 474
477 475 def storewrapper(orig, requirements, path, vfstype):
478 476 s = orig(requirements, path, vfstype)
479 477 if constants.SHALLOWREPO_REQUIREMENT in requirements:
480 478 s = shallowstore.wrapstore(s)
481 479
482 480 return s
483 481
484 482
485 483 # prefetch files before update
486 484 def applyupdates(
487 485 orig, repo, mresult, wctx, mctx, overwrite, wantfiledata, **opts
488 486 ):
489 487 if isenabled(repo):
490 488 manifest = mctx.manifest()
491 489 files = []
492 490 for f, args, msg in mresult.getactions([mergestatemod.ACTION_GET]):
493 491 files.append((f, hex(manifest[f])))
494 492 # batch fetch the needed files from the server
495 493 repo.fileservice.prefetch(files)
496 494 return orig(repo, mresult, wctx, mctx, overwrite, wantfiledata, **opts)
497 495
498 496
499 497 # Prefetch merge checkunknownfiles
500 498 def checkunknownfiles(orig, repo, wctx, mctx, force, mresult, *args, **kwargs):
501 499 if isenabled(repo):
502 500 files = []
503 501 sparsematch = repo.maybesparsematch(mctx.rev())
504 502 for f, (m, actionargs, msg) in mresult.filemap():
505 503 if sparsematch and not sparsematch(f):
506 504 continue
507 505 if m in (
508 506 mergestatemod.ACTION_CREATED,
509 507 mergestatemod.ACTION_DELETED_CHANGED,
510 508 mergestatemod.ACTION_CREATED_MERGE,
511 509 ):
512 510 files.append((f, hex(mctx.filenode(f))))
513 511 elif m == mergestatemod.ACTION_LOCAL_DIR_RENAME_GET:
514 512 f2 = actionargs[0]
515 513 files.append((f2, hex(mctx.filenode(f2))))
516 514 # batch fetch the needed files from the server
517 515 repo.fileservice.prefetch(files)
518 516 return orig(repo, wctx, mctx, force, mresult, *args, **kwargs)
519 517
520 518
521 519 # Prefetch files before status attempts to look at their size and contents
522 520 def checklookup(orig, self, files, mtime_boundary):
523 521 repo = self._repo
524 522 if isenabled(repo):
525 523 prefetchfiles = []
526 524 for parent in self._parents:
527 525 for f in files:
528 526 if f in parent:
529 527 prefetchfiles.append((f, hex(parent.filenode(f))))
530 528 # batch fetch the needed files from the server
531 529 repo.fileservice.prefetch(prefetchfiles)
532 530 return orig(self, files, mtime_boundary)
533 531
534 532
535 533 # Prefetch the logic that compares added and removed files for renames
536 534 def findrenames(orig, repo, matcher, added, removed, *args, **kwargs):
537 535 if isenabled(repo):
538 536 files = []
539 537 pmf = repo[b'.'].manifest()
540 538 for f in removed:
541 539 if f in pmf:
542 540 files.append((f, hex(pmf[f])))
543 541 # batch fetch the needed files from the server
544 542 repo.fileservice.prefetch(files)
545 543 return orig(repo, matcher, added, removed, *args, **kwargs)
546 544
547 545
548 546 # prefetch files before pathcopies check
549 547 def computeforwardmissing(orig, a, b, match=None):
550 548 missing = orig(a, b, match=match)
551 549 repo = a._repo
552 550 if isenabled(repo):
553 551 mb = b.manifest()
554 552
555 553 files = []
556 554 sparsematch = repo.maybesparsematch(b.rev())
557 555 if sparsematch:
558 556 sparsemissing = set()
559 557 for f in missing:
560 558 if sparsematch(f):
561 559 files.append((f, hex(mb[f])))
562 560 sparsemissing.add(f)
563 561 missing = sparsemissing
564 562
565 563 # batch fetch the needed files from the server
566 564 repo.fileservice.prefetch(files)
567 565 return missing
568 566
569 567
570 568 # close cache miss server connection after the command has finished
571 569 def runcommand(orig, lui, repo, *args, **kwargs):
572 570 fileservice = None
573 571 # repo can be None when running in chg:
574 572 # - at startup, reposetup was called because serve is not norepo
575 573 # - a norepo command like "help" is called
576 574 if repo and isenabled(repo):
577 575 fileservice = repo.fileservice
578 576 try:
579 577 return orig(lui, repo, *args, **kwargs)
580 578 finally:
581 579 if fileservice:
582 580 fileservice.close()
583 581
584 582
585 583 # prevent strip from stripping remotefilelogs
586 584 def _collectbrokencsets(orig, repo, files, striprev):
587 585 if isenabled(repo):
588 586 files = list([f for f in files if not repo.shallowmatch(f)])
589 587 return orig(repo, files, striprev)
590 588
591 589
592 590 # changectx wrappers
593 591 def filectx(orig, self, path, fileid=None, filelog=None):
594 592 if fileid is None:
595 593 fileid = self.filenode(path)
596 594 if isenabled(self._repo) and self._repo.shallowmatch(path):
597 595 return remotefilectx.remotefilectx(
598 596 self._repo, path, fileid=fileid, changectx=self, filelog=filelog
599 597 )
600 598 return orig(self, path, fileid=fileid, filelog=filelog)
601 599
602 600
603 601 def workingfilectx(orig, self, path, filelog=None):
604 602 if isenabled(self._repo) and self._repo.shallowmatch(path):
605 603 return remotefilectx.remoteworkingfilectx(
606 604 self._repo, path, workingctx=self, filelog=filelog
607 605 )
608 606 return orig(self, path, filelog=filelog)
609 607
610 608
611 609 # prefetch required revisions before a diff
612 610 def trydiff(
613 611 orig,
614 612 repo,
615 613 revs,
616 614 ctx1,
617 615 ctx2,
618 616 modified,
619 617 added,
620 618 removed,
621 619 copy,
622 620 getfilectx,
623 621 *args,
624 622 **kwargs
625 623 ):
626 624 if isenabled(repo):
627 625 prefetch = []
628 626 mf1 = ctx1.manifest()
629 627 for fname in modified + added + removed:
630 628 if fname in mf1:
631 629 fnode = getfilectx(fname, ctx1).filenode()
632 630 # fnode can be None if it's a edited working ctx file
633 631 if fnode:
634 632 prefetch.append((fname, hex(fnode)))
635 633 if fname not in removed:
636 634 fnode = getfilectx(fname, ctx2).filenode()
637 635 if fnode:
638 636 prefetch.append((fname, hex(fnode)))
639 637
640 638 repo.fileservice.prefetch(prefetch)
641 639
642 640 return orig(
643 641 repo,
644 642 revs,
645 643 ctx1,
646 644 ctx2,
647 645 modified,
648 646 added,
649 647 removed,
650 648 copy,
651 649 getfilectx,
652 650 *args,
653 651 **kwargs
654 652 )
655 653
656 654
657 655 # Prevent verify from processing files
658 656 # a stub for mercurial.hg.verify()
659 657 def _verify(orig, repo, level=None):
660 658 lock = repo.lock()
661 659 try:
662 660 return shallowverifier.shallowverifier(repo).verify()
663 661 finally:
664 662 lock.release()
665 663
666 664
667 665 clientonetime = False
668 666
669 667
670 668 def onetimeclientsetup(ui):
671 669 global clientonetime
672 670 if clientonetime:
673 671 return
674 672 clientonetime = True
675 673
676 674 # Don't commit filelogs until we know the commit hash, since the hash
677 675 # is present in the filelog blob.
678 676 # This violates Mercurial's filelog->manifest->changelog write order,
679 677 # but is generally fine for client repos.
680 678 pendingfilecommits = []
681 679
682 680 def addrawrevision(
683 681 orig,
684 682 self,
685 683 rawtext,
686 684 transaction,
687 685 link,
688 686 p1,
689 687 p2,
690 688 node,
691 689 flags,
692 690 cachedelta=None,
693 691 _metatuple=None,
694 692 ):
695 693 if isinstance(link, int):
696 694 pendingfilecommits.append(
697 695 (
698 696 self,
699 697 rawtext,
700 698 transaction,
701 699 link,
702 700 p1,
703 701 p2,
704 702 node,
705 703 flags,
706 704 cachedelta,
707 705 _metatuple,
708 706 )
709 707 )
710 708 return node
711 709 else:
712 710 return orig(
713 711 self,
714 712 rawtext,
715 713 transaction,
716 714 link,
717 715 p1,
718 716 p2,
719 717 node,
720 718 flags,
721 719 cachedelta,
722 720 _metatuple=_metatuple,
723 721 )
724 722
725 723 extensions.wrapfunction(
726 724 remotefilelog.remotefilelog, b'addrawrevision', addrawrevision
727 725 )
728 726
729 727 def changelogadd(orig, self, *args, **kwargs):
730 728 oldlen = len(self)
731 729 node = orig(self, *args, **kwargs)
732 730 newlen = len(self)
733 731 if oldlen != newlen:
734 732 for oldargs in pendingfilecommits:
735 733 log, rt, tr, link, p1, p2, n, fl, c, m = oldargs
736 734 linknode = self.node(link)
737 735 if linknode == node:
738 736 log.addrawrevision(rt, tr, linknode, p1, p2, n, fl, c, m)
739 737 else:
740 738 raise error.ProgrammingError(
741 739 b'pending multiple integer revisions are not supported'
742 740 )
743 741 else:
744 742 # "link" is actually wrong here (it is set to len(changelog))
745 743 # if changelog remains unchanged, skip writing file revisions
746 744 # but still do a sanity check about pending multiple revisions
747 745 if len({x[3] for x in pendingfilecommits}) > 1:
748 746 raise error.ProgrammingError(
749 747 b'pending multiple integer revisions are not supported'
750 748 )
751 749 del pendingfilecommits[:]
752 750 return node
753 751
754 752 extensions.wrapfunction(changelog.changelog, b'add', changelogadd)
755 753
756 754
757 755 def getrenamedfn(orig, repo, endrev=None):
758 756 if not isenabled(repo) or copies.usechangesetcentricalgo(repo):
759 757 return orig(repo, endrev)
760 758
761 759 rcache = {}
762 760
763 761 def getrenamed(fn, rev):
764 762 """looks up all renames for a file (up to endrev) the first
765 763 time the file is given. It indexes on the changerev and only
766 764 parses the manifest if linkrev != changerev.
767 765 Returns rename info for fn at changerev rev."""
768 766 if rev in rcache.setdefault(fn, {}):
769 767 return rcache[fn][rev]
770 768
771 769 try:
772 770 fctx = repo[rev].filectx(fn)
773 771 for ancestor in fctx.ancestors():
774 772 if ancestor.path() == fn:
775 773 renamed = ancestor.renamed()
776 774 rcache[fn][ancestor.rev()] = renamed and renamed[0]
777 775
778 776 renamed = fctx.renamed()
779 777 return renamed and renamed[0]
780 778 except error.LookupError:
781 779 return None
782 780
783 781 return getrenamed
784 782
785 783
786 784 def filelogrevset(orig, repo, subset, x):
787 785 """``filelog(pattern)``
788 786 Changesets connected to the specified filelog.
789 787
790 788 For performance reasons, ``filelog()`` does not show every changeset
791 789 that affects the requested file(s). See :hg:`help log` for details. For
792 790 a slower, more accurate result, use ``file()``.
793 791 """
794 792
795 793 if not isenabled(repo):
796 794 return orig(repo, subset, x)
797 795
798 796 # i18n: "filelog" is a keyword
799 797 pat = revset.getstring(x, _(b"filelog requires a pattern"))
800 798 m = matchmod.match(
801 799 repo.root, repo.getcwd(), [pat], default=b'relpath', ctx=repo[None]
802 800 )
803 801 s = set()
804 802
805 803 if not matchmod.patkind(pat):
806 804 # slow
807 805 for r in subset:
808 806 ctx = repo[r]
809 807 cfiles = ctx.files()
810 808 for f in m.files():
811 809 if f in cfiles:
812 810 s.add(ctx.rev())
813 811 break
814 812 else:
815 813 # partial
816 814 files = (f for f in repo[None] if m(f))
817 815 for f in files:
818 816 fctx = repo[None].filectx(f)
819 817 s.add(fctx.linkrev())
820 818 for actx in fctx.ancestors():
821 819 s.add(actx.linkrev())
822 820
823 821 return smartset.baseset([r for r in subset if r in s])
824 822
825 823
826 824 @command(b'gc', [], _(b'hg gc [REPO...]'), norepo=True)
827 825 def gc(ui, *args, **opts):
828 826 """garbage collect the client and server filelog caches"""
829 827 cachepaths = set()
830 828
831 829 # get the system client cache
832 830 systemcache = shallowutil.getcachepath(ui, allowempty=True)
833 831 if systemcache:
834 832 cachepaths.add(systemcache)
835 833
836 834 # get repo client and server cache
837 835 repopaths = []
838 836 pwd = ui.environ.get(b'PWD')
839 837 if pwd:
840 838 repopaths.append(pwd)
841 839
842 840 repopaths.extend(args)
843 841 repos = []
844 842 for repopath in repopaths:
845 843 try:
846 844 repo = hg.peer(ui, {}, repopath)
847 845 repos.append(repo)
848 846
849 847 repocache = shallowutil.getcachepath(repo.ui, allowempty=True)
850 848 if repocache:
851 849 cachepaths.add(repocache)
852 850 except error.RepoError:
853 851 pass
854 852
855 853 # gc client cache
856 854 for cachepath in cachepaths:
857 855 gcclient(ui, cachepath)
858 856
859 857 # gc server cache
860 858 for repo in repos:
861 859 remotefilelogserver.gcserver(ui, repo._repo)
862 860
863 861
864 862 def gcclient(ui, cachepath):
865 863 # get list of repos that use this cache
866 864 repospath = os.path.join(cachepath, b'repos')
867 865 if not os.path.exists(repospath):
868 866 ui.warn(_(b"no known cache at %s\n") % cachepath)
869 867 return
870 868
871 869 reposfile = open(repospath, b'rb')
872 870 repos = {r[:-1] for r in reposfile.readlines()}
873 871 reposfile.close()
874 872
875 873 # build list of useful files
876 874 validrepos = []
877 875 keepkeys = set()
878 876
879 877 sharedcache = None
880 878 filesrepacked = False
881 879
882 880 count = 0
883 881 progress = ui.makeprogress(
884 882 _(b"analyzing repositories"), unit=b"repos", total=len(repos)
885 883 )
886 884 for path in repos:
887 885 progress.update(count)
888 886 count += 1
889 887 try:
890 888 path = util.expandpath(os.path.normpath(path))
891 889 except TypeError as e:
892 890 ui.warn(_(b"warning: malformed path: %r:%s\n") % (path, e))
893 891 traceback.print_exc()
894 892 continue
895 893 try:
896 894 peer = hg.peer(ui, {}, path)
897 895 repo = peer._repo
898 896 except error.RepoError:
899 897 continue
900 898
901 899 validrepos.append(path)
902 900
903 901 # Protect against any repo or config changes that have happened since
904 902 # this repo was added to the repos file. We'd rather this loop succeed
905 903 # and too much be deleted, than the loop fail and nothing gets deleted.
906 904 if not isenabled(repo):
907 905 continue
908 906
909 907 if not util.safehasattr(repo, b'name'):
910 908 ui.warn(
911 909 _(b"repo %s is a misconfigured remotefilelog repo\n") % path
912 910 )
913 911 continue
914 912
915 913 # If garbage collection on repack and repack on hg gc are enabled
916 914 # then loose files are repacked and garbage collected.
917 915 # Otherwise regular garbage collection is performed.
918 916 repackonhggc = repo.ui.configbool(b'remotefilelog', b'repackonhggc')
919 917 gcrepack = repo.ui.configbool(b'remotefilelog', b'gcrepack')
920 918 if repackonhggc and gcrepack:
921 919 try:
922 920 repackmod.incrementalrepack(repo)
923 921 filesrepacked = True
924 922 continue
925 923 except (IOError, repackmod.RepackAlreadyRunning):
926 924 # If repack cannot be performed due to not enough disk space
927 925 # continue doing garbage collection of loose files w/o repack
928 926 pass
929 927
930 928 reponame = repo.name
931 929 if not sharedcache:
932 930 sharedcache = repo.sharedstore
933 931
934 932 # Compute a keepset which is not garbage collected
935 933 def keyfn(fname, fnode):
936 934 return fileserverclient.getcachekey(reponame, fname, hex(fnode))
937 935
938 936 keepkeys = repackmod.keepset(repo, keyfn=keyfn, lastkeepkeys=keepkeys)
939 937
940 938 progress.complete()
941 939
942 940 # write list of valid repos back
943 941 oldumask = os.umask(0o002)
944 942 try:
945 943 reposfile = open(repospath, b'wb')
946 944 reposfile.writelines([(b"%s\n" % r) for r in validrepos])
947 945 reposfile.close()
948 946 finally:
949 947 os.umask(oldumask)
950 948
951 949 # prune cache
952 950 if sharedcache is not None:
953 951 sharedcache.gc(keepkeys)
954 952 elif not filesrepacked:
955 953 ui.warn(_(b"warning: no valid repos in repofile\n"))
956 954
957 955
958 956 def log(orig, ui, repo, *pats, **opts):
959 957 if not isenabled(repo):
960 958 return orig(ui, repo, *pats, **opts)
961 959
962 960 follow = opts.get('follow')
963 961 revs = opts.get('rev')
964 962 if pats:
965 963 # Force slowpath for non-follow patterns and follows that start from
966 964 # non-working-copy-parent revs.
967 965 if not follow or revs:
968 966 # This forces the slowpath
969 967 opts['removed'] = True
970 968
971 969 # If this is a non-follow log without any revs specified, recommend that
972 970 # the user add -f to speed it up.
973 971 if not follow and not revs:
974 972 match = scmutil.match(repo[b'.'], pats, pycompat.byteskwargs(opts))
975 973 isfile = not match.anypats()
976 974 if isfile:
977 975 for file in match.files():
978 976 if not os.path.isfile(repo.wjoin(file)):
979 977 isfile = False
980 978 break
981 979
982 980 if isfile:
983 981 ui.warn(
984 982 _(
985 983 b"warning: file log can be slow on large repos - "
986 984 + b"use -f to speed it up\n"
987 985 )
988 986 )
989 987
990 988 return orig(ui, repo, *pats, **opts)
991 989
992 990
993 991 def revdatelimit(ui, revset):
994 992 """Update revset so that only changesets no older than 'prefetchdays' days
995 993 are included. The default value is set to 14 days. If 'prefetchdays' is set
996 994 to zero or negative value then date restriction is not applied.
997 995 """
998 996 days = ui.configint(b'remotefilelog', b'prefetchdays')
999 997 if days > 0:
1000 998 revset = b'(%s) & date(-%s)' % (revset, days)
1001 999 return revset
1002 1000
1003 1001
1004 1002 def readytofetch(repo):
1005 1003 """Check that enough time has passed since the last background prefetch.
1006 1004 This only relates to prefetches after operations that change the working
1007 1005 copy parent. Default delay between background prefetches is 2 minutes.
1008 1006 """
1009 1007 timeout = repo.ui.configint(b'remotefilelog', b'prefetchdelay')
1010 1008 fname = repo.vfs.join(b'lastprefetch')
1011 1009
1012 1010 ready = False
1013 1011 with open(fname, b'a'):
1014 1012 # the with construct above is used to avoid race conditions
1015 1013 modtime = os.path.getmtime(fname)
1016 1014 if (time.time() - modtime) > timeout:
1017 1015 os.utime(fname, None)
1018 1016 ready = True
1019 1017
1020 1018 return ready
1021 1019
1022 1020
1023 1021 def wcpprefetch(ui, repo, **kwargs):
1024 1022 """Prefetches in background revisions specified by bgprefetchrevs revset.
1025 1023 Does background repack if backgroundrepack flag is set in config.
1026 1024 """
1027 1025 shallow = isenabled(repo)
1028 1026 bgprefetchrevs = ui.config(b'remotefilelog', b'bgprefetchrevs')
1029 1027 isready = readytofetch(repo)
1030 1028
1031 1029 if not (shallow and bgprefetchrevs and isready):
1032 1030 return
1033 1031
1034 1032 bgrepack = repo.ui.configbool(b'remotefilelog', b'backgroundrepack')
1035 1033 # update a revset with a date limit
1036 1034 bgprefetchrevs = revdatelimit(ui, bgprefetchrevs)
1037 1035
1038 1036 def anon(unused_success):
1039 1037 if util.safehasattr(repo, b'ranprefetch') and repo.ranprefetch:
1040 1038 return
1041 1039 repo.ranprefetch = True
1042 1040 repo.backgroundprefetch(bgprefetchrevs, repack=bgrepack)
1043 1041
1044 1042 repo._afterlock(anon)
1045 1043
1046 1044
1047 1045 def pull(orig, ui, repo, *pats, **opts):
1048 1046 result = orig(ui, repo, *pats, **opts)
1049 1047
1050 1048 if isenabled(repo):
1051 1049 # prefetch if it's configured
1052 1050 prefetchrevset = ui.config(b'remotefilelog', b'pullprefetch')
1053 1051 bgrepack = repo.ui.configbool(b'remotefilelog', b'backgroundrepack')
1054 1052 bgprefetch = repo.ui.configbool(b'remotefilelog', b'backgroundprefetch')
1055 1053
1056 1054 if prefetchrevset:
1057 1055 ui.status(_(b"prefetching file contents\n"))
1058 1056 revs = scmutil.revrange(repo, [prefetchrevset])
1059 1057 base = repo[b'.'].rev()
1060 1058 if bgprefetch:
1061 1059 repo.backgroundprefetch(prefetchrevset, repack=bgrepack)
1062 1060 else:
1063 1061 repo.prefetch(revs, base=base)
1064 1062 if bgrepack:
1065 1063 repackmod.backgroundrepack(repo, incremental=True)
1066 1064 elif bgrepack:
1067 1065 repackmod.backgroundrepack(repo, incremental=True)
1068 1066
1069 1067 return result
1070 1068
1071 1069
1072 1070 def exchangepull(orig, repo, remote, *args, **kwargs):
1073 1071 # Hook into the callstream/getbundle to insert bundle capabilities
1074 1072 # during a pull.
1075 1073 def localgetbundle(
1076 1074 orig, source, heads=None, common=None, bundlecaps=None, **kwargs
1077 1075 ):
1078 1076 if not bundlecaps:
1079 1077 bundlecaps = set()
1080 1078 bundlecaps.add(constants.BUNDLE2_CAPABLITY)
1081 1079 return orig(
1082 1080 source, heads=heads, common=common, bundlecaps=bundlecaps, **kwargs
1083 1081 )
1084 1082
1085 1083 if util.safehasattr(remote, b'_callstream'):
1086 1084 remote._localrepo = repo
1087 1085 elif util.safehasattr(remote, b'getbundle'):
1088 1086 extensions.wrapfunction(remote, b'getbundle', localgetbundle)
1089 1087
1090 1088 return orig(repo, remote, *args, **kwargs)
1091 1089
1092 1090
1093 1091 def _fileprefetchhook(repo, revmatches):
1094 1092 if isenabled(repo):
1095 1093 allfiles = []
1096 1094 for rev, match in revmatches:
1097 1095 if rev == wdirrev or rev is None:
1098 1096 continue
1099 1097 ctx = repo[rev]
1100 1098 mf = ctx.manifest()
1101 1099 sparsematch = repo.maybesparsematch(ctx.rev())
1102 1100 for path in ctx.walk(match):
1103 1101 if (not sparsematch or sparsematch(path)) and path in mf:
1104 1102 allfiles.append((path, hex(mf[path])))
1105 1103 repo.fileservice.prefetch(allfiles)
1106 1104
1107 1105
1108 1106 @command(
1109 1107 b'debugremotefilelog',
1110 1108 [
1111 1109 (b'd', b'decompress', None, _(b'decompress the filelog first')),
1112 1110 ],
1113 1111 _(b'hg debugremotefilelog <path>'),
1114 1112 norepo=True,
1115 1113 )
1116 1114 def debugremotefilelog(ui, path, **opts):
1117 1115 return debugcommands.debugremotefilelog(ui, path, **opts)
1118 1116
1119 1117
1120 1118 @command(
1121 1119 b'verifyremotefilelog',
1122 1120 [
1123 1121 (b'd', b'decompress', None, _(b'decompress the filelogs first')),
1124 1122 ],
1125 1123 _(b'hg verifyremotefilelogs <directory>'),
1126 1124 norepo=True,
1127 1125 )
1128 1126 def verifyremotefilelog(ui, path, **opts):
1129 1127 return debugcommands.verifyremotefilelog(ui, path, **opts)
1130 1128
1131 1129
1132 1130 @command(
1133 1131 b'debugdatapack',
1134 1132 [
1135 1133 (b'', b'long', None, _(b'print the long hashes')),
1136 1134 (b'', b'node', b'', _(b'dump the contents of node'), b'NODE'),
1137 1135 ],
1138 1136 _(b'hg debugdatapack <paths>'),
1139 1137 norepo=True,
1140 1138 )
1141 1139 def debugdatapack(ui, *paths, **opts):
1142 1140 return debugcommands.debugdatapack(ui, *paths, **opts)
1143 1141
1144 1142
1145 1143 @command(b'debughistorypack', [], _(b'hg debughistorypack <path>'), norepo=True)
1146 1144 def debughistorypack(ui, path, **opts):
1147 1145 return debugcommands.debughistorypack(ui, path)
1148 1146
1149 1147
1150 1148 @command(b'debugkeepset', [], _(b'hg debugkeepset'))
1151 1149 def debugkeepset(ui, repo, **opts):
1152 1150 # The command is used to measure keepset computation time
1153 1151 def keyfn(fname, fnode):
1154 1152 return fileserverclient.getcachekey(repo.name, fname, hex(fnode))
1155 1153
1156 1154 repackmod.keepset(repo, keyfn)
1157 1155 return
1158 1156
1159 1157
1160 1158 @command(b'debugwaitonrepack', [], _(b'hg debugwaitonrepack'))
1161 1159 def debugwaitonrepack(ui, repo, **opts):
1162 1160 return debugcommands.debugwaitonrepack(repo)
1163 1161
1164 1162
1165 1163 @command(b'debugwaitonprefetch', [], _(b'hg debugwaitonprefetch'))
1166 1164 def debugwaitonprefetch(ui, repo, **opts):
1167 1165 return debugcommands.debugwaitonprefetch(repo)
1168 1166
1169 1167
1170 1168 def resolveprefetchopts(ui, opts):
1171 1169 if not opts.get(b'rev'):
1172 1170 revset = [b'.', b'draft()']
1173 1171
1174 1172 prefetchrevset = ui.config(b'remotefilelog', b'pullprefetch', None)
1175 1173 if prefetchrevset:
1176 1174 revset.append(b'(%s)' % prefetchrevset)
1177 1175 bgprefetchrevs = ui.config(b'remotefilelog', b'bgprefetchrevs', None)
1178 1176 if bgprefetchrevs:
1179 1177 revset.append(b'(%s)' % bgprefetchrevs)
1180 1178 revset = b'+'.join(revset)
1181 1179
1182 1180 # update a revset with a date limit
1183 1181 revset = revdatelimit(ui, revset)
1184 1182
1185 1183 opts[b'rev'] = [revset]
1186 1184
1187 1185 if not opts.get(b'base'):
1188 1186 opts[b'base'] = None
1189 1187
1190 1188 return opts
1191 1189
1192 1190
1193 1191 @command(
1194 1192 b'prefetch',
1195 1193 [
1196 1194 (b'r', b'rev', [], _(b'prefetch the specified revisions'), _(b'REV')),
1197 1195 (b'', b'repack', False, _(b'run repack after prefetch')),
1198 1196 (b'b', b'base', b'', _(b"rev that is assumed to already be local")),
1199 1197 ]
1200 1198 + commands.walkopts,
1201 1199 _(b'hg prefetch [OPTIONS] [FILE...]'),
1202 1200 helpcategory=command.CATEGORY_MAINTENANCE,
1203 1201 )
1204 1202 def prefetch(ui, repo, *pats, **opts):
1205 1203 """prefetch file revisions from the server
1206 1204
1207 1205 Prefetchs file revisions for the specified revs and stores them in the
1208 1206 local remotefilelog cache. If no rev is specified, the default rev is
1209 1207 used which is the union of dot, draft, pullprefetch and bgprefetchrev.
1210 1208 File names or patterns can be used to limit which files are downloaded.
1211 1209
1212 1210 Return 0 on success.
1213 1211 """
1214 1212 opts = pycompat.byteskwargs(opts)
1215 1213 if not isenabled(repo):
1216 1214 raise error.Abort(_(b"repo is not shallow"))
1217 1215
1218 1216 opts = resolveprefetchopts(ui, opts)
1219 1217 revs = scmutil.revrange(repo, opts.get(b'rev'))
1220 1218 repo.prefetch(revs, opts.get(b'base'), pats, opts)
1221 1219
1222 1220 # Run repack in background
1223 1221 if opts.get(b'repack'):
1224 1222 repackmod.backgroundrepack(repo, incremental=True)
1225 1223
1226 1224
1227 1225 @command(
1228 1226 b'repack',
1229 1227 [
1230 1228 (b'', b'background', None, _(b'run in a background process'), None),
1231 1229 (b'', b'incremental', None, _(b'do an incremental repack'), None),
1232 1230 (
1233 1231 b'',
1234 1232 b'packsonly',
1235 1233 None,
1236 1234 _(b'only repack packs (skip loose objects)'),
1237 1235 None,
1238 1236 ),
1239 1237 ],
1240 1238 _(b'hg repack [OPTIONS]'),
1241 1239 )
1242 1240 def repack_(ui, repo, *pats, **opts):
1243 1241 if opts.get('background'):
1244 1242 repackmod.backgroundrepack(
1245 1243 repo,
1246 1244 incremental=opts.get('incremental'),
1247 1245 packsonly=opts.get('packsonly', False),
1248 1246 )
1249 1247 return
1250 1248
1251 1249 options = {b'packsonly': opts.get('packsonly')}
1252 1250
1253 1251 try:
1254 1252 if opts.get('incremental'):
1255 1253 repackmod.incrementalrepack(repo, options=options)
1256 1254 else:
1257 1255 repackmod.fullrepack(repo, options=options)
1258 1256 except repackmod.RepackAlreadyRunning as ex:
1259 1257 # Don't propogate the exception if the repack is already in
1260 1258 # progress, since we want the command to exit 0.
1261 1259 repo.ui.warn(b'%s\n' % ex)
@@ -1,2636 +1,2664
1 1 # bundle2.py - generic container format to transmit arbitrary data.
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """Handling of the new bundle2 format
8 8
9 9 The goal of bundle2 is to act as an atomically packet to transmit a set of
10 10 payloads in an application agnostic way. It consist in a sequence of "parts"
11 11 that will be handed to and processed by the application layer.
12 12
13 13
14 14 General format architecture
15 15 ===========================
16 16
17 17 The format is architectured as follow
18 18
19 19 - magic string
20 20 - stream level parameters
21 21 - payload parts (any number)
22 22 - end of stream marker.
23 23
24 24 the Binary format
25 25 ============================
26 26
27 27 All numbers are unsigned and big-endian.
28 28
29 29 stream level parameters
30 30 ------------------------
31 31
32 32 Binary format is as follow
33 33
34 34 :params size: int32
35 35
36 36 The total number of Bytes used by the parameters
37 37
38 38 :params value: arbitrary number of Bytes
39 39
40 40 A blob of `params size` containing the serialized version of all stream level
41 41 parameters.
42 42
43 43 The blob contains a space separated list of parameters. Parameters with value
44 44 are stored in the form `<name>=<value>`. Both name and value are urlquoted.
45 45
46 46 Empty name are obviously forbidden.
47 47
48 48 Name MUST start with a letter. If this first letter is lower case, the
49 49 parameter is advisory and can be safely ignored. However when the first
50 50 letter is capital, the parameter is mandatory and the bundling process MUST
51 51 stop if he is not able to proceed it.
52 52
53 53 Stream parameters use a simple textual format for two main reasons:
54 54
55 55 - Stream level parameters should remain simple and we want to discourage any
56 56 crazy usage.
57 57 - Textual data allow easy human inspection of a bundle2 header in case of
58 58 troubles.
59 59
60 60 Any Applicative level options MUST go into a bundle2 part instead.
61 61
62 62 Payload part
63 63 ------------------------
64 64
65 65 Binary format is as follow
66 66
67 67 :header size: int32
68 68
69 69 The total number of Bytes used by the part header. When the header is empty
70 70 (size = 0) this is interpreted as the end of stream marker.
71 71
72 72 :header:
73 73
74 74 The header defines how to interpret the part. It contains two piece of
75 75 data: the part type, and the part parameters.
76 76
77 77 The part type is used to route an application level handler, that can
78 78 interpret payload.
79 79
80 80 Part parameters are passed to the application level handler. They are
81 81 meant to convey information that will help the application level object to
82 82 interpret the part payload.
83 83
84 84 The binary format of the header is has follow
85 85
86 86 :typesize: (one byte)
87 87
88 88 :parttype: alphanumerical part name (restricted to [a-zA-Z0-9_:-]*)
89 89
90 90 :partid: A 32bits integer (unique in the bundle) that can be used to refer
91 91 to this part.
92 92
93 93 :parameters:
94 94
95 95 Part's parameter may have arbitrary content, the binary structure is::
96 96
97 97 <mandatory-count><advisory-count><param-sizes><param-data>
98 98
99 99 :mandatory-count: 1 byte, number of mandatory parameters
100 100
101 101 :advisory-count: 1 byte, number of advisory parameters
102 102
103 103 :param-sizes:
104 104
105 105 N couple of bytes, where N is the total number of parameters. Each
106 106 couple contains (<size-of-key>, <size-of-value) for one parameter.
107 107
108 108 :param-data:
109 109
110 110 A blob of bytes from which each parameter key and value can be
111 111 retrieved using the list of size couples stored in the previous
112 112 field.
113 113
114 114 Mandatory parameters comes first, then the advisory ones.
115 115
116 116 Each parameter's key MUST be unique within the part.
117 117
118 118 :payload:
119 119
120 120 payload is a series of `<chunksize><chunkdata>`.
121 121
122 122 `chunksize` is an int32, `chunkdata` are plain bytes (as much as
123 123 `chunksize` says)` The payload part is concluded by a zero size chunk.
124 124
125 125 The current implementation always produces either zero or one chunk.
126 126 This is an implementation limitation that will ultimately be lifted.
127 127
128 128 `chunksize` can be negative to trigger special case processing. No such
129 129 processing is in place yet.
130 130
131 131 Bundle processing
132 132 ============================
133 133
134 134 Each part is processed in order using a "part handler". Handler are registered
135 135 for a certain part type.
136 136
137 137 The matching of a part to its handler is case insensitive. The case of the
138 138 part type is used to know if a part is mandatory or advisory. If the Part type
139 139 contains any uppercase char it is considered mandatory. When no handler is
140 140 known for a Mandatory part, the process is aborted and an exception is raised.
141 141 If the part is advisory and no handler is known, the part is ignored. When the
142 142 process is aborted, the full bundle is still read from the stream to keep the
143 143 channel usable. But none of the part read from an abort are processed. In the
144 144 future, dropping the stream may become an option for channel we do not care to
145 145 preserve.
146 146 """
147 147
148 148
149 149 import collections
150 150 import errno
151 151 import os
152 152 import re
153 153 import string
154 154 import struct
155 155 import sys
156 156
157 157 from .i18n import _
158 158 from .node import (
159 159 hex,
160 160 short,
161 161 )
162 162 from . import (
163 163 bookmarks,
164 164 changegroup,
165 165 encoding,
166 166 error,
167 167 obsolete,
168 168 phases,
169 169 pushkey,
170 170 pycompat,
171 171 requirements,
172 172 scmutil,
173 173 streamclone,
174 174 tags,
175 175 url,
176 176 util,
177 177 )
178 178 from .utils import (
179 179 stringutil,
180 180 urlutil,
181 181 )
182 182 from .interfaces import repository
183 183
184 184 urlerr = util.urlerr
185 185 urlreq = util.urlreq
186 186
187 187 _pack = struct.pack
188 188 _unpack = struct.unpack
189 189
190 190 _fstreamparamsize = b'>i'
191 191 _fpartheadersize = b'>i'
192 192 _fparttypesize = b'>B'
193 193 _fpartid = b'>I'
194 194 _fpayloadsize = b'>i'
195 195 _fpartparamcount = b'>BB'
196 196
197 197 preferedchunksize = 32768
198 198
199 199 _parttypeforbidden = re.compile(b'[^a-zA-Z0-9_:-]')
200 200
201 201
202 202 def outdebug(ui, message):
203 203 """debug regarding output stream (bundling)"""
204 204 if ui.configbool(b'devel', b'bundle2.debug'):
205 205 ui.debug(b'bundle2-output: %s\n' % message)
206 206
207 207
208 208 def indebug(ui, message):
209 209 """debug on input stream (unbundling)"""
210 210 if ui.configbool(b'devel', b'bundle2.debug'):
211 211 ui.debug(b'bundle2-input: %s\n' % message)
212 212
213 213
214 214 def validateparttype(parttype):
215 215 """raise ValueError if a parttype contains invalid character"""
216 216 if _parttypeforbidden.search(parttype):
217 217 raise ValueError(parttype)
218 218
219 219
220 220 def _makefpartparamsizes(nbparams):
221 221 """return a struct format to read part parameter sizes
222 222
223 223 The number parameters is variable so we need to build that format
224 224 dynamically.
225 225 """
226 226 return b'>' + (b'BB' * nbparams)
227 227
228 228
229 229 parthandlermapping = {}
230 230
231 231
232 232 def parthandler(parttype, params=()):
233 233 """decorator that register a function as a bundle2 part handler
234 234
235 235 eg::
236 236
237 237 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
238 238 def myparttypehandler(...):
239 239 '''process a part of type "my part".'''
240 240 ...
241 241 """
242 242 validateparttype(parttype)
243 243
244 244 def _decorator(func):
245 245 lparttype = parttype.lower() # enforce lower case matching.
246 246 assert lparttype not in parthandlermapping
247 247 parthandlermapping[lparttype] = func
248 248 func.params = frozenset(params)
249 249 return func
250 250
251 251 return _decorator
252 252
253 253
254 254 class unbundlerecords:
255 255 """keep record of what happens during and unbundle
256 256
257 257 New records are added using `records.add('cat', obj)`. Where 'cat' is a
258 258 category of record and obj is an arbitrary object.
259 259
260 260 `records['cat']` will return all entries of this category 'cat'.
261 261
262 262 Iterating on the object itself will yield `('category', obj)` tuples
263 263 for all entries.
264 264
265 265 All iterations happens in chronological order.
266 266 """
267 267
268 268 def __init__(self):
269 269 self._categories = {}
270 270 self._sequences = []
271 271 self._replies = {}
272 272
273 273 def add(self, category, entry, inreplyto=None):
274 274 """add a new record of a given category.
275 275
276 276 The entry can then be retrieved in the list returned by
277 277 self['category']."""
278 278 self._categories.setdefault(category, []).append(entry)
279 279 self._sequences.append((category, entry))
280 280 if inreplyto is not None:
281 281 self.getreplies(inreplyto).add(category, entry)
282 282
283 283 def getreplies(self, partid):
284 284 """get the records that are replies to a specific part"""
285 285 return self._replies.setdefault(partid, unbundlerecords())
286 286
287 287 def __getitem__(self, cat):
288 288 return tuple(self._categories.get(cat, ()))
289 289
290 290 def __iter__(self):
291 291 return iter(self._sequences)
292 292
293 293 def __len__(self):
294 294 return len(self._sequences)
295 295
296 296 def __nonzero__(self):
297 297 return bool(self._sequences)
298 298
299 299 __bool__ = __nonzero__
300 300
301 301
302 302 class bundleoperation:
303 303 """an object that represents a single bundling process
304 304
305 305 Its purpose is to carry unbundle-related objects and states.
306 306
307 307 A new object should be created at the beginning of each bundle processing.
308 308 The object is to be returned by the processing function.
309 309
310 310 The object has very little content now it will ultimately contain:
311 311 * an access to the repo the bundle is applied to,
312 312 * a ui object,
313 313 * a way to retrieve a transaction to add changes to the repo,
314 314 * a way to record the result of processing each part,
315 315 * a way to construct a bundle response when applicable.
316 316 """
317 317
318 318 def __init__(
319 319 self,
320 320 repo,
321 321 transactiongetter,
322 322 captureoutput=True,
323 323 source=b'',
324 324 remote=None,
325 325 ):
326 326 self.repo = repo
327 327 # the peer object who produced this bundle if available
328 328 self.remote = remote
329 329 self.ui = repo.ui
330 330 self.records = unbundlerecords()
331 331 self.reply = None
332 332 self.captureoutput = captureoutput
333 333 self.hookargs = {}
334 334 self._gettransaction = transactiongetter
335 335 # carries value that can modify part behavior
336 336 self.modes = {}
337 337 self.source = source
338 338
339 339 def gettransaction(self):
340 340 transaction = self._gettransaction()
341 341
342 342 if self.hookargs:
343 343 # the ones added to the transaction supercede those added
344 344 # to the operation.
345 345 self.hookargs.update(transaction.hookargs)
346 346 transaction.hookargs = self.hookargs
347 347
348 348 # mark the hookargs as flushed. further attempts to add to
349 349 # hookargs will result in an abort.
350 350 self.hookargs = None
351 351
352 352 return transaction
353 353
354 354 def addhookargs(self, hookargs):
355 355 if self.hookargs is None:
356 356 raise error.ProgrammingError(
357 357 b'attempted to add hookargs to '
358 358 b'operation after transaction started'
359 359 )
360 360 self.hookargs.update(hookargs)
361 361
362 362
363 363 class TransactionUnavailable(RuntimeError):
364 364 pass
365 365
366 366
367 367 def _notransaction():
368 368 """default method to get a transaction while processing a bundle
369 369
370 370 Raise an exception to highlight the fact that no transaction was expected
371 371 to be created"""
372 372 raise TransactionUnavailable()
373 373
374 374
375 375 def applybundle(repo, unbundler, tr, source, url=None, remote=None, **kwargs):
376 376 # transform me into unbundler.apply() as soon as the freeze is lifted
377 377 if isinstance(unbundler, unbundle20):
378 378 tr.hookargs[b'bundle2'] = b'1'
379 379 if source is not None and b'source' not in tr.hookargs:
380 380 tr.hookargs[b'source'] = source
381 381 if url is not None and b'url' not in tr.hookargs:
382 382 tr.hookargs[b'url'] = url
383 383 return processbundle(
384 384 repo, unbundler, lambda: tr, source=source, remote=remote
385 385 )
386 386 else:
387 387 # the transactiongetter won't be used, but we might as well set it
388 388 op = bundleoperation(repo, lambda: tr, source=source, remote=remote)
389 389 _processchangegroup(op, unbundler, tr, source, url, **kwargs)
390 390 return op
391 391
392 392
393 393 class partiterator:
394 394 def __init__(self, repo, op, unbundler):
395 395 self.repo = repo
396 396 self.op = op
397 397 self.unbundler = unbundler
398 398 self.iterator = None
399 399 self.count = 0
400 400 self.current = None
401 401
402 402 def __enter__(self):
403 403 def func():
404 404 itr = enumerate(self.unbundler.iterparts(), 1)
405 405 for count, p in itr:
406 406 self.count = count
407 407 self.current = p
408 408 yield p
409 409 p.consume()
410 410 self.current = None
411 411
412 412 self.iterator = func()
413 413 return self.iterator
414 414
415 415 def __exit__(self, type, exc, tb):
416 416 if not self.iterator:
417 417 return
418 418
419 419 # Only gracefully abort in a normal exception situation. User aborts
420 420 # like Ctrl+C throw a KeyboardInterrupt which is not a base Exception,
421 421 # and should not gracefully cleanup.
422 422 if isinstance(exc, Exception):
423 423 # Any exceptions seeking to the end of the bundle at this point are
424 424 # almost certainly related to the underlying stream being bad.
425 425 # And, chances are that the exception we're handling is related to
426 426 # getting in that bad state. So, we swallow the seeking error and
427 427 # re-raise the original error.
428 428 seekerror = False
429 429 try:
430 430 if self.current:
431 431 # consume the part content to not corrupt the stream.
432 432 self.current.consume()
433 433
434 434 for part in self.iterator:
435 435 # consume the bundle content
436 436 part.consume()
437 437 except Exception:
438 438 seekerror = True
439 439
440 440 # Small hack to let caller code distinguish exceptions from bundle2
441 441 # processing from processing the old format. This is mostly needed
442 442 # to handle different return codes to unbundle according to the type
443 443 # of bundle. We should probably clean up or drop this return code
444 444 # craziness in a future version.
445 445 exc.duringunbundle2 = True
446 446 salvaged = []
447 447 replycaps = None
448 448 if self.op.reply is not None:
449 449 salvaged = self.op.reply.salvageoutput()
450 450 replycaps = self.op.reply.capabilities
451 451 exc._replycaps = replycaps
452 452 exc._bundle2salvagedoutput = salvaged
453 453
454 454 # Re-raising from a variable loses the original stack. So only use
455 455 # that form if we need to.
456 456 if seekerror:
457 457 raise exc
458 458
459 459 self.repo.ui.debug(
460 460 b'bundle2-input-bundle: %i parts total\n' % self.count
461 461 )
462 462
463 463
464 464 def processbundle(
465 465 repo,
466 466 unbundler,
467 467 transactiongetter=None,
468 468 op=None,
469 469 source=b'',
470 470 remote=None,
471 471 ):
472 472 """This function process a bundle, apply effect to/from a repo
473 473
474 474 It iterates over each part then searches for and uses the proper handling
475 475 code to process the part. Parts are processed in order.
476 476
477 477 Unknown Mandatory part will abort the process.
478 478
479 479 It is temporarily possible to provide a prebuilt bundleoperation to the
480 480 function. This is used to ensure output is properly propagated in case of
481 481 an error during the unbundling. This output capturing part will likely be
482 482 reworked and this ability will probably go away in the process.
483 483 """
484 484 if op is None:
485 485 if transactiongetter is None:
486 486 transactiongetter = _notransaction
487 487 op = bundleoperation(
488 488 repo,
489 489 transactiongetter,
490 490 source=source,
491 491 remote=remote,
492 492 )
493 493 # todo:
494 494 # - replace this is a init function soon.
495 495 # - exception catching
496 496 unbundler.params
497 497 if repo.ui.debugflag:
498 498 msg = [b'bundle2-input-bundle:']
499 499 if unbundler.params:
500 500 msg.append(b' %i params' % len(unbundler.params))
501 501 if op._gettransaction is None or op._gettransaction is _notransaction:
502 502 msg.append(b' no-transaction')
503 503 else:
504 504 msg.append(b' with-transaction')
505 505 msg.append(b'\n')
506 506 repo.ui.debug(b''.join(msg))
507 507
508 508 processparts(repo, op, unbundler)
509 509
510 510 return op
511 511
512 512
513 513 def processparts(repo, op, unbundler):
514 514 with partiterator(repo, op, unbundler) as parts:
515 515 for part in parts:
516 516 _processpart(op, part)
517 517
518 518
519 519 def _processchangegroup(op, cg, tr, source, url, **kwargs):
520 520 if op.remote is not None and op.remote.path is not None:
521 521 remote_path = op.remote.path
522 522 kwargs = kwargs.copy()
523 523 kwargs['delta_base_reuse_policy'] = remote_path.delta_reuse_policy
524 524 ret = cg.apply(op.repo, tr, source, url, **kwargs)
525 525 op.records.add(
526 526 b'changegroup',
527 527 {
528 528 b'return': ret,
529 529 },
530 530 )
531 531 return ret
532 532
533 533
534 534 def _gethandler(op, part):
535 535 status = b'unknown' # used by debug output
536 536 try:
537 537 handler = parthandlermapping.get(part.type)
538 538 if handler is None:
539 539 status = b'unsupported-type'
540 540 raise error.BundleUnknownFeatureError(parttype=part.type)
541 541 indebug(op.ui, b'found a handler for part %s' % part.type)
542 542 unknownparams = part.mandatorykeys - handler.params
543 543 if unknownparams:
544 544 unknownparams = list(unknownparams)
545 545 unknownparams.sort()
546 546 status = b'unsupported-params (%s)' % b', '.join(unknownparams)
547 547 raise error.BundleUnknownFeatureError(
548 548 parttype=part.type, params=unknownparams
549 549 )
550 550 status = b'supported'
551 551 except error.BundleUnknownFeatureError as exc:
552 552 if part.mandatory: # mandatory parts
553 553 raise
554 554 indebug(op.ui, b'ignoring unsupported advisory part %s' % exc)
555 555 return # skip to part processing
556 556 finally:
557 557 if op.ui.debugflag:
558 558 msg = [b'bundle2-input-part: "%s"' % part.type]
559 559 if not part.mandatory:
560 560 msg.append(b' (advisory)')
561 561 nbmp = len(part.mandatorykeys)
562 562 nbap = len(part.params) - nbmp
563 563 if nbmp or nbap:
564 564 msg.append(b' (params:')
565 565 if nbmp:
566 566 msg.append(b' %i mandatory' % nbmp)
567 567 if nbap:
568 568 msg.append(b' %i advisory' % nbmp)
569 569 msg.append(b')')
570 570 msg.append(b' %s\n' % status)
571 571 op.ui.debug(b''.join(msg))
572 572
573 573 return handler
574 574
575 575
576 576 def _processpart(op, part):
577 577 """process a single part from a bundle
578 578
579 579 The part is guaranteed to have been fully consumed when the function exits
580 580 (even if an exception is raised)."""
581 581 handler = _gethandler(op, part)
582 582 if handler is None:
583 583 return
584 584
585 585 # handler is called outside the above try block so that we don't
586 586 # risk catching KeyErrors from anything other than the
587 587 # parthandlermapping lookup (any KeyError raised by handler()
588 588 # itself represents a defect of a different variety).
589 589 output = None
590 590 if op.captureoutput and op.reply is not None:
591 591 op.ui.pushbuffer(error=True, subproc=True)
592 592 output = b''
593 593 try:
594 594 handler(op, part)
595 595 finally:
596 596 if output is not None:
597 597 output = op.ui.popbuffer()
598 598 if output:
599 599 outpart = op.reply.newpart(b'output', data=output, mandatory=False)
600 600 outpart.addparam(
601 601 b'in-reply-to', pycompat.bytestr(part.id), mandatory=False
602 602 )
603 603
604 604
605 605 def decodecaps(blob):
606 606 """decode a bundle2 caps bytes blob into a dictionary
607 607
608 608 The blob is a list of capabilities (one per line)
609 609 Capabilities may have values using a line of the form::
610 610
611 611 capability=value1,value2,value3
612 612
613 613 The values are always a list."""
614 614 caps = {}
615 615 for line in blob.splitlines():
616 616 if not line:
617 617 continue
618 618 if b'=' not in line:
619 619 key, vals = line, ()
620 620 else:
621 621 key, vals = line.split(b'=', 1)
622 622 vals = vals.split(b',')
623 623 key = urlreq.unquote(key)
624 624 vals = [urlreq.unquote(v) for v in vals]
625 625 caps[key] = vals
626 626 return caps
627 627
628 628
629 629 def encodecaps(caps):
630 630 """encode a bundle2 caps dictionary into a bytes blob"""
631 631 chunks = []
632 632 for ca in sorted(caps):
633 633 vals = caps[ca]
634 634 ca = urlreq.quote(ca)
635 635 vals = [urlreq.quote(v) for v in vals]
636 636 if vals:
637 637 ca = b"%s=%s" % (ca, b','.join(vals))
638 638 chunks.append(ca)
639 639 return b'\n'.join(chunks)
640 640
641 641
642 642 bundletypes = {
643 643 b"": (b"", b'UN'), # only when using unbundle on ssh and old http servers
644 644 # since the unification ssh accepts a header but there
645 645 # is no capability signaling it.
646 646 b"HG20": (), # special-cased below
647 647 b"HG10UN": (b"HG10UN", b'UN'),
648 648 b"HG10BZ": (b"HG10", b'BZ'),
649 649 b"HG10GZ": (b"HG10GZ", b'GZ'),
650 650 }
651 651
652 652 # hgweb uses this list to communicate its preferred type
653 653 bundlepriority = [b'HG10GZ', b'HG10BZ', b'HG10UN']
654 654
655 655
656 656 class bundle20:
657 657 """represent an outgoing bundle2 container
658 658
659 659 Use the `addparam` method to add stream level parameter. and `newpart` to
660 660 populate it. Then call `getchunks` to retrieve all the binary chunks of
661 661 data that compose the bundle2 container."""
662 662
663 663 _magicstring = b'HG20'
664 664
665 665 def __init__(self, ui, capabilities=()):
666 666 self.ui = ui
667 667 self._params = []
668 668 self._parts = []
669 669 self.capabilities = dict(capabilities)
670 670 self._compengine = util.compengines.forbundletype(b'UN')
671 671 self._compopts = None
672 672 # If compression is being handled by a consumer of the raw
673 673 # data (e.g. the wire protocol), unsetting this flag tells
674 674 # consumers that the bundle is best left uncompressed.
675 675 self.prefercompressed = True
676 676
677 677 def setcompression(self, alg, compopts=None):
678 678 """setup core part compression to <alg>"""
679 679 if alg in (None, b'UN'):
680 680 return
681 681 assert not any(n.lower() == b'compression' for n, v in self._params)
682 682 self.addparam(b'Compression', alg)
683 683 self._compengine = util.compengines.forbundletype(alg)
684 684 self._compopts = compopts
685 685
686 686 @property
687 687 def nbparts(self):
688 688 """total number of parts added to the bundler"""
689 689 return len(self._parts)
690 690
691 691 # methods used to defines the bundle2 content
692 692 def addparam(self, name, value=None):
693 693 """add a stream level parameter"""
694 694 if not name:
695 695 raise error.ProgrammingError(b'empty parameter name')
696 696 if name[0:1] not in pycompat.bytestr(
697 697 string.ascii_letters # pytype: disable=wrong-arg-types
698 698 ):
699 699 raise error.ProgrammingError(
700 700 b'non letter first character: %s' % name
701 701 )
702 702 self._params.append((name, value))
703 703
704 704 def addpart(self, part):
705 705 """add a new part to the bundle2 container
706 706
707 707 Parts contains the actual applicative payload."""
708 708 assert part.id is None
709 709 part.id = len(self._parts) # very cheap counter
710 710 self._parts.append(part)
711 711
712 712 def newpart(self, typeid, *args, **kwargs):
713 713 """create a new part and add it to the containers
714 714
715 715 As the part is directly added to the containers. For now, this means
716 716 that any failure to properly initialize the part after calling
717 717 ``newpart`` should result in a failure of the whole bundling process.
718 718
719 719 You can still fall back to manually create and add if you need better
720 720 control."""
721 721 part = bundlepart(typeid, *args, **kwargs)
722 722 self.addpart(part)
723 723 return part
724 724
725 725 # methods used to generate the bundle2 stream
726 726 def getchunks(self):
727 727 if self.ui.debugflag:
728 728 msg = [b'bundle2-output-bundle: "%s",' % self._magicstring]
729 729 if self._params:
730 730 msg.append(b' (%i params)' % len(self._params))
731 731 msg.append(b' %i parts total\n' % len(self._parts))
732 732 self.ui.debug(b''.join(msg))
733 733 outdebug(self.ui, b'start emission of %s stream' % self._magicstring)
734 734 yield self._magicstring
735 735 param = self._paramchunk()
736 736 outdebug(self.ui, b'bundle parameter: %s' % param)
737 737 yield _pack(_fstreamparamsize, len(param))
738 738 if param:
739 739 yield param
740 740 for chunk in self._compengine.compressstream(
741 741 self._getcorechunk(), self._compopts
742 742 ):
743 743 yield chunk
744 744
745 745 def _paramchunk(self):
746 746 """return a encoded version of all stream parameters"""
747 747 blocks = []
748 748 for par, value in self._params:
749 749 par = urlreq.quote(par)
750 750 if value is not None:
751 751 value = urlreq.quote(value)
752 752 par = b'%s=%s' % (par, value)
753 753 blocks.append(par)
754 754 return b' '.join(blocks)
755 755
756 756 def _getcorechunk(self):
757 757 """yield chunk for the core part of the bundle
758 758
759 759 (all but headers and parameters)"""
760 760 outdebug(self.ui, b'start of parts')
761 761 for part in self._parts:
762 762 outdebug(self.ui, b'bundle part: "%s"' % part.type)
763 763 for chunk in part.getchunks(ui=self.ui):
764 764 yield chunk
765 765 outdebug(self.ui, b'end of bundle')
766 766 yield _pack(_fpartheadersize, 0)
767 767
768 768 def salvageoutput(self):
769 769 """return a list with a copy of all output parts in the bundle
770 770
771 771 This is meant to be used during error handling to make sure we preserve
772 772 server output"""
773 773 salvaged = []
774 774 for part in self._parts:
775 775 if part.type.startswith(b'output'):
776 776 salvaged.append(part.copy())
777 777 return salvaged
778 778
779 779
780 780 class unpackermixin:
781 781 """A mixin to extract bytes and struct data from a stream"""
782 782
783 783 def __init__(self, fp):
784 784 self._fp = fp
785 785
786 786 def _unpack(self, format):
787 787 """unpack this struct format from the stream
788 788
789 789 This method is meant for internal usage by the bundle2 protocol only.
790 790 They directly manipulate the low level stream including bundle2 level
791 791 instruction.
792 792
793 793 Do not use it to implement higher-level logic or methods."""
794 794 data = self._readexact(struct.calcsize(format))
795 795 return _unpack(format, data)
796 796
797 797 def _readexact(self, size):
798 798 """read exactly <size> bytes from the stream
799 799
800 800 This method is meant for internal usage by the bundle2 protocol only.
801 801 They directly manipulate the low level stream including bundle2 level
802 802 instruction.
803 803
804 804 Do not use it to implement higher-level logic or methods."""
805 805 return changegroup.readexactly(self._fp, size)
806 806
807 807
808 808 def getunbundler(ui, fp, magicstring=None):
809 809 """return a valid unbundler object for a given magicstring"""
810 810 if magicstring is None:
811 811 magicstring = changegroup.readexactly(fp, 4)
812 812 magic, version = magicstring[0:2], magicstring[2:4]
813 813 if magic != b'HG':
814 814 ui.debug(
815 815 b"error: invalid magic: %r (version %r), should be 'HG'\n"
816 816 % (magic, version)
817 817 )
818 818 raise error.Abort(_(b'not a Mercurial bundle'))
819 819 unbundlerclass = formatmap.get(version)
820 820 if unbundlerclass is None:
821 821 raise error.Abort(_(b'unknown bundle version %s') % version)
822 822 unbundler = unbundlerclass(ui, fp)
823 823 indebug(ui, b'start processing of %s stream' % magicstring)
824 824 return unbundler
825 825
826 826
827 827 class unbundle20(unpackermixin):
828 828 """interpret a bundle2 stream
829 829
830 830 This class is fed with a binary stream and yields parts through its
831 831 `iterparts` methods."""
832 832
833 833 _magicstring = b'HG20'
834 834
835 835 def __init__(self, ui, fp):
836 836 """If header is specified, we do not read it out of the stream."""
837 837 self.ui = ui
838 838 self._compengine = util.compengines.forbundletype(b'UN')
839 839 self._compressed = None
840 840 super(unbundle20, self).__init__(fp)
841 841
842 842 @util.propertycache
843 843 def params(self):
844 844 """dictionary of stream level parameters"""
845 845 indebug(self.ui, b'reading bundle2 stream parameters')
846 846 params = {}
847 847 paramssize = self._unpack(_fstreamparamsize)[0]
848 848 if paramssize < 0:
849 849 raise error.BundleValueError(
850 850 b'negative bundle param size: %i' % paramssize
851 851 )
852 852 if paramssize:
853 853 params = self._readexact(paramssize)
854 854 params = self._processallparams(params)
855 855 return params
856 856
857 857 def _processallparams(self, paramsblock):
858 858 """ """
859 859 params = util.sortdict()
860 860 for p in paramsblock.split(b' '):
861 861 p = p.split(b'=', 1)
862 862 p = [urlreq.unquote(i) for i in p]
863 863 if len(p) < 2:
864 864 p.append(None)
865 865 self._processparam(*p)
866 866 params[p[0]] = p[1]
867 867 return params
868 868
869 869 def _processparam(self, name, value):
870 870 """process a parameter, applying its effect if needed
871 871
872 872 Parameter starting with a lower case letter are advisory and will be
873 873 ignored when unknown. Those starting with an upper case letter are
874 874 mandatory and will this function will raise a KeyError when unknown.
875 875
876 876 Note: no option are currently supported. Any input will be either
877 877 ignored or failing.
878 878 """
879 879 if not name:
880 880 raise ValueError('empty parameter name')
881 881 if name[0:1] not in pycompat.bytestr(
882 882 string.ascii_letters # pytype: disable=wrong-arg-types
883 883 ):
884 884 raise ValueError('non letter first character: %s' % name)
885 885 try:
886 886 handler = b2streamparamsmap[name.lower()]
887 887 except KeyError:
888 888 if name[0:1].islower():
889 889 indebug(self.ui, b"ignoring unknown parameter %s" % name)
890 890 else:
891 891 raise error.BundleUnknownFeatureError(params=(name,))
892 892 else:
893 893 handler(self, name, value)
894 894
895 895 def _forwardchunks(self):
896 896 """utility to transfer a bundle2 as binary
897 897
898 898 This is made necessary by the fact the 'getbundle' command over 'ssh'
899 899 have no way to know then the reply end, relying on the bundle to be
900 900 interpreted to know its end. This is terrible and we are sorry, but we
901 901 needed to move forward to get general delta enabled.
902 902 """
903 903 yield self._magicstring
904 904 assert 'params' not in vars(self)
905 905 paramssize = self._unpack(_fstreamparamsize)[0]
906 906 if paramssize < 0:
907 907 raise error.BundleValueError(
908 908 b'negative bundle param size: %i' % paramssize
909 909 )
910 910 if paramssize:
911 911 params = self._readexact(paramssize)
912 912 self._processallparams(params)
913 913 # The payload itself is decompressed below, so drop
914 914 # the compression parameter passed down to compensate.
915 915 outparams = []
916 916 for p in params.split(b' '):
917 917 k, v = p.split(b'=', 1)
918 918 if k.lower() != b'compression':
919 919 outparams.append(p)
920 920 outparams = b' '.join(outparams)
921 921 yield _pack(_fstreamparamsize, len(outparams))
922 922 yield outparams
923 923 else:
924 924 yield _pack(_fstreamparamsize, paramssize)
925 925 # From there, payload might need to be decompressed
926 926 self._fp = self._compengine.decompressorreader(self._fp)
927 927 emptycount = 0
928 928 while emptycount < 2:
929 929 # so we can brainlessly loop
930 930 assert _fpartheadersize == _fpayloadsize
931 931 size = self._unpack(_fpartheadersize)[0]
932 932 yield _pack(_fpartheadersize, size)
933 933 if size:
934 934 emptycount = 0
935 935 else:
936 936 emptycount += 1
937 937 continue
938 938 if size == flaginterrupt:
939 939 continue
940 940 elif size < 0:
941 941 raise error.BundleValueError(b'negative chunk size: %i')
942 942 yield self._readexact(size)
943 943
944 944 def iterparts(self, seekable=False):
945 945 """yield all parts contained in the stream"""
946 946 cls = seekableunbundlepart if seekable else unbundlepart
947 947 # make sure param have been loaded
948 948 self.params
949 949 # From there, payload need to be decompressed
950 950 self._fp = self._compengine.decompressorreader(self._fp)
951 951 indebug(self.ui, b'start extraction of bundle2 parts')
952 952 headerblock = self._readpartheader()
953 953 while headerblock is not None:
954 954 part = cls(self.ui, headerblock, self._fp)
955 955 yield part
956 956 # Ensure part is fully consumed so we can start reading the next
957 957 # part.
958 958 part.consume()
959 959
960 960 headerblock = self._readpartheader()
961 961 indebug(self.ui, b'end of bundle2 stream')
962 962
963 963 def _readpartheader(self):
964 964 """reads a part header size and return the bytes blob
965 965
966 966 returns None if empty"""
967 967 headersize = self._unpack(_fpartheadersize)[0]
968 968 if headersize < 0:
969 969 raise error.BundleValueError(
970 970 b'negative part header size: %i' % headersize
971 971 )
972 972 indebug(self.ui, b'part header size: %i' % headersize)
973 973 if headersize:
974 974 return self._readexact(headersize)
975 975 return None
976 976
977 977 def compressed(self):
978 978 self.params # load params
979 979 return self._compressed
980 980
981 981 def close(self):
982 982 """close underlying file"""
983 983 if util.safehasattr(self._fp, 'close'):
984 984 return self._fp.close()
985 985
986 986
987 987 formatmap = {b'20': unbundle20}
988 988
989 989 b2streamparamsmap = {}
990 990
991 991
992 992 def b2streamparamhandler(name):
993 993 """register a handler for a stream level parameter"""
994 994
995 995 def decorator(func):
996 996 assert name not in formatmap
997 997 b2streamparamsmap[name] = func
998 998 return func
999 999
1000 1000 return decorator
1001 1001
1002 1002
1003 1003 @b2streamparamhandler(b'compression')
1004 1004 def processcompression(unbundler, param, value):
1005 1005 """read compression parameter and install payload decompression"""
1006 1006 if value not in util.compengines.supportedbundletypes:
1007 1007 raise error.BundleUnknownFeatureError(params=(param,), values=(value,))
1008 1008 unbundler._compengine = util.compengines.forbundletype(value)
1009 1009 if value is not None:
1010 1010 unbundler._compressed = True
1011 1011
1012 1012
1013 1013 class bundlepart:
1014 1014 """A bundle2 part contains application level payload
1015 1015
1016 1016 The part `type` is used to route the part to the application level
1017 1017 handler.
1018 1018
1019 1019 The part payload is contained in ``part.data``. It could be raw bytes or a
1020 1020 generator of byte chunks.
1021 1021
1022 1022 You can add parameters to the part using the ``addparam`` method.
1023 1023 Parameters can be either mandatory (default) or advisory. Remote side
1024 1024 should be able to safely ignore the advisory ones.
1025 1025
1026 1026 Both data and parameters cannot be modified after the generation has begun.
1027 1027 """
1028 1028
1029 1029 def __init__(
1030 1030 self,
1031 1031 parttype,
1032 1032 mandatoryparams=(),
1033 1033 advisoryparams=(),
1034 1034 data=b'',
1035 1035 mandatory=True,
1036 1036 ):
1037 1037 validateparttype(parttype)
1038 1038 self.id = None
1039 1039 self.type = parttype
1040 1040 self._data = data
1041 1041 self._mandatoryparams = list(mandatoryparams)
1042 1042 self._advisoryparams = list(advisoryparams)
1043 1043 # checking for duplicated entries
1044 1044 self._seenparams = set()
1045 1045 for pname, __ in self._mandatoryparams + self._advisoryparams:
1046 1046 if pname in self._seenparams:
1047 1047 raise error.ProgrammingError(b'duplicated params: %s' % pname)
1048 1048 self._seenparams.add(pname)
1049 1049 # status of the part's generation:
1050 1050 # - None: not started,
1051 1051 # - False: currently generated,
1052 1052 # - True: generation done.
1053 1053 self._generated = None
1054 1054 self.mandatory = mandatory
1055 1055
1056 1056 def __repr__(self):
1057 1057 cls = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
1058 1058 return '<%s object at %x; id: %s; type: %s; mandatory: %s>' % (
1059 1059 cls,
1060 1060 id(self),
1061 1061 self.id,
1062 1062 self.type,
1063 1063 self.mandatory,
1064 1064 )
1065 1065
1066 1066 def copy(self):
1067 1067 """return a copy of the part
1068 1068
1069 1069 The new part have the very same content but no partid assigned yet.
1070 1070 Parts with generated data cannot be copied."""
1071 1071 assert not util.safehasattr(self.data, 'next')
1072 1072 return self.__class__(
1073 1073 self.type,
1074 1074 self._mandatoryparams,
1075 1075 self._advisoryparams,
1076 1076 self._data,
1077 1077 self.mandatory,
1078 1078 )
1079 1079
1080 1080 # methods used to defines the part content
1081 1081 @property
1082 1082 def data(self):
1083 1083 return self._data
1084 1084
1085 1085 @data.setter
1086 1086 def data(self, data):
1087 1087 if self._generated is not None:
1088 1088 raise error.ReadOnlyPartError(b'part is being generated')
1089 1089 self._data = data
1090 1090
1091 1091 @property
1092 1092 def mandatoryparams(self):
1093 1093 # make it an immutable tuple to force people through ``addparam``
1094 1094 return tuple(self._mandatoryparams)
1095 1095
1096 1096 @property
1097 1097 def advisoryparams(self):
1098 1098 # make it an immutable tuple to force people through ``addparam``
1099 1099 return tuple(self._advisoryparams)
1100 1100
1101 1101 def addparam(self, name, value=b'', mandatory=True):
1102 1102 """add a parameter to the part
1103 1103
1104 1104 If 'mandatory' is set to True, the remote handler must claim support
1105 1105 for this parameter or the unbundling will be aborted.
1106 1106
1107 1107 The 'name' and 'value' cannot exceed 255 bytes each.
1108 1108 """
1109 1109 if self._generated is not None:
1110 1110 raise error.ReadOnlyPartError(b'part is being generated')
1111 1111 if name in self._seenparams:
1112 1112 raise ValueError(b'duplicated params: %s' % name)
1113 1113 self._seenparams.add(name)
1114 1114 params = self._advisoryparams
1115 1115 if mandatory:
1116 1116 params = self._mandatoryparams
1117 1117 params.append((name, value))
1118 1118
1119 1119 # methods used to generates the bundle2 stream
1120 1120 def getchunks(self, ui):
1121 1121 if self._generated is not None:
1122 1122 raise error.ProgrammingError(b'part can only be consumed once')
1123 1123 self._generated = False
1124 1124
1125 1125 if ui.debugflag:
1126 1126 msg = [b'bundle2-output-part: "%s"' % self.type]
1127 1127 if not self.mandatory:
1128 1128 msg.append(b' (advisory)')
1129 1129 nbmp = len(self.mandatoryparams)
1130 1130 nbap = len(self.advisoryparams)
1131 1131 if nbmp or nbap:
1132 1132 msg.append(b' (params:')
1133 1133 if nbmp:
1134 1134 msg.append(b' %i mandatory' % nbmp)
1135 1135 if nbap:
1136 1136 msg.append(b' %i advisory' % nbmp)
1137 1137 msg.append(b')')
1138 1138 if not self.data:
1139 1139 msg.append(b' empty payload')
1140 1140 elif util.safehasattr(self.data, 'next') or util.safehasattr(
1141 1141 self.data, b'__next__'
1142 1142 ):
1143 1143 msg.append(b' streamed payload')
1144 1144 else:
1145 1145 msg.append(b' %i bytes payload' % len(self.data))
1146 1146 msg.append(b'\n')
1147 1147 ui.debug(b''.join(msg))
1148 1148
1149 1149 #### header
1150 1150 if self.mandatory:
1151 1151 parttype = self.type.upper()
1152 1152 else:
1153 1153 parttype = self.type.lower()
1154 1154 outdebug(ui, b'part %s: "%s"' % (pycompat.bytestr(self.id), parttype))
1155 1155 ## parttype
1156 1156 header = [
1157 1157 _pack(_fparttypesize, len(parttype)),
1158 1158 parttype,
1159 1159 _pack(_fpartid, self.id),
1160 1160 ]
1161 1161 ## parameters
1162 1162 # count
1163 1163 manpar = self.mandatoryparams
1164 1164 advpar = self.advisoryparams
1165 1165 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
1166 1166 # size
1167 1167 parsizes = []
1168 1168 for key, value in manpar:
1169 1169 parsizes.append(len(key))
1170 1170 parsizes.append(len(value))
1171 1171 for key, value in advpar:
1172 1172 parsizes.append(len(key))
1173 1173 parsizes.append(len(value))
1174 1174 paramsizes = _pack(_makefpartparamsizes(len(parsizes) // 2), *parsizes)
1175 1175 header.append(paramsizes)
1176 1176 # key, value
1177 1177 for key, value in manpar:
1178 1178 header.append(key)
1179 1179 header.append(value)
1180 1180 for key, value in advpar:
1181 1181 header.append(key)
1182 1182 header.append(value)
1183 1183 ## finalize header
1184 1184 try:
1185 1185 headerchunk = b''.join(header)
1186 1186 except TypeError:
1187 1187 raise TypeError(
1188 1188 'Found a non-bytes trying to '
1189 1189 'build bundle part header: %r' % header
1190 1190 )
1191 1191 outdebug(ui, b'header chunk size: %i' % len(headerchunk))
1192 1192 yield _pack(_fpartheadersize, len(headerchunk))
1193 1193 yield headerchunk
1194 1194 ## payload
1195 1195 try:
1196 1196 for chunk in self._payloadchunks():
1197 1197 outdebug(ui, b'payload chunk size: %i' % len(chunk))
1198 1198 yield _pack(_fpayloadsize, len(chunk))
1199 1199 yield chunk
1200 1200 except GeneratorExit:
1201 1201 # GeneratorExit means that nobody is listening for our
1202 1202 # results anyway, so just bail quickly rather than trying
1203 1203 # to produce an error part.
1204 1204 ui.debug(b'bundle2-generatorexit\n')
1205 1205 raise
1206 1206 except BaseException as exc:
1207 1207 bexc = stringutil.forcebytestr(exc)
1208 1208 # backup exception data for later
1209 1209 ui.debug(
1210 1210 b'bundle2-input-stream-interrupt: encoding exception %s' % bexc
1211 1211 )
1212 1212 tb = sys.exc_info()[2]
1213 1213 msg = b'unexpected error: %s' % bexc
1214 1214 interpart = bundlepart(
1215 1215 b'error:abort', [(b'message', msg)], mandatory=False
1216 1216 )
1217 1217 interpart.id = 0
1218 1218 yield _pack(_fpayloadsize, -1)
1219 1219 for chunk in interpart.getchunks(ui=ui):
1220 1220 yield chunk
1221 1221 outdebug(ui, b'closing payload chunk')
1222 1222 # abort current part payload
1223 1223 yield _pack(_fpayloadsize, 0)
1224 1224 pycompat.raisewithtb(exc, tb)
1225 1225 # end of payload
1226 1226 outdebug(ui, b'closing payload chunk')
1227 1227 yield _pack(_fpayloadsize, 0)
1228 1228 self._generated = True
1229 1229
1230 1230 def _payloadchunks(self):
1231 1231 """yield chunks of a the part payload
1232 1232
1233 1233 Exists to handle the different methods to provide data to a part."""
1234 1234 # we only support fixed size data now.
1235 1235 # This will be improved in the future.
1236 1236 if util.safehasattr(self.data, 'next') or util.safehasattr(
1237 1237 self.data, b'__next__'
1238 1238 ):
1239 1239 buff = util.chunkbuffer(self.data)
1240 1240 chunk = buff.read(preferedchunksize)
1241 1241 while chunk:
1242 1242 yield chunk
1243 1243 chunk = buff.read(preferedchunksize)
1244 1244 elif len(self.data):
1245 1245 yield self.data
1246 1246
1247 1247
1248 1248 flaginterrupt = -1
1249 1249
1250 1250
1251 1251 class interrupthandler(unpackermixin):
1252 1252 """read one part and process it with restricted capability
1253 1253
1254 1254 This allows to transmit exception raised on the producer size during part
1255 1255 iteration while the consumer is reading a part.
1256 1256
1257 1257 Part processed in this manner only have access to a ui object,"""
1258 1258
1259 1259 def __init__(self, ui, fp):
1260 1260 super(interrupthandler, self).__init__(fp)
1261 1261 self.ui = ui
1262 1262
1263 1263 def _readpartheader(self):
1264 1264 """reads a part header size and return the bytes blob
1265 1265
1266 1266 returns None if empty"""
1267 1267 headersize = self._unpack(_fpartheadersize)[0]
1268 1268 if headersize < 0:
1269 1269 raise error.BundleValueError(
1270 1270 b'negative part header size: %i' % headersize
1271 1271 )
1272 1272 indebug(self.ui, b'part header size: %i\n' % headersize)
1273 1273 if headersize:
1274 1274 return self._readexact(headersize)
1275 1275 return None
1276 1276
1277 1277 def __call__(self):
1278 1278
1279 1279 self.ui.debug(
1280 1280 b'bundle2-input-stream-interrupt: opening out of band context\n'
1281 1281 )
1282 1282 indebug(self.ui, b'bundle2 stream interruption, looking for a part.')
1283 1283 headerblock = self._readpartheader()
1284 1284 if headerblock is None:
1285 1285 indebug(self.ui, b'no part found during interruption.')
1286 1286 return
1287 1287 part = unbundlepart(self.ui, headerblock, self._fp)
1288 1288 op = interruptoperation(self.ui)
1289 1289 hardabort = False
1290 1290 try:
1291 1291 _processpart(op, part)
1292 1292 except (SystemExit, KeyboardInterrupt):
1293 1293 hardabort = True
1294 1294 raise
1295 1295 finally:
1296 1296 if not hardabort:
1297 1297 part.consume()
1298 1298 self.ui.debug(
1299 1299 b'bundle2-input-stream-interrupt: closing out of band context\n'
1300 1300 )
1301 1301
1302 1302
1303 1303 class interruptoperation:
1304 1304 """A limited operation to be use by part handler during interruption
1305 1305
1306 1306 It only have access to an ui object.
1307 1307 """
1308 1308
1309 1309 def __init__(self, ui):
1310 1310 self.ui = ui
1311 1311 self.reply = None
1312 1312 self.captureoutput = False
1313 1313
1314 1314 @property
1315 1315 def repo(self):
1316 1316 raise error.ProgrammingError(b'no repo access from stream interruption')
1317 1317
1318 1318 def gettransaction(self):
1319 1319 raise TransactionUnavailable(b'no repo access from stream interruption')
1320 1320
1321 1321
1322 1322 def decodepayloadchunks(ui, fh):
1323 1323 """Reads bundle2 part payload data into chunks.
1324 1324
1325 1325 Part payload data consists of framed chunks. This function takes
1326 1326 a file handle and emits those chunks.
1327 1327 """
1328 1328 dolog = ui.configbool(b'devel', b'bundle2.debug')
1329 1329 debug = ui.debug
1330 1330
1331 1331 headerstruct = struct.Struct(_fpayloadsize)
1332 1332 headersize = headerstruct.size
1333 1333 unpack = headerstruct.unpack
1334 1334
1335 1335 readexactly = changegroup.readexactly
1336 1336 read = fh.read
1337 1337
1338 1338 chunksize = unpack(readexactly(fh, headersize))[0]
1339 1339 indebug(ui, b'payload chunk size: %i' % chunksize)
1340 1340
1341 1341 # changegroup.readexactly() is inlined below for performance.
1342 1342 while chunksize:
1343 1343 if chunksize >= 0:
1344 1344 s = read(chunksize)
1345 1345 if len(s) < chunksize:
1346 1346 raise error.Abort(
1347 1347 _(
1348 1348 b'stream ended unexpectedly '
1349 1349 b' (got %d bytes, expected %d)'
1350 1350 )
1351 1351 % (len(s), chunksize)
1352 1352 )
1353 1353
1354 1354 yield s
1355 1355 elif chunksize == flaginterrupt:
1356 1356 # Interrupt "signal" detected. The regular stream is interrupted
1357 1357 # and a bundle2 part follows. Consume it.
1358 1358 interrupthandler(ui, fh)()
1359 1359 else:
1360 1360 raise error.BundleValueError(
1361 1361 b'negative payload chunk size: %s' % chunksize
1362 1362 )
1363 1363
1364 1364 s = read(headersize)
1365 1365 if len(s) < headersize:
1366 1366 raise error.Abort(
1367 1367 _(b'stream ended unexpectedly (got %d bytes, expected %d)')
1368 1368 % (len(s), chunksize)
1369 1369 )
1370 1370
1371 1371 chunksize = unpack(s)[0]
1372 1372
1373 1373 # indebug() inlined for performance.
1374 1374 if dolog:
1375 1375 debug(b'bundle2-input: payload chunk size: %i\n' % chunksize)
1376 1376
1377 1377
1378 1378 class unbundlepart(unpackermixin):
1379 1379 """a bundle part read from a bundle"""
1380 1380
1381 1381 def __init__(self, ui, header, fp):
1382 1382 super(unbundlepart, self).__init__(fp)
1383 1383 self._seekable = util.safehasattr(fp, 'seek') and util.safehasattr(
1384 1384 fp, b'tell'
1385 1385 )
1386 1386 self.ui = ui
1387 1387 # unbundle state attr
1388 1388 self._headerdata = header
1389 1389 self._headeroffset = 0
1390 1390 self._initialized = False
1391 1391 self.consumed = False
1392 1392 # part data
1393 1393 self.id = None
1394 1394 self.type = None
1395 1395 self.mandatoryparams = None
1396 1396 self.advisoryparams = None
1397 1397 self.params = None
1398 1398 self.mandatorykeys = ()
1399 1399 self._readheader()
1400 1400 self._mandatory = None
1401 1401 self._pos = 0
1402 1402
1403 1403 def _fromheader(self, size):
1404 1404 """return the next <size> byte from the header"""
1405 1405 offset = self._headeroffset
1406 1406 data = self._headerdata[offset : (offset + size)]
1407 1407 self._headeroffset = offset + size
1408 1408 return data
1409 1409
1410 1410 def _unpackheader(self, format):
1411 1411 """read given format from header
1412 1412
1413 1413 This automatically compute the size of the format to read."""
1414 1414 data = self._fromheader(struct.calcsize(format))
1415 1415 return _unpack(format, data)
1416 1416
1417 1417 def _initparams(self, mandatoryparams, advisoryparams):
1418 1418 """internal function to setup all logic related parameters"""
1419 1419 # make it read only to prevent people touching it by mistake.
1420 1420 self.mandatoryparams = tuple(mandatoryparams)
1421 1421 self.advisoryparams = tuple(advisoryparams)
1422 1422 # user friendly UI
1423 1423 self.params = util.sortdict(self.mandatoryparams)
1424 1424 self.params.update(self.advisoryparams)
1425 1425 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
1426 1426
1427 1427 def _readheader(self):
1428 1428 """read the header and setup the object"""
1429 1429 typesize = self._unpackheader(_fparttypesize)[0]
1430 1430 self.type = self._fromheader(typesize)
1431 1431 indebug(self.ui, b'part type: "%s"' % self.type)
1432 1432 self.id = self._unpackheader(_fpartid)[0]
1433 1433 indebug(self.ui, b'part id: "%s"' % pycompat.bytestr(self.id))
1434 1434 # extract mandatory bit from type
1435 1435 self.mandatory = self.type != self.type.lower()
1436 1436 self.type = self.type.lower()
1437 1437 ## reading parameters
1438 1438 # param count
1439 1439 mancount, advcount = self._unpackheader(_fpartparamcount)
1440 1440 indebug(self.ui, b'part parameters: %i' % (mancount + advcount))
1441 1441 # param size
1442 1442 fparamsizes = _makefpartparamsizes(mancount + advcount)
1443 1443 paramsizes = self._unpackheader(fparamsizes)
1444 1444 # make it a list of couple again
1445 1445 paramsizes = list(zip(paramsizes[::2], paramsizes[1::2]))
1446 1446 # split mandatory from advisory
1447 1447 mansizes = paramsizes[:mancount]
1448 1448 advsizes = paramsizes[mancount:]
1449 1449 # retrieve param value
1450 1450 manparams = []
1451 1451 for key, value in mansizes:
1452 1452 manparams.append((self._fromheader(key), self._fromheader(value)))
1453 1453 advparams = []
1454 1454 for key, value in advsizes:
1455 1455 advparams.append((self._fromheader(key), self._fromheader(value)))
1456 1456 self._initparams(manparams, advparams)
1457 1457 ## part payload
1458 1458 self._payloadstream = util.chunkbuffer(self._payloadchunks())
1459 1459 # we read the data, tell it
1460 1460 self._initialized = True
1461 1461
1462 1462 def _payloadchunks(self):
1463 1463 """Generator of decoded chunks in the payload."""
1464 1464 return decodepayloadchunks(self.ui, self._fp)
1465 1465
1466 1466 def consume(self):
1467 1467 """Read the part payload until completion.
1468 1468
1469 1469 By consuming the part data, the underlying stream read offset will
1470 1470 be advanced to the next part (or end of stream).
1471 1471 """
1472 1472 if self.consumed:
1473 1473 return
1474 1474
1475 1475 chunk = self.read(32768)
1476 1476 while chunk:
1477 1477 self._pos += len(chunk)
1478 1478 chunk = self.read(32768)
1479 1479
1480 1480 def read(self, size=None):
1481 1481 """read payload data"""
1482 1482 if not self._initialized:
1483 1483 self._readheader()
1484 1484 if size is None:
1485 1485 data = self._payloadstream.read()
1486 1486 else:
1487 1487 data = self._payloadstream.read(size)
1488 1488 self._pos += len(data)
1489 1489 if size is None or len(data) < size:
1490 1490 if not self.consumed and self._pos:
1491 1491 self.ui.debug(
1492 1492 b'bundle2-input-part: total payload size %i\n' % self._pos
1493 1493 )
1494 1494 self.consumed = True
1495 1495 return data
1496 1496
1497 1497
1498 1498 class seekableunbundlepart(unbundlepart):
1499 1499 """A bundle2 part in a bundle that is seekable.
1500 1500
1501 1501 Regular ``unbundlepart`` instances can only be read once. This class
1502 1502 extends ``unbundlepart`` to enable bi-directional seeking within the
1503 1503 part.
1504 1504
1505 1505 Bundle2 part data consists of framed chunks. Offsets when seeking
1506 1506 refer to the decoded data, not the offsets in the underlying bundle2
1507 1507 stream.
1508 1508
1509 1509 To facilitate quickly seeking within the decoded data, instances of this
1510 1510 class maintain a mapping between offsets in the underlying stream and
1511 1511 the decoded payload. This mapping will consume memory in proportion
1512 1512 to the number of chunks within the payload (which almost certainly
1513 1513 increases in proportion with the size of the part).
1514 1514 """
1515 1515
1516 1516 def __init__(self, ui, header, fp):
1517 1517 # (payload, file) offsets for chunk starts.
1518 1518 self._chunkindex = []
1519 1519
1520 1520 super(seekableunbundlepart, self).__init__(ui, header, fp)
1521 1521
1522 1522 def _payloadchunks(self, chunknum=0):
1523 1523 '''seek to specified chunk and start yielding data'''
1524 1524 if len(self._chunkindex) == 0:
1525 1525 assert chunknum == 0, b'Must start with chunk 0'
1526 1526 self._chunkindex.append((0, self._tellfp()))
1527 1527 else:
1528 1528 assert chunknum < len(self._chunkindex), (
1529 1529 b'Unknown chunk %d' % chunknum
1530 1530 )
1531 1531 self._seekfp(self._chunkindex[chunknum][1])
1532 1532
1533 1533 pos = self._chunkindex[chunknum][0]
1534 1534
1535 1535 for chunk in decodepayloadchunks(self.ui, self._fp):
1536 1536 chunknum += 1
1537 1537 pos += len(chunk)
1538 1538 if chunknum == len(self._chunkindex):
1539 1539 self._chunkindex.append((pos, self._tellfp()))
1540 1540
1541 1541 yield chunk
1542 1542
1543 1543 def _findchunk(self, pos):
1544 1544 '''for a given payload position, return a chunk number and offset'''
1545 1545 for chunk, (ppos, fpos) in enumerate(self._chunkindex):
1546 1546 if ppos == pos:
1547 1547 return chunk, 0
1548 1548 elif ppos > pos:
1549 1549 return chunk - 1, pos - self._chunkindex[chunk - 1][0]
1550 1550 raise ValueError(b'Unknown chunk')
1551 1551
1552 1552 def tell(self):
1553 1553 return self._pos
1554 1554
1555 1555 def seek(self, offset, whence=os.SEEK_SET):
1556 1556 if whence == os.SEEK_SET:
1557 1557 newpos = offset
1558 1558 elif whence == os.SEEK_CUR:
1559 1559 newpos = self._pos + offset
1560 1560 elif whence == os.SEEK_END:
1561 1561 if not self.consumed:
1562 1562 # Can't use self.consume() here because it advances self._pos.
1563 1563 chunk = self.read(32768)
1564 1564 while chunk:
1565 1565 chunk = self.read(32768)
1566 1566 newpos = self._chunkindex[-1][0] - offset
1567 1567 else:
1568 1568 raise ValueError(b'Unknown whence value: %r' % (whence,))
1569 1569
1570 1570 if newpos > self._chunkindex[-1][0] and not self.consumed:
1571 1571 # Can't use self.consume() here because it advances self._pos.
1572 1572 chunk = self.read(32768)
1573 1573 while chunk:
1574 1574 chunk = self.read(32668)
1575 1575
1576 1576 if not 0 <= newpos <= self._chunkindex[-1][0]:
1577 1577 raise ValueError(b'Offset out of range')
1578 1578
1579 1579 if self._pos != newpos:
1580 1580 chunk, internaloffset = self._findchunk(newpos)
1581 1581 self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
1582 1582 adjust = self.read(internaloffset)
1583 1583 if len(adjust) != internaloffset:
1584 1584 raise error.Abort(_(b'Seek failed\n'))
1585 1585 self._pos = newpos
1586 1586
1587 1587 def _seekfp(self, offset, whence=0):
1588 1588 """move the underlying file pointer
1589 1589
1590 1590 This method is meant for internal usage by the bundle2 protocol only.
1591 1591 They directly manipulate the low level stream including bundle2 level
1592 1592 instruction.
1593 1593
1594 1594 Do not use it to implement higher-level logic or methods."""
1595 1595 if self._seekable:
1596 1596 return self._fp.seek(offset, whence)
1597 1597 else:
1598 1598 raise NotImplementedError(_(b'File pointer is not seekable'))
1599 1599
1600 1600 def _tellfp(self):
1601 1601 """return the file offset, or None if file is not seekable
1602 1602
1603 1603 This method is meant for internal usage by the bundle2 protocol only.
1604 1604 They directly manipulate the low level stream including bundle2 level
1605 1605 instruction.
1606 1606
1607 1607 Do not use it to implement higher-level logic or methods."""
1608 1608 if self._seekable:
1609 1609 try:
1610 1610 return self._fp.tell()
1611 1611 except IOError as e:
1612 1612 if e.errno == errno.ESPIPE:
1613 1613 self._seekable = False
1614 1614 else:
1615 1615 raise
1616 1616 return None
1617 1617
1618 1618
1619 1619 # These are only the static capabilities.
1620 1620 # Check the 'getrepocaps' function for the rest.
1621 1621 capabilities = {
1622 1622 b'HG20': (),
1623 1623 b'bookmarks': (),
1624 1624 b'error': (b'abort', b'unsupportedcontent', b'pushraced', b'pushkey'),
1625 1625 b'listkeys': (),
1626 1626 b'pushkey': (),
1627 1627 b'digests': tuple(sorted(util.DIGESTS.keys())),
1628 1628 b'remote-changegroup': (b'http', b'https'),
1629 1629 b'hgtagsfnodes': (),
1630 1630 b'phases': (b'heads',),
1631 1631 b'stream': (b'v2',),
1632 1632 }
1633 1633
1634 1634
1635 1635 def getrepocaps(repo, allowpushback=False, role=None):
1636 1636 """return the bundle2 capabilities for a given repo
1637 1637
1638 1638 Exists to allow extensions (like evolution) to mutate the capabilities.
1639 1639
1640 1640 The returned value is used for servers advertising their capabilities as
1641 1641 well as clients advertising their capabilities to servers as part of
1642 1642 bundle2 requests. The ``role`` argument specifies which is which.
1643 1643 """
1644 1644 if role not in (b'client', b'server'):
1645 1645 raise error.ProgrammingError(b'role argument must be client or server')
1646 1646
1647 1647 caps = capabilities.copy()
1648 1648 caps[b'changegroup'] = tuple(
1649 1649 sorted(changegroup.supportedincomingversions(repo))
1650 1650 )
1651 1651 if obsolete.isenabled(repo, obsolete.exchangeopt):
1652 1652 supportedformat = tuple(b'V%i' % v for v in obsolete.formats)
1653 1653 caps[b'obsmarkers'] = supportedformat
1654 1654 if allowpushback:
1655 1655 caps[b'pushback'] = ()
1656 1656 cpmode = repo.ui.config(b'server', b'concurrent-push-mode')
1657 1657 if cpmode == b'check-related':
1658 1658 caps[b'checkheads'] = (b'related',)
1659 1659 if b'phases' in repo.ui.configlist(b'devel', b'legacy.exchange'):
1660 1660 caps.pop(b'phases')
1661 1661
1662 1662 # Don't advertise stream clone support in server mode if not configured.
1663 1663 if role == b'server':
1664 1664 streamsupported = repo.ui.configbool(
1665 1665 b'server', b'uncompressed', untrusted=True
1666 1666 )
1667 1667 featuresupported = repo.ui.configbool(b'server', b'bundle2.stream')
1668 1668
1669 1669 if not streamsupported or not featuresupported:
1670 1670 caps.pop(b'stream')
1671 1671 # Else always advertise support on client, because payload support
1672 1672 # should always be advertised.
1673 1673
1674 if repo.ui.configbool(b'experimental', b'stream-v3'):
1675 if b'stream' in caps:
1676 caps[b'stream'] += (b'v3-exp',)
1677
1674 1678 # b'rev-branch-cache is no longer advertised, but still supported
1675 1679 # for legacy clients.
1676 1680
1677 1681 return caps
1678 1682
1679 1683
1680 1684 def bundle2caps(remote):
1681 1685 """return the bundle capabilities of a peer as dict"""
1682 1686 raw = remote.capable(b'bundle2')
1683 1687 if not raw and raw != b'':
1684 1688 return {}
1685 1689 capsblob = urlreq.unquote(remote.capable(b'bundle2'))
1686 1690 return decodecaps(capsblob)
1687 1691
1688 1692
1689 1693 def obsmarkersversion(caps):
1690 1694 """extract the list of supported obsmarkers versions from a bundle2caps dict"""
1691 1695 obscaps = caps.get(b'obsmarkers', ())
1692 1696 return [int(c[1:]) for c in obscaps if c.startswith(b'V')]
1693 1697
1694 1698
1695 1699 def writenewbundle(
1696 1700 ui,
1697 1701 repo,
1698 1702 source,
1699 1703 filename,
1700 1704 bundletype,
1701 1705 outgoing,
1702 1706 opts,
1703 1707 vfs=None,
1704 1708 compression=None,
1705 1709 compopts=None,
1706 1710 allow_internal=False,
1707 1711 ):
1708 1712 if bundletype.startswith(b'HG10'):
1709 1713 cg = changegroup.makechangegroup(repo, outgoing, b'01', source)
1710 1714 return writebundle(
1711 1715 ui,
1712 1716 cg,
1713 1717 filename,
1714 1718 bundletype,
1715 1719 vfs=vfs,
1716 1720 compression=compression,
1717 1721 compopts=compopts,
1718 1722 )
1719 1723 elif not bundletype.startswith(b'HG20'):
1720 1724 raise error.ProgrammingError(b'unknown bundle type: %s' % bundletype)
1721 1725
1722 1726 # enforce that no internal phase are to be bundled
1723 1727 bundled_internal = repo.revs(b"%ln and _internal()", outgoing.ancestorsof)
1724 1728 if bundled_internal and not allow_internal:
1725 1729 count = len(repo.revs(b'%ln and _internal()', outgoing.missing))
1726 1730 msg = "backup bundle would contains %d internal changesets"
1727 1731 msg %= count
1728 1732 raise error.ProgrammingError(msg)
1729 1733
1730 1734 caps = {}
1731 1735 if opts.get(b'obsolescence', False):
1732 1736 caps[b'obsmarkers'] = (b'V1',)
1733 1737 if opts.get(b'streamv2'):
1734 1738 caps[b'stream'] = [b'v2']
1735 1739 bundle = bundle20(ui, caps)
1736 1740 bundle.setcompression(compression, compopts)
1737 1741 _addpartsfromopts(ui, repo, bundle, source, outgoing, opts)
1738 1742 chunkiter = bundle.getchunks()
1739 1743
1740 1744 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1741 1745
1742 1746
1743 1747 def _addpartsfromopts(ui, repo, bundler, source, outgoing, opts):
1744 1748 # We should eventually reconcile this logic with the one behind
1745 1749 # 'exchange.getbundle2partsgenerator'.
1746 1750 #
1747 1751 # The type of input from 'getbundle' and 'writenewbundle' are a bit
1748 1752 # different right now. So we keep them separated for now for the sake of
1749 1753 # simplicity.
1750 1754
1751 1755 # we might not always want a changegroup in such bundle, for example in
1752 1756 # stream bundles
1753 1757 if opts.get(b'changegroup', True):
1754 1758 cgversion = opts.get(b'cg.version')
1755 1759 if cgversion is None:
1756 1760 cgversion = changegroup.safeversion(repo)
1757 1761 cg = changegroup.makechangegroup(repo, outgoing, cgversion, source)
1758 1762 part = bundler.newpart(b'changegroup', data=cg.getchunks())
1759 1763 part.addparam(b'version', cg.version)
1760 1764 if b'clcount' in cg.extras:
1761 1765 part.addparam(
1762 1766 b'nbchanges', b'%d' % cg.extras[b'clcount'], mandatory=False
1763 1767 )
1764 1768 if opts.get(b'phases'):
1765 1769 target_phase = phases.draft
1766 1770 for head in outgoing.ancestorsof:
1767 1771 target_phase = max(target_phase, repo[head].phase())
1768 1772 if target_phase > phases.draft:
1769 1773 part.addparam(
1770 1774 b'targetphase',
1771 1775 b'%d' % target_phase,
1772 1776 mandatory=False,
1773 1777 )
1774 1778 if repository.REPO_FEATURE_SIDE_DATA in repo.features:
1775 1779 part.addparam(b'exp-sidedata', b'1')
1776 1780
1777 1781 if opts.get(b'streamv2', False):
1778 1782 addpartbundlestream2(bundler, repo, stream=True)
1779 1783
1780 1784 if opts.get(b'tagsfnodescache', True):
1781 1785 addparttagsfnodescache(repo, bundler, outgoing)
1782 1786
1783 1787 if opts.get(b'revbranchcache', True):
1784 1788 addpartrevbranchcache(repo, bundler, outgoing)
1785 1789
1786 1790 if opts.get(b'obsolescence', False):
1787 1791 obsmarkers = repo.obsstore.relevantmarkers(outgoing.missing)
1788 1792 buildobsmarkerspart(
1789 1793 bundler,
1790 1794 obsmarkers,
1791 1795 mandatory=opts.get(b'obsolescence-mandatory', True),
1792 1796 )
1793 1797
1794 1798 if opts.get(b'phases', False):
1795 1799 headsbyphase = phases.subsetphaseheads(repo, outgoing.missing)
1796 1800 phasedata = phases.binaryencode(headsbyphase)
1797 1801 bundler.newpart(b'phase-heads', data=phasedata)
1798 1802
1799 1803
1800 1804 def addparttagsfnodescache(repo, bundler, outgoing):
1801 1805 # we include the tags fnode cache for the bundle changeset
1802 1806 # (as an optional parts)
1803 1807 cache = tags.hgtagsfnodescache(repo.unfiltered())
1804 1808 chunks = []
1805 1809
1806 1810 # .hgtags fnodes are only relevant for head changesets. While we could
1807 1811 # transfer values for all known nodes, there will likely be little to
1808 1812 # no benefit.
1809 1813 #
1810 1814 # We don't bother using a generator to produce output data because
1811 1815 # a) we only have 40 bytes per head and even esoteric numbers of heads
1812 1816 # consume little memory (1M heads is 40MB) b) we don't want to send the
1813 1817 # part if we don't have entries and knowing if we have entries requires
1814 1818 # cache lookups.
1815 1819 for node in outgoing.ancestorsof:
1816 1820 # Don't compute missing, as this may slow down serving.
1817 1821 fnode = cache.getfnode(node, computemissing=False)
1818 1822 if fnode:
1819 1823 chunks.extend([node, fnode])
1820 1824
1821 1825 if chunks:
1822 1826 bundler.newpart(b'hgtagsfnodes', data=b''.join(chunks))
1823 1827
1824 1828
1825 1829 def addpartrevbranchcache(repo, bundler, outgoing):
1826 1830 # we include the rev branch cache for the bundle changeset
1827 1831 # (as an optional parts)
1828 1832 cache = repo.revbranchcache()
1829 1833 cl = repo.unfiltered().changelog
1830 1834 branchesdata = collections.defaultdict(lambda: (set(), set()))
1831 1835 for node in outgoing.missing:
1832 1836 branch, close = cache.branchinfo(cl.rev(node))
1833 1837 branchesdata[branch][close].add(node)
1834 1838
1835 1839 def generate():
1836 1840 for branch, (nodes, closed) in sorted(branchesdata.items()):
1837 1841 utf8branch = encoding.fromlocal(branch)
1838 1842 yield rbcstruct.pack(len(utf8branch), len(nodes), len(closed))
1839 1843 yield utf8branch
1840 1844 for n in sorted(nodes):
1841 1845 yield n
1842 1846 for n in sorted(closed):
1843 1847 yield n
1844 1848
1845 1849 bundler.newpart(b'cache:rev-branch-cache', data=generate(), mandatory=False)
1846 1850
1847 1851
1848 1852 def _formatrequirementsspec(requirements):
1849 1853 requirements = [req for req in requirements if req != b"shared"]
1850 1854 return urlreq.quote(b','.join(sorted(requirements)))
1851 1855
1852 1856
1853 1857 def _formatrequirementsparams(requirements):
1854 1858 requirements = _formatrequirementsspec(requirements)
1855 1859 params = b"%s%s" % (urlreq.quote(b"requirements="), requirements)
1856 1860 return params
1857 1861
1858 1862
1859 1863 def format_remote_wanted_sidedata(repo):
1860 1864 """Formats a repo's wanted sidedata categories into a bytestring for
1861 1865 capabilities exchange."""
1862 1866 wanted = b""
1863 1867 if repo._wanted_sidedata:
1864 1868 wanted = b','.join(
1865 1869 pycompat.bytestr(c) for c in sorted(repo._wanted_sidedata)
1866 1870 )
1867 1871 return wanted
1868 1872
1869 1873
1870 1874 def read_remote_wanted_sidedata(remote):
1871 1875 sidedata_categories = remote.capable(b'exp-wanted-sidedata')
1872 1876 return read_wanted_sidedata(sidedata_categories)
1873 1877
1874 1878
1875 1879 def read_wanted_sidedata(formatted):
1876 1880 if formatted:
1877 1881 return set(formatted.split(b','))
1878 1882 return set()
1879 1883
1880 1884
1881 1885 def addpartbundlestream2(bundler, repo, **kwargs):
1882 1886 if not kwargs.get('stream', False):
1883 1887 return
1884 1888
1885 1889 if not streamclone.allowservergeneration(repo):
1886 1890 msg = _(b'stream data requested but server does not allow this feature')
1887 1891 hint = _(b'the client seems buggy')
1888 1892 raise error.Abort(msg, hint=hint)
1889 1893 if not (b'stream' in bundler.capabilities):
1890 1894 msg = _(
1891 1895 b'stream data requested but supported streaming clone versions were not specified'
1892 1896 )
1893 1897 hint = _(b'the client seems buggy')
1894 1898 raise error.Abort(msg, hint=hint)
1895 if not (b'v2' in bundler.capabilities[b'stream']):
1896 raise error.Abort(_(b'the client does not support streamclone v2'))
1899 client_supported = set(bundler.capabilities[b'stream'])
1900 server_supported = set(getrepocaps(repo, role=b'client').get(b'stream', []))
1901 common_supported = client_supported & server_supported
1902 if not common_supported:
1903 msg = _(b'no common supported version with the client: %s; %s')
1904 str_server = b','.join(sorted(server_supported))
1905 str_client = b','.join(sorted(client_supported))
1906 msg %= (str_server, str_client)
1907 raise error.Abort(msg)
1908 version = max(common_supported)
1897 1909
1898 1910 # Stream clones don't compress well. And compression undermines a
1899 1911 # goal of stream clones, which is to be fast. Communicate the desire
1900 1912 # to avoid compression to consumers of the bundle.
1901 1913 bundler.prefercompressed = False
1902 1914
1903 1915 # get the includes and excludes
1904 1916 includepats = kwargs.get('includepats')
1905 1917 excludepats = kwargs.get('excludepats')
1906 1918
1907 1919 narrowstream = repo.ui.configbool(
1908 1920 b'experimental', b'server.stream-narrow-clones'
1909 1921 )
1910 1922
1911 1923 if (includepats or excludepats) and not narrowstream:
1912 1924 raise error.Abort(_(b'server does not support narrow stream clones'))
1913 1925
1914 1926 includeobsmarkers = False
1915 1927 if repo.obsstore:
1916 1928 remoteversions = obsmarkersversion(bundler.capabilities)
1917 1929 if not remoteversions:
1918 1930 raise error.Abort(
1919 1931 _(
1920 1932 b'server has obsolescence markers, but client '
1921 1933 b'cannot receive them via stream clone'
1922 1934 )
1923 1935 )
1924 1936 elif repo.obsstore._version in remoteversions:
1925 1937 includeobsmarkers = True
1926 1938
1927 filecount, bytecount, it = streamclone.generatev2(
1928 repo, includepats, excludepats, includeobsmarkers
1929 )
1930 requirements = streamclone.streamed_requirements(repo)
1931 requirements = _formatrequirementsspec(requirements)
1932 part = bundler.newpart(b'stream2', data=it)
1933 part.addparam(b'bytecount', b'%d' % bytecount, mandatory=True)
1934 part.addparam(b'filecount', b'%d' % filecount, mandatory=True)
1935 part.addparam(b'requirements', requirements, mandatory=True)
1939 if version == b"v2":
1940 filecount, bytecount, it = streamclone.generatev2(
1941 repo, includepats, excludepats, includeobsmarkers
1942 )
1943 requirements = streamclone.streamed_requirements(repo)
1944 requirements = _formatrequirementsspec(requirements)
1945 part = bundler.newpart(b'stream2', data=it)
1946 part.addparam(b'bytecount', b'%d' % bytecount, mandatory=True)
1947 part.addparam(b'filecount', b'%d' % filecount, mandatory=True)
1948 part.addparam(b'requirements', requirements, mandatory=True)
1949 elif version == b"v3-exp":
1950 filecount, bytecount, it = streamclone.generatev2(
1951 repo, includepats, excludepats, includeobsmarkers
1952 )
1953 requirements = streamclone.streamed_requirements(repo)
1954 requirements = _formatrequirementsspec(requirements)
1955 part = bundler.newpart(b'stream3', data=it)
1956 part.addparam(b'bytecount', b'%d' % bytecount, mandatory=True)
1957 part.addparam(b'filecount', b'%d' % filecount, mandatory=True)
1958 part.addparam(b'requirements', requirements, mandatory=True)
1936 1959
1937 1960
1938 1961 def buildobsmarkerspart(bundler, markers, mandatory=True):
1939 1962 """add an obsmarker part to the bundler with <markers>
1940 1963
1941 1964 No part is created if markers is empty.
1942 1965 Raises ValueError if the bundler doesn't support any known obsmarker format.
1943 1966 """
1944 1967 if not markers:
1945 1968 return None
1946 1969
1947 1970 remoteversions = obsmarkersversion(bundler.capabilities)
1948 1971 version = obsolete.commonversion(remoteversions)
1949 1972 if version is None:
1950 1973 raise ValueError(b'bundler does not support common obsmarker format')
1951 1974 stream = obsolete.encodemarkers(markers, True, version=version)
1952 1975 return bundler.newpart(b'obsmarkers', data=stream, mandatory=mandatory)
1953 1976
1954 1977
1955 1978 def writebundle(
1956 1979 ui, cg, filename, bundletype, vfs=None, compression=None, compopts=None
1957 1980 ):
1958 1981 """Write a bundle file and return its filename.
1959 1982
1960 1983 Existing files will not be overwritten.
1961 1984 If no filename is specified, a temporary file is created.
1962 1985 bz2 compression can be turned off.
1963 1986 The bundle file will be deleted in case of errors.
1964 1987 """
1965 1988
1966 1989 if bundletype == b"HG20":
1967 1990 bundle = bundle20(ui)
1968 1991 bundle.setcompression(compression, compopts)
1969 1992 part = bundle.newpart(b'changegroup', data=cg.getchunks())
1970 1993 part.addparam(b'version', cg.version)
1971 1994 if b'clcount' in cg.extras:
1972 1995 part.addparam(
1973 1996 b'nbchanges', b'%d' % cg.extras[b'clcount'], mandatory=False
1974 1997 )
1975 1998 chunkiter = bundle.getchunks()
1976 1999 else:
1977 2000 # compression argument is only for the bundle2 case
1978 2001 assert compression is None
1979 2002 if cg.version != b'01':
1980 2003 raise error.Abort(
1981 2004 _(b'old bundle types only supports v1 changegroups')
1982 2005 )
1983 2006
1984 2007 # HG20 is the case without 2 values to unpack, but is handled above.
1985 2008 # pytype: disable=bad-unpacking
1986 2009 header, comp = bundletypes[bundletype]
1987 2010 # pytype: enable=bad-unpacking
1988 2011
1989 2012 if comp not in util.compengines.supportedbundletypes:
1990 2013 raise error.Abort(_(b'unknown stream compression type: %s') % comp)
1991 2014 compengine = util.compengines.forbundletype(comp)
1992 2015
1993 2016 def chunkiter():
1994 2017 yield header
1995 2018 for chunk in compengine.compressstream(cg.getchunks(), compopts):
1996 2019 yield chunk
1997 2020
1998 2021 chunkiter = chunkiter()
1999 2022
2000 2023 # parse the changegroup data, otherwise we will block
2001 2024 # in case of sshrepo because we don't know the end of the stream
2002 2025 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
2003 2026
2004 2027
2005 2028 def combinechangegroupresults(op):
2006 2029 """logic to combine 0 or more addchangegroup results into one"""
2007 2030 results = [r.get(b'return', 0) for r in op.records[b'changegroup']]
2008 2031 changedheads = 0
2009 2032 result = 1
2010 2033 for ret in results:
2011 2034 # If any changegroup result is 0, return 0
2012 2035 if ret == 0:
2013 2036 result = 0
2014 2037 break
2015 2038 if ret < -1:
2016 2039 changedheads += ret + 1
2017 2040 elif ret > 1:
2018 2041 changedheads += ret - 1
2019 2042 if changedheads > 0:
2020 2043 result = 1 + changedheads
2021 2044 elif changedheads < 0:
2022 2045 result = -1 + changedheads
2023 2046 return result
2024 2047
2025 2048
2026 2049 @parthandler(
2027 2050 b'changegroup',
2028 2051 (
2029 2052 b'version',
2030 2053 b'nbchanges',
2031 2054 b'exp-sidedata',
2032 2055 b'exp-wanted-sidedata',
2033 2056 b'treemanifest',
2034 2057 b'targetphase',
2035 2058 ),
2036 2059 )
2037 2060 def handlechangegroup(op, inpart):
2038 2061 """apply a changegroup part on the repo"""
2039 2062 from . import localrepo
2040 2063
2041 2064 tr = op.gettransaction()
2042 2065 unpackerversion = inpart.params.get(b'version', b'01')
2043 2066 # We should raise an appropriate exception here
2044 2067 cg = changegroup.getunbundler(unpackerversion, inpart, None)
2045 2068 # the source and url passed here are overwritten by the one contained in
2046 2069 # the transaction.hookargs argument. So 'bundle2' is a placeholder
2047 2070 nbchangesets = None
2048 2071 if b'nbchanges' in inpart.params:
2049 2072 nbchangesets = int(inpart.params.get(b'nbchanges'))
2050 2073 if b'treemanifest' in inpart.params and not scmutil.istreemanifest(op.repo):
2051 2074 if len(op.repo.changelog) != 0:
2052 2075 raise error.Abort(
2053 2076 _(
2054 2077 b"bundle contains tree manifests, but local repo is "
2055 2078 b"non-empty and does not use tree manifests"
2056 2079 )
2057 2080 )
2058 2081 op.repo.requirements.add(requirements.TREEMANIFEST_REQUIREMENT)
2059 2082 op.repo.svfs.options = localrepo.resolvestorevfsoptions(
2060 2083 op.repo.ui, op.repo.requirements, op.repo.features
2061 2084 )
2062 2085 scmutil.writereporequirements(op.repo)
2063 2086
2064 2087 extrakwargs = {}
2065 2088 targetphase = inpart.params.get(b'targetphase')
2066 2089 if targetphase is not None:
2067 2090 extrakwargs['targetphase'] = int(targetphase)
2068 2091
2069 2092 remote_sidedata = inpart.params.get(b'exp-wanted-sidedata')
2070 2093 extrakwargs['sidedata_categories'] = read_wanted_sidedata(remote_sidedata)
2071 2094
2072 2095 ret = _processchangegroup(
2073 2096 op,
2074 2097 cg,
2075 2098 tr,
2076 2099 op.source,
2077 2100 b'bundle2',
2078 2101 expectedtotal=nbchangesets,
2079 2102 **extrakwargs
2080 2103 )
2081 2104 if op.reply is not None:
2082 2105 # This is definitely not the final form of this
2083 2106 # return. But one need to start somewhere.
2084 2107 part = op.reply.newpart(b'reply:changegroup', mandatory=False)
2085 2108 part.addparam(
2086 2109 b'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False
2087 2110 )
2088 2111 part.addparam(b'return', b'%i' % ret, mandatory=False)
2089 2112 assert not inpart.read()
2090 2113
2091 2114
2092 2115 _remotechangegroupparams = tuple(
2093 2116 [b'url', b'size', b'digests']
2094 2117 + [b'digest:%s' % k for k in util.DIGESTS.keys()]
2095 2118 )
2096 2119
2097 2120
2098 2121 @parthandler(b'remote-changegroup', _remotechangegroupparams)
2099 2122 def handleremotechangegroup(op, inpart):
2100 2123 """apply a bundle10 on the repo, given an url and validation information
2101 2124
2102 2125 All the information about the remote bundle to import are given as
2103 2126 parameters. The parameters include:
2104 2127 - url: the url to the bundle10.
2105 2128 - size: the bundle10 file size. It is used to validate what was
2106 2129 retrieved by the client matches the server knowledge about the bundle.
2107 2130 - digests: a space separated list of the digest types provided as
2108 2131 parameters.
2109 2132 - digest:<digest-type>: the hexadecimal representation of the digest with
2110 2133 that name. Like the size, it is used to validate what was retrieved by
2111 2134 the client matches what the server knows about the bundle.
2112 2135
2113 2136 When multiple digest types are given, all of them are checked.
2114 2137 """
2115 2138 try:
2116 2139 raw_url = inpart.params[b'url']
2117 2140 except KeyError:
2118 2141 raise error.Abort(_(b'remote-changegroup: missing "%s" param') % b'url')
2119 2142 parsed_url = urlutil.url(raw_url)
2120 2143 if parsed_url.scheme not in capabilities[b'remote-changegroup']:
2121 2144 raise error.Abort(
2122 2145 _(b'remote-changegroup does not support %s urls')
2123 2146 % parsed_url.scheme
2124 2147 )
2125 2148
2126 2149 try:
2127 2150 size = int(inpart.params[b'size'])
2128 2151 except ValueError:
2129 2152 raise error.Abort(
2130 2153 _(b'remote-changegroup: invalid value for param "%s"') % b'size'
2131 2154 )
2132 2155 except KeyError:
2133 2156 raise error.Abort(
2134 2157 _(b'remote-changegroup: missing "%s" param') % b'size'
2135 2158 )
2136 2159
2137 2160 digests = {}
2138 2161 for typ in inpart.params.get(b'digests', b'').split():
2139 2162 param = b'digest:%s' % typ
2140 2163 try:
2141 2164 value = inpart.params[param]
2142 2165 except KeyError:
2143 2166 raise error.Abort(
2144 2167 _(b'remote-changegroup: missing "%s" param') % param
2145 2168 )
2146 2169 digests[typ] = value
2147 2170
2148 2171 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
2149 2172
2150 2173 tr = op.gettransaction()
2151 2174 from . import exchange
2152 2175
2153 2176 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
2154 2177 if not isinstance(cg, changegroup.cg1unpacker):
2155 2178 raise error.Abort(
2156 2179 _(b'%s: not a bundle version 1.0') % urlutil.hidepassword(raw_url)
2157 2180 )
2158 2181 ret = _processchangegroup(op, cg, tr, op.source, b'bundle2')
2159 2182 if op.reply is not None:
2160 2183 # This is definitely not the final form of this
2161 2184 # return. But one need to start somewhere.
2162 2185 part = op.reply.newpart(b'reply:changegroup')
2163 2186 part.addparam(
2164 2187 b'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False
2165 2188 )
2166 2189 part.addparam(b'return', b'%i' % ret, mandatory=False)
2167 2190 try:
2168 2191 real_part.validate()
2169 2192 except error.Abort as e:
2170 2193 raise error.Abort(
2171 2194 _(b'bundle at %s is corrupted:\n%s')
2172 2195 % (urlutil.hidepassword(raw_url), e.message)
2173 2196 )
2174 2197 assert not inpart.read()
2175 2198
2176 2199
2177 2200 @parthandler(b'reply:changegroup', (b'return', b'in-reply-to'))
2178 2201 def handlereplychangegroup(op, inpart):
2179 2202 ret = int(inpart.params[b'return'])
2180 2203 replyto = int(inpart.params[b'in-reply-to'])
2181 2204 op.records.add(b'changegroup', {b'return': ret}, replyto)
2182 2205
2183 2206
2184 2207 @parthandler(b'check:bookmarks')
2185 2208 def handlecheckbookmarks(op, inpart):
2186 2209 """check location of bookmarks
2187 2210
2188 2211 This part is to be used to detect push race regarding bookmark, it
2189 2212 contains binary encoded (bookmark, node) tuple. If the local state does
2190 2213 not marks the one in the part, a PushRaced exception is raised
2191 2214 """
2192 2215 bookdata = bookmarks.binarydecode(op.repo, inpart)
2193 2216
2194 2217 msgstandard = (
2195 2218 b'remote repository changed while pushing - please try again '
2196 2219 b'(bookmark "%s" move from %s to %s)'
2197 2220 )
2198 2221 msgmissing = (
2199 2222 b'remote repository changed while pushing - please try again '
2200 2223 b'(bookmark "%s" is missing, expected %s)'
2201 2224 )
2202 2225 msgexist = (
2203 2226 b'remote repository changed while pushing - please try again '
2204 2227 b'(bookmark "%s" set on %s, expected missing)'
2205 2228 )
2206 2229 for book, node in bookdata:
2207 2230 currentnode = op.repo._bookmarks.get(book)
2208 2231 if currentnode != node:
2209 2232 if node is None:
2210 2233 finalmsg = msgexist % (book, short(currentnode))
2211 2234 elif currentnode is None:
2212 2235 finalmsg = msgmissing % (book, short(node))
2213 2236 else:
2214 2237 finalmsg = msgstandard % (
2215 2238 book,
2216 2239 short(node),
2217 2240 short(currentnode),
2218 2241 )
2219 2242 raise error.PushRaced(finalmsg)
2220 2243
2221 2244
2222 2245 @parthandler(b'check:heads')
2223 2246 def handlecheckheads(op, inpart):
2224 2247 """check that head of the repo did not change
2225 2248
2226 2249 This is used to detect a push race when using unbundle.
2227 2250 This replaces the "heads" argument of unbundle."""
2228 2251 h = inpart.read(20)
2229 2252 heads = []
2230 2253 while len(h) == 20:
2231 2254 heads.append(h)
2232 2255 h = inpart.read(20)
2233 2256 assert not h
2234 2257 # Trigger a transaction so that we are guaranteed to have the lock now.
2235 2258 if op.ui.configbool(b'experimental', b'bundle2lazylocking'):
2236 2259 op.gettransaction()
2237 2260 if sorted(heads) != sorted(op.repo.heads()):
2238 2261 raise error.PushRaced(
2239 2262 b'remote repository changed while pushing - please try again'
2240 2263 )
2241 2264
2242 2265
2243 2266 @parthandler(b'check:updated-heads')
2244 2267 def handlecheckupdatedheads(op, inpart):
2245 2268 """check for race on the heads touched by a push
2246 2269
2247 2270 This is similar to 'check:heads' but focus on the heads actually updated
2248 2271 during the push. If other activities happen on unrelated heads, it is
2249 2272 ignored.
2250 2273
2251 2274 This allow server with high traffic to avoid push contention as long as
2252 2275 unrelated parts of the graph are involved."""
2253 2276 h = inpart.read(20)
2254 2277 heads = []
2255 2278 while len(h) == 20:
2256 2279 heads.append(h)
2257 2280 h = inpart.read(20)
2258 2281 assert not h
2259 2282 # trigger a transaction so that we are guaranteed to have the lock now.
2260 2283 if op.ui.configbool(b'experimental', b'bundle2lazylocking'):
2261 2284 op.gettransaction()
2262 2285
2263 2286 currentheads = set()
2264 2287 for ls in op.repo.branchmap().iterheads():
2265 2288 currentheads.update(ls)
2266 2289
2267 2290 for h in heads:
2268 2291 if h not in currentheads:
2269 2292 raise error.PushRaced(
2270 2293 b'remote repository changed while pushing - '
2271 2294 b'please try again'
2272 2295 )
2273 2296
2274 2297
2275 2298 @parthandler(b'check:phases')
2276 2299 def handlecheckphases(op, inpart):
2277 2300 """check that phase boundaries of the repository did not change
2278 2301
2279 2302 This is used to detect a push race.
2280 2303 """
2281 2304 phasetonodes = phases.binarydecode(inpart)
2282 2305 unfi = op.repo.unfiltered()
2283 2306 cl = unfi.changelog
2284 2307 phasecache = unfi._phasecache
2285 2308 msg = (
2286 2309 b'remote repository changed while pushing - please try again '
2287 2310 b'(%s is %s expected %s)'
2288 2311 )
2289 2312 for expectedphase, nodes in phasetonodes.items():
2290 2313 for n in nodes:
2291 2314 actualphase = phasecache.phase(unfi, cl.rev(n))
2292 2315 if actualphase != expectedphase:
2293 2316 finalmsg = msg % (
2294 2317 short(n),
2295 2318 phases.phasenames[actualphase],
2296 2319 phases.phasenames[expectedphase],
2297 2320 )
2298 2321 raise error.PushRaced(finalmsg)
2299 2322
2300 2323
2301 2324 @parthandler(b'output')
2302 2325 def handleoutput(op, inpart):
2303 2326 """forward output captured on the server to the client"""
2304 2327 for line in inpart.read().splitlines():
2305 2328 op.ui.status(_(b'remote: %s\n') % line)
2306 2329
2307 2330
2308 2331 @parthandler(b'replycaps')
2309 2332 def handlereplycaps(op, inpart):
2310 2333 """Notify that a reply bundle should be created
2311 2334
2312 2335 The payload contains the capabilities information for the reply"""
2313 2336 caps = decodecaps(inpart.read())
2314 2337 if op.reply is None:
2315 2338 op.reply = bundle20(op.ui, caps)
2316 2339
2317 2340
2318 2341 class AbortFromPart(error.Abort):
2319 2342 """Sub-class of Abort that denotes an error from a bundle2 part."""
2320 2343
2321 2344
2322 2345 @parthandler(b'error:abort', (b'message', b'hint'))
2323 2346 def handleerrorabort(op, inpart):
2324 2347 """Used to transmit abort error over the wire"""
2325 2348 raise AbortFromPart(
2326 2349 inpart.params[b'message'], hint=inpart.params.get(b'hint')
2327 2350 )
2328 2351
2329 2352
2330 2353 @parthandler(
2331 2354 b'error:pushkey',
2332 2355 (b'namespace', b'key', b'new', b'old', b'ret', b'in-reply-to'),
2333 2356 )
2334 2357 def handleerrorpushkey(op, inpart):
2335 2358 """Used to transmit failure of a mandatory pushkey over the wire"""
2336 2359 kwargs = {}
2337 2360 for name in (b'namespace', b'key', b'new', b'old', b'ret'):
2338 2361 value = inpart.params.get(name)
2339 2362 if value is not None:
2340 2363 kwargs[name] = value
2341 2364 raise error.PushkeyFailed(
2342 2365 inpart.params[b'in-reply-to'], **pycompat.strkwargs(kwargs)
2343 2366 )
2344 2367
2345 2368
2346 2369 @parthandler(b'error:unsupportedcontent', (b'parttype', b'params'))
2347 2370 def handleerrorunsupportedcontent(op, inpart):
2348 2371 """Used to transmit unknown content error over the wire"""
2349 2372 kwargs = {}
2350 2373 parttype = inpart.params.get(b'parttype')
2351 2374 if parttype is not None:
2352 2375 kwargs[b'parttype'] = parttype
2353 2376 params = inpart.params.get(b'params')
2354 2377 if params is not None:
2355 2378 kwargs[b'params'] = params.split(b'\0')
2356 2379
2357 2380 raise error.BundleUnknownFeatureError(**pycompat.strkwargs(kwargs))
2358 2381
2359 2382
2360 2383 @parthandler(b'error:pushraced', (b'message',))
2361 2384 def handleerrorpushraced(op, inpart):
2362 2385 """Used to transmit push race error over the wire"""
2363 2386 raise error.ResponseError(_(b'push failed:'), inpart.params[b'message'])
2364 2387
2365 2388
2366 2389 @parthandler(b'listkeys', (b'namespace',))
2367 2390 def handlelistkeys(op, inpart):
2368 2391 """retrieve pushkey namespace content stored in a bundle2"""
2369 2392 namespace = inpart.params[b'namespace']
2370 2393 r = pushkey.decodekeys(inpart.read())
2371 2394 op.records.add(b'listkeys', (namespace, r))
2372 2395
2373 2396
2374 2397 @parthandler(b'pushkey', (b'namespace', b'key', b'old', b'new'))
2375 2398 def handlepushkey(op, inpart):
2376 2399 """process a pushkey request"""
2377 2400 dec = pushkey.decode
2378 2401 namespace = dec(inpart.params[b'namespace'])
2379 2402 key = dec(inpart.params[b'key'])
2380 2403 old = dec(inpart.params[b'old'])
2381 2404 new = dec(inpart.params[b'new'])
2382 2405 # Grab the transaction to ensure that we have the lock before performing the
2383 2406 # pushkey.
2384 2407 if op.ui.configbool(b'experimental', b'bundle2lazylocking'):
2385 2408 op.gettransaction()
2386 2409 ret = op.repo.pushkey(namespace, key, old, new)
2387 2410 record = {b'namespace': namespace, b'key': key, b'old': old, b'new': new}
2388 2411 op.records.add(b'pushkey', record)
2389 2412 if op.reply is not None:
2390 2413 rpart = op.reply.newpart(b'reply:pushkey')
2391 2414 rpart.addparam(
2392 2415 b'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False
2393 2416 )
2394 2417 rpart.addparam(b'return', b'%i' % ret, mandatory=False)
2395 2418 if inpart.mandatory and not ret:
2396 2419 kwargs = {}
2397 2420 for key in (b'namespace', b'key', b'new', b'old', b'ret'):
2398 2421 if key in inpart.params:
2399 2422 kwargs[key] = inpart.params[key]
2400 2423 raise error.PushkeyFailed(
2401 2424 partid=b'%d' % inpart.id, **pycompat.strkwargs(kwargs)
2402 2425 )
2403 2426
2404 2427
2405 2428 @parthandler(b'bookmarks')
2406 2429 def handlebookmark(op, inpart):
2407 2430 """transmit bookmark information
2408 2431
2409 2432 The part contains binary encoded bookmark information.
2410 2433
2411 2434 The exact behavior of this part can be controlled by the 'bookmarks' mode
2412 2435 on the bundle operation.
2413 2436
2414 2437 When mode is 'apply' (the default) the bookmark information is applied as
2415 2438 is to the unbundling repository. Make sure a 'check:bookmarks' part is
2416 2439 issued earlier to check for push races in such update. This behavior is
2417 2440 suitable for pushing.
2418 2441
2419 2442 When mode is 'records', the information is recorded into the 'bookmarks'
2420 2443 records of the bundle operation. This behavior is suitable for pulling.
2421 2444 """
2422 2445 changes = bookmarks.binarydecode(op.repo, inpart)
2423 2446
2424 2447 pushkeycompat = op.repo.ui.configbool(
2425 2448 b'server', b'bookmarks-pushkey-compat'
2426 2449 )
2427 2450 bookmarksmode = op.modes.get(b'bookmarks', b'apply')
2428 2451
2429 2452 if bookmarksmode == b'apply':
2430 2453 tr = op.gettransaction()
2431 2454 bookstore = op.repo._bookmarks
2432 2455 if pushkeycompat:
2433 2456 allhooks = []
2434 2457 for book, node in changes:
2435 2458 hookargs = tr.hookargs.copy()
2436 2459 hookargs[b'pushkeycompat'] = b'1'
2437 2460 hookargs[b'namespace'] = b'bookmarks'
2438 2461 hookargs[b'key'] = book
2439 2462 hookargs[b'old'] = hex(bookstore.get(book, b''))
2440 2463 hookargs[b'new'] = hex(node if node is not None else b'')
2441 2464 allhooks.append(hookargs)
2442 2465
2443 2466 for hookargs in allhooks:
2444 2467 op.repo.hook(
2445 2468 b'prepushkey', throw=True, **pycompat.strkwargs(hookargs)
2446 2469 )
2447 2470
2448 2471 for book, node in changes:
2449 2472 if bookmarks.isdivergent(book):
2450 2473 msg = _(b'cannot accept divergent bookmark %s!') % book
2451 2474 raise error.Abort(msg)
2452 2475
2453 2476 bookstore.applychanges(op.repo, op.gettransaction(), changes)
2454 2477
2455 2478 if pushkeycompat:
2456 2479
2457 2480 def runhook(unused_success):
2458 2481 for hookargs in allhooks:
2459 2482 op.repo.hook(b'pushkey', **pycompat.strkwargs(hookargs))
2460 2483
2461 2484 op.repo._afterlock(runhook)
2462 2485
2463 2486 elif bookmarksmode == b'records':
2464 2487 for book, node in changes:
2465 2488 record = {b'bookmark': book, b'node': node}
2466 2489 op.records.add(b'bookmarks', record)
2467 2490 else:
2468 2491 raise error.ProgrammingError(
2469 2492 b'unknown bookmark mode: %s' % bookmarksmode
2470 2493 )
2471 2494
2472 2495
2473 2496 @parthandler(b'phase-heads')
2474 2497 def handlephases(op, inpart):
2475 2498 """apply phases from bundle part to repo"""
2476 2499 headsbyphase = phases.binarydecode(inpart)
2477 2500 phases.updatephases(op.repo.unfiltered(), op.gettransaction, headsbyphase)
2478 2501
2479 2502
2480 2503 @parthandler(b'reply:pushkey', (b'return', b'in-reply-to'))
2481 2504 def handlepushkeyreply(op, inpart):
2482 2505 """retrieve the result of a pushkey request"""
2483 2506 ret = int(inpart.params[b'return'])
2484 2507 partid = int(inpart.params[b'in-reply-to'])
2485 2508 op.records.add(b'pushkey', {b'return': ret}, partid)
2486 2509
2487 2510
2488 2511 @parthandler(b'obsmarkers')
2489 2512 def handleobsmarker(op, inpart):
2490 2513 """add a stream of obsmarkers to the repo"""
2491 2514 tr = op.gettransaction()
2492 2515 markerdata = inpart.read()
2493 2516 if op.ui.config(b'experimental', b'obsmarkers-exchange-debug'):
2494 2517 op.ui.writenoi18n(
2495 2518 b'obsmarker-exchange: %i bytes received\n' % len(markerdata)
2496 2519 )
2497 2520 # The mergemarkers call will crash if marker creation is not enabled.
2498 2521 # we want to avoid this if the part is advisory.
2499 2522 if not inpart.mandatory and op.repo.obsstore.readonly:
2500 2523 op.repo.ui.debug(
2501 2524 b'ignoring obsolescence markers, feature not enabled\n'
2502 2525 )
2503 2526 return
2504 2527 new = op.repo.obsstore.mergemarkers(tr, markerdata)
2505 2528 op.repo.invalidatevolatilesets()
2506 2529 op.records.add(b'obsmarkers', {b'new': new})
2507 2530 if op.reply is not None:
2508 2531 rpart = op.reply.newpart(b'reply:obsmarkers')
2509 2532 rpart.addparam(
2510 2533 b'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False
2511 2534 )
2512 2535 rpart.addparam(b'new', b'%i' % new, mandatory=False)
2513 2536
2514 2537
2515 2538 @parthandler(b'reply:obsmarkers', (b'new', b'in-reply-to'))
2516 2539 def handleobsmarkerreply(op, inpart):
2517 2540 """retrieve the result of a pushkey request"""
2518 2541 ret = int(inpart.params[b'new'])
2519 2542 partid = int(inpart.params[b'in-reply-to'])
2520 2543 op.records.add(b'obsmarkers', {b'new': ret}, partid)
2521 2544
2522 2545
2523 2546 @parthandler(b'hgtagsfnodes')
2524 2547 def handlehgtagsfnodes(op, inpart):
2525 2548 """Applies .hgtags fnodes cache entries to the local repo.
2526 2549
2527 2550 Payload is pairs of 20 byte changeset nodes and filenodes.
2528 2551 """
2529 2552 # Grab the transaction so we ensure that we have the lock at this point.
2530 2553 if op.ui.configbool(b'experimental', b'bundle2lazylocking'):
2531 2554 op.gettransaction()
2532 2555 cache = tags.hgtagsfnodescache(op.repo.unfiltered())
2533 2556
2534 2557 count = 0
2535 2558 while True:
2536 2559 node = inpart.read(20)
2537 2560 fnode = inpart.read(20)
2538 2561 if len(node) < 20 or len(fnode) < 20:
2539 2562 op.ui.debug(b'ignoring incomplete received .hgtags fnodes data\n')
2540 2563 break
2541 2564 cache.setfnode(node, fnode)
2542 2565 count += 1
2543 2566
2544 2567 cache.write()
2545 2568 op.ui.debug(b'applied %i hgtags fnodes cache entries\n' % count)
2546 2569
2547 2570
2548 2571 rbcstruct = struct.Struct(b'>III')
2549 2572
2550 2573
2551 2574 @parthandler(b'cache:rev-branch-cache')
2552 2575 def handlerbc(op, inpart):
2553 2576 """Legacy part, ignored for compatibility with bundles from or
2554 2577 for Mercurial before 5.7. Newer Mercurial computes the cache
2555 2578 efficiently enough during unbundling that the additional transfer
2556 2579 is unnecessary."""
2557 2580
2558 2581
2559 2582 @parthandler(b'pushvars')
2560 2583 def bundle2getvars(op, part):
2561 2584 '''unbundle a bundle2 containing shellvars on the server'''
2562 2585 # An option to disable unbundling on server-side for security reasons
2563 2586 if op.ui.configbool(b'push', b'pushvars.server'):
2564 2587 hookargs = {}
2565 2588 for key, value in part.advisoryparams:
2566 2589 key = key.upper()
2567 2590 # We want pushed variables to have USERVAR_ prepended so we know
2568 2591 # they came from the --pushvar flag.
2569 2592 key = b"USERVAR_" + key
2570 2593 hookargs[key] = value
2571 2594 op.addhookargs(hookargs)
2572 2595
2573 2596
2574 2597 @parthandler(b'stream2', (b'requirements', b'filecount', b'bytecount'))
2575 2598 def handlestreamv2bundle(op, part):
2576 2599
2577 2600 requirements = urlreq.unquote(part.params[b'requirements'])
2578 2601 requirements = requirements.split(b',') if requirements else []
2579 2602 filecount = int(part.params[b'filecount'])
2580 2603 bytecount = int(part.params[b'bytecount'])
2581 2604
2582 2605 repo = op.repo
2583 2606 if len(repo):
2584 2607 msg = _(b'cannot apply stream clone to non empty repository')
2585 2608 raise error.Abort(msg)
2586 2609
2587 2610 repo.ui.debug(b'applying stream bundle\n')
2588 2611 streamclone.applybundlev2(repo, part, filecount, bytecount, requirements)
2589 2612
2590 2613
2614 @parthandler(b'stream3', (b'requirements', b'filecount', b'bytecount'))
2615 def handlestreamv3bundle(op, part):
2616 return handlestreamv2bundle(op, part)
2617
2618
2591 2619 def widen_bundle(
2592 2620 bundler, repo, oldmatcher, newmatcher, common, known, cgversion, ellipses
2593 2621 ):
2594 2622 """generates bundle2 for widening a narrow clone
2595 2623
2596 2624 bundler is the bundle to which data should be added
2597 2625 repo is the localrepository instance
2598 2626 oldmatcher matches what the client already has
2599 2627 newmatcher matches what the client needs (including what it already has)
2600 2628 common is set of common heads between server and client
2601 2629 known is a set of revs known on the client side (used in ellipses)
2602 2630 cgversion is the changegroup version to send
2603 2631 ellipses is boolean value telling whether to send ellipses data or not
2604 2632
2605 2633 returns bundle2 of the data required for extending
2606 2634 """
2607 2635 commonnodes = set()
2608 2636 cl = repo.changelog
2609 2637 for r in repo.revs(b"::%ln", common):
2610 2638 commonnodes.add(cl.node(r))
2611 2639 if commonnodes:
2612 2640 packer = changegroup.getbundler(
2613 2641 cgversion,
2614 2642 repo,
2615 2643 oldmatcher=oldmatcher,
2616 2644 matcher=newmatcher,
2617 2645 fullnodes=commonnodes,
2618 2646 )
2619 2647 cgdata = packer.generate(
2620 2648 {repo.nullid},
2621 2649 list(commonnodes),
2622 2650 False,
2623 2651 b'narrow_widen',
2624 2652 changelog=False,
2625 2653 )
2626 2654
2627 2655 part = bundler.newpart(b'changegroup', data=cgdata)
2628 2656 part.addparam(b'version', cgversion)
2629 2657 if scmutil.istreemanifest(repo):
2630 2658 part.addparam(b'treemanifest', b'1')
2631 2659 if repository.REPO_FEATURE_SIDE_DATA in repo.features:
2632 2660 part.addparam(b'exp-sidedata', b'1')
2633 2661 wanted = format_remote_wanted_sidedata(repo)
2634 2662 part.addparam(b'exp-wanted-sidedata', wanted)
2635 2663
2636 2664 return bundler
@@ -1,2979 +1,2984
1 1 # configitems.py - centralized declaration of configuration option
2 2 #
3 3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8
9 9 import functools
10 10 import re
11 11
12 12 from . import (
13 13 encoding,
14 14 error,
15 15 )
16 16
17 17
18 18 def loadconfigtable(ui, extname, configtable):
19 19 """update config item known to the ui with the extension ones"""
20 20 for section, items in sorted(configtable.items()):
21 21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 22 knownkeys = set(knownitems)
23 23 newkeys = set(items)
24 24 for key in sorted(knownkeys & newkeys):
25 25 msg = b"extension '%s' overwrite config item '%s.%s'"
26 26 msg %= (extname, section, key)
27 27 ui.develwarn(msg, config=b'warn-config')
28 28
29 29 knownitems.update(items)
30 30
31 31
32 32 class configitem:
33 33 """represent a known config item
34 34
35 35 :section: the official config section where to find this item,
36 36 :name: the official name within the section,
37 37 :default: default value for this item,
38 38 :alias: optional list of tuples as alternatives,
39 39 :generic: this is a generic definition, match name using regular expression.
40 40 """
41 41
42 42 def __init__(
43 43 self,
44 44 section,
45 45 name,
46 46 default=None,
47 47 alias=(),
48 48 generic=False,
49 49 priority=0,
50 50 experimental=False,
51 51 ):
52 52 self.section = section
53 53 self.name = name
54 54 self.default = default
55 55 self.alias = list(alias)
56 56 self.generic = generic
57 57 self.priority = priority
58 58 self.experimental = experimental
59 59 self._re = None
60 60 if generic:
61 61 self._re = re.compile(self.name)
62 62
63 63
64 64 class itemregister(dict):
65 65 """A specialized dictionary that can handle wild-card selection"""
66 66
67 67 def __init__(self):
68 68 super(itemregister, self).__init__()
69 69 self._generics = set()
70 70
71 71 def update(self, other):
72 72 super(itemregister, self).update(other)
73 73 self._generics.update(other._generics)
74 74
75 75 def __setitem__(self, key, item):
76 76 super(itemregister, self).__setitem__(key, item)
77 77 if item.generic:
78 78 self._generics.add(item)
79 79
80 80 def get(self, key):
81 81 baseitem = super(itemregister, self).get(key)
82 82 if baseitem is not None and not baseitem.generic:
83 83 return baseitem
84 84
85 85 # search for a matching generic item
86 86 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
87 87 for item in generics:
88 88 # we use 'match' instead of 'search' to make the matching simpler
89 89 # for people unfamiliar with regular expression. Having the match
90 90 # rooted to the start of the string will produce less surprising
91 91 # result for user writing simple regex for sub-attribute.
92 92 #
93 93 # For example using "color\..*" match produces an unsurprising
94 94 # result, while using search could suddenly match apparently
95 95 # unrelated configuration that happens to contains "color."
96 96 # anywhere. This is a tradeoff where we favor requiring ".*" on
97 97 # some match to avoid the need to prefix most pattern with "^".
98 98 # The "^" seems more error prone.
99 99 if item._re.match(key):
100 100 return item
101 101
102 102 return None
103 103
104 104
105 105 coreitems = {}
106 106
107 107
108 108 def _register(configtable, *args, **kwargs):
109 109 item = configitem(*args, **kwargs)
110 110 section = configtable.setdefault(item.section, itemregister())
111 111 if item.name in section:
112 112 msg = b"duplicated config item registration for '%s.%s'"
113 113 raise error.ProgrammingError(msg % (item.section, item.name))
114 114 section[item.name] = item
115 115
116 116
117 117 # special value for case where the default is derived from other values
118 118 dynamicdefault = object()
119 119
120 120 # Registering actual config items
121 121
122 122
123 123 def getitemregister(configtable):
124 124 f = functools.partial(_register, configtable)
125 125 # export pseudo enum as configitem.*
126 126 f.dynamicdefault = dynamicdefault
127 127 return f
128 128
129 129
130 130 coreconfigitem = getitemregister(coreitems)
131 131
132 132
133 133 def _registerdiffopts(section, configprefix=b''):
134 134 coreconfigitem(
135 135 section,
136 136 configprefix + b'nodates',
137 137 default=False,
138 138 )
139 139 coreconfigitem(
140 140 section,
141 141 configprefix + b'showfunc',
142 142 default=False,
143 143 )
144 144 coreconfigitem(
145 145 section,
146 146 configprefix + b'unified',
147 147 default=None,
148 148 )
149 149 coreconfigitem(
150 150 section,
151 151 configprefix + b'git',
152 152 default=False,
153 153 )
154 154 coreconfigitem(
155 155 section,
156 156 configprefix + b'ignorews',
157 157 default=False,
158 158 )
159 159 coreconfigitem(
160 160 section,
161 161 configprefix + b'ignorewsamount',
162 162 default=False,
163 163 )
164 164 coreconfigitem(
165 165 section,
166 166 configprefix + b'ignoreblanklines',
167 167 default=False,
168 168 )
169 169 coreconfigitem(
170 170 section,
171 171 configprefix + b'ignorewseol',
172 172 default=False,
173 173 )
174 174 coreconfigitem(
175 175 section,
176 176 configprefix + b'nobinary',
177 177 default=False,
178 178 )
179 179 coreconfigitem(
180 180 section,
181 181 configprefix + b'noprefix',
182 182 default=False,
183 183 )
184 184 coreconfigitem(
185 185 section,
186 186 configprefix + b'word-diff',
187 187 default=False,
188 188 )
189 189
190 190
191 191 coreconfigitem(
192 192 b'alias',
193 193 b'.*',
194 194 default=dynamicdefault,
195 195 generic=True,
196 196 )
197 197 coreconfigitem(
198 198 b'auth',
199 199 b'cookiefile',
200 200 default=None,
201 201 )
202 202 _registerdiffopts(section=b'annotate')
203 203 # bookmarks.pushing: internal hack for discovery
204 204 coreconfigitem(
205 205 b'bookmarks',
206 206 b'pushing',
207 207 default=list,
208 208 )
209 209 # bundle.mainreporoot: internal hack for bundlerepo
210 210 coreconfigitem(
211 211 b'bundle',
212 212 b'mainreporoot',
213 213 default=b'',
214 214 )
215 215 coreconfigitem(
216 216 b'censor',
217 217 b'policy',
218 218 default=b'abort',
219 219 experimental=True,
220 220 )
221 221 coreconfigitem(
222 222 b'chgserver',
223 223 b'idletimeout',
224 224 default=3600,
225 225 )
226 226 coreconfigitem(
227 227 b'chgserver',
228 228 b'skiphash',
229 229 default=False,
230 230 )
231 231 coreconfigitem(
232 232 b'cmdserver',
233 233 b'log',
234 234 default=None,
235 235 )
236 236 coreconfigitem(
237 237 b'cmdserver',
238 238 b'max-log-files',
239 239 default=7,
240 240 )
241 241 coreconfigitem(
242 242 b'cmdserver',
243 243 b'max-log-size',
244 244 default=b'1 MB',
245 245 )
246 246 coreconfigitem(
247 247 b'cmdserver',
248 248 b'max-repo-cache',
249 249 default=0,
250 250 experimental=True,
251 251 )
252 252 coreconfigitem(
253 253 b'cmdserver',
254 254 b'message-encodings',
255 255 default=list,
256 256 )
257 257 coreconfigitem(
258 258 b'cmdserver',
259 259 b'track-log',
260 260 default=lambda: [b'chgserver', b'cmdserver', b'repocache'],
261 261 )
262 262 coreconfigitem(
263 263 b'cmdserver',
264 264 b'shutdown-on-interrupt',
265 265 default=True,
266 266 )
267 267 coreconfigitem(
268 268 b'color',
269 269 b'.*',
270 270 default=None,
271 271 generic=True,
272 272 )
273 273 coreconfigitem(
274 274 b'color',
275 275 b'mode',
276 276 default=b'auto',
277 277 )
278 278 coreconfigitem(
279 279 b'color',
280 280 b'pagermode',
281 281 default=dynamicdefault,
282 282 )
283 283 coreconfigitem(
284 284 b'command-templates',
285 285 b'graphnode',
286 286 default=None,
287 287 alias=[(b'ui', b'graphnodetemplate')],
288 288 )
289 289 coreconfigitem(
290 290 b'command-templates',
291 291 b'log',
292 292 default=None,
293 293 alias=[(b'ui', b'logtemplate')],
294 294 )
295 295 coreconfigitem(
296 296 b'command-templates',
297 297 b'mergemarker',
298 298 default=(
299 299 b'{node|short} '
300 300 b'{ifeq(tags, "tip", "", '
301 301 b'ifeq(tags, "", "", "{tags} "))}'
302 302 b'{if(bookmarks, "{bookmarks} ")}'
303 303 b'{ifeq(branch, "default", "", "{branch} ")}'
304 304 b'- {author|user}: {desc|firstline}'
305 305 ),
306 306 alias=[(b'ui', b'mergemarkertemplate')],
307 307 )
308 308 coreconfigitem(
309 309 b'command-templates',
310 310 b'pre-merge-tool-output',
311 311 default=None,
312 312 alias=[(b'ui', b'pre-merge-tool-output-template')],
313 313 )
314 314 coreconfigitem(
315 315 b'command-templates',
316 316 b'oneline-summary',
317 317 default=None,
318 318 )
319 319 coreconfigitem(
320 320 b'command-templates',
321 321 b'oneline-summary.*',
322 322 default=dynamicdefault,
323 323 generic=True,
324 324 )
325 325 _registerdiffopts(section=b'commands', configprefix=b'commit.interactive.')
326 326 coreconfigitem(
327 327 b'commands',
328 328 b'commit.post-status',
329 329 default=False,
330 330 )
331 331 coreconfigitem(
332 332 b'commands',
333 333 b'grep.all-files',
334 334 default=False,
335 335 experimental=True,
336 336 )
337 337 coreconfigitem(
338 338 b'commands',
339 339 b'merge.require-rev',
340 340 default=False,
341 341 )
342 342 coreconfigitem(
343 343 b'commands',
344 344 b'push.require-revs',
345 345 default=False,
346 346 )
347 347 coreconfigitem(
348 348 b'commands',
349 349 b'resolve.confirm',
350 350 default=False,
351 351 )
352 352 coreconfigitem(
353 353 b'commands',
354 354 b'resolve.explicit-re-merge',
355 355 default=False,
356 356 )
357 357 coreconfigitem(
358 358 b'commands',
359 359 b'resolve.mark-check',
360 360 default=b'none',
361 361 )
362 362 _registerdiffopts(section=b'commands', configprefix=b'revert.interactive.')
363 363 coreconfigitem(
364 364 b'commands',
365 365 b'show.aliasprefix',
366 366 default=list,
367 367 )
368 368 coreconfigitem(
369 369 b'commands',
370 370 b'status.relative',
371 371 default=False,
372 372 )
373 373 coreconfigitem(
374 374 b'commands',
375 375 b'status.skipstates',
376 376 default=[],
377 377 experimental=True,
378 378 )
379 379 coreconfigitem(
380 380 b'commands',
381 381 b'status.terse',
382 382 default=b'',
383 383 )
384 384 coreconfigitem(
385 385 b'commands',
386 386 b'status.verbose',
387 387 default=False,
388 388 )
389 389 coreconfigitem(
390 390 b'commands',
391 391 b'update.check',
392 392 default=None,
393 393 )
394 394 coreconfigitem(
395 395 b'commands',
396 396 b'update.requiredest',
397 397 default=False,
398 398 )
399 399 coreconfigitem(
400 400 b'committemplate',
401 401 b'.*',
402 402 default=None,
403 403 generic=True,
404 404 )
405 405 coreconfigitem(
406 406 b'convert',
407 407 b'bzr.saverev',
408 408 default=True,
409 409 )
410 410 coreconfigitem(
411 411 b'convert',
412 412 b'cvsps.cache',
413 413 default=True,
414 414 )
415 415 coreconfigitem(
416 416 b'convert',
417 417 b'cvsps.fuzz',
418 418 default=60,
419 419 )
420 420 coreconfigitem(
421 421 b'convert',
422 422 b'cvsps.logencoding',
423 423 default=None,
424 424 )
425 425 coreconfigitem(
426 426 b'convert',
427 427 b'cvsps.mergefrom',
428 428 default=None,
429 429 )
430 430 coreconfigitem(
431 431 b'convert',
432 432 b'cvsps.mergeto',
433 433 default=None,
434 434 )
435 435 coreconfigitem(
436 436 b'convert',
437 437 b'git.committeractions',
438 438 default=lambda: [b'messagedifferent'],
439 439 )
440 440 coreconfigitem(
441 441 b'convert',
442 442 b'git.extrakeys',
443 443 default=list,
444 444 )
445 445 coreconfigitem(
446 446 b'convert',
447 447 b'git.findcopiesharder',
448 448 default=False,
449 449 )
450 450 coreconfigitem(
451 451 b'convert',
452 452 b'git.remoteprefix',
453 453 default=b'remote',
454 454 )
455 455 coreconfigitem(
456 456 b'convert',
457 457 b'git.renamelimit',
458 458 default=400,
459 459 )
460 460 coreconfigitem(
461 461 b'convert',
462 462 b'git.saverev',
463 463 default=True,
464 464 )
465 465 coreconfigitem(
466 466 b'convert',
467 467 b'git.similarity',
468 468 default=50,
469 469 )
470 470 coreconfigitem(
471 471 b'convert',
472 472 b'git.skipsubmodules',
473 473 default=False,
474 474 )
475 475 coreconfigitem(
476 476 b'convert',
477 477 b'hg.clonebranches',
478 478 default=False,
479 479 )
480 480 coreconfigitem(
481 481 b'convert',
482 482 b'hg.ignoreerrors',
483 483 default=False,
484 484 )
485 485 coreconfigitem(
486 486 b'convert',
487 487 b'hg.preserve-hash',
488 488 default=False,
489 489 )
490 490 coreconfigitem(
491 491 b'convert',
492 492 b'hg.revs',
493 493 default=None,
494 494 )
495 495 coreconfigitem(
496 496 b'convert',
497 497 b'hg.saverev',
498 498 default=False,
499 499 )
500 500 coreconfigitem(
501 501 b'convert',
502 502 b'hg.sourcename',
503 503 default=None,
504 504 )
505 505 coreconfigitem(
506 506 b'convert',
507 507 b'hg.startrev',
508 508 default=None,
509 509 )
510 510 coreconfigitem(
511 511 b'convert',
512 512 b'hg.tagsbranch',
513 513 default=b'default',
514 514 )
515 515 coreconfigitem(
516 516 b'convert',
517 517 b'hg.usebranchnames',
518 518 default=True,
519 519 )
520 520 coreconfigitem(
521 521 b'convert',
522 522 b'ignoreancestorcheck',
523 523 default=False,
524 524 experimental=True,
525 525 )
526 526 coreconfigitem(
527 527 b'convert',
528 528 b'localtimezone',
529 529 default=False,
530 530 )
531 531 coreconfigitem(
532 532 b'convert',
533 533 b'p4.encoding',
534 534 default=dynamicdefault,
535 535 )
536 536 coreconfigitem(
537 537 b'convert',
538 538 b'p4.startrev',
539 539 default=0,
540 540 )
541 541 coreconfigitem(
542 542 b'convert',
543 543 b'skiptags',
544 544 default=False,
545 545 )
546 546 coreconfigitem(
547 547 b'convert',
548 548 b'svn.debugsvnlog',
549 549 default=True,
550 550 )
551 551 coreconfigitem(
552 552 b'convert',
553 553 b'svn.trunk',
554 554 default=None,
555 555 )
556 556 coreconfigitem(
557 557 b'convert',
558 558 b'svn.tags',
559 559 default=None,
560 560 )
561 561 coreconfigitem(
562 562 b'convert',
563 563 b'svn.branches',
564 564 default=None,
565 565 )
566 566 coreconfigitem(
567 567 b'convert',
568 568 b'svn.startrev',
569 569 default=0,
570 570 )
571 571 coreconfigitem(
572 572 b'convert',
573 573 b'svn.dangerous-set-commit-dates',
574 574 default=False,
575 575 )
576 576 coreconfigitem(
577 577 b'debug',
578 578 b'dirstate.delaywrite',
579 579 default=0,
580 580 )
581 581 coreconfigitem(
582 582 b'debug',
583 583 b'revlog.verifyposition.changelog',
584 584 default=b'',
585 585 )
586 586 coreconfigitem(
587 587 b'debug',
588 588 b'revlog.debug-delta',
589 589 default=False,
590 590 )
591 591 # display extra information about the bundling process
592 592 coreconfigitem(
593 593 b'debug',
594 594 b'bundling-stats',
595 595 default=False,
596 596 )
597 597 # display extra information about the unbundling process
598 598 coreconfigitem(
599 599 b'debug',
600 600 b'unbundling-stats',
601 601 default=False,
602 602 )
603 603 coreconfigitem(
604 604 b'defaults',
605 605 b'.*',
606 606 default=None,
607 607 generic=True,
608 608 )
609 609 coreconfigitem(
610 610 b'devel',
611 611 b'all-warnings',
612 612 default=False,
613 613 )
614 614 coreconfigitem(
615 615 b'devel',
616 616 b'bundle2.debug',
617 617 default=False,
618 618 )
619 619 # which kind of delta to put in the bundled changegroup. Possible value
620 620 # - '': use default behavior
621 621 # - p1: force to always use delta against p1
622 622 # - full: force to always use full content
623 623 coreconfigitem(
624 624 b'devel',
625 625 b'bundle.delta',
626 626 default=b'',
627 627 )
628 628 coreconfigitem(
629 629 b'devel',
630 630 b'cache-vfs',
631 631 default=None,
632 632 )
633 633 coreconfigitem(
634 634 b'devel',
635 635 b'check-locks',
636 636 default=False,
637 637 )
638 638 coreconfigitem(
639 639 b'devel',
640 640 b'check-relroot',
641 641 default=False,
642 642 )
643 643 # Track copy information for all file, not just "added" one (very slow)
644 644 coreconfigitem(
645 645 b'devel',
646 646 b'copy-tracing.trace-all-files',
647 647 default=False,
648 648 )
649 649 coreconfigitem(
650 650 b'devel',
651 651 b'default-date',
652 652 default=None,
653 653 )
654 654 coreconfigitem(
655 655 b'devel',
656 656 b'deprec-warn',
657 657 default=False,
658 658 )
659 659 # possible values:
660 660 # - auto (the default)
661 661 # - force-append
662 662 # - force-new
663 663 coreconfigitem(
664 664 b'devel',
665 665 b'dirstate.v2.data_update_mode',
666 666 default="auto",
667 667 )
668 668 coreconfigitem(
669 669 b'devel',
670 670 b'disableloaddefaultcerts',
671 671 default=False,
672 672 )
673 673 coreconfigitem(
674 674 b'devel',
675 675 b'warn-empty-changegroup',
676 676 default=False,
677 677 )
678 678 coreconfigitem(
679 679 b'devel',
680 680 b'legacy.exchange',
681 681 default=list,
682 682 )
683 683 # When True, revlogs use a special reference version of the nodemap, that is not
684 684 # performant but is "known" to behave properly.
685 685 coreconfigitem(
686 686 b'devel',
687 687 b'persistent-nodemap',
688 688 default=False,
689 689 )
690 690 coreconfigitem(
691 691 b'devel',
692 692 b'servercafile',
693 693 default=b'',
694 694 )
695 695 # This config option is intended for use in tests only. It is a giant
696 696 # footgun to kill security. Don't define it.
697 697 coreconfigitem(
698 698 b'devel',
699 699 b'server-insecure-exact-protocol',
700 700 default=b'',
701 701 )
702 702 coreconfigitem(
703 703 b'devel',
704 704 b'serverrequirecert',
705 705 default=False,
706 706 )
707 707 # Makes the status algorithm wait for the existence of this file
708 708 # (or until a timeout of `devel.sync.status.pre-dirstate-write-file-timeout`
709 709 # seconds) before taking the lock and writing the dirstate.
710 710 # Status signals that it's ready to wait by creating a file
711 711 # with the same name + `.waiting`.
712 712 # Useful when testing race conditions.
713 713 coreconfigitem(
714 714 b'devel',
715 715 b'sync.status.pre-dirstate-write-file',
716 716 default=None,
717 717 )
718 718 coreconfigitem(
719 719 b'devel',
720 720 b'sync.status.pre-dirstate-write-file-timeout',
721 721 default=2,
722 722 )
723 723 coreconfigitem(
724 724 b'devel',
725 725 b'sync.dirstate.post-docket-read-file',
726 726 default=None,
727 727 )
728 728 coreconfigitem(
729 729 b'devel',
730 730 b'sync.dirstate.post-docket-read-file-timeout',
731 731 default=2,
732 732 )
733 733 coreconfigitem(
734 734 b'devel',
735 735 b'sync.dirstate.pre-read-file',
736 736 default=None,
737 737 )
738 738 coreconfigitem(
739 739 b'devel',
740 740 b'sync.dirstate.pre-read-file-timeout',
741 741 default=2,
742 742 )
743 743 coreconfigitem(
744 744 b'devel',
745 745 b'strip-obsmarkers',
746 746 default=True,
747 747 )
748 748 coreconfigitem(
749 749 b'devel',
750 750 b'warn-config',
751 751 default=None,
752 752 )
753 753 coreconfigitem(
754 754 b'devel',
755 755 b'warn-config-default',
756 756 default=None,
757 757 )
758 758 coreconfigitem(
759 759 b'devel',
760 760 b'user.obsmarker',
761 761 default=None,
762 762 )
763 763 coreconfigitem(
764 764 b'devel',
765 765 b'warn-config-unknown',
766 766 default=None,
767 767 )
768 768 coreconfigitem(
769 769 b'devel',
770 770 b'debug.copies',
771 771 default=False,
772 772 )
773 773 coreconfigitem(
774 774 b'devel',
775 775 b'copy-tracing.multi-thread',
776 776 default=True,
777 777 )
778 778 coreconfigitem(
779 779 b'devel',
780 780 b'debug.extensions',
781 781 default=False,
782 782 )
783 783 coreconfigitem(
784 784 b'devel',
785 785 b'debug.repo-filters',
786 786 default=False,
787 787 )
788 788 coreconfigitem(
789 789 b'devel',
790 790 b'debug.peer-request',
791 791 default=False,
792 792 )
793 793 # If discovery.exchange-heads is False, the discovery will not start with
794 794 # remote head fetching and local head querying.
795 795 coreconfigitem(
796 796 b'devel',
797 797 b'discovery.exchange-heads',
798 798 default=True,
799 799 )
800 800 # If devel.debug.abort-update is True, then any merge with the working copy,
801 801 # e.g. [hg update], will be aborted after figuring out what needs to be done,
802 802 # but before spawning the parallel worker
803 803 coreconfigitem(
804 804 b'devel',
805 805 b'debug.abort-update',
806 806 default=False,
807 807 )
808 808 # If discovery.grow-sample is False, the sample size used in set discovery will
809 809 # not be increased through the process
810 810 coreconfigitem(
811 811 b'devel',
812 812 b'discovery.grow-sample',
813 813 default=True,
814 814 )
815 815 # When discovery.grow-sample.dynamic is True, the default, the sample size is
816 816 # adapted to the shape of the undecided set (it is set to the max of:
817 817 # <target-size>, len(roots(undecided)), len(heads(undecided)
818 818 coreconfigitem(
819 819 b'devel',
820 820 b'discovery.grow-sample.dynamic',
821 821 default=True,
822 822 )
823 823 # discovery.grow-sample.rate control the rate at which the sample grow
824 824 coreconfigitem(
825 825 b'devel',
826 826 b'discovery.grow-sample.rate',
827 827 default=1.05,
828 828 )
829 829 # If discovery.randomize is False, random sampling during discovery are
830 830 # deterministic. It is meant for integration tests.
831 831 coreconfigitem(
832 832 b'devel',
833 833 b'discovery.randomize',
834 834 default=True,
835 835 )
836 836 # Control the initial size of the discovery sample
837 837 coreconfigitem(
838 838 b'devel',
839 839 b'discovery.sample-size',
840 840 default=200,
841 841 )
842 842 # Control the initial size of the discovery for initial change
843 843 coreconfigitem(
844 844 b'devel',
845 845 b'discovery.sample-size.initial',
846 846 default=100,
847 847 )
848 848 _registerdiffopts(section=b'diff')
849 849 coreconfigitem(
850 850 b'diff',
851 851 b'merge',
852 852 default=False,
853 853 experimental=True,
854 854 )
855 855 coreconfigitem(
856 856 b'email',
857 857 b'bcc',
858 858 default=None,
859 859 )
860 860 coreconfigitem(
861 861 b'email',
862 862 b'cc',
863 863 default=None,
864 864 )
865 865 coreconfigitem(
866 866 b'email',
867 867 b'charsets',
868 868 default=list,
869 869 )
870 870 coreconfigitem(
871 871 b'email',
872 872 b'from',
873 873 default=None,
874 874 )
875 875 coreconfigitem(
876 876 b'email',
877 877 b'method',
878 878 default=b'smtp',
879 879 )
880 880 coreconfigitem(
881 881 b'email',
882 882 b'reply-to',
883 883 default=None,
884 884 )
885 885 coreconfigitem(
886 886 b'email',
887 887 b'to',
888 888 default=None,
889 889 )
890 890 coreconfigitem(
891 891 b'experimental',
892 892 b'archivemetatemplate',
893 893 default=dynamicdefault,
894 894 )
895 895 coreconfigitem(
896 896 b'experimental',
897 897 b'auto-publish',
898 898 default=b'publish',
899 899 )
900 900 coreconfigitem(
901 901 b'experimental',
902 902 b'bundle-phases',
903 903 default=False,
904 904 )
905 905 coreconfigitem(
906 906 b'experimental',
907 907 b'bundle2-advertise',
908 908 default=True,
909 909 )
910 910 coreconfigitem(
911 911 b'experimental',
912 912 b'bundle2-output-capture',
913 913 default=False,
914 914 )
915 915 coreconfigitem(
916 916 b'experimental',
917 917 b'bundle2.pushback',
918 918 default=False,
919 919 )
920 920 coreconfigitem(
921 921 b'experimental',
922 922 b'bundle2lazylocking',
923 923 default=False,
924 924 )
925 925 coreconfigitem(
926 926 b'experimental',
927 927 b'bundlecomplevel',
928 928 default=None,
929 929 )
930 930 coreconfigitem(
931 931 b'experimental',
932 932 b'bundlecomplevel.bzip2',
933 933 default=None,
934 934 )
935 935 coreconfigitem(
936 936 b'experimental',
937 937 b'bundlecomplevel.gzip',
938 938 default=None,
939 939 )
940 940 coreconfigitem(
941 941 b'experimental',
942 942 b'bundlecomplevel.none',
943 943 default=None,
944 944 )
945 945 coreconfigitem(
946 946 b'experimental',
947 947 b'bundlecomplevel.zstd',
948 948 default=None,
949 949 )
950 950 coreconfigitem(
951 951 b'experimental',
952 952 b'bundlecompthreads',
953 953 default=None,
954 954 )
955 955 coreconfigitem(
956 956 b'experimental',
957 957 b'bundlecompthreads.bzip2',
958 958 default=None,
959 959 )
960 960 coreconfigitem(
961 961 b'experimental',
962 962 b'bundlecompthreads.gzip',
963 963 default=None,
964 964 )
965 965 coreconfigitem(
966 966 b'experimental',
967 967 b'bundlecompthreads.none',
968 968 default=None,
969 969 )
970 970 coreconfigitem(
971 971 b'experimental',
972 972 b'bundlecompthreads.zstd',
973 973 default=None,
974 974 )
975 975 coreconfigitem(
976 976 b'experimental',
977 977 b'changegroup3',
978 978 default=True,
979 979 )
980 980 coreconfigitem(
981 981 b'experimental',
982 982 b'changegroup4',
983 983 default=False,
984 984 )
985 985
986 986 # might remove rank configuration once the computation has no impact
987 987 coreconfigitem(
988 988 b'experimental',
989 989 b'changelog-v2.compute-rank',
990 990 default=True,
991 991 )
992 992 coreconfigitem(
993 993 b'experimental',
994 994 b'cleanup-as-archived',
995 995 default=False,
996 996 )
997 997 coreconfigitem(
998 998 b'experimental',
999 999 b'clientcompressionengines',
1000 1000 default=list,
1001 1001 )
1002 1002 coreconfigitem(
1003 1003 b'experimental',
1004 1004 b'copytrace',
1005 1005 default=b'on',
1006 1006 )
1007 1007 coreconfigitem(
1008 1008 b'experimental',
1009 1009 b'copytrace.movecandidateslimit',
1010 1010 default=100,
1011 1011 )
1012 1012 coreconfigitem(
1013 1013 b'experimental',
1014 1014 b'copytrace.sourcecommitlimit',
1015 1015 default=100,
1016 1016 )
1017 1017 coreconfigitem(
1018 1018 b'experimental',
1019 1019 b'copies.read-from',
1020 1020 default=b"filelog-only",
1021 1021 )
1022 1022 coreconfigitem(
1023 1023 b'experimental',
1024 1024 b'copies.write-to',
1025 1025 default=b'filelog-only',
1026 1026 )
1027 1027 coreconfigitem(
1028 1028 b'experimental',
1029 1029 b'crecordtest',
1030 1030 default=None,
1031 1031 )
1032 1032 coreconfigitem(
1033 1033 b'experimental',
1034 1034 b'directaccess',
1035 1035 default=False,
1036 1036 )
1037 1037 coreconfigitem(
1038 1038 b'experimental',
1039 1039 b'directaccess.revnums',
1040 1040 default=False,
1041 1041 )
1042 1042 coreconfigitem(
1043 1043 b'experimental',
1044 1044 b'editortmpinhg',
1045 1045 default=False,
1046 1046 )
1047 1047 coreconfigitem(
1048 1048 b'experimental',
1049 1049 b'evolution',
1050 1050 default=list,
1051 1051 )
1052 1052 coreconfigitem(
1053 1053 b'experimental',
1054 1054 b'evolution.allowdivergence',
1055 1055 default=False,
1056 1056 alias=[(b'experimental', b'allowdivergence')],
1057 1057 )
1058 1058 coreconfigitem(
1059 1059 b'experimental',
1060 1060 b'evolution.allowunstable',
1061 1061 default=None,
1062 1062 )
1063 1063 coreconfigitem(
1064 1064 b'experimental',
1065 1065 b'evolution.createmarkers',
1066 1066 default=None,
1067 1067 )
1068 1068 coreconfigitem(
1069 1069 b'experimental',
1070 1070 b'evolution.effect-flags',
1071 1071 default=True,
1072 1072 alias=[(b'experimental', b'effect-flags')],
1073 1073 )
1074 1074 coreconfigitem(
1075 1075 b'experimental',
1076 1076 b'evolution.exchange',
1077 1077 default=None,
1078 1078 )
1079 1079 coreconfigitem(
1080 1080 b'experimental',
1081 1081 b'evolution.bundle-obsmarker',
1082 1082 default=False,
1083 1083 )
1084 1084 coreconfigitem(
1085 1085 b'experimental',
1086 1086 b'evolution.bundle-obsmarker:mandatory',
1087 1087 default=True,
1088 1088 )
1089 1089 coreconfigitem(
1090 1090 b'experimental',
1091 1091 b'log.topo',
1092 1092 default=False,
1093 1093 )
1094 1094 coreconfigitem(
1095 1095 b'experimental',
1096 1096 b'evolution.report-instabilities',
1097 1097 default=True,
1098 1098 )
1099 1099 coreconfigitem(
1100 1100 b'experimental',
1101 1101 b'evolution.track-operation',
1102 1102 default=True,
1103 1103 )
1104 1104 # repo-level config to exclude a revset visibility
1105 1105 #
1106 1106 # The target use case is to use `share` to expose different subset of the same
1107 1107 # repository, especially server side. See also `server.view`.
1108 1108 coreconfigitem(
1109 1109 b'experimental',
1110 1110 b'extra-filter-revs',
1111 1111 default=None,
1112 1112 )
1113 1113 coreconfigitem(
1114 1114 b'experimental',
1115 1115 b'maxdeltachainspan',
1116 1116 default=-1,
1117 1117 )
1118 1118 # tracks files which were undeleted (merge might delete them but we explicitly
1119 1119 # kept/undeleted them) and creates new filenodes for them
1120 1120 coreconfigitem(
1121 1121 b'experimental',
1122 1122 b'merge-track-salvaged',
1123 1123 default=False,
1124 1124 )
1125 1125 coreconfigitem(
1126 1126 b'experimental',
1127 1127 b'mmapindexthreshold',
1128 1128 default=None,
1129 1129 )
1130 1130 coreconfigitem(
1131 1131 b'experimental',
1132 1132 b'narrow',
1133 1133 default=False,
1134 1134 )
1135 1135 coreconfigitem(
1136 1136 b'experimental',
1137 1137 b'nonnormalparanoidcheck',
1138 1138 default=False,
1139 1139 )
1140 1140 coreconfigitem(
1141 1141 b'experimental',
1142 1142 b'exportableenviron',
1143 1143 default=list,
1144 1144 )
1145 1145 coreconfigitem(
1146 1146 b'experimental',
1147 1147 b'extendedheader.index',
1148 1148 default=None,
1149 1149 )
1150 1150 coreconfigitem(
1151 1151 b'experimental',
1152 1152 b'extendedheader.similarity',
1153 1153 default=False,
1154 1154 )
1155 1155 coreconfigitem(
1156 1156 b'experimental',
1157 1157 b'graphshorten',
1158 1158 default=False,
1159 1159 )
1160 1160 coreconfigitem(
1161 1161 b'experimental',
1162 1162 b'graphstyle.parent',
1163 1163 default=dynamicdefault,
1164 1164 )
1165 1165 coreconfigitem(
1166 1166 b'experimental',
1167 1167 b'graphstyle.missing',
1168 1168 default=dynamicdefault,
1169 1169 )
1170 1170 coreconfigitem(
1171 1171 b'experimental',
1172 1172 b'graphstyle.grandparent',
1173 1173 default=dynamicdefault,
1174 1174 )
1175 1175 coreconfigitem(
1176 1176 b'experimental',
1177 1177 b'hook-track-tags',
1178 1178 default=False,
1179 1179 )
1180 1180 coreconfigitem(
1181 1181 b'experimental',
1182 1182 b'httppostargs',
1183 1183 default=False,
1184 1184 )
1185 1185 coreconfigitem(b'experimental', b'nointerrupt', default=False)
1186 1186 coreconfigitem(b'experimental', b'nointerrupt-interactiveonly', default=True)
1187 1187
1188 1188 coreconfigitem(
1189 1189 b'experimental',
1190 1190 b'obsmarkers-exchange-debug',
1191 1191 default=False,
1192 1192 )
1193 1193 coreconfigitem(
1194 1194 b'experimental',
1195 1195 b'remotenames',
1196 1196 default=False,
1197 1197 )
1198 1198 coreconfigitem(
1199 1199 b'experimental',
1200 1200 b'removeemptydirs',
1201 1201 default=True,
1202 1202 )
1203 1203 coreconfigitem(
1204 1204 b'experimental',
1205 1205 b'revert.interactive.select-to-keep',
1206 1206 default=False,
1207 1207 )
1208 1208 coreconfigitem(
1209 1209 b'experimental',
1210 1210 b'revisions.prefixhexnode',
1211 1211 default=False,
1212 1212 )
1213 1213 # "out of experimental" todo list.
1214 1214 #
1215 1215 # * include management of a persistent nodemap in the main docket
1216 1216 # * enforce a "no-truncate" policy for mmap safety
1217 1217 # - for censoring operation
1218 1218 # - for stripping operation
1219 1219 # - for rollback operation
1220 1220 # * proper streaming (race free) of the docket file
1221 1221 # * track garbage data to evemtually allow rewriting -existing- sidedata.
1222 1222 # * Exchange-wise, we will also need to do something more efficient than
1223 1223 # keeping references to the affected revlogs, especially memory-wise when
1224 1224 # rewriting sidedata.
1225 1225 # * introduce a proper solution to reduce the number of filelog related files.
1226 1226 # * use caching for reading sidedata (similar to what we do for data).
1227 1227 # * no longer set offset=0 if sidedata_size=0 (simplify cutoff computation).
1228 1228 # * Improvement to consider
1229 1229 # - avoid compression header in chunk using the default compression?
1230 1230 # - forbid "inline" compression mode entirely?
1231 1231 # - split the data offset and flag field (the 2 bytes save are mostly trouble)
1232 1232 # - keep track of uncompressed -chunk- size (to preallocate memory better)
1233 1233 # - keep track of chain base or size (probably not that useful anymore)
1234 1234 coreconfigitem(
1235 1235 b'experimental',
1236 1236 b'revlogv2',
1237 1237 default=None,
1238 1238 )
1239 1239 coreconfigitem(
1240 1240 b'experimental',
1241 1241 b'revisions.disambiguatewithin',
1242 1242 default=None,
1243 1243 )
1244 1244 coreconfigitem(
1245 1245 b'experimental',
1246 1246 b'rust.index',
1247 1247 default=False,
1248 1248 )
1249 1249 coreconfigitem(
1250 1250 b'experimental',
1251 1251 b'server.allow-hidden-access',
1252 1252 default=list,
1253 1253 )
1254 1254 coreconfigitem(
1255 1255 b'experimental',
1256 1256 b'server.filesdata.recommended-batch-size',
1257 1257 default=50000,
1258 1258 )
1259 1259 coreconfigitem(
1260 1260 b'experimental',
1261 1261 b'server.manifestdata.recommended-batch-size',
1262 1262 default=100000,
1263 1263 )
1264 1264 coreconfigitem(
1265 1265 b'experimental',
1266 1266 b'server.stream-narrow-clones',
1267 1267 default=False,
1268 1268 )
1269 1269 coreconfigitem(
1270 1270 b'experimental',
1271 1271 b'single-head-per-branch',
1272 1272 default=False,
1273 1273 )
1274 1274 coreconfigitem(
1275 1275 b'experimental',
1276 1276 b'single-head-per-branch:account-closed-heads',
1277 1277 default=False,
1278 1278 )
1279 1279 coreconfigitem(
1280 1280 b'experimental',
1281 1281 b'single-head-per-branch:public-changes-only',
1282 1282 default=False,
1283 1283 )
1284 1284 coreconfigitem(
1285 1285 b'experimental',
1286 1286 b'sparse-read',
1287 1287 default=False,
1288 1288 )
1289 1289 coreconfigitem(
1290 1290 b'experimental',
1291 1291 b'sparse-read.density-threshold',
1292 1292 default=0.50,
1293 1293 )
1294 1294 coreconfigitem(
1295 1295 b'experimental',
1296 1296 b'sparse-read.min-gap-size',
1297 1297 default=b'65K',
1298 1298 )
1299 1299 coreconfigitem(
1300 1300 b'experimental',
1301 b'stream-v3',
1302 default=False,
1303 )
1304 coreconfigitem(
1305 b'experimental',
1301 1306 b'treemanifest',
1302 1307 default=False,
1303 1308 )
1304 1309 coreconfigitem(
1305 1310 b'experimental',
1306 1311 b'update.atomic-file',
1307 1312 default=False,
1308 1313 )
1309 1314 coreconfigitem(
1310 1315 b'experimental',
1311 1316 b'web.full-garbage-collection-rate',
1312 1317 default=1, # still forcing a full collection on each request
1313 1318 )
1314 1319 coreconfigitem(
1315 1320 b'experimental',
1316 1321 b'worker.wdir-get-thread-safe',
1317 1322 default=False,
1318 1323 )
1319 1324 coreconfigitem(
1320 1325 b'experimental',
1321 1326 b'worker.repository-upgrade',
1322 1327 default=False,
1323 1328 )
1324 1329 coreconfigitem(
1325 1330 b'experimental',
1326 1331 b'xdiff',
1327 1332 default=False,
1328 1333 )
1329 1334 coreconfigitem(
1330 1335 b'extensions',
1331 1336 b'[^:]*',
1332 1337 default=None,
1333 1338 generic=True,
1334 1339 )
1335 1340 coreconfigitem(
1336 1341 b'extensions',
1337 1342 b'[^:]*:required',
1338 1343 default=False,
1339 1344 generic=True,
1340 1345 )
1341 1346 coreconfigitem(
1342 1347 b'extdata',
1343 1348 b'.*',
1344 1349 default=None,
1345 1350 generic=True,
1346 1351 )
1347 1352 coreconfigitem(
1348 1353 b'format',
1349 1354 b'bookmarks-in-store',
1350 1355 default=False,
1351 1356 )
1352 1357 coreconfigitem(
1353 1358 b'format',
1354 1359 b'chunkcachesize',
1355 1360 default=None,
1356 1361 experimental=True,
1357 1362 )
1358 1363 coreconfigitem(
1359 1364 # Enable this dirstate format *when creating a new repository*.
1360 1365 # Which format to use for existing repos is controlled by .hg/requires
1361 1366 b'format',
1362 1367 b'use-dirstate-v2',
1363 1368 default=False,
1364 1369 experimental=True,
1365 1370 alias=[(b'format', b'exp-rc-dirstate-v2')],
1366 1371 )
1367 1372 coreconfigitem(
1368 1373 b'format',
1369 1374 b'use-dirstate-v2.automatic-upgrade-of-mismatching-repositories',
1370 1375 default=False,
1371 1376 experimental=True,
1372 1377 )
1373 1378 coreconfigitem(
1374 1379 b'format',
1375 1380 b'use-dirstate-v2.automatic-upgrade-of-mismatching-repositories:quiet',
1376 1381 default=False,
1377 1382 experimental=True,
1378 1383 )
1379 1384 coreconfigitem(
1380 1385 b'format',
1381 1386 b'use-dirstate-tracked-hint',
1382 1387 default=False,
1383 1388 experimental=True,
1384 1389 )
1385 1390 coreconfigitem(
1386 1391 b'format',
1387 1392 b'use-dirstate-tracked-hint.version',
1388 1393 default=1,
1389 1394 experimental=True,
1390 1395 )
1391 1396 coreconfigitem(
1392 1397 b'format',
1393 1398 b'use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories',
1394 1399 default=False,
1395 1400 experimental=True,
1396 1401 )
1397 1402 coreconfigitem(
1398 1403 b'format',
1399 1404 b'use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories:quiet',
1400 1405 default=False,
1401 1406 experimental=True,
1402 1407 )
1403 1408 coreconfigitem(
1404 1409 b'format',
1405 1410 b'dotencode',
1406 1411 default=True,
1407 1412 )
1408 1413 coreconfigitem(
1409 1414 b'format',
1410 1415 b'generaldelta',
1411 1416 default=False,
1412 1417 experimental=True,
1413 1418 )
1414 1419 coreconfigitem(
1415 1420 b'format',
1416 1421 b'manifestcachesize',
1417 1422 default=None,
1418 1423 experimental=True,
1419 1424 )
1420 1425 coreconfigitem(
1421 1426 b'format',
1422 1427 b'maxchainlen',
1423 1428 default=dynamicdefault,
1424 1429 experimental=True,
1425 1430 )
1426 1431 coreconfigitem(
1427 1432 b'format',
1428 1433 b'obsstore-version',
1429 1434 default=None,
1430 1435 )
1431 1436 coreconfigitem(
1432 1437 b'format',
1433 1438 b'sparse-revlog',
1434 1439 default=True,
1435 1440 )
1436 1441 coreconfigitem(
1437 1442 b'format',
1438 1443 b'revlog-compression',
1439 1444 default=lambda: [b'zstd', b'zlib'],
1440 1445 alias=[(b'experimental', b'format.compression')],
1441 1446 )
1442 1447 # Experimental TODOs:
1443 1448 #
1444 1449 # * Same as for revlogv2 (but for the reduction of the number of files)
1445 1450 # * Actually computing the rank of changesets
1446 1451 # * Improvement to investigate
1447 1452 # - storing .hgtags fnode
1448 1453 # - storing branch related identifier
1449 1454
1450 1455 coreconfigitem(
1451 1456 b'format',
1452 1457 b'exp-use-changelog-v2',
1453 1458 default=None,
1454 1459 experimental=True,
1455 1460 )
1456 1461 coreconfigitem(
1457 1462 b'format',
1458 1463 b'usefncache',
1459 1464 default=True,
1460 1465 )
1461 1466 coreconfigitem(
1462 1467 b'format',
1463 1468 b'usegeneraldelta',
1464 1469 default=True,
1465 1470 )
1466 1471 coreconfigitem(
1467 1472 b'format',
1468 1473 b'usestore',
1469 1474 default=True,
1470 1475 )
1471 1476
1472 1477
1473 1478 def _persistent_nodemap_default():
1474 1479 """compute `use-persistent-nodemap` default value
1475 1480
1476 1481 The feature is disabled unless a fast implementation is available.
1477 1482 """
1478 1483 from . import policy
1479 1484
1480 1485 return policy.importrust('revlog') is not None
1481 1486
1482 1487
1483 1488 coreconfigitem(
1484 1489 b'format',
1485 1490 b'use-persistent-nodemap',
1486 1491 default=_persistent_nodemap_default,
1487 1492 )
1488 1493 coreconfigitem(
1489 1494 b'format',
1490 1495 b'exp-use-copies-side-data-changeset',
1491 1496 default=False,
1492 1497 experimental=True,
1493 1498 )
1494 1499 coreconfigitem(
1495 1500 b'format',
1496 1501 b'use-share-safe',
1497 1502 default=True,
1498 1503 )
1499 1504 coreconfigitem(
1500 1505 b'format',
1501 1506 b'use-share-safe.automatic-upgrade-of-mismatching-repositories',
1502 1507 default=False,
1503 1508 experimental=True,
1504 1509 )
1505 1510 coreconfigitem(
1506 1511 b'format',
1507 1512 b'use-share-safe.automatic-upgrade-of-mismatching-repositories:quiet',
1508 1513 default=False,
1509 1514 experimental=True,
1510 1515 )
1511 1516
1512 1517 # Moving this on by default means we are confident about the scaling of phases.
1513 1518 # This is not garanteed to be the case at the time this message is written.
1514 1519 coreconfigitem(
1515 1520 b'format',
1516 1521 b'use-internal-phase',
1517 1522 default=False,
1518 1523 experimental=True,
1519 1524 )
1520 1525 # The interaction between the archived phase and obsolescence markers needs to
1521 1526 # be sorted out before wider usage of this are to be considered.
1522 1527 #
1523 1528 # At the time this message is written, behavior when archiving obsolete
1524 1529 # changeset differ significantly from stripping. As part of stripping, we also
1525 1530 # remove the obsolescence marker associated to the stripped changesets,
1526 1531 # revealing the precedecessors changesets when applicable. When archiving, we
1527 1532 # don't touch the obsolescence markers, keeping everything hidden. This can
1528 1533 # result in quite confusing situation for people combining exchanging draft
1529 1534 # with the archived phases. As some markers needed by others may be skipped
1530 1535 # during exchange.
1531 1536 coreconfigitem(
1532 1537 b'format',
1533 1538 b'exp-archived-phase',
1534 1539 default=False,
1535 1540 experimental=True,
1536 1541 )
1537 1542 coreconfigitem(
1538 1543 b'shelve',
1539 1544 b'store',
1540 1545 default=b'internal',
1541 1546 experimental=True,
1542 1547 )
1543 1548 coreconfigitem(
1544 1549 b'fsmonitor',
1545 1550 b'warn_when_unused',
1546 1551 default=True,
1547 1552 )
1548 1553 coreconfigitem(
1549 1554 b'fsmonitor',
1550 1555 b'warn_update_file_count',
1551 1556 default=50000,
1552 1557 )
1553 1558 coreconfigitem(
1554 1559 b'fsmonitor',
1555 1560 b'warn_update_file_count_rust',
1556 1561 default=400000,
1557 1562 )
1558 1563 coreconfigitem(
1559 1564 b'help',
1560 1565 br'hidden-command\..*',
1561 1566 default=False,
1562 1567 generic=True,
1563 1568 )
1564 1569 coreconfigitem(
1565 1570 b'help',
1566 1571 br'hidden-topic\..*',
1567 1572 default=False,
1568 1573 generic=True,
1569 1574 )
1570 1575 coreconfigitem(
1571 1576 b'hooks',
1572 1577 b'[^:]*',
1573 1578 default=dynamicdefault,
1574 1579 generic=True,
1575 1580 )
1576 1581 coreconfigitem(
1577 1582 b'hooks',
1578 1583 b'.*:run-with-plain',
1579 1584 default=True,
1580 1585 generic=True,
1581 1586 )
1582 1587 coreconfigitem(
1583 1588 b'hgweb-paths',
1584 1589 b'.*',
1585 1590 default=list,
1586 1591 generic=True,
1587 1592 )
1588 1593 coreconfigitem(
1589 1594 b'hostfingerprints',
1590 1595 b'.*',
1591 1596 default=list,
1592 1597 generic=True,
1593 1598 )
1594 1599 coreconfigitem(
1595 1600 b'hostsecurity',
1596 1601 b'ciphers',
1597 1602 default=None,
1598 1603 )
1599 1604 coreconfigitem(
1600 1605 b'hostsecurity',
1601 1606 b'minimumprotocol',
1602 1607 default=dynamicdefault,
1603 1608 )
1604 1609 coreconfigitem(
1605 1610 b'hostsecurity',
1606 1611 b'.*:minimumprotocol$',
1607 1612 default=dynamicdefault,
1608 1613 generic=True,
1609 1614 )
1610 1615 coreconfigitem(
1611 1616 b'hostsecurity',
1612 1617 b'.*:ciphers$',
1613 1618 default=dynamicdefault,
1614 1619 generic=True,
1615 1620 )
1616 1621 coreconfigitem(
1617 1622 b'hostsecurity',
1618 1623 b'.*:fingerprints$',
1619 1624 default=list,
1620 1625 generic=True,
1621 1626 )
1622 1627 coreconfigitem(
1623 1628 b'hostsecurity',
1624 1629 b'.*:verifycertsfile$',
1625 1630 default=None,
1626 1631 generic=True,
1627 1632 )
1628 1633
1629 1634 coreconfigitem(
1630 1635 b'http_proxy',
1631 1636 b'always',
1632 1637 default=False,
1633 1638 )
1634 1639 coreconfigitem(
1635 1640 b'http_proxy',
1636 1641 b'host',
1637 1642 default=None,
1638 1643 )
1639 1644 coreconfigitem(
1640 1645 b'http_proxy',
1641 1646 b'no',
1642 1647 default=list,
1643 1648 )
1644 1649 coreconfigitem(
1645 1650 b'http_proxy',
1646 1651 b'passwd',
1647 1652 default=None,
1648 1653 )
1649 1654 coreconfigitem(
1650 1655 b'http_proxy',
1651 1656 b'user',
1652 1657 default=None,
1653 1658 )
1654 1659
1655 1660 coreconfigitem(
1656 1661 b'http',
1657 1662 b'timeout',
1658 1663 default=None,
1659 1664 )
1660 1665
1661 1666 coreconfigitem(
1662 1667 b'logtoprocess',
1663 1668 b'commandexception',
1664 1669 default=None,
1665 1670 )
1666 1671 coreconfigitem(
1667 1672 b'logtoprocess',
1668 1673 b'commandfinish',
1669 1674 default=None,
1670 1675 )
1671 1676 coreconfigitem(
1672 1677 b'logtoprocess',
1673 1678 b'command',
1674 1679 default=None,
1675 1680 )
1676 1681 coreconfigitem(
1677 1682 b'logtoprocess',
1678 1683 b'develwarn',
1679 1684 default=None,
1680 1685 )
1681 1686 coreconfigitem(
1682 1687 b'logtoprocess',
1683 1688 b'uiblocked',
1684 1689 default=None,
1685 1690 )
1686 1691 coreconfigitem(
1687 1692 b'merge',
1688 1693 b'checkunknown',
1689 1694 default=b'abort',
1690 1695 )
1691 1696 coreconfigitem(
1692 1697 b'merge',
1693 1698 b'checkignored',
1694 1699 default=b'abort',
1695 1700 )
1696 1701 coreconfigitem(
1697 1702 b'experimental',
1698 1703 b'merge.checkpathconflicts',
1699 1704 default=False,
1700 1705 )
1701 1706 coreconfigitem(
1702 1707 b'merge',
1703 1708 b'followcopies',
1704 1709 default=True,
1705 1710 )
1706 1711 coreconfigitem(
1707 1712 b'merge',
1708 1713 b'on-failure',
1709 1714 default=b'continue',
1710 1715 )
1711 1716 coreconfigitem(
1712 1717 b'merge',
1713 1718 b'preferancestor',
1714 1719 default=lambda: [b'*'],
1715 1720 experimental=True,
1716 1721 )
1717 1722 coreconfigitem(
1718 1723 b'merge',
1719 1724 b'strict-capability-check',
1720 1725 default=False,
1721 1726 )
1722 1727 coreconfigitem(
1723 1728 b'merge',
1724 1729 b'disable-partial-tools',
1725 1730 default=False,
1726 1731 experimental=True,
1727 1732 )
1728 1733 coreconfigitem(
1729 1734 b'partial-merge-tools',
1730 1735 b'.*',
1731 1736 default=None,
1732 1737 generic=True,
1733 1738 experimental=True,
1734 1739 )
1735 1740 coreconfigitem(
1736 1741 b'partial-merge-tools',
1737 1742 br'.*\.patterns',
1738 1743 default=dynamicdefault,
1739 1744 generic=True,
1740 1745 priority=-1,
1741 1746 experimental=True,
1742 1747 )
1743 1748 coreconfigitem(
1744 1749 b'partial-merge-tools',
1745 1750 br'.*\.executable$',
1746 1751 default=dynamicdefault,
1747 1752 generic=True,
1748 1753 priority=-1,
1749 1754 experimental=True,
1750 1755 )
1751 1756 coreconfigitem(
1752 1757 b'partial-merge-tools',
1753 1758 br'.*\.order',
1754 1759 default=0,
1755 1760 generic=True,
1756 1761 priority=-1,
1757 1762 experimental=True,
1758 1763 )
1759 1764 coreconfigitem(
1760 1765 b'partial-merge-tools',
1761 1766 br'.*\.args',
1762 1767 default=b"$local $base $other",
1763 1768 generic=True,
1764 1769 priority=-1,
1765 1770 experimental=True,
1766 1771 )
1767 1772 coreconfigitem(
1768 1773 b'partial-merge-tools',
1769 1774 br'.*\.disable',
1770 1775 default=False,
1771 1776 generic=True,
1772 1777 priority=-1,
1773 1778 experimental=True,
1774 1779 )
1775 1780 coreconfigitem(
1776 1781 b'merge-tools',
1777 1782 b'.*',
1778 1783 default=None,
1779 1784 generic=True,
1780 1785 )
1781 1786 coreconfigitem(
1782 1787 b'merge-tools',
1783 1788 br'.*\.args$',
1784 1789 default=b"$local $base $other",
1785 1790 generic=True,
1786 1791 priority=-1,
1787 1792 )
1788 1793 coreconfigitem(
1789 1794 b'merge-tools',
1790 1795 br'.*\.binary$',
1791 1796 default=False,
1792 1797 generic=True,
1793 1798 priority=-1,
1794 1799 )
1795 1800 coreconfigitem(
1796 1801 b'merge-tools',
1797 1802 br'.*\.check$',
1798 1803 default=list,
1799 1804 generic=True,
1800 1805 priority=-1,
1801 1806 )
1802 1807 coreconfigitem(
1803 1808 b'merge-tools',
1804 1809 br'.*\.checkchanged$',
1805 1810 default=False,
1806 1811 generic=True,
1807 1812 priority=-1,
1808 1813 )
1809 1814 coreconfigitem(
1810 1815 b'merge-tools',
1811 1816 br'.*\.executable$',
1812 1817 default=dynamicdefault,
1813 1818 generic=True,
1814 1819 priority=-1,
1815 1820 )
1816 1821 coreconfigitem(
1817 1822 b'merge-tools',
1818 1823 br'.*\.fixeol$',
1819 1824 default=False,
1820 1825 generic=True,
1821 1826 priority=-1,
1822 1827 )
1823 1828 coreconfigitem(
1824 1829 b'merge-tools',
1825 1830 br'.*\.gui$',
1826 1831 default=False,
1827 1832 generic=True,
1828 1833 priority=-1,
1829 1834 )
1830 1835 coreconfigitem(
1831 1836 b'merge-tools',
1832 1837 br'.*\.mergemarkers$',
1833 1838 default=b'basic',
1834 1839 generic=True,
1835 1840 priority=-1,
1836 1841 )
1837 1842 coreconfigitem(
1838 1843 b'merge-tools',
1839 1844 br'.*\.mergemarkertemplate$',
1840 1845 default=dynamicdefault, # take from command-templates.mergemarker
1841 1846 generic=True,
1842 1847 priority=-1,
1843 1848 )
1844 1849 coreconfigitem(
1845 1850 b'merge-tools',
1846 1851 br'.*\.priority$',
1847 1852 default=0,
1848 1853 generic=True,
1849 1854 priority=-1,
1850 1855 )
1851 1856 coreconfigitem(
1852 1857 b'merge-tools',
1853 1858 br'.*\.premerge$',
1854 1859 default=dynamicdefault,
1855 1860 generic=True,
1856 1861 priority=-1,
1857 1862 )
1858 1863 coreconfigitem(
1859 1864 b'merge-tools',
1860 1865 br'.*\.regappend$',
1861 1866 default=b"",
1862 1867 generic=True,
1863 1868 priority=-1,
1864 1869 )
1865 1870 coreconfigitem(
1866 1871 b'merge-tools',
1867 1872 br'.*\.symlink$',
1868 1873 default=False,
1869 1874 generic=True,
1870 1875 priority=-1,
1871 1876 )
1872 1877 coreconfigitem(
1873 1878 b'pager',
1874 1879 b'attend-.*',
1875 1880 default=dynamicdefault,
1876 1881 generic=True,
1877 1882 )
1878 1883 coreconfigitem(
1879 1884 b'pager',
1880 1885 b'ignore',
1881 1886 default=list,
1882 1887 )
1883 1888 coreconfigitem(
1884 1889 b'pager',
1885 1890 b'pager',
1886 1891 default=dynamicdefault,
1887 1892 )
1888 1893 coreconfigitem(
1889 1894 b'patch',
1890 1895 b'eol',
1891 1896 default=b'strict',
1892 1897 )
1893 1898 coreconfigitem(
1894 1899 b'patch',
1895 1900 b'fuzz',
1896 1901 default=2,
1897 1902 )
1898 1903 coreconfigitem(
1899 1904 b'paths',
1900 1905 b'default',
1901 1906 default=None,
1902 1907 )
1903 1908 coreconfigitem(
1904 1909 b'paths',
1905 1910 b'default-push',
1906 1911 default=None,
1907 1912 )
1908 1913 coreconfigitem(
1909 1914 b'paths',
1910 1915 b'[^:]*',
1911 1916 default=None,
1912 1917 generic=True,
1913 1918 )
1914 1919 coreconfigitem(
1915 1920 b'paths',
1916 1921 b'.*:bookmarks.mode',
1917 1922 default='default',
1918 1923 generic=True,
1919 1924 )
1920 1925 coreconfigitem(
1921 1926 b'paths',
1922 1927 b'.*:multi-urls',
1923 1928 default=False,
1924 1929 generic=True,
1925 1930 )
1926 1931 coreconfigitem(
1927 1932 b'paths',
1928 1933 b'.*:pushrev',
1929 1934 default=None,
1930 1935 generic=True,
1931 1936 )
1932 1937 coreconfigitem(
1933 1938 b'paths',
1934 1939 b'.*:pushurl',
1935 1940 default=None,
1936 1941 generic=True,
1937 1942 )
1938 1943 coreconfigitem(
1939 1944 b'paths',
1940 1945 b'.*:pulled-delta-reuse-policy',
1941 1946 default=None,
1942 1947 generic=True,
1943 1948 )
1944 1949 coreconfigitem(
1945 1950 b'phases',
1946 1951 b'checksubrepos',
1947 1952 default=b'follow',
1948 1953 )
1949 1954 coreconfigitem(
1950 1955 b'phases',
1951 1956 b'new-commit',
1952 1957 default=b'draft',
1953 1958 )
1954 1959 coreconfigitem(
1955 1960 b'phases',
1956 1961 b'publish',
1957 1962 default=True,
1958 1963 )
1959 1964 coreconfigitem(
1960 1965 b'profiling',
1961 1966 b'enabled',
1962 1967 default=False,
1963 1968 )
1964 1969 coreconfigitem(
1965 1970 b'profiling',
1966 1971 b'format',
1967 1972 default=b'text',
1968 1973 )
1969 1974 coreconfigitem(
1970 1975 b'profiling',
1971 1976 b'freq',
1972 1977 default=1000,
1973 1978 )
1974 1979 coreconfigitem(
1975 1980 b'profiling',
1976 1981 b'limit',
1977 1982 default=30,
1978 1983 )
1979 1984 coreconfigitem(
1980 1985 b'profiling',
1981 1986 b'nested',
1982 1987 default=0,
1983 1988 )
1984 1989 coreconfigitem(
1985 1990 b'profiling',
1986 1991 b'output',
1987 1992 default=None,
1988 1993 )
1989 1994 coreconfigitem(
1990 1995 b'profiling',
1991 1996 b'showmax',
1992 1997 default=0.999,
1993 1998 )
1994 1999 coreconfigitem(
1995 2000 b'profiling',
1996 2001 b'showmin',
1997 2002 default=dynamicdefault,
1998 2003 )
1999 2004 coreconfigitem(
2000 2005 b'profiling',
2001 2006 b'showtime',
2002 2007 default=True,
2003 2008 )
2004 2009 coreconfigitem(
2005 2010 b'profiling',
2006 2011 b'sort',
2007 2012 default=b'inlinetime',
2008 2013 )
2009 2014 coreconfigitem(
2010 2015 b'profiling',
2011 2016 b'statformat',
2012 2017 default=b'hotpath',
2013 2018 )
2014 2019 coreconfigitem(
2015 2020 b'profiling',
2016 2021 b'time-track',
2017 2022 default=dynamicdefault,
2018 2023 )
2019 2024 coreconfigitem(
2020 2025 b'profiling',
2021 2026 b'type',
2022 2027 default=b'stat',
2023 2028 )
2024 2029 coreconfigitem(
2025 2030 b'progress',
2026 2031 b'assume-tty',
2027 2032 default=False,
2028 2033 )
2029 2034 coreconfigitem(
2030 2035 b'progress',
2031 2036 b'changedelay',
2032 2037 default=1,
2033 2038 )
2034 2039 coreconfigitem(
2035 2040 b'progress',
2036 2041 b'clear-complete',
2037 2042 default=True,
2038 2043 )
2039 2044 coreconfigitem(
2040 2045 b'progress',
2041 2046 b'debug',
2042 2047 default=False,
2043 2048 )
2044 2049 coreconfigitem(
2045 2050 b'progress',
2046 2051 b'delay',
2047 2052 default=3,
2048 2053 )
2049 2054 coreconfigitem(
2050 2055 b'progress',
2051 2056 b'disable',
2052 2057 default=False,
2053 2058 )
2054 2059 coreconfigitem(
2055 2060 b'progress',
2056 2061 b'estimateinterval',
2057 2062 default=60.0,
2058 2063 )
2059 2064 coreconfigitem(
2060 2065 b'progress',
2061 2066 b'format',
2062 2067 default=lambda: [b'topic', b'bar', b'number', b'estimate'],
2063 2068 )
2064 2069 coreconfigitem(
2065 2070 b'progress',
2066 2071 b'refresh',
2067 2072 default=0.1,
2068 2073 )
2069 2074 coreconfigitem(
2070 2075 b'progress',
2071 2076 b'width',
2072 2077 default=dynamicdefault,
2073 2078 )
2074 2079 coreconfigitem(
2075 2080 b'pull',
2076 2081 b'confirm',
2077 2082 default=False,
2078 2083 )
2079 2084 coreconfigitem(
2080 2085 b'push',
2081 2086 b'pushvars.server',
2082 2087 default=False,
2083 2088 )
2084 2089 coreconfigitem(
2085 2090 b'rewrite',
2086 2091 b'backup-bundle',
2087 2092 default=True,
2088 2093 alias=[(b'ui', b'history-editing-backup')],
2089 2094 )
2090 2095 coreconfigitem(
2091 2096 b'rewrite',
2092 2097 b'update-timestamp',
2093 2098 default=False,
2094 2099 )
2095 2100 coreconfigitem(
2096 2101 b'rewrite',
2097 2102 b'empty-successor',
2098 2103 default=b'skip',
2099 2104 experimental=True,
2100 2105 )
2101 2106 # experimental as long as format.use-dirstate-v2 is.
2102 2107 coreconfigitem(
2103 2108 b'storage',
2104 2109 b'dirstate-v2.slow-path',
2105 2110 default=b"abort",
2106 2111 experimental=True,
2107 2112 )
2108 2113 coreconfigitem(
2109 2114 b'storage',
2110 2115 b'new-repo-backend',
2111 2116 default=b'revlogv1',
2112 2117 experimental=True,
2113 2118 )
2114 2119 coreconfigitem(
2115 2120 b'storage',
2116 2121 b'revlog.optimize-delta-parent-choice',
2117 2122 default=True,
2118 2123 alias=[(b'format', b'aggressivemergedeltas')],
2119 2124 )
2120 2125 coreconfigitem(
2121 2126 b'storage',
2122 2127 b'revlog.delta-parent-search.candidate-group-chunk-size',
2123 2128 default=20,
2124 2129 )
2125 2130 coreconfigitem(
2126 2131 b'storage',
2127 2132 b'revlog.issue6528.fix-incoming',
2128 2133 default=True,
2129 2134 )
2130 2135 # experimental as long as rust is experimental (or a C version is implemented)
2131 2136 coreconfigitem(
2132 2137 b'storage',
2133 2138 b'revlog.persistent-nodemap.mmap',
2134 2139 default=True,
2135 2140 )
2136 2141 # experimental as long as format.use-persistent-nodemap is.
2137 2142 coreconfigitem(
2138 2143 b'storage',
2139 2144 b'revlog.persistent-nodemap.slow-path',
2140 2145 default=b"abort",
2141 2146 )
2142 2147
2143 2148 coreconfigitem(
2144 2149 b'storage',
2145 2150 b'revlog.reuse-external-delta',
2146 2151 default=True,
2147 2152 )
2148 2153 # This option is True unless `format.generaldelta` is set.
2149 2154 coreconfigitem(
2150 2155 b'storage',
2151 2156 b'revlog.reuse-external-delta-parent',
2152 2157 default=None,
2153 2158 )
2154 2159 coreconfigitem(
2155 2160 b'storage',
2156 2161 b'revlog.zlib.level',
2157 2162 default=None,
2158 2163 )
2159 2164 coreconfigitem(
2160 2165 b'storage',
2161 2166 b'revlog.zstd.level',
2162 2167 default=None,
2163 2168 )
2164 2169 coreconfigitem(
2165 2170 b'server',
2166 2171 b'bookmarks-pushkey-compat',
2167 2172 default=True,
2168 2173 )
2169 2174 coreconfigitem(
2170 2175 b'server',
2171 2176 b'bundle1',
2172 2177 default=True,
2173 2178 )
2174 2179 coreconfigitem(
2175 2180 b'server',
2176 2181 b'bundle1gd',
2177 2182 default=None,
2178 2183 )
2179 2184 coreconfigitem(
2180 2185 b'server',
2181 2186 b'bundle1.pull',
2182 2187 default=None,
2183 2188 )
2184 2189 coreconfigitem(
2185 2190 b'server',
2186 2191 b'bundle1gd.pull',
2187 2192 default=None,
2188 2193 )
2189 2194 coreconfigitem(
2190 2195 b'server',
2191 2196 b'bundle1.push',
2192 2197 default=None,
2193 2198 )
2194 2199 coreconfigitem(
2195 2200 b'server',
2196 2201 b'bundle1gd.push',
2197 2202 default=None,
2198 2203 )
2199 2204 coreconfigitem(
2200 2205 b'server',
2201 2206 b'bundle2.stream',
2202 2207 default=True,
2203 2208 alias=[(b'experimental', b'bundle2.stream')],
2204 2209 )
2205 2210 coreconfigitem(
2206 2211 b'server',
2207 2212 b'compressionengines',
2208 2213 default=list,
2209 2214 )
2210 2215 coreconfigitem(
2211 2216 b'server',
2212 2217 b'concurrent-push-mode',
2213 2218 default=b'check-related',
2214 2219 )
2215 2220 coreconfigitem(
2216 2221 b'server',
2217 2222 b'disablefullbundle',
2218 2223 default=False,
2219 2224 )
2220 2225 coreconfigitem(
2221 2226 b'server',
2222 2227 b'maxhttpheaderlen',
2223 2228 default=1024,
2224 2229 )
2225 2230 coreconfigitem(
2226 2231 b'server',
2227 2232 b'pullbundle',
2228 2233 default=True,
2229 2234 )
2230 2235 coreconfigitem(
2231 2236 b'server',
2232 2237 b'preferuncompressed',
2233 2238 default=False,
2234 2239 )
2235 2240 coreconfigitem(
2236 2241 b'server',
2237 2242 b'streamunbundle',
2238 2243 default=False,
2239 2244 )
2240 2245 coreconfigitem(
2241 2246 b'server',
2242 2247 b'uncompressed',
2243 2248 default=True,
2244 2249 )
2245 2250 coreconfigitem(
2246 2251 b'server',
2247 2252 b'uncompressedallowsecret',
2248 2253 default=False,
2249 2254 )
2250 2255 coreconfigitem(
2251 2256 b'server',
2252 2257 b'view',
2253 2258 default=b'served',
2254 2259 )
2255 2260 coreconfigitem(
2256 2261 b'server',
2257 2262 b'validate',
2258 2263 default=False,
2259 2264 )
2260 2265 coreconfigitem(
2261 2266 b'server',
2262 2267 b'zliblevel',
2263 2268 default=-1,
2264 2269 )
2265 2270 coreconfigitem(
2266 2271 b'server',
2267 2272 b'zstdlevel',
2268 2273 default=3,
2269 2274 )
2270 2275 coreconfigitem(
2271 2276 b'share',
2272 2277 b'pool',
2273 2278 default=None,
2274 2279 )
2275 2280 coreconfigitem(
2276 2281 b'share',
2277 2282 b'poolnaming',
2278 2283 default=b'identity',
2279 2284 )
2280 2285 coreconfigitem(
2281 2286 b'share',
2282 2287 b'safe-mismatch.source-not-safe',
2283 2288 default=b'abort',
2284 2289 )
2285 2290 coreconfigitem(
2286 2291 b'share',
2287 2292 b'safe-mismatch.source-safe',
2288 2293 default=b'abort',
2289 2294 )
2290 2295 coreconfigitem(
2291 2296 b'share',
2292 2297 b'safe-mismatch.source-not-safe.warn',
2293 2298 default=True,
2294 2299 )
2295 2300 coreconfigitem(
2296 2301 b'share',
2297 2302 b'safe-mismatch.source-safe.warn',
2298 2303 default=True,
2299 2304 )
2300 2305 coreconfigitem(
2301 2306 b'share',
2302 2307 b'safe-mismatch.source-not-safe:verbose-upgrade',
2303 2308 default=True,
2304 2309 )
2305 2310 coreconfigitem(
2306 2311 b'share',
2307 2312 b'safe-mismatch.source-safe:verbose-upgrade',
2308 2313 default=True,
2309 2314 )
2310 2315 coreconfigitem(
2311 2316 b'shelve',
2312 2317 b'maxbackups',
2313 2318 default=10,
2314 2319 )
2315 2320 coreconfigitem(
2316 2321 b'smtp',
2317 2322 b'host',
2318 2323 default=None,
2319 2324 )
2320 2325 coreconfigitem(
2321 2326 b'smtp',
2322 2327 b'local_hostname',
2323 2328 default=None,
2324 2329 )
2325 2330 coreconfigitem(
2326 2331 b'smtp',
2327 2332 b'password',
2328 2333 default=None,
2329 2334 )
2330 2335 coreconfigitem(
2331 2336 b'smtp',
2332 2337 b'port',
2333 2338 default=dynamicdefault,
2334 2339 )
2335 2340 coreconfigitem(
2336 2341 b'smtp',
2337 2342 b'tls',
2338 2343 default=b'none',
2339 2344 )
2340 2345 coreconfigitem(
2341 2346 b'smtp',
2342 2347 b'username',
2343 2348 default=None,
2344 2349 )
2345 2350 coreconfigitem(
2346 2351 b'sparse',
2347 2352 b'missingwarning',
2348 2353 default=True,
2349 2354 experimental=True,
2350 2355 )
2351 2356 coreconfigitem(
2352 2357 b'subrepos',
2353 2358 b'allowed',
2354 2359 default=dynamicdefault, # to make backporting simpler
2355 2360 )
2356 2361 coreconfigitem(
2357 2362 b'subrepos',
2358 2363 b'hg:allowed',
2359 2364 default=dynamicdefault,
2360 2365 )
2361 2366 coreconfigitem(
2362 2367 b'subrepos',
2363 2368 b'git:allowed',
2364 2369 default=dynamicdefault,
2365 2370 )
2366 2371 coreconfigitem(
2367 2372 b'subrepos',
2368 2373 b'svn:allowed',
2369 2374 default=dynamicdefault,
2370 2375 )
2371 2376 coreconfigitem(
2372 2377 b'templates',
2373 2378 b'.*',
2374 2379 default=None,
2375 2380 generic=True,
2376 2381 )
2377 2382 coreconfigitem(
2378 2383 b'templateconfig',
2379 2384 b'.*',
2380 2385 default=dynamicdefault,
2381 2386 generic=True,
2382 2387 )
2383 2388 coreconfigitem(
2384 2389 b'trusted',
2385 2390 b'groups',
2386 2391 default=list,
2387 2392 )
2388 2393 coreconfigitem(
2389 2394 b'trusted',
2390 2395 b'users',
2391 2396 default=list,
2392 2397 )
2393 2398 coreconfigitem(
2394 2399 b'ui',
2395 2400 b'_usedassubrepo',
2396 2401 default=False,
2397 2402 )
2398 2403 coreconfigitem(
2399 2404 b'ui',
2400 2405 b'allowemptycommit',
2401 2406 default=False,
2402 2407 )
2403 2408 coreconfigitem(
2404 2409 b'ui',
2405 2410 b'archivemeta',
2406 2411 default=True,
2407 2412 )
2408 2413 coreconfigitem(
2409 2414 b'ui',
2410 2415 b'askusername',
2411 2416 default=False,
2412 2417 )
2413 2418 coreconfigitem(
2414 2419 b'ui',
2415 2420 b'available-memory',
2416 2421 default=None,
2417 2422 )
2418 2423
2419 2424 coreconfigitem(
2420 2425 b'ui',
2421 2426 b'clonebundlefallback',
2422 2427 default=False,
2423 2428 )
2424 2429 coreconfigitem(
2425 2430 b'ui',
2426 2431 b'clonebundleprefers',
2427 2432 default=list,
2428 2433 )
2429 2434 coreconfigitem(
2430 2435 b'ui',
2431 2436 b'clonebundles',
2432 2437 default=True,
2433 2438 )
2434 2439 coreconfigitem(
2435 2440 b'ui',
2436 2441 b'color',
2437 2442 default=b'auto',
2438 2443 )
2439 2444 coreconfigitem(
2440 2445 b'ui',
2441 2446 b'commitsubrepos',
2442 2447 default=False,
2443 2448 )
2444 2449 coreconfigitem(
2445 2450 b'ui',
2446 2451 b'debug',
2447 2452 default=False,
2448 2453 )
2449 2454 coreconfigitem(
2450 2455 b'ui',
2451 2456 b'debugger',
2452 2457 default=None,
2453 2458 )
2454 2459 coreconfigitem(
2455 2460 b'ui',
2456 2461 b'editor',
2457 2462 default=dynamicdefault,
2458 2463 )
2459 2464 coreconfigitem(
2460 2465 b'ui',
2461 2466 b'detailed-exit-code',
2462 2467 default=False,
2463 2468 experimental=True,
2464 2469 )
2465 2470 coreconfigitem(
2466 2471 b'ui',
2467 2472 b'fallbackencoding',
2468 2473 default=None,
2469 2474 )
2470 2475 coreconfigitem(
2471 2476 b'ui',
2472 2477 b'forcecwd',
2473 2478 default=None,
2474 2479 )
2475 2480 coreconfigitem(
2476 2481 b'ui',
2477 2482 b'forcemerge',
2478 2483 default=None,
2479 2484 )
2480 2485 coreconfigitem(
2481 2486 b'ui',
2482 2487 b'formatdebug',
2483 2488 default=False,
2484 2489 )
2485 2490 coreconfigitem(
2486 2491 b'ui',
2487 2492 b'formatjson',
2488 2493 default=False,
2489 2494 )
2490 2495 coreconfigitem(
2491 2496 b'ui',
2492 2497 b'formatted',
2493 2498 default=None,
2494 2499 )
2495 2500 coreconfigitem(
2496 2501 b'ui',
2497 2502 b'interactive',
2498 2503 default=None,
2499 2504 )
2500 2505 coreconfigitem(
2501 2506 b'ui',
2502 2507 b'interface',
2503 2508 default=None,
2504 2509 )
2505 2510 coreconfigitem(
2506 2511 b'ui',
2507 2512 b'interface.chunkselector',
2508 2513 default=None,
2509 2514 )
2510 2515 coreconfigitem(
2511 2516 b'ui',
2512 2517 b'large-file-limit',
2513 2518 default=10 * (2 ** 20),
2514 2519 )
2515 2520 coreconfigitem(
2516 2521 b'ui',
2517 2522 b'logblockedtimes',
2518 2523 default=False,
2519 2524 )
2520 2525 coreconfigitem(
2521 2526 b'ui',
2522 2527 b'merge',
2523 2528 default=None,
2524 2529 )
2525 2530 coreconfigitem(
2526 2531 b'ui',
2527 2532 b'mergemarkers',
2528 2533 default=b'basic',
2529 2534 )
2530 2535 coreconfigitem(
2531 2536 b'ui',
2532 2537 b'message-output',
2533 2538 default=b'stdio',
2534 2539 )
2535 2540 coreconfigitem(
2536 2541 b'ui',
2537 2542 b'nontty',
2538 2543 default=False,
2539 2544 )
2540 2545 coreconfigitem(
2541 2546 b'ui',
2542 2547 b'origbackuppath',
2543 2548 default=None,
2544 2549 )
2545 2550 coreconfigitem(
2546 2551 b'ui',
2547 2552 b'paginate',
2548 2553 default=True,
2549 2554 )
2550 2555 coreconfigitem(
2551 2556 b'ui',
2552 2557 b'patch',
2553 2558 default=None,
2554 2559 )
2555 2560 coreconfigitem(
2556 2561 b'ui',
2557 2562 b'portablefilenames',
2558 2563 default=b'warn',
2559 2564 )
2560 2565 coreconfigitem(
2561 2566 b'ui',
2562 2567 b'promptecho',
2563 2568 default=False,
2564 2569 )
2565 2570 coreconfigitem(
2566 2571 b'ui',
2567 2572 b'quiet',
2568 2573 default=False,
2569 2574 )
2570 2575 coreconfigitem(
2571 2576 b'ui',
2572 2577 b'quietbookmarkmove',
2573 2578 default=False,
2574 2579 )
2575 2580 coreconfigitem(
2576 2581 b'ui',
2577 2582 b'relative-paths',
2578 2583 default=b'legacy',
2579 2584 )
2580 2585 coreconfigitem(
2581 2586 b'ui',
2582 2587 b'remotecmd',
2583 2588 default=b'hg',
2584 2589 )
2585 2590 coreconfigitem(
2586 2591 b'ui',
2587 2592 b'report_untrusted',
2588 2593 default=True,
2589 2594 )
2590 2595 coreconfigitem(
2591 2596 b'ui',
2592 2597 b'rollback',
2593 2598 default=True,
2594 2599 )
2595 2600 coreconfigitem(
2596 2601 b'ui',
2597 2602 b'signal-safe-lock',
2598 2603 default=True,
2599 2604 )
2600 2605 coreconfigitem(
2601 2606 b'ui',
2602 2607 b'slash',
2603 2608 default=False,
2604 2609 )
2605 2610 coreconfigitem(
2606 2611 b'ui',
2607 2612 b'ssh',
2608 2613 default=b'ssh',
2609 2614 )
2610 2615 coreconfigitem(
2611 2616 b'ui',
2612 2617 b'ssherrorhint',
2613 2618 default=None,
2614 2619 )
2615 2620 coreconfigitem(
2616 2621 b'ui',
2617 2622 b'statuscopies',
2618 2623 default=False,
2619 2624 )
2620 2625 coreconfigitem(
2621 2626 b'ui',
2622 2627 b'strict',
2623 2628 default=False,
2624 2629 )
2625 2630 coreconfigitem(
2626 2631 b'ui',
2627 2632 b'style',
2628 2633 default=b'',
2629 2634 )
2630 2635 coreconfigitem(
2631 2636 b'ui',
2632 2637 b'supportcontact',
2633 2638 default=None,
2634 2639 )
2635 2640 coreconfigitem(
2636 2641 b'ui',
2637 2642 b'textwidth',
2638 2643 default=78,
2639 2644 )
2640 2645 coreconfigitem(
2641 2646 b'ui',
2642 2647 b'timeout',
2643 2648 default=b'600',
2644 2649 )
2645 2650 coreconfigitem(
2646 2651 b'ui',
2647 2652 b'timeout.warn',
2648 2653 default=0,
2649 2654 )
2650 2655 coreconfigitem(
2651 2656 b'ui',
2652 2657 b'timestamp-output',
2653 2658 default=False,
2654 2659 )
2655 2660 coreconfigitem(
2656 2661 b'ui',
2657 2662 b'traceback',
2658 2663 default=False,
2659 2664 )
2660 2665 coreconfigitem(
2661 2666 b'ui',
2662 2667 b'tweakdefaults',
2663 2668 default=False,
2664 2669 )
2665 2670 coreconfigitem(b'ui', b'username', alias=[(b'ui', b'user')])
2666 2671 coreconfigitem(
2667 2672 b'ui',
2668 2673 b'verbose',
2669 2674 default=False,
2670 2675 )
2671 2676 coreconfigitem(
2672 2677 b'verify',
2673 2678 b'skipflags',
2674 2679 default=0,
2675 2680 )
2676 2681 coreconfigitem(
2677 2682 b'web',
2678 2683 b'allowbz2',
2679 2684 default=False,
2680 2685 )
2681 2686 coreconfigitem(
2682 2687 b'web',
2683 2688 b'allowgz',
2684 2689 default=False,
2685 2690 )
2686 2691 coreconfigitem(
2687 2692 b'web',
2688 2693 b'allow-pull',
2689 2694 alias=[(b'web', b'allowpull')],
2690 2695 default=True,
2691 2696 )
2692 2697 coreconfigitem(
2693 2698 b'web',
2694 2699 b'allow-push',
2695 2700 alias=[(b'web', b'allow_push')],
2696 2701 default=list,
2697 2702 )
2698 2703 coreconfigitem(
2699 2704 b'web',
2700 2705 b'allowzip',
2701 2706 default=False,
2702 2707 )
2703 2708 coreconfigitem(
2704 2709 b'web',
2705 2710 b'archivesubrepos',
2706 2711 default=False,
2707 2712 )
2708 2713 coreconfigitem(
2709 2714 b'web',
2710 2715 b'cache',
2711 2716 default=True,
2712 2717 )
2713 2718 coreconfigitem(
2714 2719 b'web',
2715 2720 b'comparisoncontext',
2716 2721 default=5,
2717 2722 )
2718 2723 coreconfigitem(
2719 2724 b'web',
2720 2725 b'contact',
2721 2726 default=None,
2722 2727 )
2723 2728 coreconfigitem(
2724 2729 b'web',
2725 2730 b'deny_push',
2726 2731 default=list,
2727 2732 )
2728 2733 coreconfigitem(
2729 2734 b'web',
2730 2735 b'guessmime',
2731 2736 default=False,
2732 2737 )
2733 2738 coreconfigitem(
2734 2739 b'web',
2735 2740 b'hidden',
2736 2741 default=False,
2737 2742 )
2738 2743 coreconfigitem(
2739 2744 b'web',
2740 2745 b'labels',
2741 2746 default=list,
2742 2747 )
2743 2748 coreconfigitem(
2744 2749 b'web',
2745 2750 b'logoimg',
2746 2751 default=b'hglogo.png',
2747 2752 )
2748 2753 coreconfigitem(
2749 2754 b'web',
2750 2755 b'logourl',
2751 2756 default=b'https://mercurial-scm.org/',
2752 2757 )
2753 2758 coreconfigitem(
2754 2759 b'web',
2755 2760 b'accesslog',
2756 2761 default=b'-',
2757 2762 )
2758 2763 coreconfigitem(
2759 2764 b'web',
2760 2765 b'address',
2761 2766 default=b'',
2762 2767 )
2763 2768 coreconfigitem(
2764 2769 b'web',
2765 2770 b'allow-archive',
2766 2771 alias=[(b'web', b'allow_archive')],
2767 2772 default=list,
2768 2773 )
2769 2774 coreconfigitem(
2770 2775 b'web',
2771 2776 b'allow_read',
2772 2777 default=list,
2773 2778 )
2774 2779 coreconfigitem(
2775 2780 b'web',
2776 2781 b'baseurl',
2777 2782 default=None,
2778 2783 )
2779 2784 coreconfigitem(
2780 2785 b'web',
2781 2786 b'cacerts',
2782 2787 default=None,
2783 2788 )
2784 2789 coreconfigitem(
2785 2790 b'web',
2786 2791 b'certificate',
2787 2792 default=None,
2788 2793 )
2789 2794 coreconfigitem(
2790 2795 b'web',
2791 2796 b'collapse',
2792 2797 default=False,
2793 2798 )
2794 2799 coreconfigitem(
2795 2800 b'web',
2796 2801 b'csp',
2797 2802 default=None,
2798 2803 )
2799 2804 coreconfigitem(
2800 2805 b'web',
2801 2806 b'deny_read',
2802 2807 default=list,
2803 2808 )
2804 2809 coreconfigitem(
2805 2810 b'web',
2806 2811 b'descend',
2807 2812 default=True,
2808 2813 )
2809 2814 coreconfigitem(
2810 2815 b'web',
2811 2816 b'description',
2812 2817 default=b"",
2813 2818 )
2814 2819 coreconfigitem(
2815 2820 b'web',
2816 2821 b'encoding',
2817 2822 default=lambda: encoding.encoding,
2818 2823 )
2819 2824 coreconfigitem(
2820 2825 b'web',
2821 2826 b'errorlog',
2822 2827 default=b'-',
2823 2828 )
2824 2829 coreconfigitem(
2825 2830 b'web',
2826 2831 b'ipv6',
2827 2832 default=False,
2828 2833 )
2829 2834 coreconfigitem(
2830 2835 b'web',
2831 2836 b'maxchanges',
2832 2837 default=10,
2833 2838 )
2834 2839 coreconfigitem(
2835 2840 b'web',
2836 2841 b'maxfiles',
2837 2842 default=10,
2838 2843 )
2839 2844 coreconfigitem(
2840 2845 b'web',
2841 2846 b'maxshortchanges',
2842 2847 default=60,
2843 2848 )
2844 2849 coreconfigitem(
2845 2850 b'web',
2846 2851 b'motd',
2847 2852 default=b'',
2848 2853 )
2849 2854 coreconfigitem(
2850 2855 b'web',
2851 2856 b'name',
2852 2857 default=dynamicdefault,
2853 2858 )
2854 2859 coreconfigitem(
2855 2860 b'web',
2856 2861 b'port',
2857 2862 default=8000,
2858 2863 )
2859 2864 coreconfigitem(
2860 2865 b'web',
2861 2866 b'prefix',
2862 2867 default=b'',
2863 2868 )
2864 2869 coreconfigitem(
2865 2870 b'web',
2866 2871 b'push_ssl',
2867 2872 default=True,
2868 2873 )
2869 2874 coreconfigitem(
2870 2875 b'web',
2871 2876 b'refreshinterval',
2872 2877 default=20,
2873 2878 )
2874 2879 coreconfigitem(
2875 2880 b'web',
2876 2881 b'server-header',
2877 2882 default=None,
2878 2883 )
2879 2884 coreconfigitem(
2880 2885 b'web',
2881 2886 b'static',
2882 2887 default=None,
2883 2888 )
2884 2889 coreconfigitem(
2885 2890 b'web',
2886 2891 b'staticurl',
2887 2892 default=None,
2888 2893 )
2889 2894 coreconfigitem(
2890 2895 b'web',
2891 2896 b'stripes',
2892 2897 default=1,
2893 2898 )
2894 2899 coreconfigitem(
2895 2900 b'web',
2896 2901 b'style',
2897 2902 default=b'paper',
2898 2903 )
2899 2904 coreconfigitem(
2900 2905 b'web',
2901 2906 b'templates',
2902 2907 default=None,
2903 2908 )
2904 2909 coreconfigitem(
2905 2910 b'web',
2906 2911 b'view',
2907 2912 default=b'served',
2908 2913 experimental=True,
2909 2914 )
2910 2915 coreconfigitem(
2911 2916 b'worker',
2912 2917 b'backgroundclose',
2913 2918 default=dynamicdefault,
2914 2919 )
2915 2920 # Windows defaults to a limit of 512 open files. A buffer of 128
2916 2921 # should give us enough headway.
2917 2922 coreconfigitem(
2918 2923 b'worker',
2919 2924 b'backgroundclosemaxqueue',
2920 2925 default=384,
2921 2926 )
2922 2927 coreconfigitem(
2923 2928 b'worker',
2924 2929 b'backgroundcloseminfilecount',
2925 2930 default=2048,
2926 2931 )
2927 2932 coreconfigitem(
2928 2933 b'worker',
2929 2934 b'backgroundclosethreadcount',
2930 2935 default=4,
2931 2936 )
2932 2937 coreconfigitem(
2933 2938 b'worker',
2934 2939 b'enabled',
2935 2940 default=True,
2936 2941 )
2937 2942 coreconfigitem(
2938 2943 b'worker',
2939 2944 b'numcpus',
2940 2945 default=None,
2941 2946 )
2942 2947
2943 2948 # Rebase related configuration moved to core because other extension are doing
2944 2949 # strange things. For example, shelve import the extensions to reuse some bit
2945 2950 # without formally loading it.
2946 2951 coreconfigitem(
2947 2952 b'commands',
2948 2953 b'rebase.requiredest',
2949 2954 default=False,
2950 2955 )
2951 2956 coreconfigitem(
2952 2957 b'experimental',
2953 2958 b'rebaseskipobsolete',
2954 2959 default=True,
2955 2960 )
2956 2961 coreconfigitem(
2957 2962 b'rebase',
2958 2963 b'singletransaction',
2959 2964 default=False,
2960 2965 )
2961 2966 coreconfigitem(
2962 2967 b'rebase',
2963 2968 b'experimental.inmemory',
2964 2969 default=False,
2965 2970 )
2966 2971
2967 2972 # This setting controls creation of a rebase_source extra field
2968 2973 # during rebase. When False, no such field is created. This is
2969 2974 # useful eg for incrementally converting changesets and then
2970 2975 # rebasing them onto an existing repo.
2971 2976 # WARNING: this is an advanced setting reserved for people who know
2972 2977 # exactly what they are doing. Misuse of this setting can easily
2973 2978 # result in obsmarker cycles and a vivid headache.
2974 2979 coreconfigitem(
2975 2980 b'rebase',
2976 2981 b'store-source',
2977 2982 default=True,
2978 2983 experimental=True,
2979 2984 )
@@ -1,2875 +1,2875
1 1 # exchange.py - utility to exchange data between repos.
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8
9 9 import collections
10 10 import weakref
11 11
12 12 from .i18n import _
13 13 from .node import (
14 14 hex,
15 15 nullrev,
16 16 )
17 17 from . import (
18 18 bookmarks as bookmod,
19 19 bundle2,
20 20 bundlecaches,
21 21 changegroup,
22 22 discovery,
23 23 error,
24 24 lock as lockmod,
25 25 logexchange,
26 26 narrowspec,
27 27 obsolete,
28 28 obsutil,
29 29 phases,
30 30 pushkey,
31 31 pycompat,
32 32 requirements,
33 33 scmutil,
34 34 streamclone,
35 35 url as urlmod,
36 36 util,
37 37 wireprototypes,
38 38 )
39 39 from .utils import (
40 40 hashutil,
41 41 stringutil,
42 42 urlutil,
43 43 )
44 44 from .interfaces import repository
45 45
46 46 urlerr = util.urlerr
47 47 urlreq = util.urlreq
48 48
49 49 _NARROWACL_SECTION = b'narrowacl'
50 50
51 51
52 52 def readbundle(ui, fh, fname, vfs=None):
53 53 header = changegroup.readexactly(fh, 4)
54 54
55 55 alg = None
56 56 if not fname:
57 57 fname = b"stream"
58 58 if not header.startswith(b'HG') and header.startswith(b'\0'):
59 59 fh = changegroup.headerlessfixup(fh, header)
60 60 header = b"HG10"
61 61 alg = b'UN'
62 62 elif vfs:
63 63 fname = vfs.join(fname)
64 64
65 65 magic, version = header[0:2], header[2:4]
66 66
67 67 if magic != b'HG':
68 68 raise error.Abort(_(b'%s: not a Mercurial bundle') % fname)
69 69 if version == b'10':
70 70 if alg is None:
71 71 alg = changegroup.readexactly(fh, 2)
72 72 return changegroup.cg1unpacker(fh, alg)
73 73 elif version.startswith(b'2'):
74 74 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
75 75 elif version == b'S1':
76 76 return streamclone.streamcloneapplier(fh)
77 77 else:
78 78 raise error.Abort(
79 79 _(b'%s: unknown bundle version %s') % (fname, version)
80 80 )
81 81
82 82
83 83 def _format_params(params):
84 84 parts = []
85 85 for key, value in sorted(params.items()):
86 86 value = urlreq.quote(value)
87 87 parts.append(b"%s=%s" % (key, value))
88 88 return b';'.join(parts)
89 89
90 90
91 91 def getbundlespec(ui, fh):
92 92 """Infer the bundlespec from a bundle file handle.
93 93
94 94 The input file handle is seeked and the original seek position is not
95 95 restored.
96 96 """
97 97
98 98 def speccompression(alg):
99 99 try:
100 100 return util.compengines.forbundletype(alg).bundletype()[0]
101 101 except KeyError:
102 102 return None
103 103
104 104 params = {}
105 105
106 106 b = readbundle(ui, fh, None)
107 107 if isinstance(b, changegroup.cg1unpacker):
108 108 alg = b._type
109 109 if alg == b'_truncatedBZ':
110 110 alg = b'BZ'
111 111 comp = speccompression(alg)
112 112 if not comp:
113 113 raise error.Abort(_(b'unknown compression algorithm: %s') % alg)
114 114 return b'%s-v1' % comp
115 115 elif isinstance(b, bundle2.unbundle20):
116 116 if b'Compression' in b.params:
117 117 comp = speccompression(b.params[b'Compression'])
118 118 if not comp:
119 119 raise error.Abort(
120 120 _(b'unknown compression algorithm: %s') % comp
121 121 )
122 122 else:
123 123 comp = b'none'
124 124
125 125 version = None
126 126 for part in b.iterparts():
127 127 if part.type == b'changegroup':
128 128 cgversion = part.params[b'version']
129 129 if cgversion in (b'01', b'02'):
130 130 version = b'v2'
131 131 elif cgversion in (b'03',):
132 132 version = b'v2'
133 133 params[b'cg.version'] = cgversion
134 134 else:
135 135 raise error.Abort(
136 136 _(
137 137 b'changegroup version %s does not have '
138 138 b'a known bundlespec'
139 139 )
140 140 % version,
141 141 hint=_(b'try upgrading your Mercurial client'),
142 142 )
143 143 elif part.type == b'stream2' and version is None:
144 144 # A stream2 part requires to be part of a v2 bundle
145 145 requirements = urlreq.unquote(part.params[b'requirements'])
146 146 splitted = requirements.split()
147 147 params = bundle2._formatrequirementsparams(splitted)
148 148 return b'none-v2;stream=v2;%s' % params
149 149 elif part.type == b'obsmarkers':
150 150 params[b'obsolescence'] = b'yes'
151 151 if not part.mandatory:
152 152 params[b'obsolescence-mandatory'] = b'no'
153 153
154 154 if not version:
155 155 raise error.Abort(
156 156 _(b'could not identify changegroup version in bundle')
157 157 )
158 158 spec = b'%s-%s' % (comp, version)
159 159 if params:
160 160 spec += b';'
161 161 spec += _format_params(params)
162 162 return spec
163 163
164 164 elif isinstance(b, streamclone.streamcloneapplier):
165 165 requirements = streamclone.readbundle1header(fh)[2]
166 166 formatted = bundle2._formatrequirementsparams(requirements)
167 167 return b'none-packed1;%s' % formatted
168 168 else:
169 169 raise error.Abort(_(b'unknown bundle type: %s') % b)
170 170
171 171
172 172 def _computeoutgoing(repo, heads, common):
173 173 """Computes which revs are outgoing given a set of common
174 174 and a set of heads.
175 175
176 176 This is a separate function so extensions can have access to
177 177 the logic.
178 178
179 179 Returns a discovery.outgoing object.
180 180 """
181 181 cl = repo.changelog
182 182 if common:
183 183 hasnode = cl.hasnode
184 184 common = [n for n in common if hasnode(n)]
185 185 else:
186 186 common = [repo.nullid]
187 187 if not heads:
188 188 heads = cl.heads()
189 189 return discovery.outgoing(repo, common, heads)
190 190
191 191
192 192 def _checkpublish(pushop):
193 193 repo = pushop.repo
194 194 ui = repo.ui
195 195 behavior = ui.config(b'experimental', b'auto-publish')
196 196 if pushop.publish or behavior not in (b'warn', b'confirm', b'abort'):
197 197 return
198 198 remotephases = listkeys(pushop.remote, b'phases')
199 199 if not remotephases.get(b'publishing', False):
200 200 return
201 201
202 202 if pushop.revs is None:
203 203 published = repo.filtered(b'served').revs(b'not public()')
204 204 else:
205 205 published = repo.revs(b'::%ln - public()', pushop.revs)
206 206 # we want to use pushop.revs in the revset even if they themselves are
207 207 # secret, but we don't want to have anything that the server won't see
208 208 # in the result of this expression
209 209 published &= repo.filtered(b'served')
210 210 if published:
211 211 if behavior == b'warn':
212 212 ui.warn(
213 213 _(b'%i changesets about to be published\n') % len(published)
214 214 )
215 215 elif behavior == b'confirm':
216 216 if ui.promptchoice(
217 217 _(b'push and publish %i changesets (yn)?$$ &Yes $$ &No')
218 218 % len(published)
219 219 ):
220 220 raise error.CanceledError(_(b'user quit'))
221 221 elif behavior == b'abort':
222 222 msg = _(b'push would publish %i changesets') % len(published)
223 223 hint = _(
224 224 b"use --publish or adjust 'experimental.auto-publish'"
225 225 b" config"
226 226 )
227 227 raise error.Abort(msg, hint=hint)
228 228
229 229
230 230 def _forcebundle1(op):
231 231 """return true if a pull/push must use bundle1
232 232
233 233 This function is used to allow testing of the older bundle version"""
234 234 ui = op.repo.ui
235 235 # The goal is this config is to allow developer to choose the bundle
236 236 # version used during exchanged. This is especially handy during test.
237 237 # Value is a list of bundle version to be picked from, highest version
238 238 # should be used.
239 239 #
240 240 # developer config: devel.legacy.exchange
241 241 exchange = ui.configlist(b'devel', b'legacy.exchange')
242 242 forcebundle1 = b'bundle2' not in exchange and b'bundle1' in exchange
243 243 return forcebundle1 or not op.remote.capable(b'bundle2')
244 244
245 245
246 246 class pushoperation:
247 247 """A object that represent a single push operation
248 248
249 249 Its purpose is to carry push related state and very common operations.
250 250
251 251 A new pushoperation should be created at the beginning of each push and
252 252 discarded afterward.
253 253 """
254 254
255 255 def __init__(
256 256 self,
257 257 repo,
258 258 remote,
259 259 force=False,
260 260 revs=None,
261 261 newbranch=False,
262 262 bookmarks=(),
263 263 publish=False,
264 264 pushvars=None,
265 265 ):
266 266 # repo we push from
267 267 self.repo = repo
268 268 self.ui = repo.ui
269 269 # repo we push to
270 270 self.remote = remote
271 271 # force option provided
272 272 self.force = force
273 273 # revs to be pushed (None is "all")
274 274 self.revs = revs
275 275 # bookmark explicitly pushed
276 276 self.bookmarks = bookmarks
277 277 # allow push of new branch
278 278 self.newbranch = newbranch
279 279 # step already performed
280 280 # (used to check what steps have been already performed through bundle2)
281 281 self.stepsdone = set()
282 282 # Integer version of the changegroup push result
283 283 # - None means nothing to push
284 284 # - 0 means HTTP error
285 285 # - 1 means we pushed and remote head count is unchanged *or*
286 286 # we have outgoing changesets but refused to push
287 287 # - other values as described by addchangegroup()
288 288 self.cgresult = None
289 289 # Boolean value for the bookmark push
290 290 self.bkresult = None
291 291 # discover.outgoing object (contains common and outgoing data)
292 292 self.outgoing = None
293 293 # all remote topological heads before the push
294 294 self.remoteheads = None
295 295 # Details of the remote branch pre and post push
296 296 #
297 297 # mapping: {'branch': ([remoteheads],
298 298 # [newheads],
299 299 # [unsyncedheads],
300 300 # [discardedheads])}
301 301 # - branch: the branch name
302 302 # - remoteheads: the list of remote heads known locally
303 303 # None if the branch is new
304 304 # - newheads: the new remote heads (known locally) with outgoing pushed
305 305 # - unsyncedheads: the list of remote heads unknown locally.
306 306 # - discardedheads: the list of remote heads made obsolete by the push
307 307 self.pushbranchmap = None
308 308 # testable as a boolean indicating if any nodes are missing locally.
309 309 self.incoming = None
310 310 # summary of the remote phase situation
311 311 self.remotephases = None
312 312 # phases changes that must be pushed along side the changesets
313 313 self.outdatedphases = None
314 314 # phases changes that must be pushed if changeset push fails
315 315 self.fallbackoutdatedphases = None
316 316 # outgoing obsmarkers
317 317 self.outobsmarkers = set()
318 318 # outgoing bookmarks, list of (bm, oldnode | '', newnode | '')
319 319 self.outbookmarks = []
320 320 # transaction manager
321 321 self.trmanager = None
322 322 # map { pushkey partid -> callback handling failure}
323 323 # used to handle exception from mandatory pushkey part failure
324 324 self.pkfailcb = {}
325 325 # an iterable of pushvars or None
326 326 self.pushvars = pushvars
327 327 # publish pushed changesets
328 328 self.publish = publish
329 329
330 330 @util.propertycache
331 331 def futureheads(self):
332 332 """future remote heads if the changeset push succeeds"""
333 333 return self.outgoing.ancestorsof
334 334
335 335 @util.propertycache
336 336 def fallbackheads(self):
337 337 """future remote heads if the changeset push fails"""
338 338 if self.revs is None:
339 339 # not target to push, all common are relevant
340 340 return self.outgoing.commonheads
341 341 unfi = self.repo.unfiltered()
342 342 # I want cheads = heads(::ancestorsof and ::commonheads)
343 343 # (ancestorsof is revs with secret changeset filtered out)
344 344 #
345 345 # This can be expressed as:
346 346 # cheads = ( (ancestorsof and ::commonheads)
347 347 # + (commonheads and ::ancestorsof))"
348 348 # )
349 349 #
350 350 # while trying to push we already computed the following:
351 351 # common = (::commonheads)
352 352 # missing = ((commonheads::ancestorsof) - commonheads)
353 353 #
354 354 # We can pick:
355 355 # * ancestorsof part of common (::commonheads)
356 356 common = self.outgoing.common
357 357 rev = self.repo.changelog.index.rev
358 358 cheads = [node for node in self.revs if rev(node) in common]
359 359 # and
360 360 # * commonheads parents on missing
361 361 revset = unfi.set(
362 362 b'%ln and parents(roots(%ln))',
363 363 self.outgoing.commonheads,
364 364 self.outgoing.missing,
365 365 )
366 366 cheads.extend(c.node() for c in revset)
367 367 return cheads
368 368
369 369 @property
370 370 def commonheads(self):
371 371 """set of all common heads after changeset bundle push"""
372 372 if self.cgresult:
373 373 return self.futureheads
374 374 else:
375 375 return self.fallbackheads
376 376
377 377
378 378 # mapping of message used when pushing bookmark
379 379 bookmsgmap = {
380 380 b'update': (
381 381 _(b"updating bookmark %s\n"),
382 382 _(b'updating bookmark %s failed\n'),
383 383 ),
384 384 b'export': (
385 385 _(b"exporting bookmark %s\n"),
386 386 _(b'exporting bookmark %s failed\n'),
387 387 ),
388 388 b'delete': (
389 389 _(b"deleting remote bookmark %s\n"),
390 390 _(b'deleting remote bookmark %s failed\n'),
391 391 ),
392 392 }
393 393
394 394
395 395 def push(
396 396 repo,
397 397 remote,
398 398 force=False,
399 399 revs=None,
400 400 newbranch=False,
401 401 bookmarks=(),
402 402 publish=False,
403 403 opargs=None,
404 404 ):
405 405 """Push outgoing changesets (limited by revs) from a local
406 406 repository to remote. Return an integer:
407 407 - None means nothing to push
408 408 - 0 means HTTP error
409 409 - 1 means we pushed and remote head count is unchanged *or*
410 410 we have outgoing changesets but refused to push
411 411 - other values as described by addchangegroup()
412 412 """
413 413 if opargs is None:
414 414 opargs = {}
415 415 pushop = pushoperation(
416 416 repo,
417 417 remote,
418 418 force,
419 419 revs,
420 420 newbranch,
421 421 bookmarks,
422 422 publish,
423 423 **pycompat.strkwargs(opargs)
424 424 )
425 425 if pushop.remote.local():
426 426 missing = (
427 427 set(pushop.repo.requirements) - pushop.remote.local().supported
428 428 )
429 429 if missing:
430 430 msg = _(
431 431 b"required features are not"
432 432 b" supported in the destination:"
433 433 b" %s"
434 434 ) % (b', '.join(sorted(missing)))
435 435 raise error.Abort(msg)
436 436
437 437 if not pushop.remote.canpush():
438 438 raise error.Abort(_(b"destination does not support push"))
439 439
440 440 if not pushop.remote.capable(b'unbundle'):
441 441 raise error.Abort(
442 442 _(
443 443 b'cannot push: destination does not support the '
444 444 b'unbundle wire protocol command'
445 445 )
446 446 )
447 447 for category in sorted(bundle2.read_remote_wanted_sidedata(pushop.remote)):
448 448 # Check that a computer is registered for that category for at least
449 449 # one revlog kind.
450 450 for kind, computers in repo._sidedata_computers.items():
451 451 if computers.get(category):
452 452 break
453 453 else:
454 454 raise error.Abort(
455 455 _(
456 456 b'cannot push: required sidedata category not supported'
457 457 b" by this client: '%s'"
458 458 )
459 459 % pycompat.bytestr(category)
460 460 )
461 461 # get lock as we might write phase data
462 462 wlock = lock = None
463 463 try:
464 464 # bundle2 push may receive a reply bundle touching bookmarks
465 465 # requiring the wlock. Take it now to ensure proper ordering.
466 466 maypushback = pushop.ui.configbool(b'experimental', b'bundle2.pushback')
467 467 if (
468 468 (not _forcebundle1(pushop))
469 469 and maypushback
470 470 and not bookmod.bookmarksinstore(repo)
471 471 ):
472 472 wlock = pushop.repo.wlock()
473 473 lock = pushop.repo.lock()
474 474 pushop.trmanager = transactionmanager(
475 475 pushop.repo, b'push-response', pushop.remote.url()
476 476 )
477 477 except error.LockUnavailable as err:
478 478 # source repo cannot be locked.
479 479 # We do not abort the push, but just disable the local phase
480 480 # synchronisation.
481 481 msg = b'cannot lock source repository: %s\n' % stringutil.forcebytestr(
482 482 err
483 483 )
484 484 pushop.ui.debug(msg)
485 485
486 486 with wlock or util.nullcontextmanager():
487 487 with lock or util.nullcontextmanager():
488 488 with pushop.trmanager or util.nullcontextmanager():
489 489 pushop.repo.checkpush(pushop)
490 490 _checkpublish(pushop)
491 491 _pushdiscovery(pushop)
492 492 if not pushop.force:
493 493 _checksubrepostate(pushop)
494 494 if not _forcebundle1(pushop):
495 495 _pushbundle2(pushop)
496 496 _pushchangeset(pushop)
497 497 _pushsyncphase(pushop)
498 498 _pushobsolete(pushop)
499 499 _pushbookmark(pushop)
500 500
501 501 if repo.ui.configbool(b'experimental', b'remotenames'):
502 502 logexchange.pullremotenames(repo, remote)
503 503
504 504 return pushop
505 505
506 506
507 507 # list of steps to perform discovery before push
508 508 pushdiscoveryorder = []
509 509
510 510 # Mapping between step name and function
511 511 #
512 512 # This exists to help extensions wrap steps if necessary
513 513 pushdiscoverymapping = {}
514 514
515 515
516 516 def pushdiscovery(stepname):
517 517 """decorator for function performing discovery before push
518 518
519 519 The function is added to the step -> function mapping and appended to the
520 520 list of steps. Beware that decorated function will be added in order (this
521 521 may matter).
522 522
523 523 You can only use this decorator for a new step, if you want to wrap a step
524 524 from an extension, change the pushdiscovery dictionary directly."""
525 525
526 526 def dec(func):
527 527 assert stepname not in pushdiscoverymapping
528 528 pushdiscoverymapping[stepname] = func
529 529 pushdiscoveryorder.append(stepname)
530 530 return func
531 531
532 532 return dec
533 533
534 534
535 535 def _pushdiscovery(pushop):
536 536 """Run all discovery steps"""
537 537 for stepname in pushdiscoveryorder:
538 538 step = pushdiscoverymapping[stepname]
539 539 step(pushop)
540 540
541 541
542 542 def _checksubrepostate(pushop):
543 543 """Ensure all outgoing referenced subrepo revisions are present locally"""
544 544
545 545 repo = pushop.repo
546 546
547 547 # If the repository does not use subrepos, skip the expensive
548 548 # manifest checks.
549 549 if not len(repo.file(b'.hgsub')) or not len(repo.file(b'.hgsubstate')):
550 550 return
551 551
552 552 for n in pushop.outgoing.missing:
553 553 ctx = repo[n]
554 554
555 555 if b'.hgsub' in ctx.manifest() and b'.hgsubstate' in ctx.files():
556 556 for subpath in sorted(ctx.substate):
557 557 sub = ctx.sub(subpath)
558 558 sub.verify(onpush=True)
559 559
560 560
561 561 @pushdiscovery(b'changeset')
562 562 def _pushdiscoverychangeset(pushop):
563 563 """discover the changeset that need to be pushed"""
564 564 fci = discovery.findcommonincoming
565 565 if pushop.revs:
566 566 commoninc = fci(
567 567 pushop.repo,
568 568 pushop.remote,
569 569 force=pushop.force,
570 570 ancestorsof=pushop.revs,
571 571 )
572 572 else:
573 573 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
574 574 common, inc, remoteheads = commoninc
575 575 fco = discovery.findcommonoutgoing
576 576 outgoing = fco(
577 577 pushop.repo,
578 578 pushop.remote,
579 579 onlyheads=pushop.revs,
580 580 commoninc=commoninc,
581 581 force=pushop.force,
582 582 )
583 583 pushop.outgoing = outgoing
584 584 pushop.remoteheads = remoteheads
585 585 pushop.incoming = inc
586 586
587 587
588 588 @pushdiscovery(b'phase')
589 589 def _pushdiscoveryphase(pushop):
590 590 """discover the phase that needs to be pushed
591 591
592 592 (computed for both success and failure case for changesets push)"""
593 593 outgoing = pushop.outgoing
594 594 unfi = pushop.repo.unfiltered()
595 595 remotephases = listkeys(pushop.remote, b'phases')
596 596
597 597 if (
598 598 pushop.ui.configbool(b'ui', b'_usedassubrepo')
599 599 and remotephases # server supports phases
600 600 and not pushop.outgoing.missing # no changesets to be pushed
601 601 and remotephases.get(b'publishing', False)
602 602 ):
603 603 # When:
604 604 # - this is a subrepo push
605 605 # - and remote support phase
606 606 # - and no changeset are to be pushed
607 607 # - and remote is publishing
608 608 # We may be in issue 3781 case!
609 609 # We drop the possible phase synchronisation done by
610 610 # courtesy to publish changesets possibly locally draft
611 611 # on the remote.
612 612 pushop.outdatedphases = []
613 613 pushop.fallbackoutdatedphases = []
614 614 return
615 615
616 616 pushop.remotephases = phases.remotephasessummary(
617 617 pushop.repo, pushop.fallbackheads, remotephases
618 618 )
619 619 droots = pushop.remotephases.draftroots
620 620
621 621 extracond = b''
622 622 if not pushop.remotephases.publishing:
623 623 extracond = b' and public()'
624 624 revset = b'heads((%%ln::%%ln) %s)' % extracond
625 625 # Get the list of all revs draft on remote by public here.
626 626 # XXX Beware that revset break if droots is not strictly
627 627 # XXX root we may want to ensure it is but it is costly
628 628 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
629 629 if not pushop.remotephases.publishing and pushop.publish:
630 630 future = list(
631 631 unfi.set(
632 632 b'%ln and (not public() or %ln::)', pushop.futureheads, droots
633 633 )
634 634 )
635 635 elif not outgoing.missing:
636 636 future = fallback
637 637 else:
638 638 # adds changeset we are going to push as draft
639 639 #
640 640 # should not be necessary for publishing server, but because of an
641 641 # issue fixed in xxxxx we have to do it anyway.
642 642 fdroots = list(
643 643 unfi.set(b'roots(%ln + %ln::)', outgoing.missing, droots)
644 644 )
645 645 fdroots = [f.node() for f in fdroots]
646 646 future = list(unfi.set(revset, fdroots, pushop.futureheads))
647 647 pushop.outdatedphases = future
648 648 pushop.fallbackoutdatedphases = fallback
649 649
650 650
651 651 @pushdiscovery(b'obsmarker')
652 652 def _pushdiscoveryobsmarkers(pushop):
653 653 if not obsolete.isenabled(pushop.repo, obsolete.exchangeopt):
654 654 return
655 655
656 656 if not pushop.repo.obsstore:
657 657 return
658 658
659 659 if b'obsolete' not in listkeys(pushop.remote, b'namespaces'):
660 660 return
661 661
662 662 repo = pushop.repo
663 663 # very naive computation, that can be quite expensive on big repo.
664 664 # However: evolution is currently slow on them anyway.
665 665 nodes = (c.node() for c in repo.set(b'::%ln', pushop.futureheads))
666 666 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
667 667
668 668
669 669 @pushdiscovery(b'bookmarks')
670 670 def _pushdiscoverybookmarks(pushop):
671 671 ui = pushop.ui
672 672 repo = pushop.repo.unfiltered()
673 673 remote = pushop.remote
674 674 ui.debug(b"checking for updated bookmarks\n")
675 675 ancestors = ()
676 676 if pushop.revs:
677 677 revnums = pycompat.maplist(repo.changelog.rev, pushop.revs)
678 678 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
679 679
680 680 remotebookmark = bookmod.unhexlifybookmarks(listkeys(remote, b'bookmarks'))
681 681
682 682 explicit = {
683 683 repo._bookmarks.expandname(bookmark) for bookmark in pushop.bookmarks
684 684 }
685 685
686 686 comp = bookmod.comparebookmarks(repo, repo._bookmarks, remotebookmark)
687 687 return _processcompared(pushop, ancestors, explicit, remotebookmark, comp)
688 688
689 689
690 690 def _processcompared(pushop, pushed, explicit, remotebms, comp):
691 691 """take decision on bookmarks to push to the remote repo
692 692
693 693 Exists to help extensions alter this behavior.
694 694 """
695 695 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
696 696
697 697 repo = pushop.repo
698 698
699 699 for b, scid, dcid in advsrc:
700 700 if b in explicit:
701 701 explicit.remove(b)
702 702 if not pushed or repo[scid].rev() in pushed:
703 703 pushop.outbookmarks.append((b, dcid, scid))
704 704 # search added bookmark
705 705 for b, scid, dcid in addsrc:
706 706 if b in explicit:
707 707 explicit.remove(b)
708 708 if bookmod.isdivergent(b):
709 709 pushop.ui.warn(_(b'cannot push divergent bookmark %s!\n') % b)
710 710 pushop.bkresult = 2
711 711 else:
712 712 pushop.outbookmarks.append((b, b'', scid))
713 713 # search for overwritten bookmark
714 714 for b, scid, dcid in list(advdst) + list(diverge) + list(differ):
715 715 if b in explicit:
716 716 explicit.remove(b)
717 717 pushop.outbookmarks.append((b, dcid, scid))
718 718 # search for bookmark to delete
719 719 for b, scid, dcid in adddst:
720 720 if b in explicit:
721 721 explicit.remove(b)
722 722 # treat as "deleted locally"
723 723 pushop.outbookmarks.append((b, dcid, b''))
724 724 # identical bookmarks shouldn't get reported
725 725 for b, scid, dcid in same:
726 726 if b in explicit:
727 727 explicit.remove(b)
728 728
729 729 if explicit:
730 730 explicit = sorted(explicit)
731 731 # we should probably list all of them
732 732 pushop.ui.warn(
733 733 _(
734 734 b'bookmark %s does not exist on the local '
735 735 b'or remote repository!\n'
736 736 )
737 737 % explicit[0]
738 738 )
739 739 pushop.bkresult = 2
740 740
741 741 pushop.outbookmarks.sort()
742 742
743 743
744 744 def _pushcheckoutgoing(pushop):
745 745 outgoing = pushop.outgoing
746 746 unfi = pushop.repo.unfiltered()
747 747 if not outgoing.missing:
748 748 # nothing to push
749 749 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
750 750 return False
751 751 # something to push
752 752 if not pushop.force:
753 753 # if repo.obsstore == False --> no obsolete
754 754 # then, save the iteration
755 755 if unfi.obsstore:
756 756 # this message are here for 80 char limit reason
757 757 mso = _(b"push includes obsolete changeset: %s!")
758 758 mspd = _(b"push includes phase-divergent changeset: %s!")
759 759 mscd = _(b"push includes content-divergent changeset: %s!")
760 760 mst = {
761 761 b"orphan": _(b"push includes orphan changeset: %s!"),
762 762 b"phase-divergent": mspd,
763 763 b"content-divergent": mscd,
764 764 }
765 765 # If we are to push if there is at least one
766 766 # obsolete or unstable changeset in missing, at
767 767 # least one of the missinghead will be obsolete or
768 768 # unstable. So checking heads only is ok
769 769 for node in outgoing.ancestorsof:
770 770 ctx = unfi[node]
771 771 if ctx.obsolete():
772 772 raise error.Abort(mso % ctx)
773 773 elif ctx.isunstable():
774 774 # TODO print more than one instability in the abort
775 775 # message
776 776 raise error.Abort(mst[ctx.instabilities()[0]] % ctx)
777 777
778 778 discovery.checkheads(pushop)
779 779 return True
780 780
781 781
782 782 # List of names of steps to perform for an outgoing bundle2, order matters.
783 783 b2partsgenorder = []
784 784
785 785 # Mapping between step name and function
786 786 #
787 787 # This exists to help extensions wrap steps if necessary
788 788 b2partsgenmapping = {}
789 789
790 790
791 791 def b2partsgenerator(stepname, idx=None):
792 792 """decorator for function generating bundle2 part
793 793
794 794 The function is added to the step -> function mapping and appended to the
795 795 list of steps. Beware that decorated functions will be added in order
796 796 (this may matter).
797 797
798 798 You can only use this decorator for new steps, if you want to wrap a step
799 799 from an extension, attack the b2partsgenmapping dictionary directly."""
800 800
801 801 def dec(func):
802 802 assert stepname not in b2partsgenmapping
803 803 b2partsgenmapping[stepname] = func
804 804 if idx is None:
805 805 b2partsgenorder.append(stepname)
806 806 else:
807 807 b2partsgenorder.insert(idx, stepname)
808 808 return func
809 809
810 810 return dec
811 811
812 812
813 813 def _pushb2ctxcheckheads(pushop, bundler):
814 814 """Generate race condition checking parts
815 815
816 816 Exists as an independent function to aid extensions
817 817 """
818 818 # * 'force' do not check for push race,
819 819 # * if we don't push anything, there are nothing to check.
820 820 if not pushop.force and pushop.outgoing.ancestorsof:
821 821 allowunrelated = b'related' in bundler.capabilities.get(
822 822 b'checkheads', ()
823 823 )
824 824 emptyremote = pushop.pushbranchmap is None
825 825 if not allowunrelated or emptyremote:
826 826 bundler.newpart(b'check:heads', data=iter(pushop.remoteheads))
827 827 else:
828 828 affected = set()
829 829 for branch, heads in pushop.pushbranchmap.items():
830 830 remoteheads, newheads, unsyncedheads, discardedheads = heads
831 831 if remoteheads is not None:
832 832 remote = set(remoteheads)
833 833 affected |= set(discardedheads) & remote
834 834 affected |= remote - set(newheads)
835 835 if affected:
836 836 data = iter(sorted(affected))
837 837 bundler.newpart(b'check:updated-heads', data=data)
838 838
839 839
840 840 def _pushing(pushop):
841 841 """return True if we are pushing anything"""
842 842 return bool(
843 843 pushop.outgoing.missing
844 844 or pushop.outdatedphases
845 845 or pushop.outobsmarkers
846 846 or pushop.outbookmarks
847 847 )
848 848
849 849
850 850 @b2partsgenerator(b'check-bookmarks')
851 851 def _pushb2checkbookmarks(pushop, bundler):
852 852 """insert bookmark move checking"""
853 853 if not _pushing(pushop) or pushop.force:
854 854 return
855 855 b2caps = bundle2.bundle2caps(pushop.remote)
856 856 hasbookmarkcheck = b'bookmarks' in b2caps
857 857 if not (pushop.outbookmarks and hasbookmarkcheck):
858 858 return
859 859 data = []
860 860 for book, old, new in pushop.outbookmarks:
861 861 data.append((book, old))
862 862 checkdata = bookmod.binaryencode(pushop.repo, data)
863 863 bundler.newpart(b'check:bookmarks', data=checkdata)
864 864
865 865
866 866 @b2partsgenerator(b'check-phases')
867 867 def _pushb2checkphases(pushop, bundler):
868 868 """insert phase move checking"""
869 869 if not _pushing(pushop) or pushop.force:
870 870 return
871 871 b2caps = bundle2.bundle2caps(pushop.remote)
872 872 hasphaseheads = b'heads' in b2caps.get(b'phases', ())
873 873 if pushop.remotephases is not None and hasphaseheads:
874 874 # check that the remote phase has not changed
875 875 checks = {p: [] for p in phases.allphases}
876 876 checks[phases.public].extend(pushop.remotephases.publicheads)
877 877 checks[phases.draft].extend(pushop.remotephases.draftroots)
878 878 if any(checks.values()):
879 879 for phase in checks:
880 880 checks[phase].sort()
881 881 checkdata = phases.binaryencode(checks)
882 882 bundler.newpart(b'check:phases', data=checkdata)
883 883
884 884
885 885 @b2partsgenerator(b'changeset')
886 886 def _pushb2ctx(pushop, bundler):
887 887 """handle changegroup push through bundle2
888 888
889 889 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
890 890 """
891 891 if b'changesets' in pushop.stepsdone:
892 892 return
893 893 pushop.stepsdone.add(b'changesets')
894 894 # Send known heads to the server for race detection.
895 895 if not _pushcheckoutgoing(pushop):
896 896 return
897 897 pushop.repo.prepushoutgoinghooks(pushop)
898 898
899 899 _pushb2ctxcheckheads(pushop, bundler)
900 900
901 901 b2caps = bundle2.bundle2caps(pushop.remote)
902 902 version = b'01'
903 903 cgversions = b2caps.get(b'changegroup')
904 904 if cgversions: # 3.1 and 3.2 ship with an empty value
905 905 cgversions = [
906 906 v
907 907 for v in cgversions
908 908 if v in changegroup.supportedoutgoingversions(pushop.repo)
909 909 ]
910 910 if not cgversions:
911 911 raise error.Abort(_(b'no common changegroup version'))
912 912 version = max(cgversions)
913 913
914 914 remote_sidedata = bundle2.read_remote_wanted_sidedata(pushop.remote)
915 915 cgstream = changegroup.makestream(
916 916 pushop.repo,
917 917 pushop.outgoing,
918 918 version,
919 919 b'push',
920 920 bundlecaps=b2caps,
921 921 remote_sidedata=remote_sidedata,
922 922 )
923 923 cgpart = bundler.newpart(b'changegroup', data=cgstream)
924 924 if cgversions:
925 925 cgpart.addparam(b'version', version)
926 926 if scmutil.istreemanifest(pushop.repo):
927 927 cgpart.addparam(b'treemanifest', b'1')
928 928 if repository.REPO_FEATURE_SIDE_DATA in pushop.repo.features:
929 929 cgpart.addparam(b'exp-sidedata', b'1')
930 930
931 931 def handlereply(op):
932 932 """extract addchangegroup returns from server reply"""
933 933 cgreplies = op.records.getreplies(cgpart.id)
934 934 assert len(cgreplies[b'changegroup']) == 1
935 935 pushop.cgresult = cgreplies[b'changegroup'][0][b'return']
936 936
937 937 return handlereply
938 938
939 939
940 940 @b2partsgenerator(b'phase')
941 941 def _pushb2phases(pushop, bundler):
942 942 """handle phase push through bundle2"""
943 943 if b'phases' in pushop.stepsdone:
944 944 return
945 945 b2caps = bundle2.bundle2caps(pushop.remote)
946 946 ui = pushop.repo.ui
947 947
948 948 legacyphase = b'phases' in ui.configlist(b'devel', b'legacy.exchange')
949 949 haspushkey = b'pushkey' in b2caps
950 950 hasphaseheads = b'heads' in b2caps.get(b'phases', ())
951 951
952 952 if hasphaseheads and not legacyphase:
953 953 return _pushb2phaseheads(pushop, bundler)
954 954 elif haspushkey:
955 955 return _pushb2phasespushkey(pushop, bundler)
956 956
957 957
958 958 def _pushb2phaseheads(pushop, bundler):
959 959 """push phase information through a bundle2 - binary part"""
960 960 pushop.stepsdone.add(b'phases')
961 961 if pushop.outdatedphases:
962 962 updates = {p: [] for p in phases.allphases}
963 963 updates[0].extend(h.node() for h in pushop.outdatedphases)
964 964 phasedata = phases.binaryencode(updates)
965 965 bundler.newpart(b'phase-heads', data=phasedata)
966 966
967 967
968 968 def _pushb2phasespushkey(pushop, bundler):
969 969 """push phase information through a bundle2 - pushkey part"""
970 970 pushop.stepsdone.add(b'phases')
971 971 part2node = []
972 972
973 973 def handlefailure(pushop, exc):
974 974 targetid = int(exc.partid)
975 975 for partid, node in part2node:
976 976 if partid == targetid:
977 977 raise error.Abort(_(b'updating %s to public failed') % node)
978 978
979 979 enc = pushkey.encode
980 980 for newremotehead in pushop.outdatedphases:
981 981 part = bundler.newpart(b'pushkey')
982 982 part.addparam(b'namespace', enc(b'phases'))
983 983 part.addparam(b'key', enc(newremotehead.hex()))
984 984 part.addparam(b'old', enc(b'%d' % phases.draft))
985 985 part.addparam(b'new', enc(b'%d' % phases.public))
986 986 part2node.append((part.id, newremotehead))
987 987 pushop.pkfailcb[part.id] = handlefailure
988 988
989 989 def handlereply(op):
990 990 for partid, node in part2node:
991 991 partrep = op.records.getreplies(partid)
992 992 results = partrep[b'pushkey']
993 993 assert len(results) <= 1
994 994 msg = None
995 995 if not results:
996 996 msg = _(b'server ignored update of %s to public!\n') % node
997 997 elif not int(results[0][b'return']):
998 998 msg = _(b'updating %s to public failed!\n') % node
999 999 if msg is not None:
1000 1000 pushop.ui.warn(msg)
1001 1001
1002 1002 return handlereply
1003 1003
1004 1004
1005 1005 @b2partsgenerator(b'obsmarkers')
1006 1006 def _pushb2obsmarkers(pushop, bundler):
1007 1007 if b'obsmarkers' in pushop.stepsdone:
1008 1008 return
1009 1009 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
1010 1010 if obsolete.commonversion(remoteversions) is None:
1011 1011 return
1012 1012 pushop.stepsdone.add(b'obsmarkers')
1013 1013 if pushop.outobsmarkers:
1014 1014 markers = obsutil.sortedmarkers(pushop.outobsmarkers)
1015 1015 bundle2.buildobsmarkerspart(bundler, markers)
1016 1016
1017 1017
1018 1018 @b2partsgenerator(b'bookmarks')
1019 1019 def _pushb2bookmarks(pushop, bundler):
1020 1020 """handle bookmark push through bundle2"""
1021 1021 if b'bookmarks' in pushop.stepsdone:
1022 1022 return
1023 1023 b2caps = bundle2.bundle2caps(pushop.remote)
1024 1024
1025 1025 legacy = pushop.repo.ui.configlist(b'devel', b'legacy.exchange')
1026 1026 legacybooks = b'bookmarks' in legacy
1027 1027
1028 1028 if not legacybooks and b'bookmarks' in b2caps:
1029 1029 return _pushb2bookmarkspart(pushop, bundler)
1030 1030 elif b'pushkey' in b2caps:
1031 1031 return _pushb2bookmarkspushkey(pushop, bundler)
1032 1032
1033 1033
1034 1034 def _bmaction(old, new):
1035 1035 """small utility for bookmark pushing"""
1036 1036 if not old:
1037 1037 return b'export'
1038 1038 elif not new:
1039 1039 return b'delete'
1040 1040 return b'update'
1041 1041
1042 1042
1043 1043 def _abortonsecretctx(pushop, node, b):
1044 1044 """abort if a given bookmark points to a secret changeset"""
1045 1045 if node and pushop.repo[node].phase() == phases.secret:
1046 1046 raise error.Abort(
1047 1047 _(b'cannot push bookmark %s as it points to a secret changeset') % b
1048 1048 )
1049 1049
1050 1050
1051 1051 def _pushb2bookmarkspart(pushop, bundler):
1052 1052 pushop.stepsdone.add(b'bookmarks')
1053 1053 if not pushop.outbookmarks:
1054 1054 return
1055 1055
1056 1056 allactions = []
1057 1057 data = []
1058 1058 for book, old, new in pushop.outbookmarks:
1059 1059 _abortonsecretctx(pushop, new, book)
1060 1060 data.append((book, new))
1061 1061 allactions.append((book, _bmaction(old, new)))
1062 1062 checkdata = bookmod.binaryencode(pushop.repo, data)
1063 1063 bundler.newpart(b'bookmarks', data=checkdata)
1064 1064
1065 1065 def handlereply(op):
1066 1066 ui = pushop.ui
1067 1067 # if success
1068 1068 for book, action in allactions:
1069 1069 ui.status(bookmsgmap[action][0] % book)
1070 1070
1071 1071 return handlereply
1072 1072
1073 1073
1074 1074 def _pushb2bookmarkspushkey(pushop, bundler):
1075 1075 pushop.stepsdone.add(b'bookmarks')
1076 1076 part2book = []
1077 1077 enc = pushkey.encode
1078 1078
1079 1079 def handlefailure(pushop, exc):
1080 1080 targetid = int(exc.partid)
1081 1081 for partid, book, action in part2book:
1082 1082 if partid == targetid:
1083 1083 raise error.Abort(bookmsgmap[action][1].rstrip() % book)
1084 1084 # we should not be called for part we did not generated
1085 1085 assert False
1086 1086
1087 1087 for book, old, new in pushop.outbookmarks:
1088 1088 _abortonsecretctx(pushop, new, book)
1089 1089 part = bundler.newpart(b'pushkey')
1090 1090 part.addparam(b'namespace', enc(b'bookmarks'))
1091 1091 part.addparam(b'key', enc(book))
1092 1092 part.addparam(b'old', enc(hex(old)))
1093 1093 part.addparam(b'new', enc(hex(new)))
1094 1094 action = b'update'
1095 1095 if not old:
1096 1096 action = b'export'
1097 1097 elif not new:
1098 1098 action = b'delete'
1099 1099 part2book.append((part.id, book, action))
1100 1100 pushop.pkfailcb[part.id] = handlefailure
1101 1101
1102 1102 def handlereply(op):
1103 1103 ui = pushop.ui
1104 1104 for partid, book, action in part2book:
1105 1105 partrep = op.records.getreplies(partid)
1106 1106 results = partrep[b'pushkey']
1107 1107 assert len(results) <= 1
1108 1108 if not results:
1109 1109 pushop.ui.warn(_(b'server ignored bookmark %s update\n') % book)
1110 1110 else:
1111 1111 ret = int(results[0][b'return'])
1112 1112 if ret:
1113 1113 ui.status(bookmsgmap[action][0] % book)
1114 1114 else:
1115 1115 ui.warn(bookmsgmap[action][1] % book)
1116 1116 if pushop.bkresult is not None:
1117 1117 pushop.bkresult = 1
1118 1118
1119 1119 return handlereply
1120 1120
1121 1121
1122 1122 @b2partsgenerator(b'pushvars', idx=0)
1123 1123 def _getbundlesendvars(pushop, bundler):
1124 1124 '''send shellvars via bundle2'''
1125 1125 pushvars = pushop.pushvars
1126 1126 if pushvars:
1127 1127 shellvars = {}
1128 1128 for raw in pushvars:
1129 1129 if b'=' not in raw:
1130 1130 msg = (
1131 1131 b"unable to parse variable '%s', should follow "
1132 1132 b"'KEY=VALUE' or 'KEY=' format"
1133 1133 )
1134 1134 raise error.Abort(msg % raw)
1135 1135 k, v = raw.split(b'=', 1)
1136 1136 shellvars[k] = v
1137 1137
1138 1138 part = bundler.newpart(b'pushvars')
1139 1139
1140 1140 for key, value in shellvars.items():
1141 1141 part.addparam(key, value, mandatory=False)
1142 1142
1143 1143
1144 1144 def _pushbundle2(pushop):
1145 1145 """push data to the remote using bundle2
1146 1146
1147 1147 The only currently supported type of data is changegroup but this will
1148 1148 evolve in the future."""
1149 1149 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
1150 1150 pushback = pushop.trmanager and pushop.ui.configbool(
1151 1151 b'experimental', b'bundle2.pushback'
1152 1152 )
1153 1153
1154 1154 # create reply capability
1155 1155 capsblob = bundle2.encodecaps(
1156 1156 bundle2.getrepocaps(pushop.repo, allowpushback=pushback, role=b'client')
1157 1157 )
1158 1158 bundler.newpart(b'replycaps', data=capsblob)
1159 1159 replyhandlers = []
1160 1160 for partgenname in b2partsgenorder:
1161 1161 partgen = b2partsgenmapping[partgenname]
1162 1162 ret = partgen(pushop, bundler)
1163 1163 if callable(ret):
1164 1164 replyhandlers.append(ret)
1165 1165 # do not push if nothing to push
1166 1166 if bundler.nbparts <= 1:
1167 1167 return
1168 1168 stream = util.chunkbuffer(bundler.getchunks())
1169 1169 try:
1170 1170 try:
1171 1171 with pushop.remote.commandexecutor() as e:
1172 1172 reply = e.callcommand(
1173 1173 b'unbundle',
1174 1174 {
1175 1175 b'bundle': stream,
1176 1176 b'heads': [b'force'],
1177 1177 b'url': pushop.remote.url(),
1178 1178 },
1179 1179 ).result()
1180 1180 except error.BundleValueError as exc:
1181 1181 raise error.RemoteError(_(b'missing support for %s') % exc)
1182 1182 try:
1183 1183 trgetter = None
1184 1184 if pushback:
1185 1185 trgetter = pushop.trmanager.transaction
1186 1186 op = bundle2.processbundle(
1187 1187 pushop.repo,
1188 1188 reply,
1189 1189 trgetter,
1190 1190 remote=pushop.remote,
1191 1191 )
1192 1192 except error.BundleValueError as exc:
1193 1193 raise error.RemoteError(_(b'missing support for %s') % exc)
1194 1194 except bundle2.AbortFromPart as exc:
1195 1195 pushop.ui.error(_(b'remote: %s\n') % exc)
1196 1196 if exc.hint is not None:
1197 1197 pushop.ui.error(_(b'remote: %s\n') % (b'(%s)' % exc.hint))
1198 1198 raise error.RemoteError(_(b'push failed on remote'))
1199 1199 except error.PushkeyFailed as exc:
1200 1200 partid = int(exc.partid)
1201 1201 if partid not in pushop.pkfailcb:
1202 1202 raise
1203 1203 pushop.pkfailcb[partid](pushop, exc)
1204 1204 for rephand in replyhandlers:
1205 1205 rephand(op)
1206 1206
1207 1207
1208 1208 def _pushchangeset(pushop):
1209 1209 """Make the actual push of changeset bundle to remote repo"""
1210 1210 if b'changesets' in pushop.stepsdone:
1211 1211 return
1212 1212 pushop.stepsdone.add(b'changesets')
1213 1213 if not _pushcheckoutgoing(pushop):
1214 1214 return
1215 1215
1216 1216 # Should have verified this in push().
1217 1217 assert pushop.remote.capable(b'unbundle')
1218 1218
1219 1219 pushop.repo.prepushoutgoinghooks(pushop)
1220 1220 outgoing = pushop.outgoing
1221 1221 # TODO: get bundlecaps from remote
1222 1222 bundlecaps = None
1223 1223 # create a changegroup from local
1224 1224 if pushop.revs is None and not (
1225 1225 outgoing.excluded or pushop.repo.changelog.filteredrevs
1226 1226 ):
1227 1227 # push everything,
1228 1228 # use the fast path, no race possible on push
1229 1229 cg = changegroup.makechangegroup(
1230 1230 pushop.repo,
1231 1231 outgoing,
1232 1232 b'01',
1233 1233 b'push',
1234 1234 fastpath=True,
1235 1235 bundlecaps=bundlecaps,
1236 1236 )
1237 1237 else:
1238 1238 cg = changegroup.makechangegroup(
1239 1239 pushop.repo, outgoing, b'01', b'push', bundlecaps=bundlecaps
1240 1240 )
1241 1241
1242 1242 # apply changegroup to remote
1243 1243 # local repo finds heads on server, finds out what
1244 1244 # revs it must push. once revs transferred, if server
1245 1245 # finds it has different heads (someone else won
1246 1246 # commit/push race), server aborts.
1247 1247 if pushop.force:
1248 1248 remoteheads = [b'force']
1249 1249 else:
1250 1250 remoteheads = pushop.remoteheads
1251 1251 # ssh: return remote's addchangegroup()
1252 1252 # http: return remote's addchangegroup() or 0 for error
1253 1253 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads, pushop.repo.url())
1254 1254
1255 1255
1256 1256 def _pushsyncphase(pushop):
1257 1257 """synchronise phase information locally and remotely"""
1258 1258 cheads = pushop.commonheads
1259 1259 # even when we don't push, exchanging phase data is useful
1260 1260 remotephases = listkeys(pushop.remote, b'phases')
1261 1261 if (
1262 1262 pushop.ui.configbool(b'ui', b'_usedassubrepo')
1263 1263 and remotephases # server supports phases
1264 1264 and pushop.cgresult is None # nothing was pushed
1265 1265 and remotephases.get(b'publishing', False)
1266 1266 ):
1267 1267 # When:
1268 1268 # - this is a subrepo push
1269 1269 # - and remote support phase
1270 1270 # - and no changeset was pushed
1271 1271 # - and remote is publishing
1272 1272 # We may be in issue 3871 case!
1273 1273 # We drop the possible phase synchronisation done by
1274 1274 # courtesy to publish changesets possibly locally draft
1275 1275 # on the remote.
1276 1276 remotephases = {b'publishing': b'True'}
1277 1277 if not remotephases: # old server or public only reply from non-publishing
1278 1278 _localphasemove(pushop, cheads)
1279 1279 # don't push any phase data as there is nothing to push
1280 1280 else:
1281 1281 ana = phases.analyzeremotephases(pushop.repo, cheads, remotephases)
1282 1282 pheads, droots = ana
1283 1283 ### Apply remote phase on local
1284 1284 if remotephases.get(b'publishing', False):
1285 1285 _localphasemove(pushop, cheads)
1286 1286 else: # publish = False
1287 1287 _localphasemove(pushop, pheads)
1288 1288 _localphasemove(pushop, cheads, phases.draft)
1289 1289 ### Apply local phase on remote
1290 1290
1291 1291 if pushop.cgresult:
1292 1292 if b'phases' in pushop.stepsdone:
1293 1293 # phases already pushed though bundle2
1294 1294 return
1295 1295 outdated = pushop.outdatedphases
1296 1296 else:
1297 1297 outdated = pushop.fallbackoutdatedphases
1298 1298
1299 1299 pushop.stepsdone.add(b'phases')
1300 1300
1301 1301 # filter heads already turned public by the push
1302 1302 outdated = [c for c in outdated if c.node() not in pheads]
1303 1303 # fallback to independent pushkey command
1304 1304 for newremotehead in outdated:
1305 1305 with pushop.remote.commandexecutor() as e:
1306 1306 r = e.callcommand(
1307 1307 b'pushkey',
1308 1308 {
1309 1309 b'namespace': b'phases',
1310 1310 b'key': newremotehead.hex(),
1311 1311 b'old': b'%d' % phases.draft,
1312 1312 b'new': b'%d' % phases.public,
1313 1313 },
1314 1314 ).result()
1315 1315
1316 1316 if not r:
1317 1317 pushop.ui.warn(
1318 1318 _(b'updating %s to public failed!\n') % newremotehead
1319 1319 )
1320 1320
1321 1321
1322 1322 def _localphasemove(pushop, nodes, phase=phases.public):
1323 1323 """move <nodes> to <phase> in the local source repo"""
1324 1324 if pushop.trmanager:
1325 1325 phases.advanceboundary(
1326 1326 pushop.repo, pushop.trmanager.transaction(), phase, nodes
1327 1327 )
1328 1328 else:
1329 1329 # repo is not locked, do not change any phases!
1330 1330 # Informs the user that phases should have been moved when
1331 1331 # applicable.
1332 1332 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
1333 1333 phasestr = phases.phasenames[phase]
1334 1334 if actualmoves:
1335 1335 pushop.ui.status(
1336 1336 _(
1337 1337 b'cannot lock source repo, skipping '
1338 1338 b'local %s phase update\n'
1339 1339 )
1340 1340 % phasestr
1341 1341 )
1342 1342
1343 1343
1344 1344 def _pushobsolete(pushop):
1345 1345 """utility function to push obsolete markers to a remote"""
1346 1346 if b'obsmarkers' in pushop.stepsdone:
1347 1347 return
1348 1348 repo = pushop.repo
1349 1349 remote = pushop.remote
1350 1350 pushop.stepsdone.add(b'obsmarkers')
1351 1351 if pushop.outobsmarkers:
1352 1352 pushop.ui.debug(b'try to push obsolete markers to remote\n')
1353 1353 rslts = []
1354 1354 markers = obsutil.sortedmarkers(pushop.outobsmarkers)
1355 1355 remotedata = obsolete._pushkeyescape(markers)
1356 1356 for key in sorted(remotedata, reverse=True):
1357 1357 # reverse sort to ensure we end with dump0
1358 1358 data = remotedata[key]
1359 1359 rslts.append(remote.pushkey(b'obsolete', key, b'', data))
1360 1360 if [r for r in rslts if not r]:
1361 1361 msg = _(b'failed to push some obsolete markers!\n')
1362 1362 repo.ui.warn(msg)
1363 1363
1364 1364
1365 1365 def _pushbookmark(pushop):
1366 1366 """Update bookmark position on remote"""
1367 1367 if pushop.cgresult == 0 or b'bookmarks' in pushop.stepsdone:
1368 1368 return
1369 1369 pushop.stepsdone.add(b'bookmarks')
1370 1370 ui = pushop.ui
1371 1371 remote = pushop.remote
1372 1372
1373 1373 for b, old, new in pushop.outbookmarks:
1374 1374 action = b'update'
1375 1375 if not old:
1376 1376 action = b'export'
1377 1377 elif not new:
1378 1378 action = b'delete'
1379 1379
1380 1380 with remote.commandexecutor() as e:
1381 1381 r = e.callcommand(
1382 1382 b'pushkey',
1383 1383 {
1384 1384 b'namespace': b'bookmarks',
1385 1385 b'key': b,
1386 1386 b'old': hex(old),
1387 1387 b'new': hex(new),
1388 1388 },
1389 1389 ).result()
1390 1390
1391 1391 if r:
1392 1392 ui.status(bookmsgmap[action][0] % b)
1393 1393 else:
1394 1394 ui.warn(bookmsgmap[action][1] % b)
1395 1395 # discovery can have set the value form invalid entry
1396 1396 if pushop.bkresult is not None:
1397 1397 pushop.bkresult = 1
1398 1398
1399 1399
1400 1400 class pulloperation:
1401 1401 """A object that represent a single pull operation
1402 1402
1403 1403 It purpose is to carry pull related state and very common operation.
1404 1404
1405 1405 A new should be created at the beginning of each pull and discarded
1406 1406 afterward.
1407 1407 """
1408 1408
1409 1409 def __init__(
1410 1410 self,
1411 1411 repo,
1412 1412 remote,
1413 1413 heads=None,
1414 1414 force=False,
1415 1415 bookmarks=(),
1416 1416 remotebookmarks=None,
1417 1417 streamclonerequested=None,
1418 1418 includepats=None,
1419 1419 excludepats=None,
1420 1420 depth=None,
1421 1421 path=None,
1422 1422 ):
1423 1423 # repo we pull into
1424 1424 self.repo = repo
1425 1425 # repo we pull from
1426 1426 self.remote = remote
1427 1427 # path object used to build this remote
1428 1428 #
1429 1429 # Ideally, the remote peer would carry that directly.
1430 1430 self.remote_path = path
1431 1431 # revision we try to pull (None is "all")
1432 1432 self.heads = heads
1433 1433 # bookmark pulled explicitly
1434 1434 self.explicitbookmarks = [
1435 1435 repo._bookmarks.expandname(bookmark) for bookmark in bookmarks
1436 1436 ]
1437 1437 # do we force pull?
1438 1438 self.force = force
1439 1439 # whether a streaming clone was requested
1440 1440 self.streamclonerequested = streamclonerequested
1441 1441 # transaction manager
1442 1442 self.trmanager = None
1443 1443 # set of common changeset between local and remote before pull
1444 1444 self.common = None
1445 1445 # set of pulled head
1446 1446 self.rheads = None
1447 1447 # list of missing changeset to fetch remotely
1448 1448 self.fetch = None
1449 1449 # remote bookmarks data
1450 1450 self.remotebookmarks = remotebookmarks
1451 1451 # result of changegroup pulling (used as return code by pull)
1452 1452 self.cgresult = None
1453 1453 # list of step already done
1454 1454 self.stepsdone = set()
1455 1455 # Whether we attempted a clone from pre-generated bundles.
1456 1456 self.clonebundleattempted = False
1457 1457 # Set of file patterns to include.
1458 1458 self.includepats = includepats
1459 1459 # Set of file patterns to exclude.
1460 1460 self.excludepats = excludepats
1461 1461 # Number of ancestor changesets to pull from each pulled head.
1462 1462 self.depth = depth
1463 1463
1464 1464 @util.propertycache
1465 1465 def pulledsubset(self):
1466 1466 """heads of the set of changeset target by the pull"""
1467 1467 # compute target subset
1468 1468 if self.heads is None:
1469 1469 # We pulled every thing possible
1470 1470 # sync on everything common
1471 1471 c = set(self.common)
1472 1472 ret = list(self.common)
1473 1473 for n in self.rheads:
1474 1474 if n not in c:
1475 1475 ret.append(n)
1476 1476 return ret
1477 1477 else:
1478 1478 # We pulled a specific subset
1479 1479 # sync on this subset
1480 1480 return self.heads
1481 1481
1482 1482 @util.propertycache
1483 1483 def canusebundle2(self):
1484 1484 return not _forcebundle1(self)
1485 1485
1486 1486 @util.propertycache
1487 1487 def remotebundle2caps(self):
1488 1488 return bundle2.bundle2caps(self.remote)
1489 1489
1490 1490 def gettransaction(self):
1491 1491 # deprecated; talk to trmanager directly
1492 1492 return self.trmanager.transaction()
1493 1493
1494 1494
1495 1495 class transactionmanager(util.transactional):
1496 1496 """An object to manage the life cycle of a transaction
1497 1497
1498 1498 It creates the transaction on demand and calls the appropriate hooks when
1499 1499 closing the transaction."""
1500 1500
1501 1501 def __init__(self, repo, source, url):
1502 1502 self.repo = repo
1503 1503 self.source = source
1504 1504 self.url = url
1505 1505 self._tr = None
1506 1506
1507 1507 def transaction(self):
1508 1508 """Return an open transaction object, constructing if necessary"""
1509 1509 if not self._tr:
1510 1510 trname = b'%s\n%s' % (self.source, urlutil.hidepassword(self.url))
1511 1511 self._tr = self.repo.transaction(trname)
1512 1512 self._tr.hookargs[b'source'] = self.source
1513 1513 self._tr.hookargs[b'url'] = self.url
1514 1514 return self._tr
1515 1515
1516 1516 def close(self):
1517 1517 """close transaction if created"""
1518 1518 if self._tr is not None:
1519 1519 self._tr.close()
1520 1520
1521 1521 def release(self):
1522 1522 """release transaction if created"""
1523 1523 if self._tr is not None:
1524 1524 self._tr.release()
1525 1525
1526 1526
1527 1527 def listkeys(remote, namespace):
1528 1528 with remote.commandexecutor() as e:
1529 1529 return e.callcommand(b'listkeys', {b'namespace': namespace}).result()
1530 1530
1531 1531
1532 1532 def _fullpullbundle2(repo, pullop):
1533 1533 # The server may send a partial reply, i.e. when inlining
1534 1534 # pre-computed bundles. In that case, update the common
1535 1535 # set based on the results and pull another bundle.
1536 1536 #
1537 1537 # There are two indicators that the process is finished:
1538 1538 # - no changeset has been added, or
1539 1539 # - all remote heads are known locally.
1540 1540 # The head check must use the unfiltered view as obsoletion
1541 1541 # markers can hide heads.
1542 1542 unfi = repo.unfiltered()
1543 1543 unficl = unfi.changelog
1544 1544
1545 1545 def headsofdiff(h1, h2):
1546 1546 """Returns heads(h1 % h2)"""
1547 1547 res = unfi.set(b'heads(%ln %% %ln)', h1, h2)
1548 1548 return {ctx.node() for ctx in res}
1549 1549
1550 1550 def headsofunion(h1, h2):
1551 1551 """Returns heads((h1 + h2) - null)"""
1552 1552 res = unfi.set(b'heads((%ln + %ln - null))', h1, h2)
1553 1553 return {ctx.node() for ctx in res}
1554 1554
1555 1555 while True:
1556 1556 old_heads = unficl.heads()
1557 1557 clstart = len(unficl)
1558 1558 _pullbundle2(pullop)
1559 1559 if requirements.NARROW_REQUIREMENT in repo.requirements:
1560 1560 # XXX narrow clones filter the heads on the server side during
1561 1561 # XXX getbundle and result in partial replies as well.
1562 1562 # XXX Disable pull bundles in this case as band aid to avoid
1563 1563 # XXX extra round trips.
1564 1564 break
1565 1565 if clstart == len(unficl):
1566 1566 break
1567 1567 if all(unficl.hasnode(n) for n in pullop.rheads):
1568 1568 break
1569 1569 new_heads = headsofdiff(unficl.heads(), old_heads)
1570 1570 pullop.common = headsofunion(new_heads, pullop.common)
1571 1571 pullop.rheads = set(pullop.rheads) - pullop.common
1572 1572
1573 1573
1574 1574 def add_confirm_callback(repo, pullop):
1575 1575 """adds a finalize callback to transaction which can be used to show stats
1576 1576 to user and confirm the pull before committing transaction"""
1577 1577
1578 1578 tr = pullop.trmanager.transaction()
1579 1579 scmutil.registersummarycallback(
1580 1580 repo, tr, txnname=b'pull', as_validator=True
1581 1581 )
1582 1582 reporef = weakref.ref(repo.unfiltered())
1583 1583
1584 1584 def prompt(tr):
1585 1585 repo = reporef()
1586 1586 cm = _(b'accept incoming changes (yn)?$$ &Yes $$ &No')
1587 1587 if repo.ui.promptchoice(cm):
1588 1588 raise error.Abort(b"user aborted")
1589 1589
1590 1590 tr.addvalidator(b'900-pull-prompt', prompt)
1591 1591
1592 1592
1593 1593 def pull(
1594 1594 repo,
1595 1595 remote,
1596 1596 path=None,
1597 1597 heads=None,
1598 1598 force=False,
1599 1599 bookmarks=(),
1600 1600 opargs=None,
1601 1601 streamclonerequested=None,
1602 1602 includepats=None,
1603 1603 excludepats=None,
1604 1604 depth=None,
1605 1605 confirm=None,
1606 1606 ):
1607 1607 """Fetch repository data from a remote.
1608 1608
1609 1609 This is the main function used to retrieve data from a remote repository.
1610 1610
1611 1611 ``repo`` is the local repository to clone into.
1612 1612 ``remote`` is a peer instance.
1613 1613 ``heads`` is an iterable of revisions we want to pull. ``None`` (the
1614 1614 default) means to pull everything from the remote.
1615 1615 ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
1616 1616 default, all remote bookmarks are pulled.
1617 1617 ``opargs`` are additional keyword arguments to pass to ``pulloperation``
1618 1618 initialization.
1619 1619 ``streamclonerequested`` is a boolean indicating whether a "streaming
1620 1620 clone" is requested. A "streaming clone" is essentially a raw file copy
1621 1621 of revlogs from the server. This only works when the local repository is
1622 1622 empty. The default value of ``None`` means to respect the server
1623 1623 configuration for preferring stream clones.
1624 1624 ``includepats`` and ``excludepats`` define explicit file patterns to
1625 1625 include and exclude in storage, respectively. If not defined, narrow
1626 1626 patterns from the repo instance are used, if available.
1627 1627 ``depth`` is an integer indicating the DAG depth of history we're
1628 1628 interested in. If defined, for each revision specified in ``heads``, we
1629 1629 will fetch up to this many of its ancestors and data associated with them.
1630 1630 ``confirm`` is a boolean indicating whether the pull should be confirmed
1631 1631 before committing the transaction. This overrides HGPLAIN.
1632 1632
1633 1633 Returns the ``pulloperation`` created for this pull.
1634 1634 """
1635 1635 if opargs is None:
1636 1636 opargs = {}
1637 1637
1638 1638 # We allow the narrow patterns to be passed in explicitly to provide more
1639 1639 # flexibility for API consumers.
1640 1640 if includepats or excludepats:
1641 1641 includepats = includepats or set()
1642 1642 excludepats = excludepats or set()
1643 1643 else:
1644 1644 includepats, excludepats = repo.narrowpats
1645 1645
1646 1646 narrowspec.validatepatterns(includepats)
1647 1647 narrowspec.validatepatterns(excludepats)
1648 1648
1649 1649 pullop = pulloperation(
1650 1650 repo,
1651 1651 remote,
1652 1652 path=path,
1653 1653 heads=heads,
1654 1654 force=force,
1655 1655 bookmarks=bookmarks,
1656 1656 streamclonerequested=streamclonerequested,
1657 1657 includepats=includepats,
1658 1658 excludepats=excludepats,
1659 1659 depth=depth,
1660 1660 **pycompat.strkwargs(opargs)
1661 1661 )
1662 1662
1663 1663 peerlocal = pullop.remote.local()
1664 1664 if peerlocal:
1665 1665 missing = set(peerlocal.requirements) - pullop.repo.supported
1666 1666 if missing:
1667 1667 msg = _(
1668 1668 b"required features are not"
1669 1669 b" supported in the destination:"
1670 1670 b" %s"
1671 1671 ) % (b', '.join(sorted(missing)))
1672 1672 raise error.Abort(msg)
1673 1673
1674 1674 for category in repo._wanted_sidedata:
1675 1675 # Check that a computer is registered for that category for at least
1676 1676 # one revlog kind.
1677 1677 for kind, computers in repo._sidedata_computers.items():
1678 1678 if computers.get(category):
1679 1679 break
1680 1680 else:
1681 1681 # This should never happen since repos are supposed to be able to
1682 1682 # generate the sidedata they require.
1683 1683 raise error.ProgrammingError(
1684 1684 _(
1685 1685 b'sidedata category requested by local side without local'
1686 1686 b"support: '%s'"
1687 1687 )
1688 1688 % pycompat.bytestr(category)
1689 1689 )
1690 1690
1691 1691 pullop.trmanager = transactionmanager(repo, b'pull', remote.url())
1692 1692 wlock = util.nullcontextmanager()
1693 1693 if not bookmod.bookmarksinstore(repo):
1694 1694 wlock = repo.wlock()
1695 1695 with wlock, repo.lock(), pullop.trmanager:
1696 1696 if confirm or (
1697 1697 repo.ui.configbool(b"pull", b"confirm") and not repo.ui.plain()
1698 1698 ):
1699 1699 add_confirm_callback(repo, pullop)
1700 1700
1701 1701 # This should ideally be in _pullbundle2(). However, it needs to run
1702 1702 # before discovery to avoid extra work.
1703 1703 _maybeapplyclonebundle(pullop)
1704 1704 streamclone.maybeperformlegacystreamclone(pullop)
1705 1705 _pulldiscovery(pullop)
1706 1706 if pullop.canusebundle2:
1707 1707 _fullpullbundle2(repo, pullop)
1708 1708 _pullchangeset(pullop)
1709 1709 _pullphase(pullop)
1710 1710 _pullbookmarks(pullop)
1711 1711 _pullobsolete(pullop)
1712 1712
1713 1713 # storing remotenames
1714 1714 if repo.ui.configbool(b'experimental', b'remotenames'):
1715 1715 logexchange.pullremotenames(repo, remote)
1716 1716
1717 1717 return pullop
1718 1718
1719 1719
1720 1720 # list of steps to perform discovery before pull
1721 1721 pulldiscoveryorder = []
1722 1722
1723 1723 # Mapping between step name and function
1724 1724 #
1725 1725 # This exists to help extensions wrap steps if necessary
1726 1726 pulldiscoverymapping = {}
1727 1727
1728 1728
1729 1729 def pulldiscovery(stepname):
1730 1730 """decorator for function performing discovery before pull
1731 1731
1732 1732 The function is added to the step -> function mapping and appended to the
1733 1733 list of steps. Beware that decorated function will be added in order (this
1734 1734 may matter).
1735 1735
1736 1736 You can only use this decorator for a new step, if you want to wrap a step
1737 1737 from an extension, change the pulldiscovery dictionary directly."""
1738 1738
1739 1739 def dec(func):
1740 1740 assert stepname not in pulldiscoverymapping
1741 1741 pulldiscoverymapping[stepname] = func
1742 1742 pulldiscoveryorder.append(stepname)
1743 1743 return func
1744 1744
1745 1745 return dec
1746 1746
1747 1747
1748 1748 def _pulldiscovery(pullop):
1749 1749 """Run all discovery steps"""
1750 1750 for stepname in pulldiscoveryorder:
1751 1751 step = pulldiscoverymapping[stepname]
1752 1752 step(pullop)
1753 1753
1754 1754
1755 1755 @pulldiscovery(b'b1:bookmarks')
1756 1756 def _pullbookmarkbundle1(pullop):
1757 1757 """fetch bookmark data in bundle1 case
1758 1758
1759 1759 If not using bundle2, we have to fetch bookmarks before changeset
1760 1760 discovery to reduce the chance and impact of race conditions."""
1761 1761 if pullop.remotebookmarks is not None:
1762 1762 return
1763 1763 if pullop.canusebundle2 and b'listkeys' in pullop.remotebundle2caps:
1764 1764 # all known bundle2 servers now support listkeys, but lets be nice with
1765 1765 # new implementation.
1766 1766 return
1767 1767 books = listkeys(pullop.remote, b'bookmarks')
1768 1768 pullop.remotebookmarks = bookmod.unhexlifybookmarks(books)
1769 1769
1770 1770
1771 1771 @pulldiscovery(b'changegroup')
1772 1772 def _pulldiscoverychangegroup(pullop):
1773 1773 """discovery phase for the pull
1774 1774
1775 1775 Current handle changeset discovery only, will change handle all discovery
1776 1776 at some point."""
1777 1777 tmp = discovery.findcommonincoming(
1778 1778 pullop.repo, pullop.remote, heads=pullop.heads, force=pullop.force
1779 1779 )
1780 1780 common, fetch, rheads = tmp
1781 1781 has_node = pullop.repo.unfiltered().changelog.index.has_node
1782 1782 if fetch and rheads:
1783 1783 # If a remote heads is filtered locally, put in back in common.
1784 1784 #
1785 1785 # This is a hackish solution to catch most of "common but locally
1786 1786 # hidden situation". We do not performs discovery on unfiltered
1787 1787 # repository because it end up doing a pathological amount of round
1788 1788 # trip for w huge amount of changeset we do not care about.
1789 1789 #
1790 1790 # If a set of such "common but filtered" changeset exist on the server
1791 1791 # but are not including a remote heads, we'll not be able to detect it,
1792 1792 scommon = set(common)
1793 1793 for n in rheads:
1794 1794 if has_node(n):
1795 1795 if n not in scommon:
1796 1796 common.append(n)
1797 1797 if set(rheads).issubset(set(common)):
1798 1798 fetch = []
1799 1799 pullop.common = common
1800 1800 pullop.fetch = fetch
1801 1801 pullop.rheads = rheads
1802 1802
1803 1803
1804 1804 def _pullbundle2(pullop):
1805 1805 """pull data using bundle2
1806 1806
1807 1807 For now, the only supported data are changegroup."""
1808 1808 kwargs = {b'bundlecaps': caps20to10(pullop.repo, role=b'client')}
1809 1809
1810 1810 # make ui easier to access
1811 1811 ui = pullop.repo.ui
1812 1812
1813 1813 # At the moment we don't do stream clones over bundle2. If that is
1814 1814 # implemented then here's where the check for that will go.
1815 1815 streaming = streamclone.canperformstreamclone(pullop, bundle2=True)[0]
1816 1816
1817 1817 # declare pull perimeters
1818 1818 kwargs[b'common'] = pullop.common
1819 1819 kwargs[b'heads'] = pullop.heads or pullop.rheads
1820 1820
1821 1821 # check server supports narrow and then adding includepats and excludepats
1822 1822 servernarrow = pullop.remote.capable(wireprototypes.NARROWCAP)
1823 1823 if servernarrow and pullop.includepats:
1824 1824 kwargs[b'includepats'] = pullop.includepats
1825 1825 if servernarrow and pullop.excludepats:
1826 1826 kwargs[b'excludepats'] = pullop.excludepats
1827 1827
1828 1828 if streaming:
1829 1829 kwargs[b'cg'] = False
1830 1830 kwargs[b'stream'] = True
1831 1831 pullop.stepsdone.add(b'changegroup')
1832 1832 pullop.stepsdone.add(b'phases')
1833 1833
1834 1834 else:
1835 1835 # pulling changegroup
1836 1836 pullop.stepsdone.add(b'changegroup')
1837 1837
1838 1838 kwargs[b'cg'] = pullop.fetch
1839 1839
1840 1840 legacyphase = b'phases' in ui.configlist(b'devel', b'legacy.exchange')
1841 1841 hasbinaryphase = b'heads' in pullop.remotebundle2caps.get(b'phases', ())
1842 1842 if not legacyphase and hasbinaryphase:
1843 1843 kwargs[b'phases'] = True
1844 1844 pullop.stepsdone.add(b'phases')
1845 1845
1846 1846 if b'listkeys' in pullop.remotebundle2caps:
1847 1847 if b'phases' not in pullop.stepsdone:
1848 1848 kwargs[b'listkeys'] = [b'phases']
1849 1849
1850 1850 bookmarksrequested = False
1851 1851 legacybookmark = b'bookmarks' in ui.configlist(b'devel', b'legacy.exchange')
1852 1852 hasbinarybook = b'bookmarks' in pullop.remotebundle2caps
1853 1853
1854 1854 if pullop.remotebookmarks is not None:
1855 1855 pullop.stepsdone.add(b'request-bookmarks')
1856 1856
1857 1857 if (
1858 1858 b'request-bookmarks' not in pullop.stepsdone
1859 1859 and pullop.remotebookmarks is None
1860 1860 and not legacybookmark
1861 1861 and hasbinarybook
1862 1862 ):
1863 1863 kwargs[b'bookmarks'] = True
1864 1864 bookmarksrequested = True
1865 1865
1866 1866 if b'listkeys' in pullop.remotebundle2caps:
1867 1867 if b'request-bookmarks' not in pullop.stepsdone:
1868 1868 # make sure to always includes bookmark data when migrating
1869 1869 # `hg incoming --bundle` to using this function.
1870 1870 pullop.stepsdone.add(b'request-bookmarks')
1871 1871 kwargs.setdefault(b'listkeys', []).append(b'bookmarks')
1872 1872
1873 1873 # If this is a full pull / clone and the server supports the clone bundles
1874 1874 # feature, tell the server whether we attempted a clone bundle. The
1875 1875 # presence of this flag indicates the client supports clone bundles. This
1876 1876 # will enable the server to treat clients that support clone bundles
1877 1877 # differently from those that don't.
1878 1878 if (
1879 1879 pullop.remote.capable(b'clonebundles')
1880 1880 and pullop.heads is None
1881 1881 and list(pullop.common) == [pullop.repo.nullid]
1882 1882 ):
1883 1883 kwargs[b'cbattempted'] = pullop.clonebundleattempted
1884 1884
1885 1885 if streaming:
1886 1886 pullop.repo.ui.status(_(b'streaming all changes\n'))
1887 1887 elif not pullop.fetch:
1888 1888 pullop.repo.ui.status(_(b"no changes found\n"))
1889 1889 pullop.cgresult = 0
1890 1890 else:
1891 1891 if pullop.heads is None and list(pullop.common) == [pullop.repo.nullid]:
1892 1892 pullop.repo.ui.status(_(b"requesting all changes\n"))
1893 1893 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1894 1894 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
1895 1895 if obsolete.commonversion(remoteversions) is not None:
1896 1896 kwargs[b'obsmarkers'] = True
1897 1897 pullop.stepsdone.add(b'obsmarkers')
1898 1898 _pullbundle2extraprepare(pullop, kwargs)
1899 1899
1900 1900 remote_sidedata = bundle2.read_remote_wanted_sidedata(pullop.remote)
1901 1901 if remote_sidedata:
1902 1902 kwargs[b'remote_sidedata'] = remote_sidedata
1903 1903
1904 1904 with pullop.remote.commandexecutor() as e:
1905 1905 args = dict(kwargs)
1906 1906 args[b'source'] = b'pull'
1907 1907 bundle = e.callcommand(b'getbundle', args).result()
1908 1908
1909 1909 try:
1910 1910 op = bundle2.bundleoperation(
1911 1911 pullop.repo,
1912 1912 pullop.gettransaction,
1913 1913 source=b'pull',
1914 1914 remote=pullop.remote,
1915 1915 )
1916 1916 op.modes[b'bookmarks'] = b'records'
1917 1917 bundle2.processbundle(
1918 1918 pullop.repo,
1919 1919 bundle,
1920 1920 op=op,
1921 1921 remote=pullop.remote,
1922 1922 )
1923 1923 except bundle2.AbortFromPart as exc:
1924 1924 pullop.repo.ui.error(_(b'remote: abort: %s\n') % exc)
1925 1925 raise error.RemoteError(_(b'pull failed on remote'), hint=exc.hint)
1926 1926 except error.BundleValueError as exc:
1927 1927 raise error.RemoteError(_(b'missing support for %s') % exc)
1928 1928
1929 1929 if pullop.fetch:
1930 1930 pullop.cgresult = bundle2.combinechangegroupresults(op)
1931 1931
1932 1932 # processing phases change
1933 1933 for namespace, value in op.records[b'listkeys']:
1934 1934 if namespace == b'phases':
1935 1935 _pullapplyphases(pullop, value)
1936 1936
1937 1937 # processing bookmark update
1938 1938 if bookmarksrequested:
1939 1939 books = {}
1940 1940 for record in op.records[b'bookmarks']:
1941 1941 books[record[b'bookmark']] = record[b"node"]
1942 1942 pullop.remotebookmarks = books
1943 1943 else:
1944 1944 for namespace, value in op.records[b'listkeys']:
1945 1945 if namespace == b'bookmarks':
1946 1946 pullop.remotebookmarks = bookmod.unhexlifybookmarks(value)
1947 1947
1948 1948 # bookmark data were either already there or pulled in the bundle
1949 1949 if pullop.remotebookmarks is not None:
1950 1950 _pullbookmarks(pullop)
1951 1951
1952 1952
1953 1953 def _pullbundle2extraprepare(pullop, kwargs):
1954 1954 """hook function so that extensions can extend the getbundle call"""
1955 1955
1956 1956
1957 1957 def _pullchangeset(pullop):
1958 1958 """pull changeset from unbundle into the local repo"""
1959 1959 # We delay the open of the transaction as late as possible so we
1960 1960 # don't open transaction for nothing or you break future useful
1961 1961 # rollback call
1962 1962 if b'changegroup' in pullop.stepsdone:
1963 1963 return
1964 1964 pullop.stepsdone.add(b'changegroup')
1965 1965 if not pullop.fetch:
1966 1966 pullop.repo.ui.status(_(b"no changes found\n"))
1967 1967 pullop.cgresult = 0
1968 1968 return
1969 1969 tr = pullop.gettransaction()
1970 1970 if pullop.heads is None and list(pullop.common) == [pullop.repo.nullid]:
1971 1971 pullop.repo.ui.status(_(b"requesting all changes\n"))
1972 1972 elif pullop.heads is None and pullop.remote.capable(b'changegroupsubset'):
1973 1973 # issue1320, avoid a race if remote changed after discovery
1974 1974 pullop.heads = pullop.rheads
1975 1975
1976 1976 if pullop.remote.capable(b'getbundle'):
1977 1977 # TODO: get bundlecaps from remote
1978 1978 cg = pullop.remote.getbundle(
1979 1979 b'pull', common=pullop.common, heads=pullop.heads or pullop.rheads
1980 1980 )
1981 1981 elif pullop.heads is None:
1982 1982 with pullop.remote.commandexecutor() as e:
1983 1983 cg = e.callcommand(
1984 1984 b'changegroup',
1985 1985 {
1986 1986 b'nodes': pullop.fetch,
1987 1987 b'source': b'pull',
1988 1988 },
1989 1989 ).result()
1990 1990
1991 1991 elif not pullop.remote.capable(b'changegroupsubset'):
1992 1992 raise error.Abort(
1993 1993 _(
1994 1994 b"partial pull cannot be done because "
1995 1995 b"other repository doesn't support "
1996 1996 b"changegroupsubset."
1997 1997 )
1998 1998 )
1999 1999 else:
2000 2000 with pullop.remote.commandexecutor() as e:
2001 2001 cg = e.callcommand(
2002 2002 b'changegroupsubset',
2003 2003 {
2004 2004 b'bases': pullop.fetch,
2005 2005 b'heads': pullop.heads,
2006 2006 b'source': b'pull',
2007 2007 },
2008 2008 ).result()
2009 2009
2010 2010 bundleop = bundle2.applybundle(
2011 2011 pullop.repo,
2012 2012 cg,
2013 2013 tr,
2014 2014 b'pull',
2015 2015 pullop.remote.url(),
2016 2016 remote=pullop.remote,
2017 2017 )
2018 2018 pullop.cgresult = bundle2.combinechangegroupresults(bundleop)
2019 2019
2020 2020
2021 2021 def _pullphase(pullop):
2022 2022 # Get remote phases data from remote
2023 2023 if b'phases' in pullop.stepsdone:
2024 2024 return
2025 2025 remotephases = listkeys(pullop.remote, b'phases')
2026 2026 _pullapplyphases(pullop, remotephases)
2027 2027
2028 2028
2029 2029 def _pullapplyphases(pullop, remotephases):
2030 2030 """apply phase movement from observed remote state"""
2031 2031 if b'phases' in pullop.stepsdone:
2032 2032 return
2033 2033 pullop.stepsdone.add(b'phases')
2034 2034 publishing = bool(remotephases.get(b'publishing', False))
2035 2035 if remotephases and not publishing:
2036 2036 # remote is new and non-publishing
2037 2037 pheads, _dr = phases.analyzeremotephases(
2038 2038 pullop.repo, pullop.pulledsubset, remotephases
2039 2039 )
2040 2040 dheads = pullop.pulledsubset
2041 2041 else:
2042 2042 # Remote is old or publishing all common changesets
2043 2043 # should be seen as public
2044 2044 pheads = pullop.pulledsubset
2045 2045 dheads = []
2046 2046 unfi = pullop.repo.unfiltered()
2047 2047 phase = unfi._phasecache.phase
2048 2048 rev = unfi.changelog.index.get_rev
2049 2049 public = phases.public
2050 2050 draft = phases.draft
2051 2051
2052 2052 # exclude changesets already public locally and update the others
2053 2053 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
2054 2054 if pheads:
2055 2055 tr = pullop.gettransaction()
2056 2056 phases.advanceboundary(pullop.repo, tr, public, pheads)
2057 2057
2058 2058 # exclude changesets already draft locally and update the others
2059 2059 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
2060 2060 if dheads:
2061 2061 tr = pullop.gettransaction()
2062 2062 phases.advanceboundary(pullop.repo, tr, draft, dheads)
2063 2063
2064 2064
2065 2065 def _pullbookmarks(pullop):
2066 2066 """process the remote bookmark information to update the local one"""
2067 2067 if b'bookmarks' in pullop.stepsdone:
2068 2068 return
2069 2069 pullop.stepsdone.add(b'bookmarks')
2070 2070 repo = pullop.repo
2071 2071 remotebookmarks = pullop.remotebookmarks
2072 2072 bookmarks_mode = None
2073 2073 if pullop.remote_path is not None:
2074 2074 bookmarks_mode = pullop.remote_path.bookmarks_mode
2075 2075 bookmod.updatefromremote(
2076 2076 repo.ui,
2077 2077 repo,
2078 2078 remotebookmarks,
2079 2079 pullop.remote.url(),
2080 2080 pullop.gettransaction,
2081 2081 explicit=pullop.explicitbookmarks,
2082 2082 mode=bookmarks_mode,
2083 2083 )
2084 2084
2085 2085
2086 2086 def _pullobsolete(pullop):
2087 2087 """utility function to pull obsolete markers from a remote
2088 2088
2089 2089 The `gettransaction` is function that return the pull transaction, creating
2090 2090 one if necessary. We return the transaction to inform the calling code that
2091 2091 a new transaction have been created (when applicable).
2092 2092
2093 2093 Exists mostly to allow overriding for experimentation purpose"""
2094 2094 if b'obsmarkers' in pullop.stepsdone:
2095 2095 return
2096 2096 pullop.stepsdone.add(b'obsmarkers')
2097 2097 tr = None
2098 2098 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
2099 2099 pullop.repo.ui.debug(b'fetching remote obsolete markers\n')
2100 2100 remoteobs = listkeys(pullop.remote, b'obsolete')
2101 2101 if b'dump0' in remoteobs:
2102 2102 tr = pullop.gettransaction()
2103 2103 markers = []
2104 2104 for key in sorted(remoteobs, reverse=True):
2105 2105 if key.startswith(b'dump'):
2106 2106 data = util.b85decode(remoteobs[key])
2107 2107 version, newmarks = obsolete._readmarkers(data)
2108 2108 markers += newmarks
2109 2109 if markers:
2110 2110 pullop.repo.obsstore.add(tr, markers)
2111 2111 pullop.repo.invalidatevolatilesets()
2112 2112 return tr
2113 2113
2114 2114
2115 2115 def applynarrowacl(repo, kwargs):
2116 2116 """Apply narrow fetch access control.
2117 2117
2118 2118 This massages the named arguments for getbundle wire protocol commands
2119 2119 so requested data is filtered through access control rules.
2120 2120 """
2121 2121 ui = repo.ui
2122 2122 # TODO this assumes existence of HTTP and is a layering violation.
2123 2123 username = ui.shortuser(ui.environ.get(b'REMOTE_USER') or ui.username())
2124 2124 user_includes = ui.configlist(
2125 2125 _NARROWACL_SECTION,
2126 2126 username + b'.includes',
2127 2127 ui.configlist(_NARROWACL_SECTION, b'default.includes'),
2128 2128 )
2129 2129 user_excludes = ui.configlist(
2130 2130 _NARROWACL_SECTION,
2131 2131 username + b'.excludes',
2132 2132 ui.configlist(_NARROWACL_SECTION, b'default.excludes'),
2133 2133 )
2134 2134 if not user_includes:
2135 2135 raise error.Abort(
2136 2136 _(b"%s configuration for user %s is empty")
2137 2137 % (_NARROWACL_SECTION, username)
2138 2138 )
2139 2139
2140 2140 user_includes = [
2141 2141 b'path:.' if p == b'*' else b'path:' + p for p in user_includes
2142 2142 ]
2143 2143 user_excludes = [
2144 2144 b'path:.' if p == b'*' else b'path:' + p for p in user_excludes
2145 2145 ]
2146 2146
2147 2147 req_includes = set(kwargs.get('includepats', []))
2148 2148 req_excludes = set(kwargs.get('excludepats', []))
2149 2149
2150 2150 req_includes, req_excludes, invalid_includes = narrowspec.restrictpatterns(
2151 2151 req_includes, req_excludes, user_includes, user_excludes
2152 2152 )
2153 2153
2154 2154 if invalid_includes:
2155 2155 raise error.Abort(
2156 2156 _(b"The following includes are not accessible for %s: %s")
2157 2157 % (username, stringutil.pprint(invalid_includes))
2158 2158 )
2159 2159
2160 2160 new_args = {}
2161 2161 new_args.update(kwargs)
2162 2162 new_args['narrow'] = True
2163 2163 new_args['narrow_acl'] = True
2164 2164 new_args['includepats'] = req_includes
2165 2165 if req_excludes:
2166 2166 new_args['excludepats'] = req_excludes
2167 2167
2168 2168 return new_args
2169 2169
2170 2170
2171 2171 def _computeellipsis(repo, common, heads, known, match, depth=None):
2172 2172 """Compute the shape of a narrowed DAG.
2173 2173
2174 2174 Args:
2175 2175 repo: The repository we're transferring.
2176 2176 common: The roots of the DAG range we're transferring.
2177 2177 May be just [nullid], which means all ancestors of heads.
2178 2178 heads: The heads of the DAG range we're transferring.
2179 2179 match: The narrowmatcher that allows us to identify relevant changes.
2180 2180 depth: If not None, only consider nodes to be full nodes if they are at
2181 2181 most depth changesets away from one of heads.
2182 2182
2183 2183 Returns:
2184 2184 A tuple of (visitnodes, relevant_nodes, ellipsisroots) where:
2185 2185
2186 2186 visitnodes: The list of nodes (either full or ellipsis) which
2187 2187 need to be sent to the client.
2188 2188 relevant_nodes: The set of changelog nodes which change a file inside
2189 2189 the narrowspec. The client needs these as non-ellipsis nodes.
2190 2190 ellipsisroots: A dict of {rev: parents} that is used in
2191 2191 narrowchangegroup to produce ellipsis nodes with the
2192 2192 correct parents.
2193 2193 """
2194 2194 cl = repo.changelog
2195 2195 mfl = repo.manifestlog
2196 2196
2197 2197 clrev = cl.rev
2198 2198
2199 2199 commonrevs = {clrev(n) for n in common} | {nullrev}
2200 2200 headsrevs = {clrev(n) for n in heads}
2201 2201
2202 2202 if depth:
2203 2203 revdepth = {h: 0 for h in headsrevs}
2204 2204
2205 2205 ellipsisheads = collections.defaultdict(set)
2206 2206 ellipsisroots = collections.defaultdict(set)
2207 2207
2208 2208 def addroot(head, curchange):
2209 2209 """Add a root to an ellipsis head, splitting heads with 3 roots."""
2210 2210 ellipsisroots[head].add(curchange)
2211 2211 # Recursively split ellipsis heads with 3 roots by finding the
2212 2212 # roots' youngest common descendant which is an elided merge commit.
2213 2213 # That descendant takes 2 of the 3 roots as its own, and becomes a
2214 2214 # root of the head.
2215 2215 while len(ellipsisroots[head]) > 2:
2216 2216 child, roots = splithead(head)
2217 2217 splitroots(head, child, roots)
2218 2218 head = child # Recurse in case we just added a 3rd root
2219 2219
2220 2220 def splitroots(head, child, roots):
2221 2221 ellipsisroots[head].difference_update(roots)
2222 2222 ellipsisroots[head].add(child)
2223 2223 ellipsisroots[child].update(roots)
2224 2224 ellipsisroots[child].discard(child)
2225 2225
2226 2226 def splithead(head):
2227 2227 r1, r2, r3 = sorted(ellipsisroots[head])
2228 2228 for nr1, nr2 in ((r2, r3), (r1, r3), (r1, r2)):
2229 2229 mid = repo.revs(
2230 2230 b'sort(merge() & %d::%d & %d::%d, -rev)', nr1, head, nr2, head
2231 2231 )
2232 2232 for j in mid:
2233 2233 if j == nr2:
2234 2234 return nr2, (nr1, nr2)
2235 2235 if j not in ellipsisroots or len(ellipsisroots[j]) < 2:
2236 2236 return j, (nr1, nr2)
2237 2237 raise error.Abort(
2238 2238 _(
2239 2239 b'Failed to split up ellipsis node! head: %d, '
2240 2240 b'roots: %d %d %d'
2241 2241 )
2242 2242 % (head, r1, r2, r3)
2243 2243 )
2244 2244
2245 2245 missing = list(cl.findmissingrevs(common=commonrevs, heads=headsrevs))
2246 2246 visit = reversed(missing)
2247 2247 relevant_nodes = set()
2248 2248 visitnodes = [cl.node(m) for m in missing]
2249 2249 required = set(headsrevs) | known
2250 2250 for rev in visit:
2251 2251 clrev = cl.changelogrevision(rev)
2252 2252 ps = [prev for prev in cl.parentrevs(rev) if prev != nullrev]
2253 2253 if depth is not None:
2254 2254 curdepth = revdepth[rev]
2255 2255 for p in ps:
2256 2256 revdepth[p] = min(curdepth + 1, revdepth.get(p, depth + 1))
2257 2257 needed = False
2258 2258 shallow_enough = depth is None or revdepth[rev] <= depth
2259 2259 if shallow_enough:
2260 2260 curmf = mfl[clrev.manifest].read()
2261 2261 if ps:
2262 2262 # We choose to not trust the changed files list in
2263 2263 # changesets because it's not always correct. TODO: could
2264 2264 # we trust it for the non-merge case?
2265 2265 p1mf = mfl[cl.changelogrevision(ps[0]).manifest].read()
2266 2266 needed = bool(curmf.diff(p1mf, match))
2267 2267 if not needed and len(ps) > 1:
2268 2268 # For merge changes, the list of changed files is not
2269 2269 # helpful, since we need to emit the merge if a file
2270 2270 # in the narrow spec has changed on either side of the
2271 2271 # merge. As a result, we do a manifest diff to check.
2272 2272 p2mf = mfl[cl.changelogrevision(ps[1]).manifest].read()
2273 2273 needed = bool(curmf.diff(p2mf, match))
2274 2274 else:
2275 2275 # For a root node, we need to include the node if any
2276 2276 # files in the node match the narrowspec.
2277 2277 needed = any(curmf.walk(match))
2278 2278
2279 2279 if needed:
2280 2280 for head in ellipsisheads[rev]:
2281 2281 addroot(head, rev)
2282 2282 for p in ps:
2283 2283 required.add(p)
2284 2284 relevant_nodes.add(cl.node(rev))
2285 2285 else:
2286 2286 if not ps:
2287 2287 ps = [nullrev]
2288 2288 if rev in required:
2289 2289 for head in ellipsisheads[rev]:
2290 2290 addroot(head, rev)
2291 2291 for p in ps:
2292 2292 ellipsisheads[p].add(rev)
2293 2293 else:
2294 2294 for p in ps:
2295 2295 ellipsisheads[p] |= ellipsisheads[rev]
2296 2296
2297 2297 # add common changesets as roots of their reachable ellipsis heads
2298 2298 for c in commonrevs:
2299 2299 for head in ellipsisheads[c]:
2300 2300 addroot(head, c)
2301 2301 return visitnodes, relevant_nodes, ellipsisroots
2302 2302
2303 2303
2304 2304 def caps20to10(repo, role):
2305 2305 """return a set with appropriate options to use bundle20 during getbundle"""
2306 2306 caps = {b'HG20'}
2307 2307 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role=role))
2308 2308 caps.add(b'bundle2=' + urlreq.quote(capsblob))
2309 2309 return caps
2310 2310
2311 2311
2312 2312 # List of names of steps to perform for a bundle2 for getbundle, order matters.
2313 2313 getbundle2partsorder = []
2314 2314
2315 2315 # Mapping between step name and function
2316 2316 #
2317 2317 # This exists to help extensions wrap steps if necessary
2318 2318 getbundle2partsmapping = {}
2319 2319
2320 2320
2321 2321 def getbundle2partsgenerator(stepname, idx=None):
2322 2322 """decorator for function generating bundle2 part for getbundle
2323 2323
2324 2324 The function is added to the step -> function mapping and appended to the
2325 2325 list of steps. Beware that decorated functions will be added in order
2326 2326 (this may matter).
2327 2327
2328 2328 You can only use this decorator for new steps, if you want to wrap a step
2329 2329 from an extension, attack the getbundle2partsmapping dictionary directly."""
2330 2330
2331 2331 def dec(func):
2332 2332 assert stepname not in getbundle2partsmapping
2333 2333 getbundle2partsmapping[stepname] = func
2334 2334 if idx is None:
2335 2335 getbundle2partsorder.append(stepname)
2336 2336 else:
2337 2337 getbundle2partsorder.insert(idx, stepname)
2338 2338 return func
2339 2339
2340 2340 return dec
2341 2341
2342 2342
2343 2343 def bundle2requested(bundlecaps):
2344 2344 if bundlecaps is not None:
2345 2345 return any(cap.startswith(b'HG2') for cap in bundlecaps)
2346 2346 return False
2347 2347
2348 2348
2349 2349 def getbundlechunks(
2350 2350 repo,
2351 2351 source,
2352 2352 heads=None,
2353 2353 common=None,
2354 2354 bundlecaps=None,
2355 2355 remote_sidedata=None,
2356 2356 **kwargs
2357 2357 ):
2358 2358 """Return chunks constituting a bundle's raw data.
2359 2359
2360 2360 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
2361 2361 passed.
2362 2362
2363 2363 Returns a 2-tuple of a dict with metadata about the generated bundle
2364 2364 and an iterator over raw chunks (of varying sizes).
2365 2365 """
2366 2366 kwargs = pycompat.byteskwargs(kwargs)
2367 2367 info = {}
2368 2368 usebundle2 = bundle2requested(bundlecaps)
2369 2369 # bundle10 case
2370 2370 if not usebundle2:
2371 2371 if bundlecaps and not kwargs.get(b'cg', True):
2372 2372 raise ValueError(
2373 2373 _(b'request for bundle10 must include changegroup')
2374 2374 )
2375 2375
2376 2376 if kwargs:
2377 2377 raise ValueError(
2378 2378 _(b'unsupported getbundle arguments: %s')
2379 2379 % b', '.join(sorted(kwargs.keys()))
2380 2380 )
2381 2381 outgoing = _computeoutgoing(repo, heads, common)
2382 2382 info[b'bundleversion'] = 1
2383 2383 return (
2384 2384 info,
2385 2385 changegroup.makestream(
2386 2386 repo,
2387 2387 outgoing,
2388 2388 b'01',
2389 2389 source,
2390 2390 bundlecaps=bundlecaps,
2391 2391 remote_sidedata=remote_sidedata,
2392 2392 ),
2393 2393 )
2394 2394
2395 2395 # bundle20 case
2396 2396 info[b'bundleversion'] = 2
2397 2397 b2caps = {}
2398 2398 for bcaps in bundlecaps:
2399 2399 if bcaps.startswith(b'bundle2='):
2400 2400 blob = urlreq.unquote(bcaps[len(b'bundle2=') :])
2401 2401 b2caps.update(bundle2.decodecaps(blob))
2402 2402 bundler = bundle2.bundle20(repo.ui, b2caps)
2403 2403
2404 2404 kwargs[b'heads'] = heads
2405 2405 kwargs[b'common'] = common
2406 2406
2407 2407 for name in getbundle2partsorder:
2408 2408 func = getbundle2partsmapping[name]
2409 2409 func(
2410 2410 bundler,
2411 2411 repo,
2412 2412 source,
2413 2413 bundlecaps=bundlecaps,
2414 2414 b2caps=b2caps,
2415 2415 remote_sidedata=remote_sidedata,
2416 2416 **pycompat.strkwargs(kwargs)
2417 2417 )
2418 2418
2419 2419 info[b'prefercompressed'] = bundler.prefercompressed
2420 2420
2421 2421 return info, bundler.getchunks()
2422 2422
2423 2423
2424 @getbundle2partsgenerator(b'stream2')
2424 @getbundle2partsgenerator(b'stream')
2425 2425 def _getbundlestream2(bundler, repo, *args, **kwargs):
2426 2426 return bundle2.addpartbundlestream2(bundler, repo, **kwargs)
2427 2427
2428 2428
2429 2429 @getbundle2partsgenerator(b'changegroup')
2430 2430 def _getbundlechangegrouppart(
2431 2431 bundler,
2432 2432 repo,
2433 2433 source,
2434 2434 bundlecaps=None,
2435 2435 b2caps=None,
2436 2436 heads=None,
2437 2437 common=None,
2438 2438 remote_sidedata=None,
2439 2439 **kwargs
2440 2440 ):
2441 2441 """add a changegroup part to the requested bundle"""
2442 2442 if not kwargs.get('cg', True) or not b2caps:
2443 2443 return
2444 2444
2445 2445 version = b'01'
2446 2446 cgversions = b2caps.get(b'changegroup')
2447 2447 if cgversions: # 3.1 and 3.2 ship with an empty value
2448 2448 cgversions = [
2449 2449 v
2450 2450 for v in cgversions
2451 2451 if v in changegroup.supportedoutgoingversions(repo)
2452 2452 ]
2453 2453 if not cgversions:
2454 2454 raise error.Abort(_(b'no common changegroup version'))
2455 2455 version = max(cgversions)
2456 2456
2457 2457 outgoing = _computeoutgoing(repo, heads, common)
2458 2458 if not outgoing.missing:
2459 2459 return
2460 2460
2461 2461 if kwargs.get('narrow', False):
2462 2462 include = sorted(filter(bool, kwargs.get('includepats', [])))
2463 2463 exclude = sorted(filter(bool, kwargs.get('excludepats', [])))
2464 2464 matcher = narrowspec.match(repo.root, include=include, exclude=exclude)
2465 2465 else:
2466 2466 matcher = None
2467 2467
2468 2468 cgstream = changegroup.makestream(
2469 2469 repo,
2470 2470 outgoing,
2471 2471 version,
2472 2472 source,
2473 2473 bundlecaps=bundlecaps,
2474 2474 matcher=matcher,
2475 2475 remote_sidedata=remote_sidedata,
2476 2476 )
2477 2477
2478 2478 part = bundler.newpart(b'changegroup', data=cgstream)
2479 2479 if cgversions:
2480 2480 part.addparam(b'version', version)
2481 2481
2482 2482 part.addparam(b'nbchanges', b'%d' % len(outgoing.missing), mandatory=False)
2483 2483
2484 2484 if scmutil.istreemanifest(repo):
2485 2485 part.addparam(b'treemanifest', b'1')
2486 2486
2487 2487 if repository.REPO_FEATURE_SIDE_DATA in repo.features:
2488 2488 part.addparam(b'exp-sidedata', b'1')
2489 2489 sidedata = bundle2.format_remote_wanted_sidedata(repo)
2490 2490 part.addparam(b'exp-wanted-sidedata', sidedata)
2491 2491
2492 2492 if (
2493 2493 kwargs.get('narrow', False)
2494 2494 and kwargs.get('narrow_acl', False)
2495 2495 and (include or exclude)
2496 2496 ):
2497 2497 # this is mandatory because otherwise ACL clients won't work
2498 2498 narrowspecpart = bundler.newpart(b'Narrow:responsespec')
2499 2499 narrowspecpart.data = b'%s\0%s' % (
2500 2500 b'\n'.join(include),
2501 2501 b'\n'.join(exclude),
2502 2502 )
2503 2503
2504 2504
2505 2505 @getbundle2partsgenerator(b'bookmarks')
2506 2506 def _getbundlebookmarkpart(
2507 2507 bundler, repo, source, bundlecaps=None, b2caps=None, **kwargs
2508 2508 ):
2509 2509 """add a bookmark part to the requested bundle"""
2510 2510 if not kwargs.get('bookmarks', False):
2511 2511 return
2512 2512 if not b2caps or b'bookmarks' not in b2caps:
2513 2513 raise error.Abort(_(b'no common bookmarks exchange method'))
2514 2514 books = bookmod.listbinbookmarks(repo)
2515 2515 data = bookmod.binaryencode(repo, books)
2516 2516 if data:
2517 2517 bundler.newpart(b'bookmarks', data=data)
2518 2518
2519 2519
2520 2520 @getbundle2partsgenerator(b'listkeys')
2521 2521 def _getbundlelistkeysparts(
2522 2522 bundler, repo, source, bundlecaps=None, b2caps=None, **kwargs
2523 2523 ):
2524 2524 """add parts containing listkeys namespaces to the requested bundle"""
2525 2525 listkeys = kwargs.get('listkeys', ())
2526 2526 for namespace in listkeys:
2527 2527 part = bundler.newpart(b'listkeys')
2528 2528 part.addparam(b'namespace', namespace)
2529 2529 keys = repo.listkeys(namespace).items()
2530 2530 part.data = pushkey.encodekeys(keys)
2531 2531
2532 2532
2533 2533 @getbundle2partsgenerator(b'obsmarkers')
2534 2534 def _getbundleobsmarkerpart(
2535 2535 bundler, repo, source, bundlecaps=None, b2caps=None, heads=None, **kwargs
2536 2536 ):
2537 2537 """add an obsolescence markers part to the requested bundle"""
2538 2538 if kwargs.get('obsmarkers', False):
2539 2539 if heads is None:
2540 2540 heads = repo.heads()
2541 2541 subset = [c.node() for c in repo.set(b'::%ln', heads)]
2542 2542 markers = repo.obsstore.relevantmarkers(subset)
2543 2543 markers = obsutil.sortedmarkers(markers)
2544 2544 bundle2.buildobsmarkerspart(bundler, markers)
2545 2545
2546 2546
2547 2547 @getbundle2partsgenerator(b'phases')
2548 2548 def _getbundlephasespart(
2549 2549 bundler, repo, source, bundlecaps=None, b2caps=None, heads=None, **kwargs
2550 2550 ):
2551 2551 """add phase heads part to the requested bundle"""
2552 2552 if kwargs.get('phases', False):
2553 2553 if not b2caps or b'heads' not in b2caps.get(b'phases'):
2554 2554 raise error.Abort(_(b'no common phases exchange method'))
2555 2555 if heads is None:
2556 2556 heads = repo.heads()
2557 2557
2558 2558 headsbyphase = collections.defaultdict(set)
2559 2559 if repo.publishing():
2560 2560 headsbyphase[phases.public] = heads
2561 2561 else:
2562 2562 # find the appropriate heads to move
2563 2563
2564 2564 phase = repo._phasecache.phase
2565 2565 node = repo.changelog.node
2566 2566 rev = repo.changelog.rev
2567 2567 for h in heads:
2568 2568 headsbyphase[phase(repo, rev(h))].add(h)
2569 2569 seenphases = list(headsbyphase.keys())
2570 2570
2571 2571 # We do not handle anything but public and draft phase for now)
2572 2572 if seenphases:
2573 2573 assert max(seenphases) <= phases.draft
2574 2574
2575 2575 # if client is pulling non-public changesets, we need to find
2576 2576 # intermediate public heads.
2577 2577 draftheads = headsbyphase.get(phases.draft, set())
2578 2578 if draftheads:
2579 2579 publicheads = headsbyphase.get(phases.public, set())
2580 2580
2581 2581 revset = b'heads(only(%ln, %ln) and public())'
2582 2582 extraheads = repo.revs(revset, draftheads, publicheads)
2583 2583 for r in extraheads:
2584 2584 headsbyphase[phases.public].add(node(r))
2585 2585
2586 2586 # transform data in a format used by the encoding function
2587 2587 phasemapping = {
2588 2588 phase: sorted(headsbyphase[phase]) for phase in phases.allphases
2589 2589 }
2590 2590
2591 2591 # generate the actual part
2592 2592 phasedata = phases.binaryencode(phasemapping)
2593 2593 bundler.newpart(b'phase-heads', data=phasedata)
2594 2594
2595 2595
2596 2596 @getbundle2partsgenerator(b'hgtagsfnodes')
2597 2597 def _getbundletagsfnodes(
2598 2598 bundler,
2599 2599 repo,
2600 2600 source,
2601 2601 bundlecaps=None,
2602 2602 b2caps=None,
2603 2603 heads=None,
2604 2604 common=None,
2605 2605 **kwargs
2606 2606 ):
2607 2607 """Transfer the .hgtags filenodes mapping.
2608 2608
2609 2609 Only values for heads in this bundle will be transferred.
2610 2610
2611 2611 The part data consists of pairs of 20 byte changeset node and .hgtags
2612 2612 filenodes raw values.
2613 2613 """
2614 2614 # Don't send unless:
2615 2615 # - changeset are being exchanged,
2616 2616 # - the client supports it.
2617 2617 if not b2caps or not (kwargs.get('cg', True) and b'hgtagsfnodes' in b2caps):
2618 2618 return
2619 2619
2620 2620 outgoing = _computeoutgoing(repo, heads, common)
2621 2621 bundle2.addparttagsfnodescache(repo, bundler, outgoing)
2622 2622
2623 2623
2624 2624 @getbundle2partsgenerator(b'cache:rev-branch-cache')
2625 2625 def _getbundlerevbranchcache(
2626 2626 bundler,
2627 2627 repo,
2628 2628 source,
2629 2629 bundlecaps=None,
2630 2630 b2caps=None,
2631 2631 heads=None,
2632 2632 common=None,
2633 2633 **kwargs
2634 2634 ):
2635 2635 """Transfer the rev-branch-cache mapping
2636 2636
2637 2637 The payload is a series of data related to each branch
2638 2638
2639 2639 1) branch name length
2640 2640 2) number of open heads
2641 2641 3) number of closed heads
2642 2642 4) open heads nodes
2643 2643 5) closed heads nodes
2644 2644 """
2645 2645 # Don't send unless:
2646 2646 # - changeset are being exchanged,
2647 2647 # - the client supports it.
2648 2648 # - narrow bundle isn't in play (not currently compatible).
2649 2649 if (
2650 2650 not kwargs.get('cg', True)
2651 2651 or not b2caps
2652 2652 or b'rev-branch-cache' not in b2caps
2653 2653 or kwargs.get('narrow', False)
2654 2654 or repo.ui.has_section(_NARROWACL_SECTION)
2655 2655 ):
2656 2656 return
2657 2657
2658 2658 outgoing = _computeoutgoing(repo, heads, common)
2659 2659 bundle2.addpartrevbranchcache(repo, bundler, outgoing)
2660 2660
2661 2661
2662 2662 def check_heads(repo, their_heads, context):
2663 2663 """check if the heads of a repo have been modified
2664 2664
2665 2665 Used by peer for unbundling.
2666 2666 """
2667 2667 heads = repo.heads()
2668 2668 heads_hash = hashutil.sha1(b''.join(sorted(heads))).digest()
2669 2669 if not (
2670 2670 their_heads == [b'force']
2671 2671 or their_heads == heads
2672 2672 or their_heads == [b'hashed', heads_hash]
2673 2673 ):
2674 2674 # someone else committed/pushed/unbundled while we
2675 2675 # were transferring data
2676 2676 raise error.PushRaced(
2677 2677 b'repository changed while %s - please try again' % context
2678 2678 )
2679 2679
2680 2680
2681 2681 def unbundle(repo, cg, heads, source, url):
2682 2682 """Apply a bundle to a repo.
2683 2683
2684 2684 this function makes sure the repo is locked during the application and have
2685 2685 mechanism to check that no push race occurred between the creation of the
2686 2686 bundle and its application.
2687 2687
2688 2688 If the push was raced as PushRaced exception is raised."""
2689 2689 r = 0
2690 2690 # need a transaction when processing a bundle2 stream
2691 2691 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
2692 2692 lockandtr = [None, None, None]
2693 2693 recordout = None
2694 2694 # quick fix for output mismatch with bundle2 in 3.4
2695 2695 captureoutput = repo.ui.configbool(
2696 2696 b'experimental', b'bundle2-output-capture'
2697 2697 )
2698 2698 if url.startswith(b'remote:http:') or url.startswith(b'remote:https:'):
2699 2699 captureoutput = True
2700 2700 try:
2701 2701 # note: outside bundle1, 'heads' is expected to be empty and this
2702 2702 # 'check_heads' call wil be a no-op
2703 2703 check_heads(repo, heads, b'uploading changes')
2704 2704 # push can proceed
2705 2705 if not isinstance(cg, bundle2.unbundle20):
2706 2706 # legacy case: bundle1 (changegroup 01)
2707 2707 txnname = b"\n".join([source, urlutil.hidepassword(url)])
2708 2708 with repo.lock(), repo.transaction(txnname) as tr:
2709 2709 op = bundle2.applybundle(repo, cg, tr, source, url)
2710 2710 r = bundle2.combinechangegroupresults(op)
2711 2711 else:
2712 2712 r = None
2713 2713 try:
2714 2714
2715 2715 def gettransaction():
2716 2716 if not lockandtr[2]:
2717 2717 if not bookmod.bookmarksinstore(repo):
2718 2718 lockandtr[0] = repo.wlock()
2719 2719 lockandtr[1] = repo.lock()
2720 2720 lockandtr[2] = repo.transaction(source)
2721 2721 lockandtr[2].hookargs[b'source'] = source
2722 2722 lockandtr[2].hookargs[b'url'] = url
2723 2723 lockandtr[2].hookargs[b'bundle2'] = b'1'
2724 2724 return lockandtr[2]
2725 2725
2726 2726 # Do greedy locking by default until we're satisfied with lazy
2727 2727 # locking.
2728 2728 if not repo.ui.configbool(
2729 2729 b'experimental', b'bundle2lazylocking'
2730 2730 ):
2731 2731 gettransaction()
2732 2732
2733 2733 op = bundle2.bundleoperation(
2734 2734 repo,
2735 2735 gettransaction,
2736 2736 captureoutput=captureoutput,
2737 2737 source=b'push',
2738 2738 )
2739 2739 try:
2740 2740 op = bundle2.processbundle(repo, cg, op=op)
2741 2741 finally:
2742 2742 r = op.reply
2743 2743 if captureoutput and r is not None:
2744 2744 repo.ui.pushbuffer(error=True, subproc=True)
2745 2745
2746 2746 def recordout(output):
2747 2747 r.newpart(b'output', data=output, mandatory=False)
2748 2748
2749 2749 if lockandtr[2] is not None:
2750 2750 lockandtr[2].close()
2751 2751 except BaseException as exc:
2752 2752 exc.duringunbundle2 = True
2753 2753 if captureoutput and r is not None:
2754 2754 parts = exc._bundle2salvagedoutput = r.salvageoutput()
2755 2755
2756 2756 def recordout(output):
2757 2757 part = bundle2.bundlepart(
2758 2758 b'output', data=output, mandatory=False
2759 2759 )
2760 2760 parts.append(part)
2761 2761
2762 2762 raise
2763 2763 finally:
2764 2764 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
2765 2765 if recordout is not None:
2766 2766 recordout(repo.ui.popbuffer())
2767 2767 return r
2768 2768
2769 2769
2770 2770 def _maybeapplyclonebundle(pullop):
2771 2771 """Apply a clone bundle from a remote, if possible."""
2772 2772
2773 2773 repo = pullop.repo
2774 2774 remote = pullop.remote
2775 2775
2776 2776 if not repo.ui.configbool(b'ui', b'clonebundles'):
2777 2777 return
2778 2778
2779 2779 # Only run if local repo is empty.
2780 2780 if len(repo):
2781 2781 return
2782 2782
2783 2783 if pullop.heads:
2784 2784 return
2785 2785
2786 2786 if not remote.capable(b'clonebundles'):
2787 2787 return
2788 2788
2789 2789 with remote.commandexecutor() as e:
2790 2790 res = e.callcommand(b'clonebundles', {}).result()
2791 2791
2792 2792 # If we call the wire protocol command, that's good enough to record the
2793 2793 # attempt.
2794 2794 pullop.clonebundleattempted = True
2795 2795
2796 2796 entries = bundlecaches.parseclonebundlesmanifest(repo, res)
2797 2797 if not entries:
2798 2798 repo.ui.note(
2799 2799 _(
2800 2800 b'no clone bundles available on remote; '
2801 2801 b'falling back to regular clone\n'
2802 2802 )
2803 2803 )
2804 2804 return
2805 2805
2806 2806 entries = bundlecaches.filterclonebundleentries(
2807 2807 repo, entries, streamclonerequested=pullop.streamclonerequested
2808 2808 )
2809 2809
2810 2810 if not entries:
2811 2811 # There is a thundering herd concern here. However, if a server
2812 2812 # operator doesn't advertise bundles appropriate for its clients,
2813 2813 # they deserve what's coming. Furthermore, from a client's
2814 2814 # perspective, no automatic fallback would mean not being able to
2815 2815 # clone!
2816 2816 repo.ui.warn(
2817 2817 _(
2818 2818 b'no compatible clone bundles available on server; '
2819 2819 b'falling back to regular clone\n'
2820 2820 )
2821 2821 )
2822 2822 repo.ui.warn(
2823 2823 _(b'(you may want to report this to the server operator)\n')
2824 2824 )
2825 2825 return
2826 2826
2827 2827 entries = bundlecaches.sortclonebundleentries(repo.ui, entries)
2828 2828
2829 2829 url = entries[0][b'URL']
2830 2830 repo.ui.status(_(b'applying clone bundle from %s\n') % url)
2831 2831 if trypullbundlefromurl(repo.ui, repo, url):
2832 2832 repo.ui.status(_(b'finished applying clone bundle\n'))
2833 2833 # Bundle failed.
2834 2834 #
2835 2835 # We abort by default to avoid the thundering herd of
2836 2836 # clients flooding a server that was expecting expensive
2837 2837 # clone load to be offloaded.
2838 2838 elif repo.ui.configbool(b'ui', b'clonebundlefallback'):
2839 2839 repo.ui.warn(_(b'falling back to normal clone\n'))
2840 2840 else:
2841 2841 raise error.Abort(
2842 2842 _(b'error applying bundle'),
2843 2843 hint=_(
2844 2844 b'if this error persists, consider contacting '
2845 2845 b'the server operator or disable clone '
2846 2846 b'bundles via '
2847 2847 b'"--config ui.clonebundles=false"'
2848 2848 ),
2849 2849 )
2850 2850
2851 2851
2852 2852 def trypullbundlefromurl(ui, repo, url):
2853 2853 """Attempt to apply a bundle from a URL."""
2854 2854 with repo.lock(), repo.transaction(b'bundleurl') as tr:
2855 2855 try:
2856 2856 fh = urlmod.open(ui, url)
2857 2857 cg = readbundle(ui, fh, b'stream')
2858 2858
2859 2859 if isinstance(cg, streamclone.streamcloneapplier):
2860 2860 cg.apply(repo)
2861 2861 else:
2862 2862 bundle2.applybundle(repo, cg, tr, b'clonebundles', url)
2863 2863 return True
2864 2864 except urlerr.httperror as e:
2865 2865 ui.warn(
2866 2866 _(b'HTTP error fetching bundle: %s\n')
2867 2867 % stringutil.forcebytestr(e)
2868 2868 )
2869 2869 except urlerr.urlerror as e:
2870 2870 ui.warn(
2871 2871 _(b'error fetching bundle: %s\n')
2872 2872 % stringutil.forcebytestr(e.reason)
2873 2873 )
2874 2874
2875 2875 return False
@@ -1,962 +1,966
1 1 # streamclone.py - producing and consuming streaming repository data
2 2 #
3 3 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8
9 9 import contextlib
10 10 import os
11 11 import struct
12 12
13 13 from .i18n import _
14 14 from .pycompat import open
15 15 from .interfaces import repository
16 16 from . import (
17 17 bookmarks,
18 18 bundle2 as bundle2mod,
19 19 cacheutil,
20 20 error,
21 21 narrowspec,
22 22 phases,
23 23 pycompat,
24 24 requirements as requirementsmod,
25 25 scmutil,
26 26 store,
27 27 transaction,
28 28 util,
29 29 )
30 30 from .revlogutils import (
31 31 nodemap,
32 32 )
33 33
34 34
35 35 def new_stream_clone_requirements(default_requirements, streamed_requirements):
36 36 """determine the final set of requirement for a new stream clone
37 37
38 38 this method combine the "default" requirements that a new repository would
39 39 use with the constaint we get from the stream clone content. We keep local
40 40 configuration choice when possible.
41 41 """
42 42 requirements = set(default_requirements)
43 43 requirements -= requirementsmod.STREAM_FIXED_REQUIREMENTS
44 44 requirements.update(streamed_requirements)
45 45 return requirements
46 46
47 47
48 48 def streamed_requirements(repo):
49 49 """the set of requirement the new clone will have to support
50 50
51 51 This is used for advertising the stream options and to generate the actual
52 52 stream content."""
53 53 requiredformats = (
54 54 repo.requirements & requirementsmod.STREAM_FIXED_REQUIREMENTS
55 55 )
56 56 return requiredformats
57 57
58 58
59 59 def canperformstreamclone(pullop, bundle2=False):
60 60 """Whether it is possible to perform a streaming clone as part of pull.
61 61
62 62 ``bundle2`` will cause the function to consider stream clone through
63 63 bundle2 and only through bundle2.
64 64
65 65 Returns a tuple of (supported, requirements). ``supported`` is True if
66 66 streaming clone is supported and False otherwise. ``requirements`` is
67 67 a set of repo requirements from the remote, or ``None`` if stream clone
68 68 isn't supported.
69 69 """
70 70 repo = pullop.repo
71 71 remote = pullop.remote
72 72
73 73 # should we consider streaming clone at all ?
74 74 streamrequested = pullop.streamclonerequested
75 75 # If we don't have a preference, let the server decide for us. This
76 76 # likely only comes into play in LANs.
77 77 if streamrequested is None:
78 78 # The server can advertise whether to prefer streaming clone.
79 79 streamrequested = remote.capable(b'stream-preferred')
80 80 if not streamrequested:
81 81 return False, None
82 82
83 83 # Streaming clone only works on an empty destination repository
84 84 if len(repo):
85 85 return False, None
86 86
87 87 # Streaming clone only works if all data is being requested.
88 88 if pullop.heads:
89 89 return False, None
90 90
91 91 bundle2supported = False
92 92 if pullop.canusebundle2:
93 93 local_caps = bundle2mod.getrepocaps(repo, role=b'client')
94 94 local_supported = set(local_caps.get(b'stream', []))
95 95 remote_supported = set(pullop.remotebundle2caps.get(b'stream', []))
96 96 bundle2supported = bool(local_supported & remote_supported)
97 97 # else
98 98 # Server doesn't support bundle2 stream clone or doesn't support
99 99 # the versions we support. Fall back and possibly allow legacy.
100 100
101 101 # Ensures legacy code path uses available bundle2.
102 102 if bundle2supported and not bundle2:
103 103 return False, None
104 104 # Ensures bundle2 doesn't try to do a stream clone if it isn't supported.
105 105 elif bundle2 and not bundle2supported:
106 106 return False, None
107 107
108 108 # In order for stream clone to work, the client has to support all the
109 109 # requirements advertised by the server.
110 110 #
111 111 # The server advertises its requirements via the "stream" and "streamreqs"
112 112 # capability. "stream" (a value-less capability) is advertised if and only
113 113 # if the only requirement is "revlogv1." Else, the "streamreqs" capability
114 114 # is advertised and contains a comma-delimited list of requirements.
115 115 requirements = set()
116 116 if remote.capable(b'stream'):
117 117 requirements.add(requirementsmod.REVLOGV1_REQUIREMENT)
118 118 else:
119 119 streamreqs = remote.capable(b'streamreqs')
120 120 # This is weird and shouldn't happen with modern servers.
121 121 if not streamreqs:
122 122 pullop.repo.ui.warn(
123 123 _(
124 124 b'warning: stream clone requested but server has them '
125 125 b'disabled\n'
126 126 )
127 127 )
128 128 return False, None
129 129
130 130 streamreqs = set(streamreqs.split(b','))
131 131 # Server requires something we don't support. Bail.
132 132 missingreqs = streamreqs - repo.supported
133 133 if missingreqs:
134 134 pullop.repo.ui.warn(
135 135 _(
136 136 b'warning: stream clone requested but client is missing '
137 137 b'requirements: %s\n'
138 138 )
139 139 % b', '.join(sorted(missingreqs))
140 140 )
141 141 pullop.repo.ui.warn(
142 142 _(
143 143 b'(see https://www.mercurial-scm.org/wiki/MissingRequirement '
144 144 b'for more information)\n'
145 145 )
146 146 )
147 147 return False, None
148 148 requirements = streamreqs
149 149
150 150 return True, requirements
151 151
152 152
153 153 def maybeperformlegacystreamclone(pullop):
154 154 """Possibly perform a legacy stream clone operation.
155 155
156 156 Legacy stream clones are performed as part of pull but before all other
157 157 operations.
158 158
159 159 A legacy stream clone will not be performed if a bundle2 stream clone is
160 160 supported.
161 161 """
162 162 from . import localrepo
163 163
164 164 supported, requirements = canperformstreamclone(pullop)
165 165
166 166 if not supported:
167 167 return
168 168
169 169 repo = pullop.repo
170 170 remote = pullop.remote
171 171
172 172 # Save remote branchmap. We will use it later to speed up branchcache
173 173 # creation.
174 174 rbranchmap = None
175 175 if remote.capable(b'branchmap'):
176 176 with remote.commandexecutor() as e:
177 177 rbranchmap = e.callcommand(b'branchmap', {}).result()
178 178
179 179 repo.ui.status(_(b'streaming all changes\n'))
180 180
181 181 with remote.commandexecutor() as e:
182 182 fp = e.callcommand(b'stream_out', {}).result()
183 183
184 184 # TODO strictly speaking, this code should all be inside the context
185 185 # manager because the context manager is supposed to ensure all wire state
186 186 # is flushed when exiting. But the legacy peers don't do this, so it
187 187 # doesn't matter.
188 188 l = fp.readline()
189 189 try:
190 190 resp = int(l)
191 191 except ValueError:
192 192 raise error.ResponseError(
193 193 _(b'unexpected response from remote server:'), l
194 194 )
195 195 if resp == 1:
196 196 raise error.Abort(_(b'operation forbidden by server'))
197 197 elif resp == 2:
198 198 raise error.Abort(_(b'locking the remote repository failed'))
199 199 elif resp != 0:
200 200 raise error.Abort(_(b'the server sent an unknown error code'))
201 201
202 202 l = fp.readline()
203 203 try:
204 204 filecount, bytecount = map(int, l.split(b' ', 1))
205 205 except (ValueError, TypeError):
206 206 raise error.ResponseError(
207 207 _(b'unexpected response from remote server:'), l
208 208 )
209 209
210 210 with repo.lock():
211 211 consumev1(repo, fp, filecount, bytecount)
212 212 repo.requirements = new_stream_clone_requirements(
213 213 repo.requirements,
214 214 requirements,
215 215 )
216 216 repo.svfs.options = localrepo.resolvestorevfsoptions(
217 217 repo.ui, repo.requirements, repo.features
218 218 )
219 219 scmutil.writereporequirements(repo)
220 220 nodemap.post_stream_cleanup(repo)
221 221
222 222 if rbranchmap:
223 223 repo._branchcaches.replace(repo, rbranchmap)
224 224
225 225 repo.invalidate()
226 226
227 227
228 228 def allowservergeneration(repo):
229 229 """Whether streaming clones are allowed from the server."""
230 230 if repository.REPO_FEATURE_STREAM_CLONE not in repo.features:
231 231 return False
232 232
233 233 if not repo.ui.configbool(b'server', b'uncompressed', untrusted=True):
234 234 return False
235 235
236 236 # The way stream clone works makes it impossible to hide secret changesets.
237 237 # So don't allow this by default.
238 238 secret = phases.hassecret(repo)
239 239 if secret:
240 240 return repo.ui.configbool(b'server', b'uncompressedallowsecret')
241 241
242 242 return True
243 243
244 244
245 245 # This is it's own function so extensions can override it.
246 246 def _walkstreamfiles(repo, matcher=None, phase=False, obsolescence=False):
247 247 return repo.store.walk(matcher, phase=phase, obsolescence=obsolescence)
248 248
249 249
250 250 def generatev1(repo):
251 251 """Emit content for version 1 of a streaming clone.
252 252
253 253 This returns a 3-tuple of (file count, byte size, data iterator).
254 254
255 255 The data iterator consists of N entries for each file being transferred.
256 256 Each file entry starts as a line with the file name and integer size
257 257 delimited by a null byte.
258 258
259 259 The raw file data follows. Following the raw file data is the next file
260 260 entry, or EOF.
261 261
262 262 When used on the wire protocol, an additional line indicating protocol
263 263 success will be prepended to the stream. This function is not responsible
264 264 for adding it.
265 265
266 266 This function will obtain a repository lock to ensure a consistent view of
267 267 the store is captured. It therefore may raise LockError.
268 268 """
269 269 entries = []
270 270 total_bytes = 0
271 271 # Get consistent snapshot of repo, lock during scan.
272 272 with repo.lock():
273 273 repo.ui.debug(b'scanning\n')
274 274 for entry in _walkstreamfiles(repo):
275 275 for f in entry.files():
276 276 file_size = f.file_size(repo.store.vfs)
277 277 if file_size:
278 278 entries.append((f.unencoded_path, file_size))
279 279 total_bytes += file_size
280 280 _test_sync_point_walk_1(repo)
281 281 _test_sync_point_walk_2(repo)
282 282
283 283 repo.ui.debug(
284 284 b'%d files, %d bytes to transfer\n' % (len(entries), total_bytes)
285 285 )
286 286
287 287 svfs = repo.svfs
288 288 debugflag = repo.ui.debugflag
289 289
290 290 def emitrevlogdata():
291 291 for name, size in entries:
292 292 if debugflag:
293 293 repo.ui.debug(b'sending %s (%d bytes)\n' % (name, size))
294 294 # partially encode name over the wire for backwards compat
295 295 yield b'%s\0%d\n' % (store.encodedir(name), size)
296 296 # auditing at this stage is both pointless (paths are already
297 297 # trusted by the local repo) and expensive
298 298 with svfs(name, b'rb', auditpath=False) as fp:
299 299 if size <= 65536:
300 300 yield fp.read(size)
301 301 else:
302 302 for chunk in util.filechunkiter(fp, limit=size):
303 303 yield chunk
304 304
305 305 return len(entries), total_bytes, emitrevlogdata()
306 306
307 307
308 308 def generatev1wireproto(repo):
309 309 """Emit content for version 1 of streaming clone suitable for the wire.
310 310
311 311 This is the data output from ``generatev1()`` with 2 header lines. The
312 312 first line indicates overall success. The 2nd contains the file count and
313 313 byte size of payload.
314 314
315 315 The success line contains "0" for success, "1" for stream generation not
316 316 allowed, and "2" for error locking the repository (possibly indicating
317 317 a permissions error for the server process).
318 318 """
319 319 if not allowservergeneration(repo):
320 320 yield b'1\n'
321 321 return
322 322
323 323 try:
324 324 filecount, bytecount, it = generatev1(repo)
325 325 except error.LockError:
326 326 yield b'2\n'
327 327 return
328 328
329 329 # Indicates successful response.
330 330 yield b'0\n'
331 331 yield b'%d %d\n' % (filecount, bytecount)
332 332 for chunk in it:
333 333 yield chunk
334 334
335 335
336 336 def generatebundlev1(repo, compression=b'UN'):
337 337 """Emit content for version 1 of a stream clone bundle.
338 338
339 339 The first 4 bytes of the output ("HGS1") denote this as stream clone
340 340 bundle version 1.
341 341
342 342 The next 2 bytes indicate the compression type. Only "UN" is currently
343 343 supported.
344 344
345 345 The next 16 bytes are two 64-bit big endian unsigned integers indicating
346 346 file count and byte count, respectively.
347 347
348 348 The next 2 bytes is a 16-bit big endian unsigned short declaring the length
349 349 of the requirements string, including a trailing \0. The following N bytes
350 350 are the requirements string, which is ASCII containing a comma-delimited
351 351 list of repo requirements that are needed to support the data.
352 352
353 353 The remaining content is the output of ``generatev1()`` (which may be
354 354 compressed in the future).
355 355
356 356 Returns a tuple of (requirements, data generator).
357 357 """
358 358 if compression != b'UN':
359 359 raise ValueError(b'we do not support the compression argument yet')
360 360
361 361 requirements = streamed_requirements(repo)
362 362 requires = b','.join(sorted(requirements))
363 363
364 364 def gen():
365 365 yield b'HGS1'
366 366 yield compression
367 367
368 368 filecount, bytecount, it = generatev1(repo)
369 369 repo.ui.status(
370 370 _(b'writing %d bytes for %d files\n') % (bytecount, filecount)
371 371 )
372 372
373 373 yield struct.pack(b'>QQ', filecount, bytecount)
374 374 yield struct.pack(b'>H', len(requires) + 1)
375 375 yield requires + b'\0'
376 376
377 377 # This is where we'll add compression in the future.
378 378 assert compression == b'UN'
379 379
380 380 progress = repo.ui.makeprogress(
381 381 _(b'bundle'), total=bytecount, unit=_(b'bytes')
382 382 )
383 383 progress.update(0)
384 384
385 385 for chunk in it:
386 386 progress.increment(step=len(chunk))
387 387 yield chunk
388 388
389 389 progress.complete()
390 390
391 391 return requirements, gen()
392 392
393 393
394 394 def consumev1(repo, fp, filecount, bytecount):
395 395 """Apply the contents from version 1 of a streaming clone file handle.
396 396
397 397 This takes the output from "stream_out" and applies it to the specified
398 398 repository.
399 399
400 400 Like "stream_out," the status line added by the wire protocol is not
401 401 handled by this function.
402 402 """
403 403 with repo.lock():
404 404 repo.ui.status(
405 405 _(b'%d files to transfer, %s of data\n')
406 406 % (filecount, util.bytecount(bytecount))
407 407 )
408 408 progress = repo.ui.makeprogress(
409 409 _(b'clone'), total=bytecount, unit=_(b'bytes')
410 410 )
411 411 progress.update(0)
412 412 start = util.timer()
413 413
414 414 # TODO: get rid of (potential) inconsistency
415 415 #
416 416 # If transaction is started and any @filecache property is
417 417 # changed at this point, it causes inconsistency between
418 418 # in-memory cached property and streamclone-ed file on the
419 419 # disk. Nested transaction prevents transaction scope "clone"
420 420 # below from writing in-memory changes out at the end of it,
421 421 # even though in-memory changes are discarded at the end of it
422 422 # regardless of transaction nesting.
423 423 #
424 424 # But transaction nesting can't be simply prohibited, because
425 425 # nesting occurs also in ordinary case (e.g. enabling
426 426 # clonebundles).
427 427
428 428 with repo.transaction(b'clone'):
429 429 with repo.svfs.backgroundclosing(repo.ui, expectedcount=filecount):
430 430 for i in range(filecount):
431 431 # XXX doesn't support '\n' or '\r' in filenames
432 432 l = fp.readline()
433 433 try:
434 434 name, size = l.split(b'\0', 1)
435 435 size = int(size)
436 436 except (ValueError, TypeError):
437 437 raise error.ResponseError(
438 438 _(b'unexpected response from remote server:'), l
439 439 )
440 440 if repo.ui.debugflag:
441 441 repo.ui.debug(
442 442 b'adding %s (%s)\n' % (name, util.bytecount(size))
443 443 )
444 444 # for backwards compat, name was partially encoded
445 445 path = store.decodedir(name)
446 446 with repo.svfs(path, b'w', backgroundclose=True) as ofp:
447 447 for chunk in util.filechunkiter(fp, limit=size):
448 448 progress.increment(step=len(chunk))
449 449 ofp.write(chunk)
450 450
451 451 # force @filecache properties to be reloaded from
452 452 # streamclone-ed file at next access
453 453 repo.invalidate(clearfilecache=True)
454 454
455 455 elapsed = util.timer() - start
456 456 if elapsed <= 0:
457 457 elapsed = 0.001
458 458 progress.complete()
459 459 repo.ui.status(
460 460 _(b'transferred %s in %.1f seconds (%s/sec)\n')
461 461 % (
462 462 util.bytecount(bytecount),
463 463 elapsed,
464 464 util.bytecount(bytecount / elapsed),
465 465 )
466 466 )
467 467
468 468
469 469 def readbundle1header(fp):
470 470 compression = fp.read(2)
471 471 if compression != b'UN':
472 472 raise error.Abort(
473 473 _(
474 474 b'only uncompressed stream clone bundles are '
475 475 b'supported; got %s'
476 476 )
477 477 % compression
478 478 )
479 479
480 480 filecount, bytecount = struct.unpack(b'>QQ', fp.read(16))
481 481 requireslen = struct.unpack(b'>H', fp.read(2))[0]
482 482 requires = fp.read(requireslen)
483 483
484 484 if not requires.endswith(b'\0'):
485 485 raise error.Abort(
486 486 _(
487 487 b'malformed stream clone bundle: '
488 488 b'requirements not properly encoded'
489 489 )
490 490 )
491 491
492 492 requirements = set(requires.rstrip(b'\0').split(b','))
493 493
494 494 return filecount, bytecount, requirements
495 495
496 496
497 497 def applybundlev1(repo, fp):
498 498 """Apply the content from a stream clone bundle version 1.
499 499
500 500 We assume the 4 byte header has been read and validated and the file handle
501 501 is at the 2 byte compression identifier.
502 502 """
503 503 if len(repo):
504 504 raise error.Abort(
505 505 _(b'cannot apply stream clone bundle on non-empty repo')
506 506 )
507 507
508 508 filecount, bytecount, requirements = readbundle1header(fp)
509 509 missingreqs = requirements - repo.supported
510 510 if missingreqs:
511 511 raise error.Abort(
512 512 _(b'unable to apply stream clone: unsupported format: %s')
513 513 % b', '.join(sorted(missingreqs))
514 514 )
515 515
516 516 consumev1(repo, fp, filecount, bytecount)
517 517 nodemap.post_stream_cleanup(repo)
518 518
519 519
520 520 class streamcloneapplier:
521 521 """Class to manage applying streaming clone bundles.
522 522
523 523 We need to wrap ``applybundlev1()`` in a dedicated type to enable bundle
524 524 readers to perform bundle type-specific functionality.
525 525 """
526 526
527 527 def __init__(self, fh):
528 528 self._fh = fh
529 529
530 530 def apply(self, repo):
531 531 return applybundlev1(repo, self._fh)
532 532
533 533
534 534 # type of file to stream
535 535 _fileappend = 0 # append only file
536 536 _filefull = 1 # full snapshot file
537 537
538 538 # Source of the file
539 539 _srcstore = b's' # store (svfs)
540 540 _srccache = b'c' # cache (cache)
541 541
542 542 # This is it's own function so extensions can override it.
543 543 def _walkstreamfullstorefiles(repo):
544 544 """list snapshot file from the store"""
545 545 fnames = []
546 546 if not repo.publishing():
547 547 fnames.append(b'phaseroots')
548 548 return fnames
549 549
550 550
551 551 def _filterfull(entry, copy, vfsmap):
552 552 """actually copy the snapshot files"""
553 553 src, name, ftype, data = entry
554 554 if ftype != _filefull:
555 555 return entry
556 556 return (src, name, ftype, copy(vfsmap[src].join(name)))
557 557
558 558
559 559 @contextlib.contextmanager
560 560 def maketempcopies():
561 561 """return a function to temporary copy file"""
562 562
563 563 files = []
564 564 dst_dir = pycompat.mkdtemp(prefix=b'hg-clone-')
565 565 try:
566 566
567 567 def copy(src):
568 568 fd, dst = pycompat.mkstemp(
569 569 prefix=os.path.basename(src), dir=dst_dir
570 570 )
571 571 os.close(fd)
572 572 files.append(dst)
573 573 util.copyfiles(src, dst, hardlink=True)
574 574 return dst
575 575
576 576 yield copy
577 577 finally:
578 578 for tmp in files:
579 579 util.tryunlink(tmp)
580 580 util.tryrmdir(dst_dir)
581 581
582 582
583 583 def _makemap(repo):
584 584 """make a (src -> vfs) map for the repo"""
585 585 vfsmap = {
586 586 _srcstore: repo.svfs,
587 587 _srccache: repo.cachevfs,
588 588 }
589 589 # we keep repo.vfs out of the on purpose, ther are too many danger there
590 590 # (eg: .hg/hgrc)
591 591 assert repo.vfs not in vfsmap.values()
592 592
593 593 return vfsmap
594 594
595 595
596 596 def _emit2(repo, entries, totalfilesize):
597 597 """actually emit the stream bundle"""
598 598 vfsmap = _makemap(repo)
599 599 # we keep repo.vfs out of the on purpose, ther are too many danger there
600 600 # (eg: .hg/hgrc),
601 601 #
602 602 # this assert is duplicated (from _makemap) as author might think this is
603 603 # fine, while this is really not fine.
604 604 if repo.vfs in vfsmap.values():
605 605 raise error.ProgrammingError(
606 606 b'repo.vfs must not be added to vfsmap for security reasons'
607 607 )
608 608
609 609 progress = repo.ui.makeprogress(
610 610 _(b'bundle'), total=totalfilesize, unit=_(b'bytes')
611 611 )
612 612 progress.update(0)
613 613 with maketempcopies() as copy, progress:
614 614 # copy is delayed until we are in the try
615 615 entries = [_filterfull(e, copy, vfsmap) for e in entries]
616 616 yield None # this release the lock on the repository
617 617 totalbytecount = 0
618 618
619 619 for src, name, ftype, data in entries:
620 620 vfs = vfsmap[src]
621 621 yield src
622 622 yield util.uvarintencode(len(name))
623 623 if ftype == _fileappend:
624 624 fp = vfs(name)
625 625 size = data
626 626 elif ftype == _filefull:
627 627 fp = open(data, b'rb')
628 628 size = util.fstat(fp).st_size
629 629 bytecount = 0
630 630 try:
631 631 yield util.uvarintencode(size)
632 632 yield name
633 633 if size <= 65536:
634 634 chunks = (fp.read(size),)
635 635 else:
636 636 chunks = util.filechunkiter(fp, limit=size)
637 637 for chunk in chunks:
638 638 bytecount += len(chunk)
639 639 totalbytecount += len(chunk)
640 640 progress.update(totalbytecount)
641 641 yield chunk
642 642 if bytecount != size:
643 643 # Would most likely be caused by a race due to `hg strip` or
644 644 # a revlog split
645 645 raise error.Abort(
646 646 _(
647 647 b'clone could only read %d bytes from %s, but '
648 648 b'expected %d bytes'
649 649 )
650 650 % (bytecount, name, size)
651 651 )
652 652 finally:
653 653 fp.close()
654 654
655 655
656 656 def _test_sync_point_walk_1(repo):
657 657 """a function for synchronisation during tests"""
658 658
659 659
660 660 def _test_sync_point_walk_2(repo):
661 661 """a function for synchronisation during tests"""
662 662
663 663
664 664 def _entries_walk(repo, includes, excludes, includeobsmarkers):
665 665 """emit a seris of files information useful to clone a repo
666 666
667 667 return (vfs-key, entry) iterator
668 668
669 669 Where `entry` is StoreEntry. (used even for cache entries)
670 670 """
671 671 assert repo._currentlock(repo._lockref) is not None
672 672
673 673 matcher = None
674 674 if includes or excludes:
675 675 matcher = narrowspec.match(repo.root, includes, excludes)
676 676
677 677 phase = not repo.publishing()
678 678 entries = _walkstreamfiles(
679 679 repo,
680 680 matcher,
681 681 phase=phase,
682 682 obsolescence=includeobsmarkers,
683 683 )
684 684 for entry in entries:
685 685 yield (_srcstore, entry)
686 686
687 687 for name in cacheutil.cachetocopy(repo):
688 688 if repo.cachevfs.exists(name):
689 689 # not really a StoreEntry, but close enough
690 690 entry = store.SimpleStoreEntry(
691 691 entry_path=name,
692 692 is_volatile=True,
693 693 )
694 694 yield (_srccache, entry)
695 695
696 696
697 697 def _v2_walk(repo, includes, excludes, includeobsmarkers):
698 698 """emit a seris of files information useful to clone a repo
699 699
700 700 return (entries, totalfilesize)
701 701
702 702 entries is a list of tuple (vfs-key, file-path, file-type, size)
703 703
704 704 - `vfs-key`: is a key to the right vfs to write the file (see _makemap)
705 705 - `name`: file path of the file to copy (to be feed to the vfss)
706 706 - `file-type`: do this file need to be copied with the source lock ?
707 707 - `size`: the size of the file (or None)
708 708 """
709 709 assert repo._currentlock(repo._lockref) is not None
710 710 files = []
711 711 totalfilesize = 0
712 712
713 713 vfsmap = _makemap(repo)
714 714 entries = _entries_walk(repo, includes, excludes, includeobsmarkers)
715 715 for vfs_key, entry in entries:
716 716 vfs = vfsmap[vfs_key]
717 717 for f in entry.files():
718 718 file_size = f.file_size(vfs)
719 719 if file_size:
720 720 ft = _fileappend
721 721 if f.is_volatile:
722 722 ft = _filefull
723 723 files.append((vfs_key, f.unencoded_path, ft, file_size))
724 724 totalfilesize += file_size
725 725 return files, totalfilesize
726 726
727 727
728 728 def generatev2(repo, includes, excludes, includeobsmarkers):
729 729 """Emit content for version 2 of a streaming clone.
730 730
731 731 the data stream consists the following entries:
732 732 1) A char representing the file destination (eg: store or cache)
733 733 2) A varint containing the length of the filename
734 734 3) A varint containing the length of file data
735 735 4) N bytes containing the filename (the internal, store-agnostic form)
736 736 5) N bytes containing the file data
737 737
738 738 Returns a 3-tuple of (file count, file size, data iterator).
739 739 """
740 740
741 741 with repo.lock():
742 742
743 743 repo.ui.debug(b'scanning\n')
744 744
745 745 entries, totalfilesize = _v2_walk(
746 746 repo,
747 747 includes=includes,
748 748 excludes=excludes,
749 749 includeobsmarkers=includeobsmarkers,
750 750 )
751 751
752 752 chunks = _emit2(repo, entries, totalfilesize)
753 753 first = next(chunks)
754 754 assert first is None
755 755 _test_sync_point_walk_1(repo)
756 756 _test_sync_point_walk_2(repo)
757 757
758 758 return len(entries), totalfilesize, chunks
759 759
760 760
761 def generatev3(repo, includes, excludes, includeobsmarkers):
762 return generatev2(repo, includes, excludes, includeobsmarkers)
763
764
761 765 @contextlib.contextmanager
762 766 def nested(*ctxs):
763 767 this = ctxs[0]
764 768 rest = ctxs[1:]
765 769 with this:
766 770 if rest:
767 771 with nested(*rest):
768 772 yield
769 773 else:
770 774 yield
771 775
772 776
773 777 def consumev2(repo, fp, filecount, filesize):
774 778 """Apply the contents from a version 2 streaming clone.
775 779
776 780 Data is read from an object that only needs to provide a ``read(size)``
777 781 method.
778 782 """
779 783 with repo.lock():
780 784 repo.ui.status(
781 785 _(b'%d files to transfer, %s of data\n')
782 786 % (filecount, util.bytecount(filesize))
783 787 )
784 788
785 789 start = util.timer()
786 790 progress = repo.ui.makeprogress(
787 791 _(b'clone'), total=filesize, unit=_(b'bytes')
788 792 )
789 793 progress.update(0)
790 794
791 795 vfsmap = _makemap(repo)
792 796 # we keep repo.vfs out of the on purpose, ther are too many danger
793 797 # there (eg: .hg/hgrc),
794 798 #
795 799 # this assert is duplicated (from _makemap) as author might think this
796 800 # is fine, while this is really not fine.
797 801 if repo.vfs in vfsmap.values():
798 802 raise error.ProgrammingError(
799 803 b'repo.vfs must not be added to vfsmap for security reasons'
800 804 )
801 805
802 806 with repo.transaction(b'clone'):
803 807 ctxs = (vfs.backgroundclosing(repo.ui) for vfs in vfsmap.values())
804 808 with nested(*ctxs):
805 809 for i in range(filecount):
806 810 src = util.readexactly(fp, 1)
807 811 vfs = vfsmap[src]
808 812 namelen = util.uvarintdecodestream(fp)
809 813 datalen = util.uvarintdecodestream(fp)
810 814
811 815 name = util.readexactly(fp, namelen)
812 816
813 817 if repo.ui.debugflag:
814 818 repo.ui.debug(
815 819 b'adding [%s] %s (%s)\n'
816 820 % (src, name, util.bytecount(datalen))
817 821 )
818 822
819 823 with vfs(name, b'w') as ofp:
820 824 for chunk in util.filechunkiter(fp, limit=datalen):
821 825 progress.increment(step=len(chunk))
822 826 ofp.write(chunk)
823 827
824 828 # force @filecache properties to be reloaded from
825 829 # streamclone-ed file at next access
826 830 repo.invalidate(clearfilecache=True)
827 831
828 832 elapsed = util.timer() - start
829 833 if elapsed <= 0:
830 834 elapsed = 0.001
831 835 repo.ui.status(
832 836 _(b'transferred %s in %.1f seconds (%s/sec)\n')
833 837 % (
834 838 util.bytecount(progress.pos),
835 839 elapsed,
836 840 util.bytecount(progress.pos / elapsed),
837 841 )
838 842 )
839 843 progress.complete()
840 844
841 845
842 846 def applybundlev2(repo, fp, filecount, filesize, requirements):
843 847 from . import localrepo
844 848
845 849 missingreqs = [r for r in requirements if r not in repo.supported]
846 850 if missingreqs:
847 851 raise error.Abort(
848 852 _(b'unable to apply stream clone: unsupported format: %s')
849 853 % b', '.join(sorted(missingreqs))
850 854 )
851 855
852 856 consumev2(repo, fp, filecount, filesize)
853 857
854 858 repo.requirements = new_stream_clone_requirements(
855 859 repo.requirements,
856 860 requirements,
857 861 )
858 862 repo.svfs.options = localrepo.resolvestorevfsoptions(
859 863 repo.ui, repo.requirements, repo.features
860 864 )
861 865 scmutil.writereporequirements(repo)
862 866 nodemap.post_stream_cleanup(repo)
863 867
864 868
865 869 def _copy_files(src_vfs_map, dst_vfs_map, entries, progress):
866 870 hardlink = [True]
867 871
868 872 def copy_used():
869 873 hardlink[0] = False
870 874 progress.topic = _(b'copying')
871 875
872 876 for k, path, size in entries:
873 877 src_vfs = src_vfs_map[k]
874 878 dst_vfs = dst_vfs_map[k]
875 879 src_path = src_vfs.join(path)
876 880 dst_path = dst_vfs.join(path)
877 881 # We cannot use dirname and makedirs of dst_vfs here because the store
878 882 # encoding confuses them. See issue 6581 for details.
879 883 dirname = os.path.dirname(dst_path)
880 884 if not os.path.exists(dirname):
881 885 util.makedirs(dirname)
882 886 dst_vfs.register_file(path)
883 887 # XXX we could use the #nb_bytes argument.
884 888 util.copyfile(
885 889 src_path,
886 890 dst_path,
887 891 hardlink=hardlink[0],
888 892 no_hardlink_cb=copy_used,
889 893 check_fs_hardlink=False,
890 894 )
891 895 progress.increment()
892 896 return hardlink[0]
893 897
894 898
895 899 def local_copy(src_repo, dest_repo):
896 900 """copy all content from one local repository to another
897 901
898 902 This is useful for local clone"""
899 903 src_store_requirements = {
900 904 r
901 905 for r in src_repo.requirements
902 906 if r not in requirementsmod.WORKING_DIR_REQUIREMENTS
903 907 }
904 908 dest_store_requirements = {
905 909 r
906 910 for r in dest_repo.requirements
907 911 if r not in requirementsmod.WORKING_DIR_REQUIREMENTS
908 912 }
909 913 assert src_store_requirements == dest_store_requirements
910 914
911 915 with dest_repo.lock():
912 916 with src_repo.lock():
913 917
914 918 # bookmark is not integrated to the streaming as it might use the
915 919 # `repo.vfs` and they are too many sentitive data accessible
916 920 # through `repo.vfs` to expose it to streaming clone.
917 921 src_book_vfs = bookmarks.bookmarksvfs(src_repo)
918 922 srcbookmarks = src_book_vfs.join(b'bookmarks')
919 923 bm_count = 0
920 924 if os.path.exists(srcbookmarks):
921 925 bm_count = 1
922 926
923 927 entries, totalfilesize = _v2_walk(
924 928 src_repo,
925 929 includes=None,
926 930 excludes=None,
927 931 includeobsmarkers=True,
928 932 )
929 933 src_vfs_map = _makemap(src_repo)
930 934 dest_vfs_map = _makemap(dest_repo)
931 935 progress = src_repo.ui.makeprogress(
932 936 topic=_(b'linking'),
933 937 total=len(entries) + bm_count,
934 938 unit=_(b'files'),
935 939 )
936 940 # copy files
937 941 #
938 942 # We could copy the full file while the source repository is locked
939 943 # and the other one without the lock. However, in the linking case,
940 944 # this would also requires checks that nobody is appending any data
941 945 # to the files while we do the clone, so this is not done yet. We
942 946 # could do this blindly when copying files.
943 947 files = ((k, path, size) for k, path, ftype, size in entries)
944 948 hardlink = _copy_files(src_vfs_map, dest_vfs_map, files, progress)
945 949
946 950 # copy bookmarks over
947 951 if bm_count:
948 952 dst_book_vfs = bookmarks.bookmarksvfs(dest_repo)
949 953 dstbookmarks = dst_book_vfs.join(b'bookmarks')
950 954 util.copyfile(srcbookmarks, dstbookmarks)
951 955 progress.complete()
952 956 if hardlink:
953 957 msg = b'linked %d files\n'
954 958 else:
955 959 msg = b'copied %d files\n'
956 960 src_repo.ui.debug(msg % (len(entries) + bm_count))
957 961
958 962 with dest_repo.transaction(b"localclone") as tr:
959 963 dest_repo.store.write(tr)
960 964
961 965 # clean up transaction file as they do not make sense
962 966 transaction.cleanup_undo_files(dest_repo.ui.warn, dest_repo.vfs_map)
@@ -1,802 +1,1024
1 1 #require serve no-reposimplestore no-chg
2 2
3 #testcases stream-legacy stream-bundle2
3 #testcases stream-legacy stream-bundle2-v2 stream-bundle2-v3
4 4
5 5 #if stream-legacy
6 6 $ cat << EOF >> $HGRCPATH
7 7 > [server]
8 8 > bundle2.stream = no
9 9 > EOF
10 10 #endif
11 #if stream-bundle2-v3
12 $ cat << EOF >> $HGRCPATH
13 > [experimental]
14 > stream-v3 = yes
15 > EOF
16 #endif
11 17
12 18 Initialize repository
13 19
14 20 $ hg init server
15 21 $ cd server
16 22 $ sh $TESTDIR/testlib/stream_clone_setup.sh
17 23 adding 00changelog-ab349180a0405010.nd
18 24 adding 00changelog.d
19 25 adding 00changelog.i
20 26 adding 00changelog.n
21 27 adding 00manifest.d
22 28 adding 00manifest.i
23 29 adding container/isam-build-centos7/bazel-coverage-generator-sandboxfs-compatibility-0758e3e4f6057904d44399bd666faba9e7f40686.patch
24 30 adding data/foo.d
25 31 adding data/foo.i
26 32 adding data/foo.n
27 33 adding data/undo.babar
28 34 adding data/undo.d
29 35 adding data/undo.foo.d
30 36 adding data/undo.foo.i
31 37 adding data/undo.foo.n
32 38 adding data/undo.i
33 39 adding data/undo.n
34 40 adding data/undo.py
35 41 adding foo.d
36 42 adding foo.i
37 43 adding foo.n
38 44 adding meta/foo.d
39 45 adding meta/foo.i
40 46 adding meta/foo.n
41 47 adding meta/undo.babar
42 48 adding meta/undo.d
43 49 adding meta/undo.foo.d
44 50 adding meta/undo.foo.i
45 51 adding meta/undo.foo.n
46 52 adding meta/undo.i
47 53 adding meta/undo.n
48 54 adding meta/undo.py
49 55 adding savanah/foo.d
50 56 adding savanah/foo.i
51 57 adding savanah/foo.n
52 58 adding savanah/undo.babar
53 59 adding savanah/undo.d
54 60 adding savanah/undo.foo.d
55 61 adding savanah/undo.foo.i
56 62 adding savanah/undo.foo.n
57 63 adding savanah/undo.i
58 64 adding savanah/undo.n
59 65 adding savanah/undo.py
60 66 adding store/C\xc3\xa9lesteVille_is_a_Capital_City (esc)
61 67 adding store/foo.d
62 68 adding store/foo.i
63 69 adding store/foo.n
64 70 adding store/undo.babar
65 71 adding store/undo.d
66 72 adding store/undo.foo.d
67 73 adding store/undo.foo.i
68 74 adding store/undo.foo.n
69 75 adding store/undo.i
70 76 adding store/undo.n
71 77 adding store/undo.py
72 78 adding undo.babar
73 79 adding undo.d
74 80 adding undo.foo.d
75 81 adding undo.foo.i
76 82 adding undo.foo.n
77 83 adding undo.i
78 84 adding undo.n
79 85 adding undo.py
80 86
81 87 $ hg --config server.uncompressed=false serve -p $HGPORT -d --pid-file=hg.pid
82 88 $ cat hg.pid > $DAEMON_PIDS
83 89 $ cd ..
84 90
85 91 Check local clone
86 92 ==================
87 93
88 94 The logic is close enough of uncompressed.
89 95 This is present here to reuse the testing around file with "special" names.
90 96
91 97 $ hg clone server local-clone
92 98 updating to branch default
93 99 1088 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 100
95 101 Check that the clone went well
96 102
97 103 $ hg verify -R local-clone -q
98 104
99 105 Check uncompressed
100 106 ==================
101 107
102 108 Cannot stream clone when server.uncompressed is set
103 109
104 110 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=stream_out'
105 111 200 Script output follows
106 112
107 113 1
108 114
109 115 #if stream-legacy
110 116 $ hg debugcapabilities http://localhost:$HGPORT
111 117 Main capabilities:
112 118 batch
113 119 branchmap
114 120 $USUAL_BUNDLE2_CAPS_SERVER$
115 121 changegroupsubset
116 122 compression=$BUNDLE2_COMPRESSIONS$
117 123 getbundle
118 124 httpheader=1024
119 125 httpmediatype=0.1rx,0.1tx,0.2tx
120 126 known
121 127 lookup
122 128 pushkey
123 129 unbundle=HG10GZ,HG10BZ,HG10UN
124 130 unbundlehash
125 131 Bundle2 capabilities:
126 132 HG20
127 133 bookmarks
128 134 changegroup
129 135 01
130 136 02
131 137 03
132 138 checkheads
133 139 related
134 140 digests
135 141 md5
136 142 sha1
137 143 sha512
138 144 error
139 145 abort
140 146 unsupportedcontent
141 147 pushraced
142 148 pushkey
143 149 hgtagsfnodes
144 150 listkeys
145 151 phases
146 152 heads
147 153 pushkey
148 154 remote-changegroup
149 155 http
150 156 https
151 157
152 158 $ hg clone --stream -U http://localhost:$HGPORT server-disabled
153 159 warning: stream clone requested but server has them disabled
154 160 requesting all changes
155 161 adding changesets
156 162 adding manifests
157 163 adding file changes
158 164 added 3 changesets with 1088 changes to 1088 files
159 165 new changesets 96ee1d7354c4:5223b5e3265f
160 166
161 167 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle' content-type --bodyfile body --hgproto 0.2 --requestheader "x-hgarg-1=bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=0000000000000000000000000000000000000000&heads=c17445101a72edac06facd130d14808dfbd5c7c2&stream=1"
162 168 200 Script output follows
163 169 content-type: application/mercurial-0.2
164 170
165 171
166 172 $ f --size body --hexdump --bytes 100
167 173 body: size=140
168 174 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......|
169 175 0010: 73 0b 45 52 52 4f 52 3a 41 42 4f 52 54 00 00 00 |s.ERROR:ABORT...|
170 176 0020: 00 01 01 07 3c 04 16 6d 65 73 73 61 67 65 73 74 |....<..messagest|
171 177 0030: 72 65 61 6d 20 64 61 74 61 20 72 65 71 75 65 73 |ream data reques|
172 178 0040: 74 65 64 20 62 75 74 20 73 65 72 76 65 72 20 64 |ted but server d|
173 179 0050: 6f 65 73 20 6e 6f 74 20 61 6c 6c 6f 77 20 74 68 |oes not allow th|
174 180 0060: 69 73 20 66 |is f|
175 181
176 182 #endif
177 #if stream-bundle2
183 #if stream-bundle2-v2
184 $ hg debugcapabilities http://localhost:$HGPORT
185 Main capabilities:
186 batch
187 branchmap
188 $USUAL_BUNDLE2_CAPS_SERVER$
189 changegroupsubset
190 compression=$BUNDLE2_COMPRESSIONS$
191 getbundle
192 httpheader=1024
193 httpmediatype=0.1rx,0.1tx,0.2tx
194 known
195 lookup
196 pushkey
197 unbundle=HG10GZ,HG10BZ,HG10UN
198 unbundlehash
199 Bundle2 capabilities:
200 HG20
201 bookmarks
202 changegroup
203 01
204 02
205 03
206 checkheads
207 related
208 digests
209 md5
210 sha1
211 sha512
212 error
213 abort
214 unsupportedcontent
215 pushraced
216 pushkey
217 hgtagsfnodes
218 listkeys
219 phases
220 heads
221 pushkey
222 remote-changegroup
223 http
224 https
225
226 $ hg clone --stream -U http://localhost:$HGPORT server-disabled
227 warning: stream clone requested but server has them disabled
228 requesting all changes
229 adding changesets
230 adding manifests
231 adding file changes
232 added 3 changesets with 1088 changes to 1088 files
233 new changesets 96ee1d7354c4:5223b5e3265f
234
235 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle' content-type --bodyfile body --hgproto 0.2 --requestheader "x-hgarg-1=bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=0000000000000000000000000000000000000000&heads=c17445101a72edac06facd130d14808dfbd5c7c2&stream=1"
236 200 Script output follows
237 content-type: application/mercurial-0.2
238
239
240 $ f --size body --hexdump --bytes 100
241 body: size=140
242 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......|
243 0010: 73 0b 45 52 52 4f 52 3a 41 42 4f 52 54 00 00 00 |s.ERROR:ABORT...|
244 0020: 00 01 01 07 3c 04 16 6d 65 73 73 61 67 65 73 74 |....<..messagest|
245 0030: 72 65 61 6d 20 64 61 74 61 20 72 65 71 75 65 73 |ream data reques|
246 0040: 74 65 64 20 62 75 74 20 73 65 72 76 65 72 20 64 |ted but server d|
247 0050: 6f 65 73 20 6e 6f 74 20 61 6c 6c 6f 77 20 74 68 |oes not allow th|
248 0060: 69 73 20 66 |is f|
249
250 #endif
251 #if stream-bundle2-v3
178 252 $ hg debugcapabilities http://localhost:$HGPORT
179 253 Main capabilities:
180 254 batch
181 255 branchmap
182 256 $USUAL_BUNDLE2_CAPS_SERVER$
183 257 changegroupsubset
184 258 compression=$BUNDLE2_COMPRESSIONS$
185 259 getbundle
186 260 httpheader=1024
187 261 httpmediatype=0.1rx,0.1tx,0.2tx
188 262 known
189 263 lookup
190 264 pushkey
191 265 unbundle=HG10GZ,HG10BZ,HG10UN
192 266 unbundlehash
193 267 Bundle2 capabilities:
194 268 HG20
195 269 bookmarks
196 270 changegroup
197 271 01
198 272 02
199 273 03
200 274 checkheads
201 275 related
202 276 digests
203 277 md5
204 278 sha1
205 279 sha512
206 280 error
207 281 abort
208 282 unsupportedcontent
209 283 pushraced
210 284 pushkey
211 285 hgtagsfnodes
212 286 listkeys
213 287 phases
214 288 heads
215 289 pushkey
216 290 remote-changegroup
217 291 http
218 292 https
219 293
220 294 $ hg clone --stream -U http://localhost:$HGPORT server-disabled
221 295 warning: stream clone requested but server has them disabled
222 296 requesting all changes
223 297 adding changesets
224 298 adding manifests
225 299 adding file changes
226 300 added 3 changesets with 1088 changes to 1088 files
227 301 new changesets 96ee1d7354c4:5223b5e3265f
228 302
229 303 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle' content-type --bodyfile body --hgproto 0.2 --requestheader "x-hgarg-1=bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=0000000000000000000000000000000000000000&heads=c17445101a72edac06facd130d14808dfbd5c7c2&stream=1"
230 304 200 Script output follows
231 305 content-type: application/mercurial-0.2
232 306
233 307
234 308 $ f --size body --hexdump --bytes 100
235 309 body: size=140
236 310 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......|
237 311 0010: 73 0b 45 52 52 4f 52 3a 41 42 4f 52 54 00 00 00 |s.ERROR:ABORT...|
238 312 0020: 00 01 01 07 3c 04 16 6d 65 73 73 61 67 65 73 74 |....<..messagest|
239 313 0030: 72 65 61 6d 20 64 61 74 61 20 72 65 71 75 65 73 |ream data reques|
240 314 0040: 74 65 64 20 62 75 74 20 73 65 72 76 65 72 20 64 |ted but server d|
241 315 0050: 6f 65 73 20 6e 6f 74 20 61 6c 6c 6f 77 20 74 68 |oes not allow th|
242 316 0060: 69 73 20 66 |is f|
243 317
244 318 #endif
245 319
246 320 $ killdaemons.py
247 321 $ cd server
248 322 $ hg serve -p $HGPORT -d --pid-file=hg.pid --error errors.txt
249 323 $ cat hg.pid > $DAEMON_PIDS
250 324 $ cd ..
251 325
252 326 Basic clone
253 327
254 328 #if stream-legacy
255 329 $ hg clone --stream -U http://localhost:$HGPORT clone1
256 330 streaming all changes
257 331 1090 files to transfer, 102 KB of data (no-zstd !)
258 332 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
259 333 1090 files to transfer, 98.8 KB of data (zstd !)
260 334 transferred 98.8 KB in * seconds (* */sec) (glob) (zstd !)
261 335 searching for changes
262 336 no changes found
263 337 $ cat server/errors.txt
264 338 #endif
265 #if stream-bundle2
339 #if stream-bundle2-v2
340 $ hg clone --stream -U http://localhost:$HGPORT clone1
341 streaming all changes
342 1093 files to transfer, 102 KB of data (no-zstd !)
343 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
344 1093 files to transfer, 98.9 KB of data (zstd !)
345 transferred 98.9 KB in * seconds (* */sec) (glob) (zstd !)
346
347 $ ls -1 clone1/.hg/cache
348 branch2-base
349 branch2-immutable
350 branch2-served
351 branch2-served.hidden
352 branch2-visible
353 branch2-visible-hidden
354 rbc-names-v1
355 rbc-revs-v1
356 tags2
357 tags2-served
358 $ cat server/errors.txt
359 #endif
360 #if stream-bundle2-v3
266 361 $ hg clone --stream -U http://localhost:$HGPORT clone1
267 362 streaming all changes
268 363 1093 files to transfer, 102 KB of data (no-zstd !)
269 364 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
270 365 1093 files to transfer, 98.9 KB of data (zstd !)
271 366 transferred 98.9 KB in * seconds (* */sec) (glob) (zstd !)
272 367
273 368 $ ls -1 clone1/.hg/cache
274 369 branch2-base
275 370 branch2-immutable
276 371 branch2-served
277 372 branch2-served.hidden
278 373 branch2-visible
279 374 branch2-visible-hidden
280 375 rbc-names-v1
281 376 rbc-revs-v1
282 377 tags2
283 378 tags2-served
284 379 $ cat server/errors.txt
285 380 #endif
286 381
287 382 getbundle requests with stream=1 are uncompressed
288 383
289 384 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle' content-type --bodyfile body --hgproto '0.1 0.2 comp=zlib,none' --requestheader "x-hgarg-1=bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Astream%253Dv2&cg=0&common=0000000000000000000000000000000000000000&heads=c17445101a72edac06facd130d14808dfbd5c7c2&stream=1"
290 385 200 Script output follows
291 386 content-type: application/mercurial-0.2
292 387
293 388
294 389 #if no-zstd no-rust
295 390 $ f --size --hex --bytes 256 body
296 391 body: size=119123
297 392 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......|
298 393 0010: 62 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |b.STREAM2.......|
299 394 0020: 06 09 04 0c 26 62 79 74 65 63 6f 75 6e 74 31 30 |....&bytecount10|
300 395 0030: 34 31 31 35 66 69 6c 65 63 6f 75 6e 74 31 30 39 |4115filecount109|
301 396 0040: 33 72 65 71 75 69 72 65 6d 65 6e 74 73 67 65 6e |3requirementsgen|
302 397 0050: 65 72 61 6c 64 65 6c 74 61 25 32 43 72 65 76 6c |eraldelta%2Crevl|
303 398 0060: 6f 67 76 31 25 32 43 73 70 61 72 73 65 72 65 76 |ogv1%2Csparserev|
304 399 0070: 6c 6f 67 00 00 80 00 73 08 42 64 61 74 61 2f 30 |log....s.Bdata/0|
305 400 0080: 2e 69 00 03 00 01 00 00 00 00 00 00 00 02 00 00 |.i..............|
306 401 0090: 00 01 00 00 00 00 00 00 00 01 ff ff ff ff ff ff |................|
307 402 00a0: ff ff 80 29 63 a0 49 d3 23 87 bf ce fe 56 67 92 |...)c.I.#....Vg.|
308 403 00b0: 67 2c 69 d1 ec 39 00 00 00 00 00 00 00 00 00 00 |g,i..9..........|
309 404 00c0: 00 00 75 30 73 26 45 64 61 74 61 2f 30 30 63 68 |..u0s&Edata/00ch|
310 405 00d0: 61 6e 67 65 6c 6f 67 2d 61 62 33 34 39 31 38 30 |angelog-ab349180|
311 406 00e0: 61 30 34 30 35 30 31 30 2e 6e 64 2e 69 00 03 00 |a0405010.nd.i...|
312 407 00f0: 01 00 00 00 00 00 00 00 05 00 00 00 04 00 00 00 |................|
313 408 #endif
314 409 #if zstd no-rust
315 410 $ f --size --hex --bytes 256 body
316 411 body: size=116310 (no-bigendian !)
317 412 body: size=116305 (bigendian !)
318 413 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......|
319 414 0010: 7c 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 ||.STREAM2.......|
320 415 0020: 06 09 04 0c 40 62 79 74 65 63 6f 75 6e 74 31 30 |....@bytecount10|
321 416 0030: 31 32 37 36 66 69 6c 65 63 6f 75 6e 74 31 30 39 |1276filecount109| (no-bigendian !)
322 417 0030: 31 32 37 31 66 69 6c 65 63 6f 75 6e 74 31 30 39 |1271filecount109| (bigendian !)
323 418 0040: 33 72 65 71 75 69 72 65 6d 65 6e 74 73 67 65 6e |3requirementsgen|
324 419 0050: 65 72 61 6c 64 65 6c 74 61 25 32 43 72 65 76 6c |eraldelta%2Crevl|
325 420 0060: 6f 67 2d 63 6f 6d 70 72 65 73 73 69 6f 6e 2d 7a |og-compression-z|
326 421 0070: 73 74 64 25 32 43 72 65 76 6c 6f 67 76 31 25 32 |std%2Crevlogv1%2|
327 422 0080: 43 73 70 61 72 73 65 72 65 76 6c 6f 67 00 00 80 |Csparserevlog...|
328 423 0090: 00 73 08 42 64 61 74 61 2f 30 2e 69 00 03 00 01 |.s.Bdata/0.i....|
329 424 00a0: 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 00 |................|
330 425 00b0: 00 00 00 01 ff ff ff ff ff ff ff ff 80 29 63 a0 |.............)c.|
331 426 00c0: 49 d3 23 87 bf ce fe 56 67 92 67 2c 69 d1 ec 39 |I.#....Vg.g,i..9|
332 427 00d0: 00 00 00 00 00 00 00 00 00 00 00 00 75 30 73 26 |............u0s&|
333 428 00e0: 45 64 61 74 61 2f 30 30 63 68 61 6e 67 65 6c 6f |Edata/00changelo|
334 429 00f0: 67 2d 61 62 33 34 39 31 38 30 61 30 34 30 35 30 |g-ab349180a04050|
335 430 #endif
336 431 #if zstd rust no-dirstate-v2
337 432 $ f --size --hex --bytes 256 body
338 433 body: size=116310
339 434 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......|
340 435 0010: 7c 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 ||.STREAM2.......|
341 436 0020: 06 09 04 0c 40 62 79 74 65 63 6f 75 6e 74 31 30 |....@bytecount10|
342 437 0030: 31 32 37 36 66 69 6c 65 63 6f 75 6e 74 31 30 39 |1276filecount109|
343 438 0040: 33 72 65 71 75 69 72 65 6d 65 6e 74 73 67 65 6e |3requirementsgen|
344 439 0050: 65 72 61 6c 64 65 6c 74 61 25 32 43 72 65 76 6c |eraldelta%2Crevl|
345 440 0060: 6f 67 2d 63 6f 6d 70 72 65 73 73 69 6f 6e 2d 7a |og-compression-z|
346 441 0070: 73 74 64 25 32 43 72 65 76 6c 6f 67 76 31 25 32 |std%2Crevlogv1%2|
347 442 0080: 43 73 70 61 72 73 65 72 65 76 6c 6f 67 00 00 80 |Csparserevlog...|
348 443 0090: 00 73 08 42 64 61 74 61 2f 30 2e 69 00 03 00 01 |.s.Bdata/0.i....|
349 444 00a0: 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 00 |................|
350 445 00b0: 00 00 00 01 ff ff ff ff ff ff ff ff 80 29 63 a0 |.............)c.|
351 446 00c0: 49 d3 23 87 bf ce fe 56 67 92 67 2c 69 d1 ec 39 |I.#....Vg.g,i..9|
352 447 00d0: 00 00 00 00 00 00 00 00 00 00 00 00 75 30 73 26 |............u0s&|
353 448 00e0: 45 64 61 74 61 2f 30 30 63 68 61 6e 67 65 6c 6f |Edata/00changelo|
354 449 00f0: 67 2d 61 62 33 34 39 31 38 30 61 30 34 30 35 30 |g-ab349180a04050|
355 450 #endif
356 451 #if zstd dirstate-v2
357 452 $ f --size --hex --bytes 256 body
358 453 body: size=109549
359 454 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......|
360 455 0010: c0 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2.......|
361 456 0020: 05 09 04 0c 85 62 79 74 65 63 6f 75 6e 74 39 35 |.....bytecount95|
362 457 0030: 38 39 37 66 69 6c 65 63 6f 75 6e 74 31 30 33 30 |897filecount1030|
363 458 0040: 72 65 71 75 69 72 65 6d 65 6e 74 73 64 6f 74 65 |requirementsdote|
364 459 0050: 6e 63 6f 64 65 25 32 43 65 78 70 2d 64 69 72 73 |ncode%2Cexp-dirs|
365 460 0060: 74 61 74 65 2d 76 32 25 32 43 66 6e 63 61 63 68 |tate-v2%2Cfncach|
366 461 0070: 65 25 32 43 67 65 6e 65 72 61 6c 64 65 6c 74 61 |e%2Cgeneraldelta|
367 462 0080: 25 32 43 70 65 72 73 69 73 74 65 6e 74 2d 6e 6f |%2Cpersistent-no|
368 463 0090: 64 65 6d 61 70 25 32 43 72 65 76 6c 6f 67 2d 63 |demap%2Crevlog-c|
369 464 00a0: 6f 6d 70 72 65 73 73 69 6f 6e 2d 7a 73 74 64 25 |ompression-zstd%|
370 465 00b0: 32 43 72 65 76 6c 6f 67 76 31 25 32 43 73 70 61 |2Crevlogv1%2Cspa|
371 466 00c0: 72 73 65 72 65 76 6c 6f 67 25 32 43 73 74 6f 72 |rserevlog%2Cstor|
372 467 00d0: 65 00 00 80 00 73 08 42 64 61 74 61 2f 30 2e 69 |e....s.Bdata/0.i|
373 468 00e0: 00 03 00 01 00 00 00 00 00 00 00 02 00 00 00 01 |................|
374 469 00f0: 00 00 00 00 00 00 00 01 ff ff ff ff ff ff ff ff |................|
375 470 #endif
376 471
377 472 --uncompressed is an alias to --stream
378 473
379 474 #if stream-legacy
380 475 $ hg clone --uncompressed -U http://localhost:$HGPORT clone1-uncompressed
381 476 streaming all changes
382 477 1090 files to transfer, 102 KB of data (no-zstd !)
383 478 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
384 479 1090 files to transfer, 98.8 KB of data (zstd !)
385 480 transferred 98.8 KB in * seconds (* */sec) (glob) (zstd !)
386 481 searching for changes
387 482 no changes found
388 483 #endif
389 #if stream-bundle2
484 #if stream-bundle2-v2
485 $ hg clone --uncompressed -U http://localhost:$HGPORT clone1-uncompressed
486 streaming all changes
487 1093 files to transfer, 102 KB of data (no-zstd !)
488 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
489 1093 files to transfer, 98.9 KB of data (zstd !)
490 transferred 98.9 KB in * seconds (* */sec) (glob) (zstd !)
491 #endif
492 #if stream-bundle2-v3
390 493 $ hg clone --uncompressed -U http://localhost:$HGPORT clone1-uncompressed
391 494 streaming all changes
392 495 1093 files to transfer, 102 KB of data (no-zstd !)
393 496 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
394 497 1093 files to transfer, 98.9 KB of data (zstd !)
395 498 transferred 98.9 KB in * seconds (* */sec) (glob) (zstd !)
396 499 #endif
397 500
398 501 Clone with background file closing enabled
399 502
400 503 #if stream-legacy
401 504 $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --stream -U http://localhost:$HGPORT clone-background | grep -v adding
402 505 using http://localhost:$HGPORT/
403 506 sending capabilities command
404 507 sending branchmap command
405 508 streaming all changes
406 509 sending stream_out command
407 510 1090 files to transfer, 102 KB of data (no-zstd !)
408 511 1090 files to transfer, 98.8 KB of data (zstd !)
409 512 starting 4 threads for background file closing
410 513 updating the branch cache
411 514 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
412 515 transferred 98.8 KB in * seconds (* */sec) (glob) (zstd !)
413 516 query 1; heads
414 517 sending batch command
415 518 searching for changes
416 519 all remote heads known locally
417 520 no changes found
418 521 sending getbundle command
419 522 bundle2-input-bundle: with-transaction
420 523 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
421 524 bundle2-input-part: "phase-heads" supported
422 525 bundle2-input-part: total payload size 24
423 526 bundle2-input-bundle: 2 parts total
424 527 checking for updated bookmarks
425 528 updating the branch cache
426 529 (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob)
427 530 #endif
428 #if stream-bundle2
531 #if stream-bundle2-v2
429 532 $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --stream -U http://localhost:$HGPORT clone-background | grep -v adding
430 533 using http://localhost:$HGPORT/
431 534 sending capabilities command
432 535 query 1; heads
433 536 sending batch command
434 537 streaming all changes
435 538 sending getbundle command
436 539 bundle2-input-bundle: with-transaction
437 540 bundle2-input-part: "stream2" (params: 3 mandatory) supported
438 541 applying stream bundle
439 542 1093 files to transfer, 102 KB of data (no-zstd !)
440 543 1093 files to transfer, 98.9 KB of data (zstd !)
441 544 starting 4 threads for background file closing
442 545 starting 4 threads for background file closing
443 546 updating the branch cache
444 547 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
445 548 bundle2-input-part: total payload size 118984 (no-zstd !)
446 549 transferred 98.9 KB in * seconds (* */sec) (glob) (zstd !)
447 550 bundle2-input-part: total payload size 116145 (zstd no-bigendian !)
448 551 bundle2-input-part: total payload size 116140 (zstd bigendian !)
449 552 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
450 553 bundle2-input-bundle: 2 parts total
451 554 checking for updated bookmarks
452 555 updating the branch cache
453 556 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
454 557 #endif
558 #if stream-bundle2-v3
559 $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --stream -U http://localhost:$HGPORT clone-background | grep -v adding
560 using http://localhost:$HGPORT/
561 sending capabilities command
562 query 1; heads
563 sending batch command
564 streaming all changes
565 sending getbundle command
566 bundle2-input-bundle: with-transaction
567 bundle2-input-part: "stream3" (params: 3 mandatory) supported
568 applying stream bundle
569 1093 files to transfer, 102 KB of data (no-zstd !)
570 1093 files to transfer, 98.9 KB of data (zstd !)
571 starting 4 threads for background file closing
572 starting 4 threads for background file closing
573 updating the branch cache
574 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
575 bundle2-input-part: total payload size 118984 (no-zstd !)
576 transferred 98.9 KB in * seconds (* */sec) (glob) (zstd !)
577 bundle2-input-part: total payload size 116145 (zstd no-bigendian !)
578 bundle2-input-part: total payload size 116140 (zstd bigendian !)
579 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
580 bundle2-input-bundle: 2 parts total
581 checking for updated bookmarks
582 updating the branch cache
583 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
584 #endif
455 585
456 586 Cannot stream clone when there are secret changesets
457 587
458 588 $ hg -R server phase --force --secret -r tip
459 589 $ hg clone --stream -U http://localhost:$HGPORT secret-denied
460 590 warning: stream clone requested but server has them disabled
461 591 requesting all changes
462 592 adding changesets
463 593 adding manifests
464 594 adding file changes
465 595 added 2 changesets with 1025 changes to 1025 files
466 596 new changesets 96ee1d7354c4:c17445101a72
467 597
468 598 $ killdaemons.py
469 599
470 600 Streaming of secrets can be overridden by server config
471 601
472 602 $ cd server
473 603 $ hg serve --config server.uncompressedallowsecret=true -p $HGPORT -d --pid-file=hg.pid
474 604 $ cat hg.pid > $DAEMON_PIDS
475 605 $ cd ..
476 606
477 607 #if stream-legacy
478 608 $ hg clone --stream -U http://localhost:$HGPORT secret-allowed
479 609 streaming all changes
480 610 1090 files to transfer, 102 KB of data (no-zstd !)
481 611 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
482 612 1090 files to transfer, 98.8 KB of data (zstd !)
483 613 transferred 98.8 KB in * seconds (* */sec) (glob) (zstd !)
484 614 searching for changes
485 615 no changes found
486 616 #endif
487 #if stream-bundle2
617 #if stream-bundle2-v2
618 $ hg clone --stream -U http://localhost:$HGPORT secret-allowed
619 streaming all changes
620 1093 files to transfer, 102 KB of data (no-zstd !)
621 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
622 1093 files to transfer, 98.9 KB of data (zstd !)
623 transferred 98.9 KB in * seconds (* */sec) (glob) (zstd !)
624 #endif
625 #if stream-bundle2-v3
488 626 $ hg clone --stream -U http://localhost:$HGPORT secret-allowed
489 627 streaming all changes
490 628 1093 files to transfer, 102 KB of data (no-zstd !)
491 629 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
492 630 1093 files to transfer, 98.9 KB of data (zstd !)
493 631 transferred 98.9 KB in * seconds (* */sec) (glob) (zstd !)
494 632 #endif
495 633
496 634 $ killdaemons.py
497 635
498 636 Verify interaction between preferuncompressed and secret presence
499 637
500 638 $ cd server
501 639 $ hg serve --config server.preferuncompressed=true -p $HGPORT -d --pid-file=hg.pid
502 640 $ cat hg.pid > $DAEMON_PIDS
503 641 $ cd ..
504 642
505 643 $ hg clone -U http://localhost:$HGPORT preferuncompressed-secret
506 644 requesting all changes
507 645 adding changesets
508 646 adding manifests
509 647 adding file changes
510 648 added 2 changesets with 1025 changes to 1025 files
511 649 new changesets 96ee1d7354c4:c17445101a72
512 650
513 651 $ killdaemons.py
514 652
515 653 Clone not allowed when full bundles disabled and can't serve secrets
516 654
517 655 $ cd server
518 656 $ hg serve --config server.disablefullbundle=true -p $HGPORT -d --pid-file=hg.pid
519 657 $ cat hg.pid > $DAEMON_PIDS
520 658 $ cd ..
521 659
522 660 $ hg clone --stream http://localhost:$HGPORT secret-full-disabled
523 661 warning: stream clone requested but server has them disabled
524 662 requesting all changes
525 663 remote: abort: server has pull-based clones disabled
526 664 abort: pull failed on remote
527 665 (remove --pull if specified or upgrade Mercurial)
528 666 [100]
529 667
530 668 Local stream clone with secrets involved
531 669 (This is just a test over behavior: if you have access to the repo's files,
532 670 there is no security so it isn't important to prevent a clone here.)
533 671
534 672 $ hg clone -U --stream server local-secret
535 673 warning: stream clone requested but server has them disabled
536 674 requesting all changes
537 675 adding changesets
538 676 adding manifests
539 677 adding file changes
540 678 added 2 changesets with 1025 changes to 1025 files
541 679 new changesets 96ee1d7354c4:c17445101a72
542 680
543 681 Stream clone while repo is changing:
544 682
545 683 $ mkdir changing
546 684 $ cd changing
547 685
548 686 extension for delaying the server process so we reliably can modify the repo
549 687 while cloning
550 688
551 689 $ cat > stream_steps.py <<EOF
552 690 > import os
553 691 > import sys
554 692 > from mercurial import (
555 693 > encoding,
556 694 > extensions,
557 695 > streamclone,
558 696 > testing,
559 697 > )
560 698 > WALKED_FILE_1 = encoding.environ[b'HG_TEST_STREAM_WALKED_FILE_1']
561 699 > WALKED_FILE_2 = encoding.environ[b'HG_TEST_STREAM_WALKED_FILE_2']
562 700 >
563 701 > def _test_sync_point_walk_1(orig, repo):
564 702 > testing.write_file(WALKED_FILE_1)
565 703 >
566 704 > def _test_sync_point_walk_2(orig, repo):
567 705 > assert repo._currentlock(repo._lockref) is None
568 706 > testing.wait_file(WALKED_FILE_2)
569 707 >
570 708 > extensions.wrapfunction(
571 709 > streamclone,
572 710 > '_test_sync_point_walk_1',
573 711 > _test_sync_point_walk_1
574 712 > )
575 713 > extensions.wrapfunction(
576 714 > streamclone,
577 715 > '_test_sync_point_walk_2',
578 716 > _test_sync_point_walk_2
579 717 > )
580 718 > EOF
581 719
582 720 prepare repo with small and big file to cover both code paths in emitrevlogdata
583 721
584 722 $ hg init repo
585 723 $ touch repo/f1
586 724 $ $TESTDIR/seq.py 50000 > repo/f2
587 725 $ hg -R repo ci -Aqm "0"
588 726 $ HG_TEST_STREAM_WALKED_FILE_1="$TESTTMP/sync_file_walked_1"
589 727 $ export HG_TEST_STREAM_WALKED_FILE_1
590 728 $ HG_TEST_STREAM_WALKED_FILE_2="$TESTTMP/sync_file_walked_2"
591 729 $ export HG_TEST_STREAM_WALKED_FILE_2
592 730 $ HG_TEST_STREAM_WALKED_FILE_3="$TESTTMP/sync_file_walked_3"
593 731 $ export HG_TEST_STREAM_WALKED_FILE_3
594 732 # $ cat << EOF >> $HGRCPATH
595 733 # > [hooks]
596 734 # > pre-clone=rm -f "$TESTTMP/sync_file_walked_*"
597 735 # > EOF
598 736 $ hg serve -R repo -p $HGPORT1 -d --error errors.log --pid-file=hg.pid --config extensions.stream_steps="$RUNTESTDIR/testlib/ext-stream-clone-steps.py"
599 737 $ cat hg.pid >> $DAEMON_PIDS
600 738
601 739 clone while modifying the repo between stating file with write lock and
602 740 actually serving file content
603 741
604 742 $ (hg clone -q --stream -U http://localhost:$HGPORT1 clone; touch "$HG_TEST_STREAM_WALKED_FILE_3") &
605 743 $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_1
606 744 $ echo >> repo/f1
607 745 $ echo >> repo/f2
608 746 $ hg -R repo ci -m "1" --config ui.timeout.warn=-1
609 747 $ touch $HG_TEST_STREAM_WALKED_FILE_2
610 748 $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_3
611 749 $ hg -R clone id
612 750 000000000000
613 751 $ cat errors.log
614 752 $ cd ..
615 753
616 754 Stream repository with bookmarks
617 755 --------------------------------
618 756
619 757 (revert introduction of secret changeset)
620 758
621 759 $ hg -R server phase --draft 'secret()'
622 760
623 761 add a bookmark
624 762
625 763 $ hg -R server bookmark -r tip some-bookmark
626 764
627 765 clone it
628 766
629 767 #if stream-legacy
630 768 $ hg clone --stream http://localhost:$HGPORT with-bookmarks
631 769 streaming all changes
632 770 1090 files to transfer, 102 KB of data (no-zstd !)
633 771 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
634 772 1090 files to transfer, 98.8 KB of data (zstd !)
635 773 transferred 98.8 KB in * seconds (* */sec) (glob) (zstd !)
636 774 searching for changes
637 775 no changes found
638 776 updating to branch default
639 777 1088 files updated, 0 files merged, 0 files removed, 0 files unresolved
640 778 #endif
641 #if stream-bundle2
779 #if stream-bundle2-v2
780 $ hg clone --stream http://localhost:$HGPORT with-bookmarks
781 streaming all changes
782 1096 files to transfer, 102 KB of data (no-zstd !)
783 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
784 1096 files to transfer, 99.1 KB of data (zstd !)
785 transferred 99.1 KB in * seconds (* */sec) (glob) (zstd !)
786 updating to branch default
787 1088 files updated, 0 files merged, 0 files removed, 0 files unresolved
788 #endif
789 #if stream-bundle2-v3
642 790 $ hg clone --stream http://localhost:$HGPORT with-bookmarks
643 791 streaming all changes
644 792 1096 files to transfer, 102 KB of data (no-zstd !)
645 793 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
646 794 1096 files to transfer, 99.1 KB of data (zstd !)
647 795 transferred 99.1 KB in * seconds (* */sec) (glob) (zstd !)
648 796 updating to branch default
649 797 1088 files updated, 0 files merged, 0 files removed, 0 files unresolved
650 798 #endif
651 799 $ hg verify -R with-bookmarks -q
652 800 $ hg -R with-bookmarks bookmarks
653 801 some-bookmark 2:5223b5e3265f
654 802
655 803 Stream repository with phases
656 804 -----------------------------
657 805
658 806 Clone as publishing
659 807
660 808 $ hg -R server phase -r 'all()'
661 809 0: draft
662 810 1: draft
663 811 2: draft
664 812
665 813 #if stream-legacy
666 814 $ hg clone --stream http://localhost:$HGPORT phase-publish
667 815 streaming all changes
668 816 1090 files to transfer, 102 KB of data (no-zstd !)
669 817 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
670 818 1090 files to transfer, 98.8 KB of data (zstd !)
671 819 transferred 98.8 KB in * seconds (* */sec) (glob) (zstd !)
672 820 searching for changes
673 821 no changes found
674 822 updating to branch default
675 823 1088 files updated, 0 files merged, 0 files removed, 0 files unresolved
676 824 #endif
677 #if stream-bundle2
825 #if stream-bundle2-v2
826 $ hg clone --stream http://localhost:$HGPORT phase-publish
827 streaming all changes
828 1096 files to transfer, 102 KB of data (no-zstd !)
829 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
830 1096 files to transfer, 99.1 KB of data (zstd !)
831 transferred 99.1 KB in * seconds (* */sec) (glob) (zstd !)
832 updating to branch default
833 1088 files updated, 0 files merged, 0 files removed, 0 files unresolved
834 #endif
835 #if stream-bundle2-v3
678 836 $ hg clone --stream http://localhost:$HGPORT phase-publish
679 837 streaming all changes
680 838 1096 files to transfer, 102 KB of data (no-zstd !)
681 839 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
682 840 1096 files to transfer, 99.1 KB of data (zstd !)
683 841 transferred 99.1 KB in * seconds (* */sec) (glob) (zstd !)
684 842 updating to branch default
685 843 1088 files updated, 0 files merged, 0 files removed, 0 files unresolved
686 844 #endif
687 845 $ hg verify -R phase-publish -q
688 846 $ hg -R phase-publish phase -r 'all()'
689 847 0: public
690 848 1: public
691 849 2: public
692 850
693 851 Clone as non publishing
694 852
695 853 $ cat << EOF >> server/.hg/hgrc
696 854 > [phases]
697 855 > publish = False
698 856 > EOF
699 857 $ killdaemons.py
700 858 $ hg -R server serve -p $HGPORT -d --pid-file=hg.pid
701 859 $ cat hg.pid > $DAEMON_PIDS
702 860
703 861 #if stream-legacy
704 862
705 863 With v1 of the stream protocol, changeset are always cloned as public. It make
706 864 stream v1 unsuitable for non-publishing repository.
707 865
708 866 $ hg clone --stream http://localhost:$HGPORT phase-no-publish
709 867 streaming all changes
710 868 1090 files to transfer, 102 KB of data (no-zstd !)
711 869 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
712 870 1090 files to transfer, 98.8 KB of data (zstd !)
713 871 transferred 98.8 KB in * seconds (* */sec) (glob) (zstd !)
714 872 searching for changes
715 873 no changes found
716 874 updating to branch default
717 875 1088 files updated, 0 files merged, 0 files removed, 0 files unresolved
718 876 $ hg -R phase-no-publish phase -r 'all()'
719 877 0: public
720 878 1: public
721 879 2: public
722 880 #endif
723 #if stream-bundle2
881 #if stream-bundle2-v2
882 $ hg clone --stream http://localhost:$HGPORT phase-no-publish
883 streaming all changes
884 1097 files to transfer, 102 KB of data (no-zstd !)
885 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
886 1097 files to transfer, 99.1 KB of data (zstd !)
887 transferred 99.1 KB in * seconds (* */sec) (glob) (zstd !)
888 updating to branch default
889 1088 files updated, 0 files merged, 0 files removed, 0 files unresolved
890 $ hg -R phase-no-publish phase -r 'all()'
891 0: draft
892 1: draft
893 2: draft
894 #endif
895 #if stream-bundle2-v3
724 896 $ hg clone --stream http://localhost:$HGPORT phase-no-publish
725 897 streaming all changes
726 898 1097 files to transfer, 102 KB of data (no-zstd !)
727 899 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
728 900 1097 files to transfer, 99.1 KB of data (zstd !)
729 901 transferred 99.1 KB in * seconds (* */sec) (glob) (zstd !)
730 902 updating to branch default
731 903 1088 files updated, 0 files merged, 0 files removed, 0 files unresolved
732 904 $ hg -R phase-no-publish phase -r 'all()'
733 905 0: draft
734 906 1: draft
735 907 2: draft
736 908 #endif
737 909 $ hg verify -R phase-no-publish -q
738 910
739 911 $ killdaemons.py
740 912
741 913 #if stream-legacy
742 914
743 915 With v1 of the stream protocol, changeset are always cloned as public. There's
744 916 no obsolescence markers exchange in stream v1.
745 917
746 918 #endif
747 #if stream-bundle2
919 #if stream-bundle2-v2
920
921 Stream repository with obsolescence
922 -----------------------------------
923
924 Clone non-publishing with obsolescence
925
926 $ cat >> $HGRCPATH << EOF
927 > [experimental]
928 > evolution=all
929 > EOF
930
931 $ cd server
932 $ echo foo > foo
933 $ hg -q commit -m 'about to be pruned'
934 $ hg debugobsolete `hg log -r . -T '{node}'` -d '0 0' -u test --record-parents
935 1 new obsolescence markers
936 obsoleted 1 changesets
937 $ hg up null -q
938 $ hg log -T '{rev}: {phase}\n'
939 2: draft
940 1: draft
941 0: draft
942 $ hg serve -p $HGPORT -d --pid-file=hg.pid
943 $ cat hg.pid > $DAEMON_PIDS
944 $ cd ..
945
946 $ hg clone -U --stream http://localhost:$HGPORT with-obsolescence
947 streaming all changes
948 1098 files to transfer, 102 KB of data (no-zstd !)
949 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
950 1098 files to transfer, 99.5 KB of data (zstd !)
951 transferred 99.5 KB in * seconds (* */sec) (glob) (zstd !)
952 $ hg -R with-obsolescence log -T '{rev}: {phase}\n'
953 2: draft
954 1: draft
955 0: draft
956 $ hg debugobsolete -R with-obsolescence
957 8c206a663911c1f97f2f9d7382e417ae55872cfa 0 {5223b5e3265f0df40bb743da62249413d74ac70f} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
958 $ hg verify -R with-obsolescence -q
959
960 $ hg clone -U --stream --config experimental.evolution=0 http://localhost:$HGPORT with-obsolescence-no-evolution
961 streaming all changes
962 remote: abort: server has obsolescence markers, but client cannot receive them via stream clone
963 abort: pull failed on remote
964 [100]
965
966 $ killdaemons.py
967
968 #endif
969 #if stream-bundle2-v3
748 970
749 971 Stream repository with obsolescence
750 972 -----------------------------------
751 973
752 974 Clone non-publishing with obsolescence
753 975
754 976 $ cat >> $HGRCPATH << EOF
755 977 > [experimental]
756 978 > evolution=all
757 979 > EOF
758 980
759 981 $ cd server
760 982 $ echo foo > foo
761 983 $ hg -q commit -m 'about to be pruned'
762 984 $ hg debugobsolete `hg log -r . -T '{node}'` -d '0 0' -u test --record-parents
763 985 1 new obsolescence markers
764 986 obsoleted 1 changesets
765 987 $ hg up null -q
766 988 $ hg log -T '{rev}: {phase}\n'
767 989 2: draft
768 990 1: draft
769 991 0: draft
770 992 $ hg serve -p $HGPORT -d --pid-file=hg.pid
771 993 $ cat hg.pid > $DAEMON_PIDS
772 994 $ cd ..
773 995
774 996 $ hg clone -U --stream http://localhost:$HGPORT with-obsolescence
775 997 streaming all changes
776 998 1098 files to transfer, 102 KB of data (no-zstd !)
777 999 transferred 102 KB in * seconds (* */sec) (glob) (no-zstd !)
778 1000 1098 files to transfer, 99.5 KB of data (zstd !)
779 1001 transferred 99.5 KB in * seconds (* */sec) (glob) (zstd !)
780 1002 $ hg -R with-obsolescence log -T '{rev}: {phase}\n'
781 1003 2: draft
782 1004 1: draft
783 1005 0: draft
784 1006 $ hg debugobsolete -R with-obsolescence
785 1007 8c206a663911c1f97f2f9d7382e417ae55872cfa 0 {5223b5e3265f0df40bb743da62249413d74ac70f} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
786 1008 $ hg verify -R with-obsolescence -q
787 1009
788 1010 $ hg clone -U --stream --config experimental.evolution=0 http://localhost:$HGPORT with-obsolescence-no-evolution
789 1011 streaming all changes
790 1012 remote: abort: server has obsolescence markers, but client cannot receive them via stream clone
791 1013 abort: pull failed on remote
792 1014 [100]
793 1015
794 1016 $ killdaemons.py
795 1017
796 1018 #endif
797 1019
798 1020 Cloning a repo with no requirements doesn't give some obscure error
799 1021
800 1022 $ mkdir -p empty-repo/.hg
801 1023 $ hg clone -q --stream ssh://user@dummy/empty-repo empty-repo2
802 1024 $ hg --cwd empty-repo2 verify -q
General Comments 0
You need to be logged in to leave comments. Login now