##// END OF EJS Templates
engine: refactor how total dstsize is calculated...
Pulkit Goyal -
r46831:5dfa837d default
parent child Browse files
Show More
@@ -1,548 +1,543 b''
1 1 # upgrade.py - functions for in place upgrade of Mercurial repository
2 2 #
3 3 # Copyright (c) 2016-present, Gregory Szorc
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 from __future__ import absolute_import
9 9
10 10 import stat
11 11
12 12 from ..i18n import _
13 13 from ..pycompat import getattr
14 14 from .. import (
15 15 changelog,
16 16 error,
17 17 filelog,
18 18 manifest,
19 19 metadata,
20 20 pycompat,
21 21 requirements,
22 22 revlog,
23 23 scmutil,
24 24 util,
25 25 vfs as vfsmod,
26 26 )
27 27
28 28
29 29 def _revlogfrompath(repo, path):
30 30 """Obtain a revlog from a repo path.
31 31
32 32 An instance of the appropriate class is returned.
33 33 """
34 34 if path == b'00changelog.i':
35 35 return changelog.changelog(repo.svfs)
36 36 elif path.endswith(b'00manifest.i'):
37 37 mandir = path[: -len(b'00manifest.i')]
38 38 return manifest.manifestrevlog(repo.svfs, tree=mandir)
39 39 else:
40 40 # reverse of "/".join(("data", path + ".i"))
41 41 return filelog.filelog(repo.svfs, path[5:-2])
42 42
43 43
44 44 def _copyrevlog(tr, destrepo, oldrl, unencodedname):
45 45 """copy all relevant files for `oldrl` into `destrepo` store
46 46
47 47 Files are copied "as is" without any transformation. The copy is performed
48 48 without extra checks. Callers are responsible for making sure the copied
49 49 content is compatible with format of the destination repository.
50 50 """
51 51 oldrl = getattr(oldrl, '_revlog', oldrl)
52 52 newrl = _revlogfrompath(destrepo, unencodedname)
53 53 newrl = getattr(newrl, '_revlog', newrl)
54 54
55 55 oldvfs = oldrl.opener
56 56 newvfs = newrl.opener
57 57 oldindex = oldvfs.join(oldrl.indexfile)
58 58 newindex = newvfs.join(newrl.indexfile)
59 59 olddata = oldvfs.join(oldrl.datafile)
60 60 newdata = newvfs.join(newrl.datafile)
61 61
62 62 with newvfs(newrl.indexfile, b'w'):
63 63 pass # create all the directories
64 64
65 65 util.copyfile(oldindex, newindex)
66 66 copydata = oldrl.opener.exists(oldrl.datafile)
67 67 if copydata:
68 68 util.copyfile(olddata, newdata)
69 69
70 70 if not (
71 71 unencodedname.endswith(b'00changelog.i')
72 72 or unencodedname.endswith(b'00manifest.i')
73 73 ):
74 74 destrepo.svfs.fncache.add(unencodedname)
75 75 if copydata:
76 76 destrepo.svfs.fncache.add(unencodedname[:-2] + b'.d')
77 77
78 78
79 79 UPGRADE_CHANGELOG = b"changelog"
80 80 UPGRADE_MANIFEST = b"manifest"
81 81 UPGRADE_FILELOGS = b"all-filelogs"
82 82
83 83 UPGRADE_ALL_REVLOGS = frozenset(
84 84 [UPGRADE_CHANGELOG, UPGRADE_MANIFEST, UPGRADE_FILELOGS]
85 85 )
86 86
87 87
88 88 def getsidedatacompanion(srcrepo, dstrepo):
89 89 sidedatacompanion = None
90 90 removedreqs = srcrepo.requirements - dstrepo.requirements
91 91 addedreqs = dstrepo.requirements - srcrepo.requirements
92 92 if requirements.SIDEDATA_REQUIREMENT in removedreqs:
93 93
94 94 def sidedatacompanion(rl, rev):
95 95 rl = getattr(rl, '_revlog', rl)
96 96 if rl.flags(rev) & revlog.REVIDX_SIDEDATA:
97 97 return True, (), {}, 0, 0
98 98 return False, (), {}, 0, 0
99 99
100 100 elif requirements.COPIESSDC_REQUIREMENT in addedreqs:
101 101 sidedatacompanion = metadata.getsidedataadder(srcrepo, dstrepo)
102 102 elif requirements.COPIESSDC_REQUIREMENT in removedreqs:
103 103 sidedatacompanion = metadata.getsidedataremover(srcrepo, dstrepo)
104 104 return sidedatacompanion
105 105
106 106
107 107 def matchrevlog(revlogfilter, entry):
108 108 """check if a revlog is selected for cloning.
109 109
110 110 In other words, are there any updates which need to be done on revlog
111 111 or it can be blindly copied.
112 112
113 113 The store entry is checked against the passed filter"""
114 114 if entry.endswith(b'00changelog.i'):
115 115 return UPGRADE_CHANGELOG in revlogfilter
116 116 elif entry.endswith(b'00manifest.i'):
117 117 return UPGRADE_MANIFEST in revlogfilter
118 118 return UPGRADE_FILELOGS in revlogfilter
119 119
120 120
121 121 def _perform_clone(
122 122 ui,
123 123 dstrepo,
124 124 tr,
125 125 old_revlog,
126 126 unencoded,
127 127 deltareuse,
128 128 forcedeltabothparents,
129 129 revlogs,
130 130 sidedatacompanion,
131 131 oncopiedrevision,
132 132 ):
133 133 """ returns the new revlog object created"""
134 134 newrl = None
135 135 if matchrevlog(revlogs, unencoded):
136 136 ui.note(
137 137 _(b'cloning %d revisions from %s\n') % (len(old_revlog), unencoded)
138 138 )
139 139 newrl = _revlogfrompath(dstrepo, unencoded)
140 140 old_revlog.clone(
141 141 tr,
142 142 newrl,
143 143 addrevisioncb=oncopiedrevision,
144 144 deltareuse=deltareuse,
145 145 forcedeltabothparents=forcedeltabothparents,
146 146 sidedatacompanion=sidedatacompanion,
147 147 )
148 148 else:
149 149 msg = _(b'blindly copying %s containing %i revisions\n')
150 150 ui.note(msg % (unencoded, len(old_revlog)))
151 151 _copyrevlog(tr, dstrepo, old_revlog, unencoded)
152 152
153 153 newrl = _revlogfrompath(dstrepo, unencoded)
154 154 return newrl
155 155
156 156
157 157 def _clonerevlogs(
158 158 ui,
159 159 srcrepo,
160 160 dstrepo,
161 161 tr,
162 162 deltareuse,
163 163 forcedeltabothparents,
164 164 revlogs=UPGRADE_ALL_REVLOGS,
165 165 ):
166 166 """Copy revlogs between 2 repos."""
167 167 revcount = 0
168 168 srcsize = 0
169 169 srcrawsize = 0
170 170 dstsize = 0
171 171 fcount = 0
172 172 frevcount = 0
173 173 fsrcsize = 0
174 174 frawsize = 0
175 175 fdstsize = 0
176 176 mcount = 0
177 177 mrevcount = 0
178 178 msrcsize = 0
179 179 mrawsize = 0
180 180 mdstsize = 0
181 181 crevcount = 0
182 182 csrcsize = 0
183 183 crawsize = 0
184 184 cdstsize = 0
185 185
186 186 alldatafiles = list(srcrepo.store.walk())
187 187 # mapping of data files which needs to be cloned
188 188 # key is unencoded filename
189 189 # value is revlog_object_from_srcrepo
190 190 manifests = {}
191 191 changelogs = {}
192 192 filelogs = {}
193 193
194 194 # Perform a pass to collect metadata. This validates we can open all
195 195 # source files and allows a unified progress bar to be displayed.
196 196 for unencoded, encoded, size in alldatafiles:
197 197 if unencoded.endswith(b'.d'):
198 198 continue
199 199
200 200 rl = _revlogfrompath(srcrepo, unencoded)
201 201
202 202 info = rl.storageinfo(
203 203 exclusivefiles=True,
204 204 revisionscount=True,
205 205 trackedsize=True,
206 206 storedsize=True,
207 207 )
208 208
209 209 revcount += info[b'revisionscount'] or 0
210 210 datasize = info[b'storedsize'] or 0
211 211 rawsize = info[b'trackedsize'] or 0
212 212
213 213 srcsize += datasize
214 214 srcrawsize += rawsize
215 215
216 216 # This is for the separate progress bars.
217 217 if isinstance(rl, changelog.changelog):
218 218 changelogs[unencoded] = rl
219 219 crevcount += len(rl)
220 220 csrcsize += datasize
221 221 crawsize += rawsize
222 222 elif isinstance(rl, manifest.manifestrevlog):
223 223 manifests[unencoded] = rl
224 224 mcount += 1
225 225 mrevcount += len(rl)
226 226 msrcsize += datasize
227 227 mrawsize += rawsize
228 228 elif isinstance(rl, filelog.filelog):
229 229 filelogs[unencoded] = rl
230 230 fcount += 1
231 231 frevcount += len(rl)
232 232 fsrcsize += datasize
233 233 frawsize += rawsize
234 234 else:
235 235 error.ProgrammingError(b'unknown revlog type')
236 236
237 237 if not revcount:
238 238 return
239 239
240 240 ui.status(
241 241 _(
242 242 b'migrating %d total revisions (%d in filelogs, %d in manifests, '
243 243 b'%d in changelog)\n'
244 244 )
245 245 % (revcount, frevcount, mrevcount, crevcount)
246 246 )
247 247 ui.status(
248 248 _(b'migrating %s in store; %s tracked data\n')
249 249 % ((util.bytecount(srcsize), util.bytecount(srcrawsize)))
250 250 )
251 251
252 252 # Used to keep track of progress.
253 253 progress = None
254 254
255 255 def oncopiedrevision(rl, rev, node):
256 256 progress.increment()
257 257
258 258 sidedatacompanion = getsidedatacompanion(srcrepo, dstrepo)
259 259
260 260 # Migrating filelogs
261 261 ui.status(
262 262 _(
263 263 b'migrating %d filelogs containing %d revisions '
264 264 b'(%s in store; %s tracked data)\n'
265 265 )
266 266 % (
267 267 fcount,
268 268 frevcount,
269 269 util.bytecount(fsrcsize),
270 270 util.bytecount(frawsize),
271 271 )
272 272 )
273 273 progress = srcrepo.ui.makeprogress(_(b'file revisions'), total=frevcount)
274 274 for unencoded, oldrl in sorted(filelogs.items()):
275 275 newrl = _perform_clone(
276 276 ui,
277 277 dstrepo,
278 278 tr,
279 279 oldrl,
280 280 unencoded,
281 281 deltareuse,
282 282 forcedeltabothparents,
283 283 revlogs,
284 284 sidedatacompanion,
285 285 oncopiedrevision,
286 286 )
287 287 info = newrl.storageinfo(storedsize=True)
288 datasize = info[b'storedsize'] or 0
289 dstsize += datasize
290 fdstsize += datasize
288 fdstsize += info[b'storedsize'] or 0
291 289 ui.status(
292 290 _(
293 291 b'finished migrating %d filelog revisions across %d '
294 292 b'filelogs; change in size: %s\n'
295 293 )
296 294 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
297 295 )
298 296
299 297 # Migrating manifests
300 298 ui.status(
301 299 _(
302 300 b'migrating %d manifests containing %d revisions '
303 301 b'(%s in store; %s tracked data)\n'
304 302 )
305 303 % (
306 304 mcount,
307 305 mrevcount,
308 306 util.bytecount(msrcsize),
309 307 util.bytecount(mrawsize),
310 308 )
311 309 )
312 310 if progress:
313 311 progress.complete()
314 312 progress = srcrepo.ui.makeprogress(
315 313 _(b'manifest revisions'), total=mrevcount
316 314 )
317 315 for unencoded, oldrl in sorted(manifests.items()):
318 316 newrl = _perform_clone(
319 317 ui,
320 318 dstrepo,
321 319 tr,
322 320 oldrl,
323 321 unencoded,
324 322 deltareuse,
325 323 forcedeltabothparents,
326 324 revlogs,
327 325 sidedatacompanion,
328 326 oncopiedrevision,
329 327 )
330 328 info = newrl.storageinfo(storedsize=True)
331 datasize = info[b'storedsize'] or 0
332 dstsize += datasize
333 mdstsize += datasize
329 mdstsize += info[b'storedsize'] or 0
334 330 ui.status(
335 331 _(
336 332 b'finished migrating %d manifest revisions across %d '
337 333 b'manifests; change in size: %s\n'
338 334 )
339 335 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
340 336 )
341 337
342 338 # Migrating changelog
343 339 ui.status(
344 340 _(
345 341 b'migrating changelog containing %d revisions '
346 342 b'(%s in store; %s tracked data)\n'
347 343 )
348 344 % (
349 345 crevcount,
350 346 util.bytecount(csrcsize),
351 347 util.bytecount(crawsize),
352 348 )
353 349 )
354 350 if progress:
355 351 progress.complete()
356 352 progress = srcrepo.ui.makeprogress(
357 353 _(b'changelog revisions'), total=crevcount
358 354 )
359 355 for unencoded, oldrl in sorted(changelogs.items()):
360 356 newrl = _perform_clone(
361 357 ui,
362 358 dstrepo,
363 359 tr,
364 360 oldrl,
365 361 unencoded,
366 362 deltareuse,
367 363 forcedeltabothparents,
368 364 revlogs,
369 365 sidedatacompanion,
370 366 oncopiedrevision,
371 367 )
372 368 info = newrl.storageinfo(storedsize=True)
373 datasize = info[b'storedsize'] or 0
374 dstsize += datasize
375 cdstsize += datasize
369 cdstsize += info[b'storedsize'] or 0
376 370 progress.complete()
377 371 ui.status(
378 372 _(
379 373 b'finished migrating %d changelog revisions; change in size: '
380 374 b'%s\n'
381 375 )
382 376 % (crevcount, util.bytecount(cdstsize - csrcsize))
383 377 )
384 378
379 dstsize = fdstsize + mdstsize + cdstsize
385 380 ui.status(
386 381 _(
387 382 b'finished migrating %d total revisions; total change in store '
388 383 b'size: %s\n'
389 384 )
390 385 % (revcount, util.bytecount(dstsize - srcsize))
391 386 )
392 387
393 388
394 389 def _filterstorefile(srcrepo, dstrepo, requirements, path, mode, st):
395 390 """Determine whether to copy a store file during upgrade.
396 391
397 392 This function is called when migrating store files from ``srcrepo`` to
398 393 ``dstrepo`` as part of upgrading a repository.
399 394
400 395 Args:
401 396 srcrepo: repo we are copying from
402 397 dstrepo: repo we are copying to
403 398 requirements: set of requirements for ``dstrepo``
404 399 path: store file being examined
405 400 mode: the ``ST_MODE`` file type of ``path``
406 401 st: ``stat`` data structure for ``path``
407 402
408 403 Function should return ``True`` if the file is to be copied.
409 404 """
410 405 # Skip revlogs.
411 406 if path.endswith((b'.i', b'.d', b'.n', b'.nd')):
412 407 return False
413 408 # Skip transaction related files.
414 409 if path.startswith(b'undo'):
415 410 return False
416 411 # Only copy regular files.
417 412 if mode != stat.S_IFREG:
418 413 return False
419 414 # Skip other skipped files.
420 415 if path in (b'lock', b'fncache'):
421 416 return False
422 417
423 418 return True
424 419
425 420
426 421 def _finishdatamigration(ui, srcrepo, dstrepo, requirements):
427 422 """Hook point for extensions to perform additional actions during upgrade.
428 423
429 424 This function is called after revlogs and store files have been copied but
430 425 before the new store is swapped into the original location.
431 426 """
432 427
433 428
434 429 def upgrade(ui, srcrepo, dstrepo, upgrade_op):
435 430 """Do the low-level work of upgrading a repository.
436 431
437 432 The upgrade is effectively performed as a copy between a source
438 433 repository and a temporary destination repository.
439 434
440 435 The source repository is unmodified for as long as possible so the
441 436 upgrade can abort at any time without causing loss of service for
442 437 readers and without corrupting the source repository.
443 438 """
444 439 assert srcrepo.currentwlock()
445 440 assert dstrepo.currentwlock()
446 441
447 442 ui.status(
448 443 _(
449 444 b'(it is safe to interrupt this process any time before '
450 445 b'data migration completes)\n'
451 446 )
452 447 )
453 448
454 449 if upgrade_op.has_upgrade_action(b're-delta-all'):
455 450 deltareuse = revlog.revlog.DELTAREUSENEVER
456 451 elif upgrade_op.has_upgrade_action(b're-delta-parent'):
457 452 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
458 453 elif upgrade_op.has_upgrade_action(b're-delta-multibase'):
459 454 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
460 455 elif upgrade_op.has_upgrade_action(b're-delta-fulladd'):
461 456 deltareuse = revlog.revlog.DELTAREUSEFULLADD
462 457 else:
463 458 deltareuse = revlog.revlog.DELTAREUSEALWAYS
464 459
465 460 with dstrepo.transaction(b'upgrade') as tr:
466 461 _clonerevlogs(
467 462 ui,
468 463 srcrepo,
469 464 dstrepo,
470 465 tr,
471 466 deltareuse,
472 467 upgrade_op.has_upgrade_action(b're-delta-multibase'),
473 468 revlogs=upgrade_op.revlogs_to_process,
474 469 )
475 470
476 471 # Now copy other files in the store directory.
477 472 # The sorted() makes execution deterministic.
478 473 for p, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)):
479 474 if not _filterstorefile(
480 475 srcrepo, dstrepo, upgrade_op.new_requirements, p, kind, st
481 476 ):
482 477 continue
483 478
484 479 srcrepo.ui.status(_(b'copying %s\n') % p)
485 480 src = srcrepo.store.rawvfs.join(p)
486 481 dst = dstrepo.store.rawvfs.join(p)
487 482 util.copyfile(src, dst, copystat=True)
488 483
489 484 _finishdatamigration(ui, srcrepo, dstrepo, requirements)
490 485
491 486 ui.status(_(b'data fully migrated to temporary repository\n'))
492 487
493 488 backuppath = pycompat.mkdtemp(prefix=b'upgradebackup.', dir=srcrepo.path)
494 489 backupvfs = vfsmod.vfs(backuppath)
495 490
496 491 # Make a backup of requires file first, as it is the first to be modified.
497 492 util.copyfile(srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires'))
498 493
499 494 # We install an arbitrary requirement that clients must not support
500 495 # as a mechanism to lock out new clients during the data swap. This is
501 496 # better than allowing a client to continue while the repository is in
502 497 # an inconsistent state.
503 498 ui.status(
504 499 _(
505 500 b'marking source repository as being upgraded; clients will be '
506 501 b'unable to read from repository\n'
507 502 )
508 503 )
509 504 scmutil.writereporequirements(
510 505 srcrepo, srcrepo.requirements | {b'upgradeinprogress'}
511 506 )
512 507
513 508 ui.status(_(b'starting in-place swap of repository data\n'))
514 509 ui.status(_(b'replaced files will be backed up at %s\n') % backuppath)
515 510
516 511 # Now swap in the new store directory. Doing it as a rename should make
517 512 # the operation nearly instantaneous and atomic (at least in well-behaved
518 513 # environments).
519 514 ui.status(_(b'replacing store...\n'))
520 515 tstart = util.timer()
521 516 util.rename(srcrepo.spath, backupvfs.join(b'store'))
522 517 util.rename(dstrepo.spath, srcrepo.spath)
523 518 elapsed = util.timer() - tstart
524 519 ui.status(
525 520 _(
526 521 b'store replacement complete; repository was inconsistent for '
527 522 b'%0.1fs\n'
528 523 )
529 524 % elapsed
530 525 )
531 526
532 527 # We first write the requirements file. Any new requirements will lock
533 528 # out legacy clients.
534 529 ui.status(
535 530 _(
536 531 b'finalizing requirements file and making repository readable '
537 532 b'again\n'
538 533 )
539 534 )
540 535 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
541 536
542 537 # The lock file from the old store won't be removed because nothing has a
543 538 # reference to its new location. So clean it up manually. Alternatively, we
544 539 # could update srcrepo.svfs and other variables to point to the new
545 540 # location. This is simpler.
546 541 backupvfs.unlink(b'store/lock')
547 542
548 543 return backuppath
General Comments 0
You need to be logged in to leave comments. Login now