##// END OF EJS Templates
remotefilelog: use sysstr to check for attribute presence...
marmoute -
r51788:a834ec41 default
parent child Browse files
Show More
@@ -1,1259 +1,1259 b''
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, '_addchangegroupfiles', shallowbundle.addchangegroupfiles
321 321 )
322 322 extensions.wrapfunction(
323 323 changegroup, 'makechangegroup', shallowbundle.makechangegroup
324 324 )
325 325 extensions.wrapfunction(localrepo, 'makestore', storewrapper)
326 326 extensions.wrapfunction(exchange, 'pull', exchangepull)
327 327 extensions.wrapfunction(merge, 'applyupdates', applyupdates)
328 328 extensions.wrapfunction(merge, '_checkunknownfiles', checkunknownfiles)
329 329 extensions.wrapfunction(context.workingctx, '_checklookup', checklookup)
330 330 extensions.wrapfunction(scmutil, '_findrenames', findrenames)
331 331 extensions.wrapfunction(
332 332 copies, '_computeforwardmissing', computeforwardmissing
333 333 )
334 334 extensions.wrapfunction(dispatch, 'runcommand', runcommand)
335 335 extensions.wrapfunction(repair, '_collectbrokencsets', _collectbrokencsets)
336 336 extensions.wrapfunction(context.changectx, 'filectx', filectx)
337 337 extensions.wrapfunction(context.workingctx, 'filectx', workingfilectx)
338 338 extensions.wrapfunction(patch, 'trydiff', trydiff)
339 339 extensions.wrapfunction(hg, 'verify', _verify)
340 340 scmutil.fileprefetchhooks.add(b'remotefilelog', _fileprefetchhook)
341 341
342 342 # disappointing hacks below
343 343 extensions.wrapfunction(scmutil, 'getrenamedfn', getrenamedfn)
344 344 extensions.wrapfunction(revset, '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, '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, '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, '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 411 pullop.remotebundle2caps[b'stream'] = []
412 412 if bundle2:
413 413 return False, None
414 414 supported, requirements = orig(pullop, bundle2=bundle2)
415 415 if requirements is not None:
416 416 requirements.add(constants.SHALLOWREPO_REQUIREMENT)
417 417 return supported, requirements
418 418
419 419 extensions.wrapfunction(
420 420 streamclone, 'canperformstreamclone', canperformstreamclone
421 421 )
422 422
423 423 try:
424 424 orig(ui, repo, *args, **opts)
425 425 finally:
426 426 if opts.get('shallow'):
427 427 for r in repos:
428 if util.safehasattr(r, b'fileservice'):
428 if util.safehasattr(r, 'fileservice'):
429 429 r.fileservice.close()
430 430
431 431
432 432 def debugdatashallow(orig, *args, **kwds):
433 433 oldlen = remotefilelog.remotefilelog.__len__
434 434 try:
435 435 remotefilelog.remotefilelog.__len__ = lambda x: 1
436 436 return orig(*args, **kwds)
437 437 finally:
438 438 remotefilelog.remotefilelog.__len__ = oldlen
439 439
440 440
441 441 def reposetup(ui, repo):
442 442 if not repo.local():
443 443 return
444 444
445 445 # put here intentionally bc doesnt work in uisetup
446 446 ui.setconfig(b'hooks', b'update.prefetch', wcpprefetch)
447 447 ui.setconfig(b'hooks', b'commit.prefetch', wcpprefetch)
448 448
449 449 isserverenabled = ui.configbool(b'remotefilelog', b'server')
450 450 isshallowclient = isenabled(repo)
451 451
452 452 if isserverenabled and isshallowclient:
453 453 raise RuntimeError(b"Cannot be both a server and shallow client.")
454 454
455 455 if isshallowclient:
456 456 setupclient(ui, repo)
457 457
458 458 if isserverenabled:
459 459 remotefilelogserver.setupserver(ui, repo)
460 460
461 461
462 462 def setupclient(ui, repo):
463 463 if not isinstance(repo, localrepo.localrepository):
464 464 return
465 465
466 466 # Even clients get the server setup since they need to have the
467 467 # wireprotocol endpoints registered.
468 468 remotefilelogserver.onetimesetup(ui)
469 469 onetimeclientsetup(ui)
470 470
471 471 shallowrepo.wraprepo(repo)
472 472 repo.store = shallowstore.wrapstore(repo.store)
473 473
474 474
475 475 def storewrapper(orig, requirements, path, vfstype):
476 476 s = orig(requirements, path, vfstype)
477 477 if constants.SHALLOWREPO_REQUIREMENT in requirements:
478 478 s = shallowstore.wrapstore(s)
479 479
480 480 return s
481 481
482 482
483 483 # prefetch files before update
484 484 def applyupdates(
485 485 orig, repo, mresult, wctx, mctx, overwrite, wantfiledata, **opts
486 486 ):
487 487 if isenabled(repo):
488 488 manifest = mctx.manifest()
489 489 files = []
490 490 for f, args, msg in mresult.getactions([mergestatemod.ACTION_GET]):
491 491 files.append((f, hex(manifest[f])))
492 492 # batch fetch the needed files from the server
493 493 repo.fileservice.prefetch(files)
494 494 return orig(repo, mresult, wctx, mctx, overwrite, wantfiledata, **opts)
495 495
496 496
497 497 # Prefetch merge checkunknownfiles
498 498 def checkunknownfiles(orig, repo, wctx, mctx, force, mresult, *args, **kwargs):
499 499 if isenabled(repo):
500 500 files = []
501 501 sparsematch = repo.maybesparsematch(mctx.rev())
502 502 for f, (m, actionargs, msg) in mresult.filemap():
503 503 if sparsematch and not sparsematch(f):
504 504 continue
505 505 if m in (
506 506 mergestatemod.ACTION_CREATED,
507 507 mergestatemod.ACTION_DELETED_CHANGED,
508 508 mergestatemod.ACTION_CREATED_MERGE,
509 509 ):
510 510 files.append((f, hex(mctx.filenode(f))))
511 511 elif m == mergestatemod.ACTION_LOCAL_DIR_RENAME_GET:
512 512 f2 = actionargs[0]
513 513 files.append((f2, hex(mctx.filenode(f2))))
514 514 # batch fetch the needed files from the server
515 515 repo.fileservice.prefetch(files)
516 516 return orig(repo, wctx, mctx, force, mresult, *args, **kwargs)
517 517
518 518
519 519 # Prefetch files before status attempts to look at their size and contents
520 520 def checklookup(orig, self, files, mtime_boundary):
521 521 repo = self._repo
522 522 if isenabled(repo):
523 523 prefetchfiles = []
524 524 for parent in self._parents:
525 525 for f in files:
526 526 if f in parent:
527 527 prefetchfiles.append((f, hex(parent.filenode(f))))
528 528 # batch fetch the needed files from the server
529 529 repo.fileservice.prefetch(prefetchfiles)
530 530 return orig(self, files, mtime_boundary)
531 531
532 532
533 533 # Prefetch the logic that compares added and removed files for renames
534 534 def findrenames(orig, repo, matcher, added, removed, *args, **kwargs):
535 535 if isenabled(repo):
536 536 files = []
537 537 pmf = repo[b'.'].manifest()
538 538 for f in removed:
539 539 if f in pmf:
540 540 files.append((f, hex(pmf[f])))
541 541 # batch fetch the needed files from the server
542 542 repo.fileservice.prefetch(files)
543 543 return orig(repo, matcher, added, removed, *args, **kwargs)
544 544
545 545
546 546 # prefetch files before pathcopies check
547 547 def computeforwardmissing(orig, a, b, match=None):
548 548 missing = orig(a, b, match=match)
549 549 repo = a._repo
550 550 if isenabled(repo):
551 551 mb = b.manifest()
552 552
553 553 files = []
554 554 sparsematch = repo.maybesparsematch(b.rev())
555 555 if sparsematch:
556 556 sparsemissing = set()
557 557 for f in missing:
558 558 if sparsematch(f):
559 559 files.append((f, hex(mb[f])))
560 560 sparsemissing.add(f)
561 561 missing = sparsemissing
562 562
563 563 # batch fetch the needed files from the server
564 564 repo.fileservice.prefetch(files)
565 565 return missing
566 566
567 567
568 568 # close cache miss server connection after the command has finished
569 569 def runcommand(orig, lui, repo, *args, **kwargs):
570 570 fileservice = None
571 571 # repo can be None when running in chg:
572 572 # - at startup, reposetup was called because serve is not norepo
573 573 # - a norepo command like "help" is called
574 574 if repo and isenabled(repo):
575 575 fileservice = repo.fileservice
576 576 try:
577 577 return orig(lui, repo, *args, **kwargs)
578 578 finally:
579 579 if fileservice:
580 580 fileservice.close()
581 581
582 582
583 583 # prevent strip from stripping remotefilelogs
584 584 def _collectbrokencsets(orig, repo, files, striprev):
585 585 if isenabled(repo):
586 586 files = list([f for f in files if not repo.shallowmatch(f)])
587 587 return orig(repo, files, striprev)
588 588
589 589
590 590 # changectx wrappers
591 591 def filectx(orig, self, path, fileid=None, filelog=None):
592 592 if fileid is None:
593 593 fileid = self.filenode(path)
594 594 if isenabled(self._repo) and self._repo.shallowmatch(path):
595 595 return remotefilectx.remotefilectx(
596 596 self._repo, path, fileid=fileid, changectx=self, filelog=filelog
597 597 )
598 598 return orig(self, path, fileid=fileid, filelog=filelog)
599 599
600 600
601 601 def workingfilectx(orig, self, path, filelog=None):
602 602 if isenabled(self._repo) and self._repo.shallowmatch(path):
603 603 return remotefilectx.remoteworkingfilectx(
604 604 self._repo, path, workingctx=self, filelog=filelog
605 605 )
606 606 return orig(self, path, filelog=filelog)
607 607
608 608
609 609 # prefetch required revisions before a diff
610 610 def trydiff(
611 611 orig,
612 612 repo,
613 613 revs,
614 614 ctx1,
615 615 ctx2,
616 616 modified,
617 617 added,
618 618 removed,
619 619 copy,
620 620 getfilectx,
621 621 *args,
622 622 **kwargs
623 623 ):
624 624 if isenabled(repo):
625 625 prefetch = []
626 626 mf1 = ctx1.manifest()
627 627 for fname in modified + added + removed:
628 628 if fname in mf1:
629 629 fnode = getfilectx(fname, ctx1).filenode()
630 630 # fnode can be None if it's a edited working ctx file
631 631 if fnode:
632 632 prefetch.append((fname, hex(fnode)))
633 633 if fname not in removed:
634 634 fnode = getfilectx(fname, ctx2).filenode()
635 635 if fnode:
636 636 prefetch.append((fname, hex(fnode)))
637 637
638 638 repo.fileservice.prefetch(prefetch)
639 639
640 640 return orig(
641 641 repo,
642 642 revs,
643 643 ctx1,
644 644 ctx2,
645 645 modified,
646 646 added,
647 647 removed,
648 648 copy,
649 649 getfilectx,
650 650 *args,
651 651 **kwargs
652 652 )
653 653
654 654
655 655 # Prevent verify from processing files
656 656 # a stub for mercurial.hg.verify()
657 657 def _verify(orig, repo, level=None):
658 658 lock = repo.lock()
659 659 try:
660 660 return shallowverifier.shallowverifier(repo).verify()
661 661 finally:
662 662 lock.release()
663 663
664 664
665 665 clientonetime = False
666 666
667 667
668 668 def onetimeclientsetup(ui):
669 669 global clientonetime
670 670 if clientonetime:
671 671 return
672 672 clientonetime = True
673 673
674 674 # Don't commit filelogs until we know the commit hash, since the hash
675 675 # is present in the filelog blob.
676 676 # This violates Mercurial's filelog->manifest->changelog write order,
677 677 # but is generally fine for client repos.
678 678 pendingfilecommits = []
679 679
680 680 def addrawrevision(
681 681 orig,
682 682 self,
683 683 rawtext,
684 684 transaction,
685 685 link,
686 686 p1,
687 687 p2,
688 688 node,
689 689 flags,
690 690 cachedelta=None,
691 691 _metatuple=None,
692 692 ):
693 693 if isinstance(link, int):
694 694 pendingfilecommits.append(
695 695 (
696 696 self,
697 697 rawtext,
698 698 transaction,
699 699 link,
700 700 p1,
701 701 p2,
702 702 node,
703 703 flags,
704 704 cachedelta,
705 705 _metatuple,
706 706 )
707 707 )
708 708 return node
709 709 else:
710 710 return orig(
711 711 self,
712 712 rawtext,
713 713 transaction,
714 714 link,
715 715 p1,
716 716 p2,
717 717 node,
718 718 flags,
719 719 cachedelta,
720 720 _metatuple=_metatuple,
721 721 )
722 722
723 723 extensions.wrapfunction(
724 724 remotefilelog.remotefilelog, 'addrawrevision', addrawrevision
725 725 )
726 726
727 727 def changelogadd(orig, self, *args, **kwargs):
728 728 oldlen = len(self)
729 729 node = orig(self, *args, **kwargs)
730 730 newlen = len(self)
731 731 if oldlen != newlen:
732 732 for oldargs in pendingfilecommits:
733 733 log, rt, tr, link, p1, p2, n, fl, c, m = oldargs
734 734 linknode = self.node(link)
735 735 if linknode == node:
736 736 log.addrawrevision(rt, tr, linknode, p1, p2, n, fl, c, m)
737 737 else:
738 738 raise error.ProgrammingError(
739 739 b'pending multiple integer revisions are not supported'
740 740 )
741 741 else:
742 742 # "link" is actually wrong here (it is set to len(changelog))
743 743 # if changelog remains unchanged, skip writing file revisions
744 744 # but still do a sanity check about pending multiple revisions
745 745 if len({x[3] for x in pendingfilecommits}) > 1:
746 746 raise error.ProgrammingError(
747 747 b'pending multiple integer revisions are not supported'
748 748 )
749 749 del pendingfilecommits[:]
750 750 return node
751 751
752 752 extensions.wrapfunction(changelog.changelog, 'add', changelogadd)
753 753
754 754
755 755 def getrenamedfn(orig, repo, endrev=None):
756 756 if not isenabled(repo) or copies.usechangesetcentricalgo(repo):
757 757 return orig(repo, endrev)
758 758
759 759 rcache = {}
760 760
761 761 def getrenamed(fn, rev):
762 762 """looks up all renames for a file (up to endrev) the first
763 763 time the file is given. It indexes on the changerev and only
764 764 parses the manifest if linkrev != changerev.
765 765 Returns rename info for fn at changerev rev."""
766 766 if rev in rcache.setdefault(fn, {}):
767 767 return rcache[fn][rev]
768 768
769 769 try:
770 770 fctx = repo[rev].filectx(fn)
771 771 for ancestor in fctx.ancestors():
772 772 if ancestor.path() == fn:
773 773 renamed = ancestor.renamed()
774 774 rcache[fn][ancestor.rev()] = renamed and renamed[0]
775 775
776 776 renamed = fctx.renamed()
777 777 return renamed and renamed[0]
778 778 except error.LookupError:
779 779 return None
780 780
781 781 return getrenamed
782 782
783 783
784 784 def filelogrevset(orig, repo, subset, x):
785 785 """``filelog(pattern)``
786 786 Changesets connected to the specified filelog.
787 787
788 788 For performance reasons, ``filelog()`` does not show every changeset
789 789 that affects the requested file(s). See :hg:`help log` for details. For
790 790 a slower, more accurate result, use ``file()``.
791 791 """
792 792
793 793 if not isenabled(repo):
794 794 return orig(repo, subset, x)
795 795
796 796 # i18n: "filelog" is a keyword
797 797 pat = revset.getstring(x, _(b"filelog requires a pattern"))
798 798 m = matchmod.match(
799 799 repo.root, repo.getcwd(), [pat], default=b'relpath', ctx=repo[None]
800 800 )
801 801 s = set()
802 802
803 803 if not matchmod.patkind(pat):
804 804 # slow
805 805 for r in subset:
806 806 ctx = repo[r]
807 807 cfiles = ctx.files()
808 808 for f in m.files():
809 809 if f in cfiles:
810 810 s.add(ctx.rev())
811 811 break
812 812 else:
813 813 # partial
814 814 files = (f for f in repo[None] if m(f))
815 815 for f in files:
816 816 fctx = repo[None].filectx(f)
817 817 s.add(fctx.linkrev())
818 818 for actx in fctx.ancestors():
819 819 s.add(actx.linkrev())
820 820
821 821 return smartset.baseset([r for r in subset if r in s])
822 822
823 823
824 824 @command(b'gc', [], _(b'hg gc [REPO...]'), norepo=True)
825 825 def gc(ui, *args, **opts):
826 826 """garbage collect the client and server filelog caches"""
827 827 cachepaths = set()
828 828
829 829 # get the system client cache
830 830 systemcache = shallowutil.getcachepath(ui, allowempty=True)
831 831 if systemcache:
832 832 cachepaths.add(systemcache)
833 833
834 834 # get repo client and server cache
835 835 repopaths = []
836 836 pwd = ui.environ.get(b'PWD')
837 837 if pwd:
838 838 repopaths.append(pwd)
839 839
840 840 repopaths.extend(args)
841 841 repos = []
842 842 for repopath in repopaths:
843 843 try:
844 844 repo = hg.peer(ui, {}, repopath)
845 845 repos.append(repo)
846 846
847 847 repocache = shallowutil.getcachepath(repo.ui, allowempty=True)
848 848 if repocache:
849 849 cachepaths.add(repocache)
850 850 except error.RepoError:
851 851 pass
852 852
853 853 # gc client cache
854 854 for cachepath in cachepaths:
855 855 gcclient(ui, cachepath)
856 856
857 857 # gc server cache
858 858 for repo in repos:
859 859 remotefilelogserver.gcserver(ui, repo._repo)
860 860
861 861
862 862 def gcclient(ui, cachepath):
863 863 # get list of repos that use this cache
864 864 repospath = os.path.join(cachepath, b'repos')
865 865 if not os.path.exists(repospath):
866 866 ui.warn(_(b"no known cache at %s\n") % cachepath)
867 867 return
868 868
869 869 reposfile = open(repospath, b'rb')
870 870 repos = {r[:-1] for r in reposfile.readlines()}
871 871 reposfile.close()
872 872
873 873 # build list of useful files
874 874 validrepos = []
875 875 keepkeys = set()
876 876
877 877 sharedcache = None
878 878 filesrepacked = False
879 879
880 880 count = 0
881 881 progress = ui.makeprogress(
882 882 _(b"analyzing repositories"), unit=b"repos", total=len(repos)
883 883 )
884 884 for path in repos:
885 885 progress.update(count)
886 886 count += 1
887 887 try:
888 888 path = util.expandpath(os.path.normpath(path))
889 889 except TypeError as e:
890 890 ui.warn(_(b"warning: malformed path: %r:%s\n") % (path, e))
891 891 traceback.print_exc()
892 892 continue
893 893 try:
894 894 peer = hg.peer(ui, {}, path)
895 895 repo = peer._repo
896 896 except error.RepoError:
897 897 continue
898 898
899 899 validrepos.append(path)
900 900
901 901 # Protect against any repo or config changes that have happened since
902 902 # this repo was added to the repos file. We'd rather this loop succeed
903 903 # and too much be deleted, than the loop fail and nothing gets deleted.
904 904 if not isenabled(repo):
905 905 continue
906 906
907 if not util.safehasattr(repo, b'name'):
907 if not util.safehasattr(repo, 'name'):
908 908 ui.warn(
909 909 _(b"repo %s is a misconfigured remotefilelog repo\n") % path
910 910 )
911 911 continue
912 912
913 913 # If garbage collection on repack and repack on hg gc are enabled
914 914 # then loose files are repacked and garbage collected.
915 915 # Otherwise regular garbage collection is performed.
916 916 repackonhggc = repo.ui.configbool(b'remotefilelog', b'repackonhggc')
917 917 gcrepack = repo.ui.configbool(b'remotefilelog', b'gcrepack')
918 918 if repackonhggc and gcrepack:
919 919 try:
920 920 repackmod.incrementalrepack(repo)
921 921 filesrepacked = True
922 922 continue
923 923 except (IOError, repackmod.RepackAlreadyRunning):
924 924 # If repack cannot be performed due to not enough disk space
925 925 # continue doing garbage collection of loose files w/o repack
926 926 pass
927 927
928 928 reponame = repo.name
929 929 if not sharedcache:
930 930 sharedcache = repo.sharedstore
931 931
932 932 # Compute a keepset which is not garbage collected
933 933 def keyfn(fname, fnode):
934 934 return fileserverclient.getcachekey(reponame, fname, hex(fnode))
935 935
936 936 keepkeys = repackmod.keepset(repo, keyfn=keyfn, lastkeepkeys=keepkeys)
937 937
938 938 progress.complete()
939 939
940 940 # write list of valid repos back
941 941 oldumask = os.umask(0o002)
942 942 try:
943 943 reposfile = open(repospath, b'wb')
944 944 reposfile.writelines([(b"%s\n" % r) for r in validrepos])
945 945 reposfile.close()
946 946 finally:
947 947 os.umask(oldumask)
948 948
949 949 # prune cache
950 950 if sharedcache is not None:
951 951 sharedcache.gc(keepkeys)
952 952 elif not filesrepacked:
953 953 ui.warn(_(b"warning: no valid repos in repofile\n"))
954 954
955 955
956 956 def log(orig, ui, repo, *pats, **opts):
957 957 if not isenabled(repo):
958 958 return orig(ui, repo, *pats, **opts)
959 959
960 960 follow = opts.get('follow')
961 961 revs = opts.get('rev')
962 962 if pats:
963 963 # Force slowpath for non-follow patterns and follows that start from
964 964 # non-working-copy-parent revs.
965 965 if not follow or revs:
966 966 # This forces the slowpath
967 967 opts['removed'] = True
968 968
969 969 # If this is a non-follow log without any revs specified, recommend that
970 970 # the user add -f to speed it up.
971 971 if not follow and not revs:
972 972 match = scmutil.match(repo[b'.'], pats, pycompat.byteskwargs(opts))
973 973 isfile = not match.anypats()
974 974 if isfile:
975 975 for file in match.files():
976 976 if not os.path.isfile(repo.wjoin(file)):
977 977 isfile = False
978 978 break
979 979
980 980 if isfile:
981 981 ui.warn(
982 982 _(
983 983 b"warning: file log can be slow on large repos - "
984 984 + b"use -f to speed it up\n"
985 985 )
986 986 )
987 987
988 988 return orig(ui, repo, *pats, **opts)
989 989
990 990
991 991 def revdatelimit(ui, revset):
992 992 """Update revset so that only changesets no older than 'prefetchdays' days
993 993 are included. The default value is set to 14 days. If 'prefetchdays' is set
994 994 to zero or negative value then date restriction is not applied.
995 995 """
996 996 days = ui.configint(b'remotefilelog', b'prefetchdays')
997 997 if days > 0:
998 998 revset = b'(%s) & date(-%s)' % (revset, days)
999 999 return revset
1000 1000
1001 1001
1002 1002 def readytofetch(repo):
1003 1003 """Check that enough time has passed since the last background prefetch.
1004 1004 This only relates to prefetches after operations that change the working
1005 1005 copy parent. Default delay between background prefetches is 2 minutes.
1006 1006 """
1007 1007 timeout = repo.ui.configint(b'remotefilelog', b'prefetchdelay')
1008 1008 fname = repo.vfs.join(b'lastprefetch')
1009 1009
1010 1010 ready = False
1011 1011 with open(fname, b'a'):
1012 1012 # the with construct above is used to avoid race conditions
1013 1013 modtime = os.path.getmtime(fname)
1014 1014 if (time.time() - modtime) > timeout:
1015 1015 os.utime(fname, None)
1016 1016 ready = True
1017 1017
1018 1018 return ready
1019 1019
1020 1020
1021 1021 def wcpprefetch(ui, repo, **kwargs):
1022 1022 """Prefetches in background revisions specified by bgprefetchrevs revset.
1023 1023 Does background repack if backgroundrepack flag is set in config.
1024 1024 """
1025 1025 shallow = isenabled(repo)
1026 1026 bgprefetchrevs = ui.config(b'remotefilelog', b'bgprefetchrevs')
1027 1027 isready = readytofetch(repo)
1028 1028
1029 1029 if not (shallow and bgprefetchrevs and isready):
1030 1030 return
1031 1031
1032 1032 bgrepack = repo.ui.configbool(b'remotefilelog', b'backgroundrepack')
1033 1033 # update a revset with a date limit
1034 1034 bgprefetchrevs = revdatelimit(ui, bgprefetchrevs)
1035 1035
1036 1036 def anon(unused_success):
1037 if util.safehasattr(repo, b'ranprefetch') and repo.ranprefetch:
1037 if util.safehasattr(repo, 'ranprefetch') and repo.ranprefetch:
1038 1038 return
1039 1039 repo.ranprefetch = True
1040 1040 repo.backgroundprefetch(bgprefetchrevs, repack=bgrepack)
1041 1041
1042 1042 repo._afterlock(anon)
1043 1043
1044 1044
1045 1045 def pull(orig, ui, repo, *pats, **opts):
1046 1046 result = orig(ui, repo, *pats, **opts)
1047 1047
1048 1048 if isenabled(repo):
1049 1049 # prefetch if it's configured
1050 1050 prefetchrevset = ui.config(b'remotefilelog', b'pullprefetch')
1051 1051 bgrepack = repo.ui.configbool(b'remotefilelog', b'backgroundrepack')
1052 1052 bgprefetch = repo.ui.configbool(b'remotefilelog', b'backgroundprefetch')
1053 1053
1054 1054 if prefetchrevset:
1055 1055 ui.status(_(b"prefetching file contents\n"))
1056 1056 revs = scmutil.revrange(repo, [prefetchrevset])
1057 1057 base = repo[b'.'].rev()
1058 1058 if bgprefetch:
1059 1059 repo.backgroundprefetch(prefetchrevset, repack=bgrepack)
1060 1060 else:
1061 1061 repo.prefetch(revs, base=base)
1062 1062 if bgrepack:
1063 1063 repackmod.backgroundrepack(repo, incremental=True)
1064 1064 elif bgrepack:
1065 1065 repackmod.backgroundrepack(repo, incremental=True)
1066 1066
1067 1067 return result
1068 1068
1069 1069
1070 1070 def exchangepull(orig, repo, remote, *args, **kwargs):
1071 1071 # Hook into the callstream/getbundle to insert bundle capabilities
1072 1072 # during a pull.
1073 1073 def localgetbundle(
1074 1074 orig, source, heads=None, common=None, bundlecaps=None, **kwargs
1075 1075 ):
1076 1076 if not bundlecaps:
1077 1077 bundlecaps = set()
1078 1078 bundlecaps.add(constants.BUNDLE2_CAPABLITY)
1079 1079 return orig(
1080 1080 source, heads=heads, common=common, bundlecaps=bundlecaps, **kwargs
1081 1081 )
1082 1082
1083 if util.safehasattr(remote, b'_callstream'):
1083 if util.safehasattr(remote, '_callstream'):
1084 1084 remote._localrepo = repo
1085 elif util.safehasattr(remote, b'getbundle'):
1085 elif util.safehasattr(remote, 'getbundle'):
1086 1086 extensions.wrapfunction(remote, 'getbundle', localgetbundle)
1087 1087
1088 1088 return orig(repo, remote, *args, **kwargs)
1089 1089
1090 1090
1091 1091 def _fileprefetchhook(repo, revmatches):
1092 1092 if isenabled(repo):
1093 1093 allfiles = []
1094 1094 for rev, match in revmatches:
1095 1095 if rev == wdirrev or rev is None:
1096 1096 continue
1097 1097 ctx = repo[rev]
1098 1098 mf = ctx.manifest()
1099 1099 sparsematch = repo.maybesparsematch(ctx.rev())
1100 1100 for path in ctx.walk(match):
1101 1101 if (not sparsematch or sparsematch(path)) and path in mf:
1102 1102 allfiles.append((path, hex(mf[path])))
1103 1103 repo.fileservice.prefetch(allfiles)
1104 1104
1105 1105
1106 1106 @command(
1107 1107 b'debugremotefilelog',
1108 1108 [
1109 1109 (b'd', b'decompress', None, _(b'decompress the filelog first')),
1110 1110 ],
1111 1111 _(b'hg debugremotefilelog <path>'),
1112 1112 norepo=True,
1113 1113 )
1114 1114 def debugremotefilelog(ui, path, **opts):
1115 1115 return debugcommands.debugremotefilelog(ui, path, **opts)
1116 1116
1117 1117
1118 1118 @command(
1119 1119 b'verifyremotefilelog',
1120 1120 [
1121 1121 (b'd', b'decompress', None, _(b'decompress the filelogs first')),
1122 1122 ],
1123 1123 _(b'hg verifyremotefilelogs <directory>'),
1124 1124 norepo=True,
1125 1125 )
1126 1126 def verifyremotefilelog(ui, path, **opts):
1127 1127 return debugcommands.verifyremotefilelog(ui, path, **opts)
1128 1128
1129 1129
1130 1130 @command(
1131 1131 b'debugdatapack',
1132 1132 [
1133 1133 (b'', b'long', None, _(b'print the long hashes')),
1134 1134 (b'', b'node', b'', _(b'dump the contents of node'), b'NODE'),
1135 1135 ],
1136 1136 _(b'hg debugdatapack <paths>'),
1137 1137 norepo=True,
1138 1138 )
1139 1139 def debugdatapack(ui, *paths, **opts):
1140 1140 return debugcommands.debugdatapack(ui, *paths, **opts)
1141 1141
1142 1142
1143 1143 @command(b'debughistorypack', [], _(b'hg debughistorypack <path>'), norepo=True)
1144 1144 def debughistorypack(ui, path, **opts):
1145 1145 return debugcommands.debughistorypack(ui, path)
1146 1146
1147 1147
1148 1148 @command(b'debugkeepset', [], _(b'hg debugkeepset'))
1149 1149 def debugkeepset(ui, repo, **opts):
1150 1150 # The command is used to measure keepset computation time
1151 1151 def keyfn(fname, fnode):
1152 1152 return fileserverclient.getcachekey(repo.name, fname, hex(fnode))
1153 1153
1154 1154 repackmod.keepset(repo, keyfn)
1155 1155 return
1156 1156
1157 1157
1158 1158 @command(b'debugwaitonrepack', [], _(b'hg debugwaitonrepack'))
1159 1159 def debugwaitonrepack(ui, repo, **opts):
1160 1160 return debugcommands.debugwaitonrepack(repo)
1161 1161
1162 1162
1163 1163 @command(b'debugwaitonprefetch', [], _(b'hg debugwaitonprefetch'))
1164 1164 def debugwaitonprefetch(ui, repo, **opts):
1165 1165 return debugcommands.debugwaitonprefetch(repo)
1166 1166
1167 1167
1168 1168 def resolveprefetchopts(ui, opts):
1169 1169 if not opts.get(b'rev'):
1170 1170 revset = [b'.', b'draft()']
1171 1171
1172 1172 prefetchrevset = ui.config(b'remotefilelog', b'pullprefetch', None)
1173 1173 if prefetchrevset:
1174 1174 revset.append(b'(%s)' % prefetchrevset)
1175 1175 bgprefetchrevs = ui.config(b'remotefilelog', b'bgprefetchrevs', None)
1176 1176 if bgprefetchrevs:
1177 1177 revset.append(b'(%s)' % bgprefetchrevs)
1178 1178 revset = b'+'.join(revset)
1179 1179
1180 1180 # update a revset with a date limit
1181 1181 revset = revdatelimit(ui, revset)
1182 1182
1183 1183 opts[b'rev'] = [revset]
1184 1184
1185 1185 if not opts.get(b'base'):
1186 1186 opts[b'base'] = None
1187 1187
1188 1188 return opts
1189 1189
1190 1190
1191 1191 @command(
1192 1192 b'prefetch',
1193 1193 [
1194 1194 (b'r', b'rev', [], _(b'prefetch the specified revisions'), _(b'REV')),
1195 1195 (b'', b'repack', False, _(b'run repack after prefetch')),
1196 1196 (b'b', b'base', b'', _(b"rev that is assumed to already be local")),
1197 1197 ]
1198 1198 + commands.walkopts,
1199 1199 _(b'hg prefetch [OPTIONS] [FILE...]'),
1200 1200 helpcategory=command.CATEGORY_MAINTENANCE,
1201 1201 )
1202 1202 def prefetch(ui, repo, *pats, **opts):
1203 1203 """prefetch file revisions from the server
1204 1204
1205 1205 Prefetchs file revisions for the specified revs and stores them in the
1206 1206 local remotefilelog cache. If no rev is specified, the default rev is
1207 1207 used which is the union of dot, draft, pullprefetch and bgprefetchrev.
1208 1208 File names or patterns can be used to limit which files are downloaded.
1209 1209
1210 1210 Return 0 on success.
1211 1211 """
1212 1212 opts = pycompat.byteskwargs(opts)
1213 1213 if not isenabled(repo):
1214 1214 raise error.Abort(_(b"repo is not shallow"))
1215 1215
1216 1216 opts = resolveprefetchopts(ui, opts)
1217 1217 revs = scmutil.revrange(repo, opts.get(b'rev'))
1218 1218 repo.prefetch(revs, opts.get(b'base'), pats, opts)
1219 1219
1220 1220 # Run repack in background
1221 1221 if opts.get(b'repack'):
1222 1222 repackmod.backgroundrepack(repo, incremental=True)
1223 1223
1224 1224
1225 1225 @command(
1226 1226 b'repack',
1227 1227 [
1228 1228 (b'', b'background', None, _(b'run in a background process'), None),
1229 1229 (b'', b'incremental', None, _(b'do an incremental repack'), None),
1230 1230 (
1231 1231 b'',
1232 1232 b'packsonly',
1233 1233 None,
1234 1234 _(b'only repack packs (skip loose objects)'),
1235 1235 None,
1236 1236 ),
1237 1237 ],
1238 1238 _(b'hg repack [OPTIONS]'),
1239 1239 )
1240 1240 def repack_(ui, repo, *pats, **opts):
1241 1241 if opts.get('background'):
1242 1242 repackmod.backgroundrepack(
1243 1243 repo,
1244 1244 incremental=opts.get('incremental'),
1245 1245 packsonly=opts.get('packsonly', False),
1246 1246 )
1247 1247 return
1248 1248
1249 1249 options = {b'packsonly': opts.get('packsonly')}
1250 1250
1251 1251 try:
1252 1252 if opts.get('incremental'):
1253 1253 repackmod.incrementalrepack(repo, options=options)
1254 1254 else:
1255 1255 repackmod.fullrepack(repo, options=options)
1256 1256 except repackmod.RepackAlreadyRunning as ex:
1257 1257 # Don't propogate the exception if the repack is already in
1258 1258 # progress, since we want the command to exit 0.
1259 1259 repo.ui.warn(b'%s\n' % ex)
@@ -1,446 +1,446 b''
1 1 # remotefilelogserver.py - server logic for a remotefilelog server
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
8 8 import os
9 9 import stat
10 10 import time
11 11 import zlib
12 12
13 13 from mercurial.i18n import _
14 14 from mercurial.node import bin, hex
15 15 from mercurial.pycompat import open
16 16 from mercurial import (
17 17 changegroup,
18 18 changelog,
19 19 context,
20 20 error,
21 21 extensions,
22 22 match,
23 23 scmutil,
24 24 store,
25 25 streamclone,
26 26 util,
27 27 wireprotoserver,
28 28 wireprototypes,
29 29 wireprotov1server,
30 30 )
31 31 from . import (
32 32 constants,
33 33 shallowutil,
34 34 )
35 35
36 36 _sshv1server = wireprotoserver.sshv1protocolhandler
37 37
38 38
39 39 def setupserver(ui, repo):
40 40 """Sets up a normal Mercurial repo so it can serve files to shallow repos."""
41 41 onetimesetup(ui)
42 42
43 43 # don't send files to shallow clients during pulls
44 44 def generatefiles(
45 45 orig, self, changedfiles, linknodes, commonrevs, source, *args, **kwargs
46 46 ):
47 47 caps = self._bundlecaps or []
48 48 if constants.BUNDLE2_CAPABLITY in caps:
49 49 # only send files that don't match the specified patterns
50 50 includepattern = None
51 51 excludepattern = None
52 52 for cap in self._bundlecaps or []:
53 53 if cap.startswith(b"includepattern="):
54 54 includepattern = cap[len(b"includepattern=") :].split(b'\0')
55 55 elif cap.startswith(b"excludepattern="):
56 56 excludepattern = cap[len(b"excludepattern=") :].split(b'\0')
57 57
58 58 m = match.always()
59 59 if includepattern or excludepattern:
60 60 m = match.match(
61 61 repo.root, b'', None, includepattern, excludepattern
62 62 )
63 63
64 64 changedfiles = list([f for f in changedfiles if not m(f)])
65 65 return orig(
66 66 self, changedfiles, linknodes, commonrevs, source, *args, **kwargs
67 67 )
68 68
69 69 extensions.wrapfunction(
70 70 changegroup.cgpacker, 'generatefiles', generatefiles
71 71 )
72 72
73 73
74 74 onetime = False
75 75
76 76
77 77 def onetimesetup(ui):
78 78 """Configures the wireprotocol for both clients and servers."""
79 79 global onetime
80 80 if onetime:
81 81 return
82 82 onetime = True
83 83
84 84 # support file content requests
85 85 wireprotov1server.wireprotocommand(
86 86 b'x_rfl_getflogheads', b'path', permission=b'pull'
87 87 )(getflogheads)
88 88 wireprotov1server.wireprotocommand(
89 89 b'x_rfl_getfiles', b'', permission=b'pull'
90 90 )(getfiles)
91 91 wireprotov1server.wireprotocommand(
92 92 b'x_rfl_getfile', b'file node', permission=b'pull'
93 93 )(getfile)
94 94
95 95 class streamstate:
96 96 match = None
97 97 shallowremote = False
98 98 noflatmf = False
99 99
100 100 state = streamstate()
101 101
102 102 def stream_out_shallow(repo, proto, other):
103 103 includepattern = None
104 104 excludepattern = None
105 105 raw = other.get(b'includepattern')
106 106 if raw:
107 107 includepattern = raw.split(b'\0')
108 108 raw = other.get(b'excludepattern')
109 109 if raw:
110 110 excludepattern = raw.split(b'\0')
111 111
112 112 oldshallow = state.shallowremote
113 113 oldmatch = state.match
114 114 oldnoflatmf = state.noflatmf
115 115 try:
116 116 state.shallowremote = True
117 117 state.match = match.always()
118 118 state.noflatmf = other.get(b'noflatmanifest') == b'True'
119 119 if includepattern or excludepattern:
120 120 state.match = match.match(
121 121 repo.root, b'', None, includepattern, excludepattern
122 122 )
123 123 streamres = wireprotov1server.stream(repo, proto)
124 124
125 125 # Force the first value to execute, so the file list is computed
126 126 # within the try/finally scope
127 127 first = next(streamres.gen)
128 128 second = next(streamres.gen)
129 129
130 130 def gen():
131 131 yield first
132 132 yield second
133 133 for value in streamres.gen:
134 134 yield value
135 135
136 136 return wireprototypes.streamres(gen())
137 137 finally:
138 138 state.shallowremote = oldshallow
139 139 state.match = oldmatch
140 140 state.noflatmf = oldnoflatmf
141 141
142 142 wireprotov1server.commands[b'stream_out_shallow'] = (
143 143 stream_out_shallow,
144 144 b'*',
145 145 )
146 146
147 147 # don't clone filelogs to shallow clients
148 148 def _walkstreamfiles(
149 149 orig, repo, matcher=None, phase=False, obsolescence=False
150 150 ):
151 151 if state.shallowremote:
152 152 # if we are shallow ourselves, stream our local commits
153 153 if shallowutil.isenabled(repo):
154 154 striplen = len(repo.store.path) + 1
155 155 readdir = repo.store.rawvfs.readdir
156 156 visit = [os.path.join(repo.store.path, b'data')]
157 157 while visit:
158 158 p = visit.pop()
159 159 for f, kind, st in readdir(p, stat=True):
160 160 fp = p + b'/' + f
161 161 if kind == stat.S_IFREG:
162 162 if not fp.endswith(b'.i') and not fp.endswith(
163 163 b'.d'
164 164 ):
165 165 n = util.pconvert(fp[striplen:])
166 166 d = store.decodedir(n)
167 167 yield store.SimpleStoreEntry(
168 168 entry_path=d,
169 169 is_volatile=False,
170 170 file_size=st.st_size,
171 171 )
172 172
173 173 if kind == stat.S_IFDIR:
174 174 visit.append(fp)
175 175
176 176 if scmutil.istreemanifest(repo):
177 177 for entry in repo.store.data_entries():
178 178 if not entry.is_revlog:
179 179 continue
180 180 if entry.is_manifestlog:
181 181 yield entry
182 182
183 183 # Return .d and .i files that do not match the shallow pattern
184 184 match = state.match
185 185 if match and not match.always():
186 186 for entry in repo.store.data_entries():
187 187 if not entry.is_revlog:
188 188 continue
189 189 if not state.match(entry.target_id):
190 190 yield entry
191 191
192 192 for x in repo.store.top_entries():
193 193 if state.noflatmf and x[1][:11] == b'00manifest.':
194 194 continue
195 195 yield x
196 196
197 197 elif shallowutil.isenabled(repo):
198 198 # don't allow cloning from a shallow repo to a full repo
199 199 # since it would require fetching every version of every
200 200 # file in order to create the revlogs.
201 201 raise error.Abort(
202 202 _(b"Cannot clone from a shallow repo to a full repo.")
203 203 )
204 204 else:
205 205 for x in orig(
206 206 repo, matcher, phase=phase, obsolescence=obsolescence
207 207 ):
208 208 yield x
209 209
210 210 extensions.wrapfunction(streamclone, '_walkstreamfiles', _walkstreamfiles)
211 211
212 212 # expose remotefilelog capabilities
213 213 def _capabilities(orig, repo, proto):
214 214 caps = orig(repo, proto)
215 215 if shallowutil.isenabled(repo) or ui.configbool(
216 216 b'remotefilelog', b'server'
217 217 ):
218 218 if isinstance(proto, _sshv1server):
219 219 # legacy getfiles method which only works over ssh
220 220 caps.append(constants.NETWORK_CAP_LEGACY_SSH_GETFILES)
221 221 caps.append(b'x_rfl_getflogheads')
222 222 caps.append(b'x_rfl_getfile')
223 223 return caps
224 224
225 225 extensions.wrapfunction(wireprotov1server, '_capabilities', _capabilities)
226 226
227 227 def _adjustlinkrev(orig, self, *args, **kwargs):
228 228 # When generating file blobs, taking the real path is too slow on large
229 229 # repos, so force it to just return the linkrev directly.
230 230 repo = self._repo
231 if util.safehasattr(repo, b'forcelinkrev') and repo.forcelinkrev:
231 if util.safehasattr(repo, 'forcelinkrev') and repo.forcelinkrev:
232 232 return self._filelog.linkrev(self._filelog.rev(self._filenode))
233 233 return orig(self, *args, **kwargs)
234 234
235 235 extensions.wrapfunction(
236 236 context.basefilectx, '_adjustlinkrev', _adjustlinkrev
237 237 )
238 238
239 239 def _iscmd(orig, cmd):
240 240 if cmd == b'x_rfl_getfiles':
241 241 return False
242 242 return orig(cmd)
243 243
244 244 extensions.wrapfunction(wireprotoserver, 'iscmd', _iscmd)
245 245
246 246
247 247 def _loadfileblob(repo, cachepath, path, node):
248 248 filecachepath = os.path.join(cachepath, path, hex(node))
249 249 if not os.path.exists(filecachepath) or os.path.getsize(filecachepath) == 0:
250 250 filectx = repo.filectx(path, fileid=node)
251 251 if filectx.node() == repo.nullid:
252 252 repo.changelog = changelog.changelog(repo.svfs)
253 253 filectx = repo.filectx(path, fileid=node)
254 254
255 255 text = createfileblob(filectx)
256 256 # TODO configurable compression engines
257 257 text = zlib.compress(text)
258 258
259 259 # everything should be user & group read/writable
260 260 oldumask = os.umask(0o002)
261 261 try:
262 262 dirname = os.path.dirname(filecachepath)
263 263 if not os.path.exists(dirname):
264 264 try:
265 265 os.makedirs(dirname)
266 266 except FileExistsError:
267 267 pass
268 268
269 269 f = None
270 270 try:
271 271 f = util.atomictempfile(filecachepath, b"wb")
272 272 f.write(text)
273 273 except (IOError, OSError):
274 274 # Don't abort if the user only has permission to read,
275 275 # and not write.
276 276 pass
277 277 finally:
278 278 if f:
279 279 f.close()
280 280 finally:
281 281 os.umask(oldumask)
282 282 else:
283 283 with open(filecachepath, b"rb") as f:
284 284 text = f.read()
285 285 return text
286 286
287 287
288 288 def getflogheads(repo, proto, path):
289 289 """A server api for requesting a filelog's heads"""
290 290 flog = repo.file(path)
291 291 heads = flog.heads()
292 292 return b'\n'.join((hex(head) for head in heads if head != repo.nullid))
293 293
294 294
295 295 def getfile(repo, proto, file, node):
296 296 """A server api for requesting a particular version of a file. Can be used
297 297 in batches to request many files at once. The return protocol is:
298 298 <errorcode>\0<data/errormsg> where <errorcode> is 0 for success or
299 299 non-zero for an error.
300 300
301 301 data is a compressed blob with revlog flag and ancestors information. See
302 302 createfileblob for its content.
303 303 """
304 304 if shallowutil.isenabled(repo):
305 305 return b'1\0' + _(b'cannot fetch remote files from shallow repo')
306 306 cachepath = repo.ui.config(b"remotefilelog", b"servercachepath")
307 307 if not cachepath:
308 308 cachepath = os.path.join(repo.path, b"remotefilelogcache")
309 309 node = bin(node.strip())
310 310 if node == repo.nullid:
311 311 return b'0\0'
312 312 return b'0\0' + _loadfileblob(repo, cachepath, file, node)
313 313
314 314
315 315 def getfiles(repo, proto):
316 316 """A server api for requesting particular versions of particular files."""
317 317 if shallowutil.isenabled(repo):
318 318 raise error.Abort(_(b'cannot fetch remote files from shallow repo'))
319 319 if not isinstance(proto, _sshv1server):
320 320 raise error.Abort(_(b'cannot fetch remote files over non-ssh protocol'))
321 321
322 322 def streamer():
323 323 fin = proto._fin
324 324
325 325 cachepath = repo.ui.config(b"remotefilelog", b"servercachepath")
326 326 if not cachepath:
327 327 cachepath = os.path.join(repo.path, b"remotefilelogcache")
328 328
329 329 while True:
330 330 request = fin.readline()[:-1]
331 331 if not request:
332 332 break
333 333
334 334 node = bin(request[:40])
335 335 if node == repo.nullid:
336 336 yield b'0\n'
337 337 continue
338 338
339 339 path = request[40:]
340 340
341 341 text = _loadfileblob(repo, cachepath, path, node)
342 342
343 343 yield b'%d\n%s' % (len(text), text)
344 344
345 345 # it would be better to only flush after processing a whole batch
346 346 # but currently we don't know if there are more requests coming
347 347 proto._fout.flush()
348 348
349 349 return wireprototypes.streamres(streamer())
350 350
351 351
352 352 def createfileblob(filectx):
353 353 """
354 354 format:
355 355 v0:
356 356 str(len(rawtext)) + '\0' + rawtext + ancestortext
357 357 v1:
358 358 'v1' + '\n' + metalist + '\0' + rawtext + ancestortext
359 359 metalist := metalist + '\n' + meta | meta
360 360 meta := sizemeta | flagmeta
361 361 sizemeta := METAKEYSIZE + str(len(rawtext))
362 362 flagmeta := METAKEYFLAG + str(flag)
363 363
364 364 note: sizemeta must exist. METAKEYFLAG and METAKEYSIZE must have a
365 365 length of 1.
366 366 """
367 367 flog = filectx.filelog()
368 368 frev = filectx.filerev()
369 369 revlogflags = flog._revlog.flags(frev)
370 370 if revlogflags == 0:
371 371 # normal files
372 372 text = filectx.data()
373 373 else:
374 374 # lfs, read raw revision data
375 375 text = flog.rawdata(frev)
376 376
377 377 repo = filectx._repo
378 378
379 379 ancestors = [filectx]
380 380
381 381 try:
382 382 repo.forcelinkrev = True
383 383 ancestors.extend([f for f in filectx.ancestors()])
384 384
385 385 ancestortext = b""
386 386 for ancestorctx in ancestors:
387 387 parents = ancestorctx.parents()
388 388 p1 = repo.nullid
389 389 p2 = repo.nullid
390 390 if len(parents) > 0:
391 391 p1 = parents[0].filenode()
392 392 if len(parents) > 1:
393 393 p2 = parents[1].filenode()
394 394
395 395 copyname = b""
396 396 rename = ancestorctx.renamed()
397 397 if rename:
398 398 copyname = rename[0]
399 399 linknode = ancestorctx.node()
400 400 ancestortext += b"%s%s%s%s%s\0" % (
401 401 ancestorctx.filenode(),
402 402 p1,
403 403 p2,
404 404 linknode,
405 405 copyname,
406 406 )
407 407 finally:
408 408 repo.forcelinkrev = False
409 409
410 410 header = shallowutil.buildfileblobheader(len(text), revlogflags)
411 411
412 412 return b"%s\0%s%s" % (header, text, ancestortext)
413 413
414 414
415 415 def gcserver(ui, repo):
416 416 if not repo.ui.configbool(b"remotefilelog", b"server"):
417 417 return
418 418
419 419 neededfiles = set()
420 420 heads = repo.revs(b"heads(tip~25000:) - null")
421 421
422 422 cachepath = repo.vfs.join(b"remotefilelogcache")
423 423 for head in heads:
424 424 mf = repo[head].manifest()
425 425 for filename, filenode in mf.items():
426 426 filecachepath = os.path.join(cachepath, filename, hex(filenode))
427 427 neededfiles.add(filecachepath)
428 428
429 429 # delete unneeded older files
430 430 days = repo.ui.configint(b"remotefilelog", b"serverexpiration")
431 431 expiration = time.time() - (days * 24 * 60 * 60)
432 432
433 433 progress = ui.makeprogress(_(b"removing old server cache"), unit=b"files")
434 434 progress.update(0)
435 435 for root, dirs, files in os.walk(cachepath):
436 436 for file in files:
437 437 filepath = os.path.join(root, file)
438 438 progress.increment()
439 439 if filepath in neededfiles:
440 440 continue
441 441
442 442 stat = os.stat(filepath)
443 443 if stat.st_mtime < expiration:
444 444 os.remove(filepath)
445 445
446 446 progress.complete()
General Comments 0
You need to be logged in to leave comments. Login now