##// END OF EJS Templates
remotefilelog: handle copies in changesets in getrenamedfn() override...
Martin von Zweigbergk -
r42700:e387cb22 default
parent child Browse files
Show More
@@ -1,1113 +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 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 638 def getrenamedfn(orig, repo, endrev=None):
639 if not isenabled(repo):
639 if not isenabled(repo) or copies.usechangesetcentricalgo(repo):
640 640 return orig(repo, endrev)
641 641
642 642 rcache = {}
643 643
644 644 def getrenamed(fn, rev):
645 645 '''looks up all renames for a file (up to endrev) the first
646 646 time the file is given. It indexes on the changerev and only
647 647 parses the manifest if linkrev != changerev.
648 648 Returns rename info for fn at changerev rev.'''
649 649 if rev in rcache.setdefault(fn, {}):
650 650 return rcache[fn][rev]
651 651
652 652 try:
653 653 fctx = repo[rev].filectx(fn)
654 654 for ancestor in fctx.ancestors():
655 655 if ancestor.path() == fn:
656 656 renamed = ancestor.renamed()
657 657 rcache[fn][ancestor.rev()] = renamed and renamed[0]
658 658
659 659 renamed = fctx.renamed()
660 660 return renamed and renamed[0]
661 661 except error.LookupError:
662 662 return None
663 663
664 664 return getrenamed
665 665
666 666 def walkfilerevs(orig, repo, match, follow, revs, fncache):
667 667 if not isenabled(repo):
668 668 return orig(repo, match, follow, revs, fncache)
669 669
670 670 # remotefilelog's can't be walked in rev order, so throw.
671 671 # The caller will see the exception and walk the commit tree instead.
672 672 if not follow:
673 673 raise cmdutil.FileWalkError("Cannot walk via filelog")
674 674
675 675 wanted = set()
676 676 minrev, maxrev = min(revs), max(revs)
677 677
678 678 pctx = repo['.']
679 679 for filename in match.files():
680 680 if filename not in pctx:
681 681 raise error.Abort(_('cannot follow file not in parent '
682 682 'revision: "%s"') % filename)
683 683 fctx = pctx[filename]
684 684
685 685 linkrev = fctx.linkrev()
686 686 if linkrev >= minrev and linkrev <= maxrev:
687 687 fncache.setdefault(linkrev, []).append(filename)
688 688 wanted.add(linkrev)
689 689
690 690 for ancestor in fctx.ancestors():
691 691 linkrev = ancestor.linkrev()
692 692 if linkrev >= minrev and linkrev <= maxrev:
693 693 fncache.setdefault(linkrev, []).append(ancestor.path())
694 694 wanted.add(linkrev)
695 695
696 696 return wanted
697 697
698 698 def filelogrevset(orig, repo, subset, x):
699 699 """``filelog(pattern)``
700 700 Changesets connected to the specified filelog.
701 701
702 702 For performance reasons, ``filelog()`` does not show every changeset
703 703 that affects the requested file(s). See :hg:`help log` for details. For
704 704 a slower, more accurate result, use ``file()``.
705 705 """
706 706
707 707 if not isenabled(repo):
708 708 return orig(repo, subset, x)
709 709
710 710 # i18n: "filelog" is a keyword
711 711 pat = revset.getstring(x, _("filelog requires a pattern"))
712 712 m = match.match(repo.root, repo.getcwd(), [pat], default='relpath',
713 713 ctx=repo[None])
714 714 s = set()
715 715
716 716 if not match.patkind(pat):
717 717 # slow
718 718 for r in subset:
719 719 ctx = repo[r]
720 720 cfiles = ctx.files()
721 721 for f in m.files():
722 722 if f in cfiles:
723 723 s.add(ctx.rev())
724 724 break
725 725 else:
726 726 # partial
727 727 files = (f for f in repo[None] if m(f))
728 728 for f in files:
729 729 fctx = repo[None].filectx(f)
730 730 s.add(fctx.linkrev())
731 731 for actx in fctx.ancestors():
732 732 s.add(actx.linkrev())
733 733
734 734 return smartset.baseset([r for r in subset if r in s])
735 735
736 736 @command('gc', [], _('hg gc [REPO...]'), norepo=True)
737 737 def gc(ui, *args, **opts):
738 738 '''garbage collect the client and server filelog caches
739 739 '''
740 740 cachepaths = set()
741 741
742 742 # get the system client cache
743 743 systemcache = shallowutil.getcachepath(ui, allowempty=True)
744 744 if systemcache:
745 745 cachepaths.add(systemcache)
746 746
747 747 # get repo client and server cache
748 748 repopaths = []
749 749 pwd = ui.environ.get('PWD')
750 750 if pwd:
751 751 repopaths.append(pwd)
752 752
753 753 repopaths.extend(args)
754 754 repos = []
755 755 for repopath in repopaths:
756 756 try:
757 757 repo = hg.peer(ui, {}, repopath)
758 758 repos.append(repo)
759 759
760 760 repocache = shallowutil.getcachepath(repo.ui, allowempty=True)
761 761 if repocache:
762 762 cachepaths.add(repocache)
763 763 except error.RepoError:
764 764 pass
765 765
766 766 # gc client cache
767 767 for cachepath in cachepaths:
768 768 gcclient(ui, cachepath)
769 769
770 770 # gc server cache
771 771 for repo in repos:
772 772 remotefilelogserver.gcserver(ui, repo._repo)
773 773
774 774 def gcclient(ui, cachepath):
775 775 # get list of repos that use this cache
776 776 repospath = os.path.join(cachepath, 'repos')
777 777 if not os.path.exists(repospath):
778 778 ui.warn(_("no known cache at %s\n") % cachepath)
779 779 return
780 780
781 781 reposfile = open(repospath, 'rb')
782 782 repos = {r[:-1] for r in reposfile.readlines()}
783 783 reposfile.close()
784 784
785 785 # build list of useful files
786 786 validrepos = []
787 787 keepkeys = set()
788 788
789 789 sharedcache = None
790 790 filesrepacked = False
791 791
792 792 count = 0
793 793 progress = ui.makeprogress(_("analyzing repositories"), unit="repos",
794 794 total=len(repos))
795 795 for path in repos:
796 796 progress.update(count)
797 797 count += 1
798 798 try:
799 799 path = ui.expandpath(os.path.normpath(path))
800 800 except TypeError as e:
801 801 ui.warn(_("warning: malformed path: %r:%s\n") % (path, e))
802 802 traceback.print_exc()
803 803 continue
804 804 try:
805 805 peer = hg.peer(ui, {}, path)
806 806 repo = peer._repo
807 807 except error.RepoError:
808 808 continue
809 809
810 810 validrepos.append(path)
811 811
812 812 # Protect against any repo or config changes that have happened since
813 813 # this repo was added to the repos file. We'd rather this loop succeed
814 814 # and too much be deleted, than the loop fail and nothing gets deleted.
815 815 if not isenabled(repo):
816 816 continue
817 817
818 818 if not util.safehasattr(repo, 'name'):
819 819 ui.warn(_("repo %s is a misconfigured remotefilelog repo\n") % path)
820 820 continue
821 821
822 822 # If garbage collection on repack and repack on hg gc are enabled
823 823 # then loose files are repacked and garbage collected.
824 824 # Otherwise regular garbage collection is performed.
825 825 repackonhggc = repo.ui.configbool('remotefilelog', 'repackonhggc')
826 826 gcrepack = repo.ui.configbool('remotefilelog', 'gcrepack')
827 827 if repackonhggc and gcrepack:
828 828 try:
829 829 repackmod.incrementalrepack(repo)
830 830 filesrepacked = True
831 831 continue
832 832 except (IOError, repackmod.RepackAlreadyRunning):
833 833 # If repack cannot be performed due to not enough disk space
834 834 # continue doing garbage collection of loose files w/o repack
835 835 pass
836 836
837 837 reponame = repo.name
838 838 if not sharedcache:
839 839 sharedcache = repo.sharedstore
840 840
841 841 # Compute a keepset which is not garbage collected
842 842 def keyfn(fname, fnode):
843 843 return fileserverclient.getcachekey(reponame, fname, hex(fnode))
844 844 keepkeys = repackmod.keepset(repo, keyfn=keyfn, lastkeepkeys=keepkeys)
845 845
846 846 progress.complete()
847 847
848 848 # write list of valid repos back
849 849 oldumask = os.umask(0o002)
850 850 try:
851 851 reposfile = open(repospath, 'wb')
852 852 reposfile.writelines([("%s\n" % r) for r in validrepos])
853 853 reposfile.close()
854 854 finally:
855 855 os.umask(oldumask)
856 856
857 857 # prune cache
858 858 if sharedcache is not None:
859 859 sharedcache.gc(keepkeys)
860 860 elif not filesrepacked:
861 861 ui.warn(_("warning: no valid repos in repofile\n"))
862 862
863 863 def log(orig, ui, repo, *pats, **opts):
864 864 if not isenabled(repo):
865 865 return orig(ui, repo, *pats, **opts)
866 866
867 867 follow = opts.get(r'follow')
868 868 revs = opts.get(r'rev')
869 869 if pats:
870 870 # Force slowpath for non-follow patterns and follows that start from
871 871 # non-working-copy-parent revs.
872 872 if not follow or revs:
873 873 # This forces the slowpath
874 874 opts[r'removed'] = True
875 875
876 876 # If this is a non-follow log without any revs specified, recommend that
877 877 # the user add -f to speed it up.
878 878 if not follow and not revs:
879 879 match = scmutil.match(repo['.'], pats, pycompat.byteskwargs(opts))
880 880 isfile = not match.anypats()
881 881 if isfile:
882 882 for file in match.files():
883 883 if not os.path.isfile(repo.wjoin(file)):
884 884 isfile = False
885 885 break
886 886
887 887 if isfile:
888 888 ui.warn(_("warning: file log can be slow on large repos - " +
889 889 "use -f to speed it up\n"))
890 890
891 891 return orig(ui, repo, *pats, **opts)
892 892
893 893 def revdatelimit(ui, revset):
894 894 """Update revset so that only changesets no older than 'prefetchdays' days
895 895 are included. The default value is set to 14 days. If 'prefetchdays' is set
896 896 to zero or negative value then date restriction is not applied.
897 897 """
898 898 days = ui.configint('remotefilelog', 'prefetchdays')
899 899 if days > 0:
900 900 revset = '(%s) & date(-%s)' % (revset, days)
901 901 return revset
902 902
903 903 def readytofetch(repo):
904 904 """Check that enough time has passed since the last background prefetch.
905 905 This only relates to prefetches after operations that change the working
906 906 copy parent. Default delay between background prefetches is 2 minutes.
907 907 """
908 908 timeout = repo.ui.configint('remotefilelog', 'prefetchdelay')
909 909 fname = repo.vfs.join('lastprefetch')
910 910
911 911 ready = False
912 912 with open(fname, 'a'):
913 913 # the with construct above is used to avoid race conditions
914 914 modtime = os.path.getmtime(fname)
915 915 if (time.time() - modtime) > timeout:
916 916 os.utime(fname, None)
917 917 ready = True
918 918
919 919 return ready
920 920
921 921 def wcpprefetch(ui, repo, **kwargs):
922 922 """Prefetches in background revisions specified by bgprefetchrevs revset.
923 923 Does background repack if backgroundrepack flag is set in config.
924 924 """
925 925 shallow = isenabled(repo)
926 926 bgprefetchrevs = ui.config('remotefilelog', 'bgprefetchrevs')
927 927 isready = readytofetch(repo)
928 928
929 929 if not (shallow and bgprefetchrevs and isready):
930 930 return
931 931
932 932 bgrepack = repo.ui.configbool('remotefilelog', 'backgroundrepack')
933 933 # update a revset with a date limit
934 934 bgprefetchrevs = revdatelimit(ui, bgprefetchrevs)
935 935
936 936 def anon():
937 937 if util.safehasattr(repo, 'ranprefetch') and repo.ranprefetch:
938 938 return
939 939 repo.ranprefetch = True
940 940 repo.backgroundprefetch(bgprefetchrevs, repack=bgrepack)
941 941
942 942 repo._afterlock(anon)
943 943
944 944 def pull(orig, ui, repo, *pats, **opts):
945 945 result = orig(ui, repo, *pats, **opts)
946 946
947 947 if isenabled(repo):
948 948 # prefetch if it's configured
949 949 prefetchrevset = ui.config('remotefilelog', 'pullprefetch')
950 950 bgrepack = repo.ui.configbool('remotefilelog', 'backgroundrepack')
951 951 bgprefetch = repo.ui.configbool('remotefilelog', 'backgroundprefetch')
952 952
953 953 if prefetchrevset:
954 954 ui.status(_("prefetching file contents\n"))
955 955 revs = scmutil.revrange(repo, [prefetchrevset])
956 956 base = repo['.'].rev()
957 957 if bgprefetch:
958 958 repo.backgroundprefetch(prefetchrevset, repack=bgrepack)
959 959 else:
960 960 repo.prefetch(revs, base=base)
961 961 if bgrepack:
962 962 repackmod.backgroundrepack(repo, incremental=True)
963 963 elif bgrepack:
964 964 repackmod.backgroundrepack(repo, incremental=True)
965 965
966 966 return result
967 967
968 968 def exchangepull(orig, repo, remote, *args, **kwargs):
969 969 # Hook into the callstream/getbundle to insert bundle capabilities
970 970 # during a pull.
971 971 def localgetbundle(orig, source, heads=None, common=None, bundlecaps=None,
972 972 **kwargs):
973 973 if not bundlecaps:
974 974 bundlecaps = set()
975 975 bundlecaps.add(constants.BUNDLE2_CAPABLITY)
976 976 return orig(source, heads=heads, common=common, bundlecaps=bundlecaps,
977 977 **kwargs)
978 978
979 979 if util.safehasattr(remote, '_callstream'):
980 980 remote._localrepo = repo
981 981 elif util.safehasattr(remote, 'getbundle'):
982 982 extensions.wrapfunction(remote, 'getbundle', localgetbundle)
983 983
984 984 return orig(repo, remote, *args, **kwargs)
985 985
986 986 def _fileprefetchhook(repo, revs, match):
987 987 if isenabled(repo):
988 988 allfiles = []
989 989 for rev in revs:
990 990 if rev == nodemod.wdirrev or rev is None:
991 991 continue
992 992 ctx = repo[rev]
993 993 mf = ctx.manifest()
994 994 sparsematch = repo.maybesparsematch(ctx.rev())
995 995 for path in ctx.walk(match):
996 996 if (not sparsematch or sparsematch(path)) and path in mf:
997 997 allfiles.append((path, hex(mf[path])))
998 998 repo.fileservice.prefetch(allfiles)
999 999
1000 1000 @command('debugremotefilelog', [
1001 1001 ('d', 'decompress', None, _('decompress the filelog first')),
1002 1002 ], _('hg debugremotefilelog <path>'), norepo=True)
1003 1003 def debugremotefilelog(ui, path, **opts):
1004 1004 return debugcommands.debugremotefilelog(ui, path, **opts)
1005 1005
1006 1006 @command('verifyremotefilelog', [
1007 1007 ('d', 'decompress', None, _('decompress the filelogs first')),
1008 1008 ], _('hg verifyremotefilelogs <directory>'), norepo=True)
1009 1009 def verifyremotefilelog(ui, path, **opts):
1010 1010 return debugcommands.verifyremotefilelog(ui, path, **opts)
1011 1011
1012 1012 @command('debugdatapack', [
1013 1013 ('', 'long', None, _('print the long hashes')),
1014 1014 ('', 'node', '', _('dump the contents of node'), 'NODE'),
1015 1015 ], _('hg debugdatapack <paths>'), norepo=True)
1016 1016 def debugdatapack(ui, *paths, **opts):
1017 1017 return debugcommands.debugdatapack(ui, *paths, **opts)
1018 1018
1019 1019 @command('debughistorypack', [
1020 1020 ], _('hg debughistorypack <path>'), norepo=True)
1021 1021 def debughistorypack(ui, path, **opts):
1022 1022 return debugcommands.debughistorypack(ui, path)
1023 1023
1024 1024 @command('debugkeepset', [
1025 1025 ], _('hg debugkeepset'))
1026 1026 def debugkeepset(ui, repo, **opts):
1027 1027 # The command is used to measure keepset computation time
1028 1028 def keyfn(fname, fnode):
1029 1029 return fileserverclient.getcachekey(repo.name, fname, hex(fnode))
1030 1030 repackmod.keepset(repo, keyfn)
1031 1031 return
1032 1032
1033 1033 @command('debugwaitonrepack', [
1034 1034 ], _('hg debugwaitonrepack'))
1035 1035 def debugwaitonrepack(ui, repo, **opts):
1036 1036 return debugcommands.debugwaitonrepack(repo)
1037 1037
1038 1038 @command('debugwaitonprefetch', [
1039 1039 ], _('hg debugwaitonprefetch'))
1040 1040 def debugwaitonprefetch(ui, repo, **opts):
1041 1041 return debugcommands.debugwaitonprefetch(repo)
1042 1042
1043 1043 def resolveprefetchopts(ui, opts):
1044 1044 if not opts.get('rev'):
1045 1045 revset = ['.', 'draft()']
1046 1046
1047 1047 prefetchrevset = ui.config('remotefilelog', 'pullprefetch', None)
1048 1048 if prefetchrevset:
1049 1049 revset.append('(%s)' % prefetchrevset)
1050 1050 bgprefetchrevs = ui.config('remotefilelog', 'bgprefetchrevs', None)
1051 1051 if bgprefetchrevs:
1052 1052 revset.append('(%s)' % bgprefetchrevs)
1053 1053 revset = '+'.join(revset)
1054 1054
1055 1055 # update a revset with a date limit
1056 1056 revset = revdatelimit(ui, revset)
1057 1057
1058 1058 opts['rev'] = [revset]
1059 1059
1060 1060 if not opts.get('base'):
1061 1061 opts['base'] = None
1062 1062
1063 1063 return opts
1064 1064
1065 1065 @command('prefetch', [
1066 1066 ('r', 'rev', [], _('prefetch the specified revisions'), _('REV')),
1067 1067 ('', 'repack', False, _('run repack after prefetch')),
1068 1068 ('b', 'base', '', _("rev that is assumed to already be local")),
1069 1069 ] + commands.walkopts, _('hg prefetch [OPTIONS] [FILE...]'))
1070 1070 def prefetch(ui, repo, *pats, **opts):
1071 1071 """prefetch file revisions from the server
1072 1072
1073 1073 Prefetchs file revisions for the specified revs and stores them in the
1074 1074 local remotefilelog cache. If no rev is specified, the default rev is
1075 1075 used which is the union of dot, draft, pullprefetch and bgprefetchrev.
1076 1076 File names or patterns can be used to limit which files are downloaded.
1077 1077
1078 1078 Return 0 on success.
1079 1079 """
1080 1080 opts = pycompat.byteskwargs(opts)
1081 1081 if not isenabled(repo):
1082 1082 raise error.Abort(_("repo is not shallow"))
1083 1083
1084 1084 opts = resolveprefetchopts(ui, opts)
1085 1085 revs = scmutil.revrange(repo, opts.get('rev'))
1086 1086 repo.prefetch(revs, opts.get('base'), pats, opts)
1087 1087
1088 1088 # Run repack in background
1089 1089 if opts.get('repack'):
1090 1090 repackmod.backgroundrepack(repo, incremental=True)
1091 1091
1092 1092 @command('repack', [
1093 1093 ('', 'background', None, _('run in a background process'), None),
1094 1094 ('', 'incremental', None, _('do an incremental repack'), None),
1095 1095 ('', 'packsonly', None, _('only repack packs (skip loose objects)'), None),
1096 1096 ], _('hg repack [OPTIONS]'))
1097 1097 def repack_(ui, repo, *pats, **opts):
1098 1098 if opts.get(r'background'):
1099 1099 repackmod.backgroundrepack(repo, incremental=opts.get(r'incremental'),
1100 1100 packsonly=opts.get(r'packsonly', False))
1101 1101 return
1102 1102
1103 1103 options = {'packsonly': opts.get(r'packsonly')}
1104 1104
1105 1105 try:
1106 1106 if opts.get(r'incremental'):
1107 1107 repackmod.incrementalrepack(repo, options=options)
1108 1108 else:
1109 1109 repackmod.fullrepack(repo, options=options)
1110 1110 except repackmod.RepackAlreadyRunning as ex:
1111 1111 # Don't propogate the exception if the repack is already in
1112 1112 # progress, since we want the command to exit 0.
1113 1113 repo.ui.warn('%s\n' % ex)
General Comments 0
You need to be logged in to leave comments. Login now