##// END OF EJS Templates
engine: refactor code to replace stores in separate function...
Pulkit Goyal -
r46837:1ca7865c default
parent child Browse files
Show More
@@ -1,520 +1,538 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 upgrade_op,
128 128 sidedatacompanion,
129 129 oncopiedrevision,
130 130 ):
131 131 """ returns the new revlog object created"""
132 132 newrl = None
133 133 if matchrevlog(upgrade_op.revlogs_to_process, unencoded):
134 134 ui.note(
135 135 _(b'cloning %d revisions from %s\n') % (len(old_revlog), unencoded)
136 136 )
137 137 newrl = _revlogfrompath(dstrepo, unencoded)
138 138 old_revlog.clone(
139 139 tr,
140 140 newrl,
141 141 addrevisioncb=oncopiedrevision,
142 142 deltareuse=upgrade_op.delta_reuse_mode,
143 143 forcedeltabothparents=upgrade_op.force_re_delta_both_parents,
144 144 sidedatacompanion=sidedatacompanion,
145 145 )
146 146 else:
147 147 msg = _(b'blindly copying %s containing %i revisions\n')
148 148 ui.note(msg % (unencoded, len(old_revlog)))
149 149 _copyrevlog(tr, dstrepo, old_revlog, unencoded)
150 150
151 151 newrl = _revlogfrompath(dstrepo, unencoded)
152 152 return newrl
153 153
154 154
155 155 def _clonerevlogs(
156 156 ui,
157 157 srcrepo,
158 158 dstrepo,
159 159 tr,
160 160 upgrade_op,
161 161 ):
162 162 """Copy revlogs between 2 repos."""
163 163 revcount = 0
164 164 srcsize = 0
165 165 srcrawsize = 0
166 166 dstsize = 0
167 167 fcount = 0
168 168 frevcount = 0
169 169 fsrcsize = 0
170 170 frawsize = 0
171 171 fdstsize = 0
172 172 mcount = 0
173 173 mrevcount = 0
174 174 msrcsize = 0
175 175 mrawsize = 0
176 176 mdstsize = 0
177 177 crevcount = 0
178 178 csrcsize = 0
179 179 crawsize = 0
180 180 cdstsize = 0
181 181
182 182 alldatafiles = list(srcrepo.store.walk())
183 183 # mapping of data files which needs to be cloned
184 184 # key is unencoded filename
185 185 # value is revlog_object_from_srcrepo
186 186 manifests = {}
187 187 changelogs = {}
188 188 filelogs = {}
189 189
190 190 # Perform a pass to collect metadata. This validates we can open all
191 191 # source files and allows a unified progress bar to be displayed.
192 192 for unencoded, encoded, size in alldatafiles:
193 193 if unencoded.endswith(b'.d'):
194 194 continue
195 195
196 196 rl = _revlogfrompath(srcrepo, unencoded)
197 197
198 198 info = rl.storageinfo(
199 199 exclusivefiles=True,
200 200 revisionscount=True,
201 201 trackedsize=True,
202 202 storedsize=True,
203 203 )
204 204
205 205 revcount += info[b'revisionscount'] or 0
206 206 datasize = info[b'storedsize'] or 0
207 207 rawsize = info[b'trackedsize'] or 0
208 208
209 209 srcsize += datasize
210 210 srcrawsize += rawsize
211 211
212 212 # This is for the separate progress bars.
213 213 if isinstance(rl, changelog.changelog):
214 214 changelogs[unencoded] = rl
215 215 crevcount += len(rl)
216 216 csrcsize += datasize
217 217 crawsize += rawsize
218 218 elif isinstance(rl, manifest.manifestrevlog):
219 219 manifests[unencoded] = rl
220 220 mcount += 1
221 221 mrevcount += len(rl)
222 222 msrcsize += datasize
223 223 mrawsize += rawsize
224 224 elif isinstance(rl, filelog.filelog):
225 225 filelogs[unencoded] = rl
226 226 fcount += 1
227 227 frevcount += len(rl)
228 228 fsrcsize += datasize
229 229 frawsize += rawsize
230 230 else:
231 231 error.ProgrammingError(b'unknown revlog type')
232 232
233 233 if not revcount:
234 234 return
235 235
236 236 ui.status(
237 237 _(
238 238 b'migrating %d total revisions (%d in filelogs, %d in manifests, '
239 239 b'%d in changelog)\n'
240 240 )
241 241 % (revcount, frevcount, mrevcount, crevcount)
242 242 )
243 243 ui.status(
244 244 _(b'migrating %s in store; %s tracked data\n')
245 245 % ((util.bytecount(srcsize), util.bytecount(srcrawsize)))
246 246 )
247 247
248 248 # Used to keep track of progress.
249 249 progress = None
250 250
251 251 def oncopiedrevision(rl, rev, node):
252 252 progress.increment()
253 253
254 254 sidedatacompanion = getsidedatacompanion(srcrepo, dstrepo)
255 255
256 256 # Migrating filelogs
257 257 ui.status(
258 258 _(
259 259 b'migrating %d filelogs containing %d revisions '
260 260 b'(%s in store; %s tracked data)\n'
261 261 )
262 262 % (
263 263 fcount,
264 264 frevcount,
265 265 util.bytecount(fsrcsize),
266 266 util.bytecount(frawsize),
267 267 )
268 268 )
269 269 progress = srcrepo.ui.makeprogress(_(b'file revisions'), total=frevcount)
270 270 for unencoded, oldrl in sorted(filelogs.items()):
271 271 newrl = _perform_clone(
272 272 ui,
273 273 dstrepo,
274 274 tr,
275 275 oldrl,
276 276 unencoded,
277 277 upgrade_op,
278 278 sidedatacompanion,
279 279 oncopiedrevision,
280 280 )
281 281 info = newrl.storageinfo(storedsize=True)
282 282 fdstsize += info[b'storedsize'] or 0
283 283 ui.status(
284 284 _(
285 285 b'finished migrating %d filelog revisions across %d '
286 286 b'filelogs; change in size: %s\n'
287 287 )
288 288 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
289 289 )
290 290
291 291 # Migrating manifests
292 292 ui.status(
293 293 _(
294 294 b'migrating %d manifests containing %d revisions '
295 295 b'(%s in store; %s tracked data)\n'
296 296 )
297 297 % (
298 298 mcount,
299 299 mrevcount,
300 300 util.bytecount(msrcsize),
301 301 util.bytecount(mrawsize),
302 302 )
303 303 )
304 304 if progress:
305 305 progress.complete()
306 306 progress = srcrepo.ui.makeprogress(
307 307 _(b'manifest revisions'), total=mrevcount
308 308 )
309 309 for unencoded, oldrl in sorted(manifests.items()):
310 310 newrl = _perform_clone(
311 311 ui,
312 312 dstrepo,
313 313 tr,
314 314 oldrl,
315 315 unencoded,
316 316 upgrade_op,
317 317 sidedatacompanion,
318 318 oncopiedrevision,
319 319 )
320 320 info = newrl.storageinfo(storedsize=True)
321 321 mdstsize += info[b'storedsize'] or 0
322 322 ui.status(
323 323 _(
324 324 b'finished migrating %d manifest revisions across %d '
325 325 b'manifests; change in size: %s\n'
326 326 )
327 327 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
328 328 )
329 329
330 330 # Migrating changelog
331 331 ui.status(
332 332 _(
333 333 b'migrating changelog containing %d revisions '
334 334 b'(%s in store; %s tracked data)\n'
335 335 )
336 336 % (
337 337 crevcount,
338 338 util.bytecount(csrcsize),
339 339 util.bytecount(crawsize),
340 340 )
341 341 )
342 342 if progress:
343 343 progress.complete()
344 344 progress = srcrepo.ui.makeprogress(
345 345 _(b'changelog revisions'), total=crevcount
346 346 )
347 347 for unencoded, oldrl in sorted(changelogs.items()):
348 348 newrl = _perform_clone(
349 349 ui,
350 350 dstrepo,
351 351 tr,
352 352 oldrl,
353 353 unencoded,
354 354 upgrade_op,
355 355 sidedatacompanion,
356 356 oncopiedrevision,
357 357 )
358 358 info = newrl.storageinfo(storedsize=True)
359 359 cdstsize += info[b'storedsize'] or 0
360 360 progress.complete()
361 361 ui.status(
362 362 _(
363 363 b'finished migrating %d changelog revisions; change in size: '
364 364 b'%s\n'
365 365 )
366 366 % (crevcount, util.bytecount(cdstsize - csrcsize))
367 367 )
368 368
369 369 dstsize = fdstsize + mdstsize + cdstsize
370 370 ui.status(
371 371 _(
372 372 b'finished migrating %d total revisions; total change in store '
373 373 b'size: %s\n'
374 374 )
375 375 % (revcount, util.bytecount(dstsize - srcsize))
376 376 )
377 377
378 378
379 379 def _filterstorefile(srcrepo, dstrepo, requirements, path, mode, st):
380 380 """Determine whether to copy a store file during upgrade.
381 381
382 382 This function is called when migrating store files from ``srcrepo`` to
383 383 ``dstrepo`` as part of upgrading a repository.
384 384
385 385 Args:
386 386 srcrepo: repo we are copying from
387 387 dstrepo: repo we are copying to
388 388 requirements: set of requirements for ``dstrepo``
389 389 path: store file being examined
390 390 mode: the ``ST_MODE`` file type of ``path``
391 391 st: ``stat`` data structure for ``path``
392 392
393 393 Function should return ``True`` if the file is to be copied.
394 394 """
395 395 # Skip revlogs.
396 396 if path.endswith((b'.i', b'.d', b'.n', b'.nd')):
397 397 return False
398 398 # Skip transaction related files.
399 399 if path.startswith(b'undo'):
400 400 return False
401 401 # Only copy regular files.
402 402 if mode != stat.S_IFREG:
403 403 return False
404 404 # Skip other skipped files.
405 405 if path in (b'lock', b'fncache'):
406 406 return False
407 407
408 408 return True
409 409
410 410
411 def _replacestores(currentrepo, upgradedrepo, backupvfs, upgrade_op):
412 """Replace the stores after current repository is upgraded
413
414 Creates a backup of current repository store at backup path
415 Replaces upgraded store files in current repo from upgraded one
416
417 Arguments:
418 currentrepo: repo object of current repository
419 upgradedrepo: repo object of the upgraded data
420 backupvfs: vfs object for the backup path
421 upgrade_op: upgrade operation object
422 to be used to decide what all is upgraded
423 """
424 # TODO: don't blindly rename everything in store
425 # There can be upgrades where store is not touched at all
426 util.rename(currentrepo.spath, backupvfs.join(b'store'))
427 util.rename(upgradedrepo.spath, currentrepo.spath)
428
429
411 430 def finishdatamigration(ui, srcrepo, dstrepo, requirements):
412 431 """Hook point for extensions to perform additional actions during upgrade.
413 432
414 433 This function is called after revlogs and store files have been copied but
415 434 before the new store is swapped into the original location.
416 435 """
417 436
418 437
419 438 def upgrade(ui, srcrepo, dstrepo, upgrade_op):
420 439 """Do the low-level work of upgrading a repository.
421 440
422 441 The upgrade is effectively performed as a copy between a source
423 442 repository and a temporary destination repository.
424 443
425 444 The source repository is unmodified for as long as possible so the
426 445 upgrade can abort at any time without causing loss of service for
427 446 readers and without corrupting the source repository.
428 447 """
429 448 assert srcrepo.currentwlock()
430 449 assert dstrepo.currentwlock()
431 450
432 451 ui.status(
433 452 _(
434 453 b'(it is safe to interrupt this process any time before '
435 454 b'data migration completes)\n'
436 455 )
437 456 )
438 457
439 458 with dstrepo.transaction(b'upgrade') as tr:
440 459 _clonerevlogs(
441 460 ui,
442 461 srcrepo,
443 462 dstrepo,
444 463 tr,
445 464 upgrade_op,
446 465 )
447 466
448 467 # Now copy other files in the store directory.
449 468 # The sorted() makes execution deterministic.
450 469 for p, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)):
451 470 if not _filterstorefile(
452 471 srcrepo, dstrepo, upgrade_op.new_requirements, p, kind, st
453 472 ):
454 473 continue
455 474
456 475 srcrepo.ui.status(_(b'copying %s\n') % p)
457 476 src = srcrepo.store.rawvfs.join(p)
458 477 dst = dstrepo.store.rawvfs.join(p)
459 478 util.copyfile(src, dst, copystat=True)
460 479
461 480 finishdatamigration(ui, srcrepo, dstrepo, requirements)
462 481
463 482 ui.status(_(b'data fully migrated to temporary repository\n'))
464 483
465 484 backuppath = pycompat.mkdtemp(prefix=b'upgradebackup.', dir=srcrepo.path)
466 485 backupvfs = vfsmod.vfs(backuppath)
467 486
468 487 # Make a backup of requires file first, as it is the first to be modified.
469 488 util.copyfile(srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires'))
470 489
471 490 # We install an arbitrary requirement that clients must not support
472 491 # as a mechanism to lock out new clients during the data swap. This is
473 492 # better than allowing a client to continue while the repository is in
474 493 # an inconsistent state.
475 494 ui.status(
476 495 _(
477 496 b'marking source repository as being upgraded; clients will be '
478 497 b'unable to read from repository\n'
479 498 )
480 499 )
481 500 scmutil.writereporequirements(
482 501 srcrepo, srcrepo.requirements | {b'upgradeinprogress'}
483 502 )
484 503
485 504 ui.status(_(b'starting in-place swap of repository data\n'))
486 505 ui.status(_(b'replaced files will be backed up at %s\n') % backuppath)
487 506
488 507 # Now swap in the new store directory. Doing it as a rename should make
489 508 # the operation nearly instantaneous and atomic (at least in well-behaved
490 509 # environments).
491 510 ui.status(_(b'replacing store...\n'))
492 511 tstart = util.timer()
493 util.rename(srcrepo.spath, backupvfs.join(b'store'))
494 util.rename(dstrepo.spath, srcrepo.spath)
512 _replacestores(srcrepo, dstrepo, backupvfs, upgrade_op)
495 513 elapsed = util.timer() - tstart
496 514 ui.status(
497 515 _(
498 516 b'store replacement complete; repository was inconsistent for '
499 517 b'%0.1fs\n'
500 518 )
501 519 % elapsed
502 520 )
503 521
504 522 # We first write the requirements file. Any new requirements will lock
505 523 # out legacy clients.
506 524 ui.status(
507 525 _(
508 526 b'finalizing requirements file and making repository readable '
509 527 b'again\n'
510 528 )
511 529 )
512 530 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
513 531
514 532 # The lock file from the old store won't be removed because nothing has a
515 533 # reference to its new location. So clean it up manually. Alternatively, we
516 534 # could update srcrepo.svfs and other variables to point to the new
517 535 # location. This is simpler.
518 536 backupvfs.unlink(b'store/lock')
519 537
520 538 return backuppath
General Comments 0
You need to be logged in to leave comments. Login now