##// END OF EJS Templates
remotefilelog: check if RFL is enabled in getrenamedfn() override...
Martin von Zweigbergk -
r42699:f93762f2 default
parent child Browse files
Show More
@@ -1,1110 +1,1113 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 from __future__ import absolute_import
128 128
129 129 import os
130 130 import time
131 131 import traceback
132 132
133 133 from mercurial.node import hex
134 134 from mercurial.i18n import _
135 135 from mercurial import (
136 136 changegroup,
137 137 changelog,
138 138 cmdutil,
139 139 commands,
140 140 configitems,
141 141 context,
142 142 copies,
143 143 debugcommands as hgdebugcommands,
144 144 dispatch,
145 145 error,
146 146 exchange,
147 147 extensions,
148 148 hg,
149 149 localrepo,
150 150 match,
151 151 merge,
152 152 node as nodemod,
153 153 patch,
154 154 pycompat,
155 155 registrar,
156 156 repair,
157 157 repoview,
158 158 revset,
159 159 scmutil,
160 160 smartset,
161 161 streamclone,
162 162 util,
163 163 )
164 164 from . import (
165 165 constants,
166 166 debugcommands,
167 167 fileserverclient,
168 168 remotefilectx,
169 169 remotefilelog,
170 170 remotefilelogserver,
171 171 repack as repackmod,
172 172 shallowbundle,
173 173 shallowrepo,
174 174 shallowstore,
175 175 shallowutil,
176 176 shallowverifier,
177 177 )
178 178
179 179 # ensures debug commands are registered
180 180 hgdebugcommands.command
181 181
182 182 cmdtable = {}
183 183 command = registrar.command(cmdtable)
184 184
185 185 configtable = {}
186 186 configitem = registrar.configitem(configtable)
187 187
188 188 configitem('remotefilelog', 'debug', default=False)
189 189
190 190 configitem('remotefilelog', 'reponame', default='')
191 191 configitem('remotefilelog', 'cachepath', default=None)
192 192 configitem('remotefilelog', 'cachegroup', default=None)
193 193 configitem('remotefilelog', 'cacheprocess', default=None)
194 194 configitem('remotefilelog', 'cacheprocess.includepath', default=None)
195 195 configitem("remotefilelog", "cachelimit", default="1000 GB")
196 196
197 197 configitem('remotefilelog', 'fallbackpath', default=configitems.dynamicdefault,
198 198 alias=[('remotefilelog', 'fallbackrepo')])
199 199
200 200 configitem('remotefilelog', 'validatecachelog', default=None)
201 201 configitem('remotefilelog', 'validatecache', default='on')
202 202 configitem('remotefilelog', 'server', default=None)
203 203 configitem('remotefilelog', 'servercachepath', default=None)
204 204 configitem("remotefilelog", "serverexpiration", default=30)
205 205 configitem('remotefilelog', 'backgroundrepack', default=False)
206 206 configitem('remotefilelog', 'bgprefetchrevs', default=None)
207 207 configitem('remotefilelog', 'pullprefetch', default=None)
208 208 configitem('remotefilelog', 'backgroundprefetch', default=False)
209 209 configitem('remotefilelog', 'prefetchdelay', default=120)
210 210 configitem('remotefilelog', 'prefetchdays', default=14)
211 211
212 212 configitem('remotefilelog', 'getfilesstep', default=10000)
213 213 configitem('remotefilelog', 'getfilestype', default='optimistic')
214 214 configitem('remotefilelog', 'batchsize', configitems.dynamicdefault)
215 215 configitem('remotefilelog', 'fetchwarning', default='')
216 216
217 217 configitem('remotefilelog', 'includepattern', default=None)
218 218 configitem('remotefilelog', 'excludepattern', default=None)
219 219
220 220 configitem('remotefilelog', 'gcrepack', default=False)
221 221 configitem('remotefilelog', 'repackonhggc', default=False)
222 222 configitem('repack', 'chainorphansbysize', default=True)
223 223
224 224 configitem('packs', 'maxpacksize', default=0)
225 225 configitem('packs', 'maxchainlen', default=1000)
226 226
227 227 # default TTL limit is 30 days
228 228 _defaultlimit = 60 * 60 * 24 * 30
229 229 configitem('remotefilelog', 'nodettl', default=_defaultlimit)
230 230
231 231 configitem('remotefilelog', 'data.gencountlimit', default=2),
232 232 configitem('remotefilelog', 'data.generations',
233 233 default=['1GB', '100MB', '1MB'])
234 234 configitem('remotefilelog', 'data.maxrepackpacks', default=50)
235 235 configitem('remotefilelog', 'data.repackmaxpacksize', default='4GB')
236 236 configitem('remotefilelog', 'data.repacksizelimit', default='100MB')
237 237
238 238 configitem('remotefilelog', 'history.gencountlimit', default=2),
239 239 configitem('remotefilelog', 'history.generations', default=['100MB'])
240 240 configitem('remotefilelog', 'history.maxrepackpacks', default=50)
241 241 configitem('remotefilelog', 'history.repackmaxpacksize', default='400MB')
242 242 configitem('remotefilelog', 'history.repacksizelimit', default='100MB')
243 243
244 244 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
245 245 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
246 246 # be specifying the version(s) of Mercurial they are tested with, or
247 247 # leave the attribute unspecified.
248 248 testedwith = 'ships-with-hg-core'
249 249
250 250 repoclass = localrepo.localrepository
251 251 repoclass._basesupported.add(constants.SHALLOWREPO_REQUIREMENT)
252 252
253 253 isenabled = shallowutil.isenabled
254 254
255 255 def uisetup(ui):
256 256 """Wraps user facing Mercurial commands to swap them out with shallow
257 257 versions.
258 258 """
259 259 hg.wirepeersetupfuncs.append(fileserverclient.peersetup)
260 260
261 261 entry = extensions.wrapcommand(commands.table, 'clone', cloneshallow)
262 262 entry[1].append(('', 'shallow', None,
263 263 _("create a shallow clone which uses remote file "
264 264 "history")))
265 265
266 266 extensions.wrapcommand(commands.table, 'debugindex',
267 267 debugcommands.debugindex)
268 268 extensions.wrapcommand(commands.table, 'debugindexdot',
269 269 debugcommands.debugindexdot)
270 270 extensions.wrapcommand(commands.table, 'log', log)
271 271 extensions.wrapcommand(commands.table, 'pull', pull)
272 272
273 273 # Prevent 'hg manifest --all'
274 274 def _manifest(orig, ui, repo, *args, **opts):
275 275 if (isenabled(repo) and opts.get(r'all')):
276 276 raise error.Abort(_("--all is not supported in a shallow repo"))
277 277
278 278 return orig(ui, repo, *args, **opts)
279 279 extensions.wrapcommand(commands.table, "manifest", _manifest)
280 280
281 281 # Wrap remotefilelog with lfs code
282 282 def _lfsloaded(loaded=False):
283 283 lfsmod = None
284 284 try:
285 285 lfsmod = extensions.find('lfs')
286 286 except KeyError:
287 287 pass
288 288 if lfsmod:
289 289 lfsmod.wrapfilelog(remotefilelog.remotefilelog)
290 290 fileserverclient._lfsmod = lfsmod
291 291 extensions.afterloaded('lfs', _lfsloaded)
292 292
293 293 # debugdata needs remotefilelog.len to work
294 294 extensions.wrapcommand(commands.table, 'debugdata', debugdatashallow)
295 295
296 296 changegroup.cgpacker = shallowbundle.shallowcg1packer
297 297
298 298 extensions.wrapfunction(changegroup, '_addchangegroupfiles',
299 299 shallowbundle.addchangegroupfiles)
300 300 extensions.wrapfunction(
301 301 changegroup, 'makechangegroup', shallowbundle.makechangegroup)
302 302 extensions.wrapfunction(localrepo, 'makestore', storewrapper)
303 303 extensions.wrapfunction(exchange, 'pull', exchangepull)
304 304 extensions.wrapfunction(merge, 'applyupdates', applyupdates)
305 305 extensions.wrapfunction(merge, '_checkunknownfiles', checkunknownfiles)
306 306 extensions.wrapfunction(context.workingctx, '_checklookup', checklookup)
307 307 extensions.wrapfunction(scmutil, '_findrenames', findrenames)
308 308 extensions.wrapfunction(copies, '_computeforwardmissing',
309 309 computeforwardmissing)
310 310 extensions.wrapfunction(dispatch, 'runcommand', runcommand)
311 311 extensions.wrapfunction(repair, '_collectbrokencsets', _collectbrokencsets)
312 312 extensions.wrapfunction(context.changectx, 'filectx', filectx)
313 313 extensions.wrapfunction(context.workingctx, 'filectx', workingfilectx)
314 314 extensions.wrapfunction(patch, 'trydiff', trydiff)
315 315 extensions.wrapfunction(hg, 'verify', _verify)
316 316 scmutil.fileprefetchhooks.add('remotefilelog', _fileprefetchhook)
317 317
318 318 # disappointing hacks below
319 scmutil.getrenamedfn = getrenamedfn
319 extensions.wrapfunction(scmutil, 'getrenamedfn', getrenamedfn)
320 320 extensions.wrapfunction(revset, 'filelog', filelogrevset)
321 321 revset.symbols['filelog'] = revset.filelog
322 322 extensions.wrapfunction(cmdutil, 'walkfilerevs', walkfilerevs)
323 323
324 324
325 325 def cloneshallow(orig, ui, repo, *args, **opts):
326 326 if opts.get(r'shallow'):
327 327 repos = []
328 328 def pull_shallow(orig, self, *args, **kwargs):
329 329 if not isenabled(self):
330 330 repos.append(self.unfiltered())
331 331 # set up the client hooks so the post-clone update works
332 332 setupclient(self.ui, self.unfiltered())
333 333
334 334 # setupclient fixed the class on the repo itself
335 335 # but we also need to fix it on the repoview
336 336 if isinstance(self, repoview.repoview):
337 337 self.__class__.__bases__ = (self.__class__.__bases__[0],
338 338 self.unfiltered().__class__)
339 339 self.requirements.add(constants.SHALLOWREPO_REQUIREMENT)
340 340 self._writerequirements()
341 341
342 342 # Since setupclient hadn't been called, exchange.pull was not
343 343 # wrapped. So we need to manually invoke our version of it.
344 344 return exchangepull(orig, self, *args, **kwargs)
345 345 else:
346 346 return orig(self, *args, **kwargs)
347 347 extensions.wrapfunction(exchange, 'pull', pull_shallow)
348 348
349 349 # Wrap the stream logic to add requirements and to pass include/exclude
350 350 # patterns around.
351 351 def setup_streamout(repo, remote):
352 352 # Replace remote.stream_out with a version that sends file
353 353 # patterns.
354 354 def stream_out_shallow(orig):
355 355 caps = remote.capabilities()
356 356 if constants.NETWORK_CAP_LEGACY_SSH_GETFILES in caps:
357 357 opts = {}
358 358 if repo.includepattern:
359 359 opts[r'includepattern'] = '\0'.join(repo.includepattern)
360 360 if repo.excludepattern:
361 361 opts[r'excludepattern'] = '\0'.join(repo.excludepattern)
362 362 return remote._callstream('stream_out_shallow', **opts)
363 363 else:
364 364 return orig()
365 365 extensions.wrapfunction(remote, 'stream_out', stream_out_shallow)
366 366 def stream_wrap(orig, op):
367 367 setup_streamout(op.repo, op.remote)
368 368 return orig(op)
369 369 extensions.wrapfunction(
370 370 streamclone, 'maybeperformlegacystreamclone', stream_wrap)
371 371
372 372 def canperformstreamclone(orig, pullop, bundle2=False):
373 373 # remotefilelog is currently incompatible with the
374 374 # bundle2 flavor of streamclones, so force us to use
375 375 # v1 instead.
376 376 if 'v2' in pullop.remotebundle2caps.get('stream', []):
377 377 pullop.remotebundle2caps['stream'] = [
378 378 c for c in pullop.remotebundle2caps['stream']
379 379 if c != 'v2']
380 380 if bundle2:
381 381 return False, None
382 382 supported, requirements = orig(pullop, bundle2=bundle2)
383 383 if requirements is not None:
384 384 requirements.add(constants.SHALLOWREPO_REQUIREMENT)
385 385 return supported, requirements
386 386 extensions.wrapfunction(
387 387 streamclone, 'canperformstreamclone', canperformstreamclone)
388 388
389 389 try:
390 390 orig(ui, repo, *args, **opts)
391 391 finally:
392 392 if opts.get(r'shallow'):
393 393 for r in repos:
394 394 if util.safehasattr(r, 'fileservice'):
395 395 r.fileservice.close()
396 396
397 397 def debugdatashallow(orig, *args, **kwds):
398 398 oldlen = remotefilelog.remotefilelog.__len__
399 399 try:
400 400 remotefilelog.remotefilelog.__len__ = lambda x: 1
401 401 return orig(*args, **kwds)
402 402 finally:
403 403 remotefilelog.remotefilelog.__len__ = oldlen
404 404
405 405 def reposetup(ui, repo):
406 406 if not repo.local():
407 407 return
408 408
409 409 # put here intentionally bc doesnt work in uisetup
410 410 ui.setconfig('hooks', 'update.prefetch', wcpprefetch)
411 411 ui.setconfig('hooks', 'commit.prefetch', wcpprefetch)
412 412
413 413 isserverenabled = ui.configbool('remotefilelog', 'server')
414 414 isshallowclient = isenabled(repo)
415 415
416 416 if isserverenabled and isshallowclient:
417 417 raise RuntimeError("Cannot be both a server and shallow client.")
418 418
419 419 if isshallowclient:
420 420 setupclient(ui, repo)
421 421
422 422 if isserverenabled:
423 423 remotefilelogserver.setupserver(ui, repo)
424 424
425 425 def setupclient(ui, repo):
426 426 if not isinstance(repo, localrepo.localrepository):
427 427 return
428 428
429 429 # Even clients get the server setup since they need to have the
430 430 # wireprotocol endpoints registered.
431 431 remotefilelogserver.onetimesetup(ui)
432 432 onetimeclientsetup(ui)
433 433
434 434 shallowrepo.wraprepo(repo)
435 435 repo.store = shallowstore.wrapstore(repo.store)
436 436
437 437 def storewrapper(orig, requirements, path, vfstype):
438 438 s = orig(requirements, path, vfstype)
439 439 if constants.SHALLOWREPO_REQUIREMENT in requirements:
440 440 s = shallowstore.wrapstore(s)
441 441
442 442 return s
443 443
444 444 # prefetch files before update
445 445 def applyupdates(orig, repo, actions, wctx, mctx, overwrite, wantfiledata,
446 446 labels=None):
447 447 if isenabled(repo):
448 448 manifest = mctx.manifest()
449 449 files = []
450 450 for f, args, msg in actions['g']:
451 451 files.append((f, hex(manifest[f])))
452 452 # batch fetch the needed files from the server
453 453 repo.fileservice.prefetch(files)
454 454 return orig(repo, actions, wctx, mctx, overwrite, wantfiledata,
455 455 labels=labels)
456 456
457 457 # Prefetch merge checkunknownfiles
458 458 def checkunknownfiles(orig, repo, wctx, mctx, force, actions,
459 459 *args, **kwargs):
460 460 if isenabled(repo):
461 461 files = []
462 462 sparsematch = repo.maybesparsematch(mctx.rev())
463 463 for f, (m, actionargs, msg) in actions.iteritems():
464 464 if sparsematch and not sparsematch(f):
465 465 continue
466 466 if m in ('c', 'dc', 'cm'):
467 467 files.append((f, hex(mctx.filenode(f))))
468 468 elif m == 'dg':
469 469 f2 = actionargs[0]
470 470 files.append((f2, hex(mctx.filenode(f2))))
471 471 # batch fetch the needed files from the server
472 472 repo.fileservice.prefetch(files)
473 473 return orig(repo, wctx, mctx, force, actions, *args, **kwargs)
474 474
475 475 # Prefetch files before status attempts to look at their size and contents
476 476 def checklookup(orig, self, files):
477 477 repo = self._repo
478 478 if isenabled(repo):
479 479 prefetchfiles = []
480 480 for parent in self._parents:
481 481 for f in files:
482 482 if f in parent:
483 483 prefetchfiles.append((f, hex(parent.filenode(f))))
484 484 # batch fetch the needed files from the server
485 485 repo.fileservice.prefetch(prefetchfiles)
486 486 return orig(self, files)
487 487
488 488 # Prefetch the logic that compares added and removed files for renames
489 489 def findrenames(orig, repo, matcher, added, removed, *args, **kwargs):
490 490 if isenabled(repo):
491 491 files = []
492 492 pmf = repo['.'].manifest()
493 493 for f in removed:
494 494 if f in pmf:
495 495 files.append((f, hex(pmf[f])))
496 496 # batch fetch the needed files from the server
497 497 repo.fileservice.prefetch(files)
498 498 return orig(repo, matcher, added, removed, *args, **kwargs)
499 499
500 500 # prefetch files before pathcopies check
501 501 def computeforwardmissing(orig, a, b, match=None):
502 502 missing = orig(a, b, match=match)
503 503 repo = a._repo
504 504 if isenabled(repo):
505 505 mb = b.manifest()
506 506
507 507 files = []
508 508 sparsematch = repo.maybesparsematch(b.rev())
509 509 if sparsematch:
510 510 sparsemissing = set()
511 511 for f in missing:
512 512 if sparsematch(f):
513 513 files.append((f, hex(mb[f])))
514 514 sparsemissing.add(f)
515 515 missing = sparsemissing
516 516
517 517 # batch fetch the needed files from the server
518 518 repo.fileservice.prefetch(files)
519 519 return missing
520 520
521 521 # close cache miss server connection after the command has finished
522 522 def runcommand(orig, lui, repo, *args, **kwargs):
523 523 fileservice = None
524 524 # repo can be None when running in chg:
525 525 # - at startup, reposetup was called because serve is not norepo
526 526 # - a norepo command like "help" is called
527 527 if repo and isenabled(repo):
528 528 fileservice = repo.fileservice
529 529 try:
530 530 return orig(lui, repo, *args, **kwargs)
531 531 finally:
532 532 if fileservice:
533 533 fileservice.close()
534 534
535 535 # prevent strip from stripping remotefilelogs
536 536 def _collectbrokencsets(orig, repo, files, striprev):
537 537 if isenabled(repo):
538 538 files = list([f for f in files if not repo.shallowmatch(f)])
539 539 return orig(repo, files, striprev)
540 540
541 541 # changectx wrappers
542 542 def filectx(orig, self, path, fileid=None, filelog=None):
543 543 if fileid is None:
544 544 fileid = self.filenode(path)
545 545 if (isenabled(self._repo) and self._repo.shallowmatch(path)):
546 546 return remotefilectx.remotefilectx(self._repo, path, fileid=fileid,
547 547 changectx=self, filelog=filelog)
548 548 return orig(self, path, fileid=fileid, filelog=filelog)
549 549
550 550 def workingfilectx(orig, self, path, filelog=None):
551 551 if (isenabled(self._repo) and self._repo.shallowmatch(path)):
552 552 return remotefilectx.remoteworkingfilectx(self._repo, path,
553 553 workingctx=self,
554 554 filelog=filelog)
555 555 return orig(self, path, filelog=filelog)
556 556
557 557 # prefetch required revisions before a diff
558 558 def trydiff(orig, repo, revs, ctx1, ctx2, modified, added, removed,
559 559 copy, getfilectx, *args, **kwargs):
560 560 if isenabled(repo):
561 561 prefetch = []
562 562 mf1 = ctx1.manifest()
563 563 for fname in modified + added + removed:
564 564 if fname in mf1:
565 565 fnode = getfilectx(fname, ctx1).filenode()
566 566 # fnode can be None if it's a edited working ctx file
567 567 if fnode:
568 568 prefetch.append((fname, hex(fnode)))
569 569 if fname not in removed:
570 570 fnode = getfilectx(fname, ctx2).filenode()
571 571 if fnode:
572 572 prefetch.append((fname, hex(fnode)))
573 573
574 574 repo.fileservice.prefetch(prefetch)
575 575
576 576 return orig(repo, revs, ctx1, ctx2, modified, added, removed, copy,
577 577 getfilectx, *args, **kwargs)
578 578
579 579 # Prevent verify from processing files
580 580 # a stub for mercurial.hg.verify()
581 581 def _verify(orig, repo, level=None):
582 582 lock = repo.lock()
583 583 try:
584 584 return shallowverifier.shallowverifier(repo).verify()
585 585 finally:
586 586 lock.release()
587 587
588 588
589 589 clientonetime = False
590 590 def onetimeclientsetup(ui):
591 591 global clientonetime
592 592 if clientonetime:
593 593 return
594 594 clientonetime = True
595 595
596 596 # Don't commit filelogs until we know the commit hash, since the hash
597 597 # is present in the filelog blob.
598 598 # This violates Mercurial's filelog->manifest->changelog write order,
599 599 # but is generally fine for client repos.
600 600 pendingfilecommits = []
601 601 def addrawrevision(orig, self, rawtext, transaction, link, p1, p2, node,
602 602 flags, cachedelta=None, _metatuple=None):
603 603 if isinstance(link, int):
604 604 pendingfilecommits.append(
605 605 (self, rawtext, transaction, link, p1, p2, node, flags,
606 606 cachedelta, _metatuple))
607 607 return node
608 608 else:
609 609 return orig(self, rawtext, transaction, link, p1, p2, node, flags,
610 610 cachedelta, _metatuple=_metatuple)
611 611 extensions.wrapfunction(
612 612 remotefilelog.remotefilelog, 'addrawrevision', addrawrevision)
613 613
614 614 def changelogadd(orig, self, *args):
615 615 oldlen = len(self)
616 616 node = orig(self, *args)
617 617 newlen = len(self)
618 618 if oldlen != newlen:
619 619 for oldargs in pendingfilecommits:
620 620 log, rt, tr, link, p1, p2, n, fl, c, m = oldargs
621 621 linknode = self.node(link)
622 622 if linknode == node:
623 623 log.addrawrevision(rt, tr, linknode, p1, p2, n, fl, c, m)
624 624 else:
625 625 raise error.ProgrammingError(
626 626 'pending multiple integer revisions are not supported')
627 627 else:
628 628 # "link" is actually wrong here (it is set to len(changelog))
629 629 # if changelog remains unchanged, skip writing file revisions
630 630 # but still do a sanity check about pending multiple revisions
631 631 if len(set(x[3] for x in pendingfilecommits)) > 1:
632 632 raise error.ProgrammingError(
633 633 'pending multiple integer revisions are not supported')
634 634 del pendingfilecommits[:]
635 635 return node
636 636 extensions.wrapfunction(changelog.changelog, 'add', changelogadd)
637 637
638 def getrenamedfn(repo, endrev=None):
638 def getrenamedfn(orig, repo, endrev=None):
639 if not isenabled(repo):
640 return orig(repo, endrev)
641
639 642 rcache = {}
640 643
641 644 def getrenamed(fn, rev):
642 645 '''looks up all renames for a file (up to endrev) the first
643 646 time the file is given. It indexes on the changerev and only
644 647 parses the manifest if linkrev != changerev.
645 648 Returns rename info for fn at changerev rev.'''
646 649 if rev in rcache.setdefault(fn, {}):
647 650 return rcache[fn][rev]
648 651
649 652 try:
650 653 fctx = repo[rev].filectx(fn)
651 654 for ancestor in fctx.ancestors():
652 655 if ancestor.path() == fn:
653 656 renamed = ancestor.renamed()
654 657 rcache[fn][ancestor.rev()] = renamed and renamed[0]
655 658
656 659 renamed = fctx.renamed()
657 660 return renamed and renamed[0]
658 661 except error.LookupError:
659 662 return None
660 663
661 664 return getrenamed
662 665
663 666 def walkfilerevs(orig, repo, match, follow, revs, fncache):
664 667 if not isenabled(repo):
665 668 return orig(repo, match, follow, revs, fncache)
666 669
667 670 # remotefilelog's can't be walked in rev order, so throw.
668 671 # The caller will see the exception and walk the commit tree instead.
669 672 if not follow:
670 673 raise cmdutil.FileWalkError("Cannot walk via filelog")
671 674
672 675 wanted = set()
673 676 minrev, maxrev = min(revs), max(revs)
674 677
675 678 pctx = repo['.']
676 679 for filename in match.files():
677 680 if filename not in pctx:
678 681 raise error.Abort(_('cannot follow file not in parent '
679 682 'revision: "%s"') % filename)
680 683 fctx = pctx[filename]
681 684
682 685 linkrev = fctx.linkrev()
683 686 if linkrev >= minrev and linkrev <= maxrev:
684 687 fncache.setdefault(linkrev, []).append(filename)
685 688 wanted.add(linkrev)
686 689
687 690 for ancestor in fctx.ancestors():
688 691 linkrev = ancestor.linkrev()
689 692 if linkrev >= minrev and linkrev <= maxrev:
690 693 fncache.setdefault(linkrev, []).append(ancestor.path())
691 694 wanted.add(linkrev)
692 695
693 696 return wanted
694 697
695 698 def filelogrevset(orig, repo, subset, x):
696 699 """``filelog(pattern)``
697 700 Changesets connected to the specified filelog.
698 701
699 702 For performance reasons, ``filelog()`` does not show every changeset
700 703 that affects the requested file(s). See :hg:`help log` for details. For
701 704 a slower, more accurate result, use ``file()``.
702 705 """
703 706
704 707 if not isenabled(repo):
705 708 return orig(repo, subset, x)
706 709
707 710 # i18n: "filelog" is a keyword
708 711 pat = revset.getstring(x, _("filelog requires a pattern"))
709 712 m = match.match(repo.root, repo.getcwd(), [pat], default='relpath',
710 713 ctx=repo[None])
711 714 s = set()
712 715
713 716 if not match.patkind(pat):
714 717 # slow
715 718 for r in subset:
716 719 ctx = repo[r]
717 720 cfiles = ctx.files()
718 721 for f in m.files():
719 722 if f in cfiles:
720 723 s.add(ctx.rev())
721 724 break
722 725 else:
723 726 # partial
724 727 files = (f for f in repo[None] if m(f))
725 728 for f in files:
726 729 fctx = repo[None].filectx(f)
727 730 s.add(fctx.linkrev())
728 731 for actx in fctx.ancestors():
729 732 s.add(actx.linkrev())
730 733
731 734 return smartset.baseset([r for r in subset if r in s])
732 735
733 736 @command('gc', [], _('hg gc [REPO...]'), norepo=True)
734 737 def gc(ui, *args, **opts):
735 738 '''garbage collect the client and server filelog caches
736 739 '''
737 740 cachepaths = set()
738 741
739 742 # get the system client cache
740 743 systemcache = shallowutil.getcachepath(ui, allowempty=True)
741 744 if systemcache:
742 745 cachepaths.add(systemcache)
743 746
744 747 # get repo client and server cache
745 748 repopaths = []
746 749 pwd = ui.environ.get('PWD')
747 750 if pwd:
748 751 repopaths.append(pwd)
749 752
750 753 repopaths.extend(args)
751 754 repos = []
752 755 for repopath in repopaths:
753 756 try:
754 757 repo = hg.peer(ui, {}, repopath)
755 758 repos.append(repo)
756 759
757 760 repocache = shallowutil.getcachepath(repo.ui, allowempty=True)
758 761 if repocache:
759 762 cachepaths.add(repocache)
760 763 except error.RepoError:
761 764 pass
762 765
763 766 # gc client cache
764 767 for cachepath in cachepaths:
765 768 gcclient(ui, cachepath)
766 769
767 770 # gc server cache
768 771 for repo in repos:
769 772 remotefilelogserver.gcserver(ui, repo._repo)
770 773
771 774 def gcclient(ui, cachepath):
772 775 # get list of repos that use this cache
773 776 repospath = os.path.join(cachepath, 'repos')
774 777 if not os.path.exists(repospath):
775 778 ui.warn(_("no known cache at %s\n") % cachepath)
776 779 return
777 780
778 781 reposfile = open(repospath, 'rb')
779 782 repos = {r[:-1] for r in reposfile.readlines()}
780 783 reposfile.close()
781 784
782 785 # build list of useful files
783 786 validrepos = []
784 787 keepkeys = set()
785 788
786 789 sharedcache = None
787 790 filesrepacked = False
788 791
789 792 count = 0
790 793 progress = ui.makeprogress(_("analyzing repositories"), unit="repos",
791 794 total=len(repos))
792 795 for path in repos:
793 796 progress.update(count)
794 797 count += 1
795 798 try:
796 799 path = ui.expandpath(os.path.normpath(path))
797 800 except TypeError as e:
798 801 ui.warn(_("warning: malformed path: %r:%s\n") % (path, e))
799 802 traceback.print_exc()
800 803 continue
801 804 try:
802 805 peer = hg.peer(ui, {}, path)
803 806 repo = peer._repo
804 807 except error.RepoError:
805 808 continue
806 809
807 810 validrepos.append(path)
808 811
809 812 # Protect against any repo or config changes that have happened since
810 813 # this repo was added to the repos file. We'd rather this loop succeed
811 814 # and too much be deleted, than the loop fail and nothing gets deleted.
812 815 if not isenabled(repo):
813 816 continue
814 817
815 818 if not util.safehasattr(repo, 'name'):
816 819 ui.warn(_("repo %s is a misconfigured remotefilelog repo\n") % path)
817 820 continue
818 821
819 822 # If garbage collection on repack and repack on hg gc are enabled
820 823 # then loose files are repacked and garbage collected.
821 824 # Otherwise regular garbage collection is performed.
822 825 repackonhggc = repo.ui.configbool('remotefilelog', 'repackonhggc')
823 826 gcrepack = repo.ui.configbool('remotefilelog', 'gcrepack')
824 827 if repackonhggc and gcrepack:
825 828 try:
826 829 repackmod.incrementalrepack(repo)
827 830 filesrepacked = True
828 831 continue
829 832 except (IOError, repackmod.RepackAlreadyRunning):
830 833 # If repack cannot be performed due to not enough disk space
831 834 # continue doing garbage collection of loose files w/o repack
832 835 pass
833 836
834 837 reponame = repo.name
835 838 if not sharedcache:
836 839 sharedcache = repo.sharedstore
837 840
838 841 # Compute a keepset which is not garbage collected
839 842 def keyfn(fname, fnode):
840 843 return fileserverclient.getcachekey(reponame, fname, hex(fnode))
841 844 keepkeys = repackmod.keepset(repo, keyfn=keyfn, lastkeepkeys=keepkeys)
842 845
843 846 progress.complete()
844 847
845 848 # write list of valid repos back
846 849 oldumask = os.umask(0o002)
847 850 try:
848 851 reposfile = open(repospath, 'wb')
849 852 reposfile.writelines([("%s\n" % r) for r in validrepos])
850 853 reposfile.close()
851 854 finally:
852 855 os.umask(oldumask)
853 856
854 857 # prune cache
855 858 if sharedcache is not None:
856 859 sharedcache.gc(keepkeys)
857 860 elif not filesrepacked:
858 861 ui.warn(_("warning: no valid repos in repofile\n"))
859 862
860 863 def log(orig, ui, repo, *pats, **opts):
861 864 if not isenabled(repo):
862 865 return orig(ui, repo, *pats, **opts)
863 866
864 867 follow = opts.get(r'follow')
865 868 revs = opts.get(r'rev')
866 869 if pats:
867 870 # Force slowpath for non-follow patterns and follows that start from
868 871 # non-working-copy-parent revs.
869 872 if not follow or revs:
870 873 # This forces the slowpath
871 874 opts[r'removed'] = True
872 875
873 876 # If this is a non-follow log without any revs specified, recommend that
874 877 # the user add -f to speed it up.
875 878 if not follow and not revs:
876 879 match = scmutil.match(repo['.'], pats, pycompat.byteskwargs(opts))
877 880 isfile = not match.anypats()
878 881 if isfile:
879 882 for file in match.files():
880 883 if not os.path.isfile(repo.wjoin(file)):
881 884 isfile = False
882 885 break
883 886
884 887 if isfile:
885 888 ui.warn(_("warning: file log can be slow on large repos - " +
886 889 "use -f to speed it up\n"))
887 890
888 891 return orig(ui, repo, *pats, **opts)
889 892
890 893 def revdatelimit(ui, revset):
891 894 """Update revset so that only changesets no older than 'prefetchdays' days
892 895 are included. The default value is set to 14 days. If 'prefetchdays' is set
893 896 to zero or negative value then date restriction is not applied.
894 897 """
895 898 days = ui.configint('remotefilelog', 'prefetchdays')
896 899 if days > 0:
897 900 revset = '(%s) & date(-%s)' % (revset, days)
898 901 return revset
899 902
900 903 def readytofetch(repo):
901 904 """Check that enough time has passed since the last background prefetch.
902 905 This only relates to prefetches after operations that change the working
903 906 copy parent. Default delay between background prefetches is 2 minutes.
904 907 """
905 908 timeout = repo.ui.configint('remotefilelog', 'prefetchdelay')
906 909 fname = repo.vfs.join('lastprefetch')
907 910
908 911 ready = False
909 912 with open(fname, 'a'):
910 913 # the with construct above is used to avoid race conditions
911 914 modtime = os.path.getmtime(fname)
912 915 if (time.time() - modtime) > timeout:
913 916 os.utime(fname, None)
914 917 ready = True
915 918
916 919 return ready
917 920
918 921 def wcpprefetch(ui, repo, **kwargs):
919 922 """Prefetches in background revisions specified by bgprefetchrevs revset.
920 923 Does background repack if backgroundrepack flag is set in config.
921 924 """
922 925 shallow = isenabled(repo)
923 926 bgprefetchrevs = ui.config('remotefilelog', 'bgprefetchrevs')
924 927 isready = readytofetch(repo)
925 928
926 929 if not (shallow and bgprefetchrevs and isready):
927 930 return
928 931
929 932 bgrepack = repo.ui.configbool('remotefilelog', 'backgroundrepack')
930 933 # update a revset with a date limit
931 934 bgprefetchrevs = revdatelimit(ui, bgprefetchrevs)
932 935
933 936 def anon():
934 937 if util.safehasattr(repo, 'ranprefetch') and repo.ranprefetch:
935 938 return
936 939 repo.ranprefetch = True
937 940 repo.backgroundprefetch(bgprefetchrevs, repack=bgrepack)
938 941
939 942 repo._afterlock(anon)
940 943
941 944 def pull(orig, ui, repo, *pats, **opts):
942 945 result = orig(ui, repo, *pats, **opts)
943 946
944 947 if isenabled(repo):
945 948 # prefetch if it's configured
946 949 prefetchrevset = ui.config('remotefilelog', 'pullprefetch')
947 950 bgrepack = repo.ui.configbool('remotefilelog', 'backgroundrepack')
948 951 bgprefetch = repo.ui.configbool('remotefilelog', 'backgroundprefetch')
949 952
950 953 if prefetchrevset:
951 954 ui.status(_("prefetching file contents\n"))
952 955 revs = scmutil.revrange(repo, [prefetchrevset])
953 956 base = repo['.'].rev()
954 957 if bgprefetch:
955 958 repo.backgroundprefetch(prefetchrevset, repack=bgrepack)
956 959 else:
957 960 repo.prefetch(revs, base=base)
958 961 if bgrepack:
959 962 repackmod.backgroundrepack(repo, incremental=True)
960 963 elif bgrepack:
961 964 repackmod.backgroundrepack(repo, incremental=True)
962 965
963 966 return result
964 967
965 968 def exchangepull(orig, repo, remote, *args, **kwargs):
966 969 # Hook into the callstream/getbundle to insert bundle capabilities
967 970 # during a pull.
968 971 def localgetbundle(orig, source, heads=None, common=None, bundlecaps=None,
969 972 **kwargs):
970 973 if not bundlecaps:
971 974 bundlecaps = set()
972 975 bundlecaps.add(constants.BUNDLE2_CAPABLITY)
973 976 return orig(source, heads=heads, common=common, bundlecaps=bundlecaps,
974 977 **kwargs)
975 978
976 979 if util.safehasattr(remote, '_callstream'):
977 980 remote._localrepo = repo
978 981 elif util.safehasattr(remote, 'getbundle'):
979 982 extensions.wrapfunction(remote, 'getbundle', localgetbundle)
980 983
981 984 return orig(repo, remote, *args, **kwargs)
982 985
983 986 def _fileprefetchhook(repo, revs, match):
984 987 if isenabled(repo):
985 988 allfiles = []
986 989 for rev in revs:
987 990 if rev == nodemod.wdirrev or rev is None:
988 991 continue
989 992 ctx = repo[rev]
990 993 mf = ctx.manifest()
991 994 sparsematch = repo.maybesparsematch(ctx.rev())
992 995 for path in ctx.walk(match):
993 996 if (not sparsematch or sparsematch(path)) and path in mf:
994 997 allfiles.append((path, hex(mf[path])))
995 998 repo.fileservice.prefetch(allfiles)
996 999
997 1000 @command('debugremotefilelog', [
998 1001 ('d', 'decompress', None, _('decompress the filelog first')),
999 1002 ], _('hg debugremotefilelog <path>'), norepo=True)
1000 1003 def debugremotefilelog(ui, path, **opts):
1001 1004 return debugcommands.debugremotefilelog(ui, path, **opts)
1002 1005
1003 1006 @command('verifyremotefilelog', [
1004 1007 ('d', 'decompress', None, _('decompress the filelogs first')),
1005 1008 ], _('hg verifyremotefilelogs <directory>'), norepo=True)
1006 1009 def verifyremotefilelog(ui, path, **opts):
1007 1010 return debugcommands.verifyremotefilelog(ui, path, **opts)
1008 1011
1009 1012 @command('debugdatapack', [
1010 1013 ('', 'long', None, _('print the long hashes')),
1011 1014 ('', 'node', '', _('dump the contents of node'), 'NODE'),
1012 1015 ], _('hg debugdatapack <paths>'), norepo=True)
1013 1016 def debugdatapack(ui, *paths, **opts):
1014 1017 return debugcommands.debugdatapack(ui, *paths, **opts)
1015 1018
1016 1019 @command('debughistorypack', [
1017 1020 ], _('hg debughistorypack <path>'), norepo=True)
1018 1021 def debughistorypack(ui, path, **opts):
1019 1022 return debugcommands.debughistorypack(ui, path)
1020 1023
1021 1024 @command('debugkeepset', [
1022 1025 ], _('hg debugkeepset'))
1023 1026 def debugkeepset(ui, repo, **opts):
1024 1027 # The command is used to measure keepset computation time
1025 1028 def keyfn(fname, fnode):
1026 1029 return fileserverclient.getcachekey(repo.name, fname, hex(fnode))
1027 1030 repackmod.keepset(repo, keyfn)
1028 1031 return
1029 1032
1030 1033 @command('debugwaitonrepack', [
1031 1034 ], _('hg debugwaitonrepack'))
1032 1035 def debugwaitonrepack(ui, repo, **opts):
1033 1036 return debugcommands.debugwaitonrepack(repo)
1034 1037
1035 1038 @command('debugwaitonprefetch', [
1036 1039 ], _('hg debugwaitonprefetch'))
1037 1040 def debugwaitonprefetch(ui, repo, **opts):
1038 1041 return debugcommands.debugwaitonprefetch(repo)
1039 1042
1040 1043 def resolveprefetchopts(ui, opts):
1041 1044 if not opts.get('rev'):
1042 1045 revset = ['.', 'draft()']
1043 1046
1044 1047 prefetchrevset = ui.config('remotefilelog', 'pullprefetch', None)
1045 1048 if prefetchrevset:
1046 1049 revset.append('(%s)' % prefetchrevset)
1047 1050 bgprefetchrevs = ui.config('remotefilelog', 'bgprefetchrevs', None)
1048 1051 if bgprefetchrevs:
1049 1052 revset.append('(%s)' % bgprefetchrevs)
1050 1053 revset = '+'.join(revset)
1051 1054
1052 1055 # update a revset with a date limit
1053 1056 revset = revdatelimit(ui, revset)
1054 1057
1055 1058 opts['rev'] = [revset]
1056 1059
1057 1060 if not opts.get('base'):
1058 1061 opts['base'] = None
1059 1062
1060 1063 return opts
1061 1064
1062 1065 @command('prefetch', [
1063 1066 ('r', 'rev', [], _('prefetch the specified revisions'), _('REV')),
1064 1067 ('', 'repack', False, _('run repack after prefetch')),
1065 1068 ('b', 'base', '', _("rev that is assumed to already be local")),
1066 1069 ] + commands.walkopts, _('hg prefetch [OPTIONS] [FILE...]'))
1067 1070 def prefetch(ui, repo, *pats, **opts):
1068 1071 """prefetch file revisions from the server
1069 1072
1070 1073 Prefetchs file revisions for the specified revs and stores them in the
1071 1074 local remotefilelog cache. If no rev is specified, the default rev is
1072 1075 used which is the union of dot, draft, pullprefetch and bgprefetchrev.
1073 1076 File names or patterns can be used to limit which files are downloaded.
1074 1077
1075 1078 Return 0 on success.
1076 1079 """
1077 1080 opts = pycompat.byteskwargs(opts)
1078 1081 if not isenabled(repo):
1079 1082 raise error.Abort(_("repo is not shallow"))
1080 1083
1081 1084 opts = resolveprefetchopts(ui, opts)
1082 1085 revs = scmutil.revrange(repo, opts.get('rev'))
1083 1086 repo.prefetch(revs, opts.get('base'), pats, opts)
1084 1087
1085 1088 # Run repack in background
1086 1089 if opts.get('repack'):
1087 1090 repackmod.backgroundrepack(repo, incremental=True)
1088 1091
1089 1092 @command('repack', [
1090 1093 ('', 'background', None, _('run in a background process'), None),
1091 1094 ('', 'incremental', None, _('do an incremental repack'), None),
1092 1095 ('', 'packsonly', None, _('only repack packs (skip loose objects)'), None),
1093 1096 ], _('hg repack [OPTIONS]'))
1094 1097 def repack_(ui, repo, *pats, **opts):
1095 1098 if opts.get(r'background'):
1096 1099 repackmod.backgroundrepack(repo, incremental=opts.get(r'incremental'),
1097 1100 packsonly=opts.get(r'packsonly', False))
1098 1101 return
1099 1102
1100 1103 options = {'packsonly': opts.get(r'packsonly')}
1101 1104
1102 1105 try:
1103 1106 if opts.get(r'incremental'):
1104 1107 repackmod.incrementalrepack(repo, options=options)
1105 1108 else:
1106 1109 repackmod.fullrepack(repo, options=options)
1107 1110 except repackmod.RepackAlreadyRunning as ex:
1108 1111 # Don't propogate the exception if the repack is already in
1109 1112 # progress, since we want the command to exit 0.
1110 1113 repo.ui.warn('%s\n' % ex)
General Comments 0
You need to be logged in to leave comments. Login now