##// END OF EJS Templates
upgrade: rename `_copyrevlogs` to `_clonerevlogs`...
marmoute -
r42917:095dcdd0 default
parent child Browse files
Show More
@@ -1,982 +1,982
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 . import (
14 14 changelog,
15 15 error,
16 16 filelog,
17 17 hg,
18 18 localrepo,
19 19 manifest,
20 20 pycompat,
21 21 revlog,
22 22 scmutil,
23 23 util,
24 24 vfs as vfsmod,
25 25 )
26 26
27 27 from .utils import (
28 28 compression,
29 29 )
30 30
31 31 def requiredsourcerequirements(repo):
32 32 """Obtain requirements required to be present to upgrade a repo.
33 33
34 34 An upgrade will not be allowed if the repository doesn't have the
35 35 requirements returned by this function.
36 36 """
37 37 return {
38 38 # Introduced in Mercurial 0.9.2.
39 39 'revlogv1',
40 40 # Introduced in Mercurial 0.9.2.
41 41 'store',
42 42 }
43 43
44 44 def blocksourcerequirements(repo):
45 45 """Obtain requirements that will prevent an upgrade from occurring.
46 46
47 47 An upgrade cannot be performed if the source repository contains a
48 48 requirements in the returned set.
49 49 """
50 50 return {
51 51 # The upgrade code does not yet support these experimental features.
52 52 # This is an artificial limitation.
53 53 'treemanifest',
54 54 # This was a precursor to generaldelta and was never enabled by default.
55 55 # It should (hopefully) not exist in the wild.
56 56 'parentdelta',
57 57 # Upgrade should operate on the actual store, not the shared link.
58 58 'shared',
59 59 }
60 60
61 61 def supportremovedrequirements(repo):
62 62 """Obtain requirements that can be removed during an upgrade.
63 63
64 64 If an upgrade were to create a repository that dropped a requirement,
65 65 the dropped requirement must appear in the returned set for the upgrade
66 66 to be allowed.
67 67 """
68 68 supported = {
69 69 localrepo.SPARSEREVLOG_REQUIREMENT,
70 70 }
71 71 for name in compression.compengines:
72 72 engine = compression.compengines[name]
73 73 if engine.available() and engine.revlogheader():
74 74 supported.add(b'exp-compression-%s' % name)
75 75 if engine.name() == 'zstd':
76 76 supported.add(b'revlog-compression-zstd')
77 77 return supported
78 78
79 79 def supporteddestrequirements(repo):
80 80 """Obtain requirements that upgrade supports in the destination.
81 81
82 82 If the result of the upgrade would create requirements not in this set,
83 83 the upgrade is disallowed.
84 84
85 85 Extensions should monkeypatch this to add their custom requirements.
86 86 """
87 87 supported = {
88 88 'dotencode',
89 89 'fncache',
90 90 'generaldelta',
91 91 'revlogv1',
92 92 'store',
93 93 localrepo.SPARSEREVLOG_REQUIREMENT,
94 94 }
95 95 for name in compression.compengines:
96 96 engine = compression.compengines[name]
97 97 if engine.available() and engine.revlogheader():
98 98 supported.add(b'exp-compression-%s' % name)
99 99 if engine.name() == 'zstd':
100 100 supported.add(b'revlog-compression-zstd')
101 101 return supported
102 102
103 103 def allowednewrequirements(repo):
104 104 """Obtain requirements that can be added to a repository during upgrade.
105 105
106 106 This is used to disallow proposed requirements from being added when
107 107 they weren't present before.
108 108
109 109 We use a list of allowed requirement additions instead of a list of known
110 110 bad additions because the whitelist approach is safer and will prevent
111 111 future, unknown requirements from accidentally being added.
112 112 """
113 113 supported = {
114 114 'dotencode',
115 115 'fncache',
116 116 'generaldelta',
117 117 localrepo.SPARSEREVLOG_REQUIREMENT,
118 118 }
119 119 for name in compression.compengines:
120 120 engine = compression.compengines[name]
121 121 if engine.available() and engine.revlogheader():
122 122 supported.add(b'exp-compression-%s' % name)
123 123 if engine.name() == 'zstd':
124 124 supported.add(b'revlog-compression-zstd')
125 125 return supported
126 126
127 127 def preservedrequirements(repo):
128 128 return set()
129 129
130 130 deficiency = 'deficiency'
131 131 optimisation = 'optimization'
132 132
133 133 class improvement(object):
134 134 """Represents an improvement that can be made as part of an upgrade.
135 135
136 136 The following attributes are defined on each instance:
137 137
138 138 name
139 139 Machine-readable string uniquely identifying this improvement. It
140 140 will be mapped to an action later in the upgrade process.
141 141
142 142 type
143 143 Either ``deficiency`` or ``optimisation``. A deficiency is an obvious
144 144 problem. An optimization is an action (sometimes optional) that
145 145 can be taken to further improve the state of the repository.
146 146
147 147 description
148 148 Message intended for humans explaining the improvement in more detail,
149 149 including the implications of it. For ``deficiency`` types, should be
150 150 worded in the present tense. For ``optimisation`` types, should be
151 151 worded in the future tense.
152 152
153 153 upgrademessage
154 154 Message intended for humans explaining what an upgrade addressing this
155 155 issue will do. Should be worded in the future tense.
156 156 """
157 157 def __init__(self, name, type, description, upgrademessage):
158 158 self.name = name
159 159 self.type = type
160 160 self.description = description
161 161 self.upgrademessage = upgrademessage
162 162
163 163 def __eq__(self, other):
164 164 if not isinstance(other, improvement):
165 165 # This is what python tell use to do
166 166 return NotImplemented
167 167 return self.name == other.name
168 168
169 169 def __ne__(self, other):
170 170 return not (self == other)
171 171
172 172 def __hash__(self):
173 173 return hash(self.name)
174 174
175 175 allformatvariant = []
176 176
177 177 def registerformatvariant(cls):
178 178 allformatvariant.append(cls)
179 179 return cls
180 180
181 181 class formatvariant(improvement):
182 182 """an improvement subclass dedicated to repository format"""
183 183 type = deficiency
184 184 ### The following attributes should be defined for each class:
185 185
186 186 # machine-readable string uniquely identifying this improvement. it will be
187 187 # mapped to an action later in the upgrade process.
188 188 name = None
189 189
190 190 # message intended for humans explaining the improvement in more detail,
191 191 # including the implications of it ``deficiency`` types, should be worded
192 192 # in the present tense.
193 193 description = None
194 194
195 195 # message intended for humans explaining what an upgrade addressing this
196 196 # issue will do. should be worded in the future tense.
197 197 upgrademessage = None
198 198
199 199 # value of current Mercurial default for new repository
200 200 default = None
201 201
202 202 def __init__(self):
203 203 raise NotImplementedError()
204 204
205 205 @staticmethod
206 206 def fromrepo(repo):
207 207 """current value of the variant in the repository"""
208 208 raise NotImplementedError()
209 209
210 210 @staticmethod
211 211 def fromconfig(repo):
212 212 """current value of the variant in the configuration"""
213 213 raise NotImplementedError()
214 214
215 215 class requirementformatvariant(formatvariant):
216 216 """formatvariant based on a 'requirement' name.
217 217
218 218 Many format variant are controlled by a 'requirement'. We define a small
219 219 subclass to factor the code.
220 220 """
221 221
222 222 # the requirement that control this format variant
223 223 _requirement = None
224 224
225 225 @staticmethod
226 226 def _newreporequirements(ui):
227 227 return localrepo.newreporequirements(
228 228 ui, localrepo.defaultcreateopts(ui))
229 229
230 230 @classmethod
231 231 def fromrepo(cls, repo):
232 232 assert cls._requirement is not None
233 233 return cls._requirement in repo.requirements
234 234
235 235 @classmethod
236 236 def fromconfig(cls, repo):
237 237 assert cls._requirement is not None
238 238 return cls._requirement in cls._newreporequirements(repo.ui)
239 239
240 240 @registerformatvariant
241 241 class fncache(requirementformatvariant):
242 242 name = 'fncache'
243 243
244 244 _requirement = 'fncache'
245 245
246 246 default = True
247 247
248 248 description = _('long and reserved filenames may not work correctly; '
249 249 'repository performance is sub-optimal')
250 250
251 251 upgrademessage = _('repository will be more resilient to storing '
252 252 'certain paths and performance of certain '
253 253 'operations should be improved')
254 254
255 255 @registerformatvariant
256 256 class dotencode(requirementformatvariant):
257 257 name = 'dotencode'
258 258
259 259 _requirement = 'dotencode'
260 260
261 261 default = True
262 262
263 263 description = _('storage of filenames beginning with a period or '
264 264 'space may not work correctly')
265 265
266 266 upgrademessage = _('repository will be better able to store files '
267 267 'beginning with a space or period')
268 268
269 269 @registerformatvariant
270 270 class generaldelta(requirementformatvariant):
271 271 name = 'generaldelta'
272 272
273 273 _requirement = 'generaldelta'
274 274
275 275 default = True
276 276
277 277 description = _('deltas within internal storage are unable to '
278 278 'choose optimal revisions; repository is larger and '
279 279 'slower than it could be; interaction with other '
280 280 'repositories may require extra network and CPU '
281 281 'resources, making "hg push" and "hg pull" slower')
282 282
283 283 upgrademessage = _('repository storage will be able to create '
284 284 'optimal deltas; new repository data will be '
285 285 'smaller and read times should decrease; '
286 286 'interacting with other repositories using this '
287 287 'storage model should require less network and '
288 288 'CPU resources, making "hg push" and "hg pull" '
289 289 'faster')
290 290
291 291 @registerformatvariant
292 292 class sparserevlog(requirementformatvariant):
293 293 name = 'sparserevlog'
294 294
295 295 _requirement = localrepo.SPARSEREVLOG_REQUIREMENT
296 296
297 297 default = True
298 298
299 299 description = _('in order to limit disk reading and memory usage on older '
300 300 'version, the span of a delta chain from its root to its '
301 301 'end is limited, whatever the relevant data in this span. '
302 302 'This can severly limit Mercurial ability to build good '
303 303 'chain of delta resulting is much more storage space being '
304 304 'taken and limit reusability of on disk delta during '
305 305 'exchange.'
306 306 )
307 307
308 308 upgrademessage = _('Revlog supports delta chain with more unused data '
309 309 'between payload. These gaps will be skipped at read '
310 310 'time. This allows for better delta chains, making a '
311 311 'better compression and faster exchange with server.')
312 312
313 313 @registerformatvariant
314 314 class removecldeltachain(formatvariant):
315 315 name = 'plain-cl-delta'
316 316
317 317 default = True
318 318
319 319 description = _('changelog storage is using deltas instead of '
320 320 'raw entries; changelog reading and any '
321 321 'operation relying on changelog data are slower '
322 322 'than they could be')
323 323
324 324 upgrademessage = _('changelog storage will be reformated to '
325 325 'store raw entries; changelog reading will be '
326 326 'faster; changelog size may be reduced')
327 327
328 328 @staticmethod
329 329 def fromrepo(repo):
330 330 # Mercurial 4.0 changed changelogs to not use delta chains. Search for
331 331 # changelogs with deltas.
332 332 cl = repo.changelog
333 333 chainbase = cl.chainbase
334 334 return all(rev == chainbase(rev) for rev in cl)
335 335
336 336 @staticmethod
337 337 def fromconfig(repo):
338 338 return True
339 339
340 340 @registerformatvariant
341 341 class compressionengine(formatvariant):
342 342 name = 'compression'
343 343 default = 'zlib'
344 344
345 345 description = _('Compresion algorithm used to compress data. '
346 346 'Some engine are faster than other')
347 347
348 348 upgrademessage = _('revlog content will be recompressed with the new '
349 349 'algorithm.')
350 350
351 351 @classmethod
352 352 def fromrepo(cls, repo):
353 353 # we allow multiple compression engine requirement to co-exist because
354 354 # strickly speaking, revlog seems to support mixed compression style.
355 355 #
356 356 # The compression used for new entries will be "the last one"
357 357 compression = 'zlib'
358 358 for req in repo.requirements:
359 359 prefix = req.startswith
360 360 if prefix('revlog-compression-') or prefix('exp-compression-'):
361 361 compression = req.split('-', 2)[2]
362 362 return compression
363 363
364 364 @classmethod
365 365 def fromconfig(cls, repo):
366 366 return repo.ui.config('format', 'revlog-compression')
367 367
368 368 @registerformatvariant
369 369 class compressionlevel(formatvariant):
370 370 name = 'compression-level'
371 371 default = 'default'
372 372
373 373 description = _('compression level')
374 374
375 375 upgrademessage = _('revlog content will be recompressed')
376 376
377 377 @classmethod
378 378 def fromrepo(cls, repo):
379 379 comp = compressionengine.fromrepo(repo)
380 380 level = None
381 381 if comp == 'zlib':
382 382 level = repo.ui.configint('storage', 'revlog.zlib.level')
383 383 elif comp == 'zstd':
384 384 level = repo.ui.configint('storage', 'revlog.zstd.level')
385 385 if level is None:
386 386 return 'default'
387 387 return bytes(level)
388 388
389 389 @classmethod
390 390 def fromconfig(cls, repo):
391 391 comp = compressionengine.fromconfig(repo)
392 392 level = None
393 393 if comp == 'zlib':
394 394 level = repo.ui.configint('storage', 'revlog.zlib.level')
395 395 elif comp == 'zstd':
396 396 level = repo.ui.configint('storage', 'revlog.zstd.level')
397 397 if level is None:
398 398 return 'default'
399 399 return bytes(level)
400 400
401 401 def finddeficiencies(repo):
402 402 """returns a list of deficiencies that the repo suffer from"""
403 403 deficiencies = []
404 404
405 405 # We could detect lack of revlogv1 and store here, but they were added
406 406 # in 0.9.2 and we don't support upgrading repos without these
407 407 # requirements, so let's not bother.
408 408
409 409 for fv in allformatvariant:
410 410 if not fv.fromrepo(repo):
411 411 deficiencies.append(fv)
412 412
413 413 return deficiencies
414 414
415 415 # search without '-' to support older form on newer client.
416 416 #
417 417 # We don't enforce backward compatibility for debug command so this
418 418 # might eventually be dropped. However, having to use two different
419 419 # forms in script when comparing result is anoying enough to add
420 420 # backward compatibility for a while.
421 421 legacy_opts_map = {
422 422 'redeltaparent': 're-delta-parent',
423 423 'redeltamultibase': 're-delta-multibase',
424 424 'redeltaall': 're-delta-all',
425 425 'redeltafulladd': 're-delta-fulladd',
426 426 }
427 427
428 428 def findoptimizations(repo):
429 429 """Determine optimisation that could be used during upgrade"""
430 430 # These are unconditionally added. There is logic later that figures out
431 431 # which ones to apply.
432 432 optimizations = []
433 433
434 434 optimizations.append(improvement(
435 435 name='re-delta-parent',
436 436 type=optimisation,
437 437 description=_('deltas within internal storage will be recalculated to '
438 438 'choose an optimal base revision where this was not '
439 439 'already done; the size of the repository may shrink and '
440 440 'various operations may become faster; the first time '
441 441 'this optimization is performed could slow down upgrade '
442 442 'execution considerably; subsequent invocations should '
443 443 'not run noticeably slower'),
444 444 upgrademessage=_('deltas within internal storage will choose a new '
445 445 'base revision if needed')))
446 446
447 447 optimizations.append(improvement(
448 448 name='re-delta-multibase',
449 449 type=optimisation,
450 450 description=_('deltas within internal storage will be recalculated '
451 451 'against multiple base revision and the smallest '
452 452 'difference will be used; the size of the repository may '
453 453 'shrink significantly when there are many merges; this '
454 454 'optimization will slow down execution in proportion to '
455 455 'the number of merges in the repository and the amount '
456 456 'of files in the repository; this slow down should not '
457 457 'be significant unless there are tens of thousands of '
458 458 'files and thousands of merges'),
459 459 upgrademessage=_('deltas within internal storage will choose an '
460 460 'optimal delta by computing deltas against multiple '
461 461 'parents; may slow down execution time '
462 462 'significantly')))
463 463
464 464 optimizations.append(improvement(
465 465 name='re-delta-all',
466 466 type=optimisation,
467 467 description=_('deltas within internal storage will always be '
468 468 'recalculated without reusing prior deltas; this will '
469 469 'likely make execution run several times slower; this '
470 470 'optimization is typically not needed'),
471 471 upgrademessage=_('deltas within internal storage will be fully '
472 472 'recomputed; this will likely drastically slow down '
473 473 'execution time')))
474 474
475 475 optimizations.append(improvement(
476 476 name='re-delta-fulladd',
477 477 type=optimisation,
478 478 description=_('every revision will be re-added as if it was new '
479 479 'content. It will go through the full storage '
480 480 'mechanism giving extensions a chance to process it '
481 481 '(eg. lfs). This is similar to "re-delta-all" but even '
482 482 'slower since more logic is involved.'),
483 483 upgrademessage=_('each revision will be added as new content to the '
484 484 'internal storage; this will likely drastically slow '
485 485 'down execution time, but some extensions might need '
486 486 'it')))
487 487
488 488 return optimizations
489 489
490 490 def determineactions(repo, deficiencies, sourcereqs, destreqs):
491 491 """Determine upgrade actions that will be performed.
492 492
493 493 Given a list of improvements as returned by ``finddeficiencies`` and
494 494 ``findoptimizations``, determine the list of upgrade actions that
495 495 will be performed.
496 496
497 497 The role of this function is to filter improvements if needed, apply
498 498 recommended optimizations from the improvements list that make sense,
499 499 etc.
500 500
501 501 Returns a list of action names.
502 502 """
503 503 newactions = []
504 504
505 505 knownreqs = supporteddestrequirements(repo)
506 506
507 507 for d in deficiencies:
508 508 name = d.name
509 509
510 510 # If the action is a requirement that doesn't show up in the
511 511 # destination requirements, prune the action.
512 512 if name in knownreqs and name not in destreqs:
513 513 continue
514 514
515 515 newactions.append(d)
516 516
517 517 # FUTURE consider adding some optimizations here for certain transitions.
518 518 # e.g. adding generaldelta could schedule parent redeltas.
519 519
520 520 return newactions
521 521
522 522 def _revlogfrompath(repo, path):
523 523 """Obtain a revlog from a repo path.
524 524
525 525 An instance of the appropriate class is returned.
526 526 """
527 527 if path == '00changelog.i':
528 528 return changelog.changelog(repo.svfs)
529 529 elif path.endswith('00manifest.i'):
530 530 mandir = path[:-len('00manifest.i')]
531 531 return manifest.manifestrevlog(repo.svfs, tree=mandir)
532 532 else:
533 533 #reverse of "/".join(("data", path + ".i"))
534 534 return filelog.filelog(repo.svfs, path[5:-2])
535 535
536 def _copyrevlogs(ui, srcrepo, dstrepo, tr, deltareuse, forcedeltabothparents):
536 def _clonerevlogs(ui, srcrepo, dstrepo, tr, deltareuse, forcedeltabothparents):
537 537 """Copy revlogs between 2 repos."""
538 538 revcount = 0
539 539 srcsize = 0
540 540 srcrawsize = 0
541 541 dstsize = 0
542 542 fcount = 0
543 543 frevcount = 0
544 544 fsrcsize = 0
545 545 frawsize = 0
546 546 fdstsize = 0
547 547 mcount = 0
548 548 mrevcount = 0
549 549 msrcsize = 0
550 550 mrawsize = 0
551 551 mdstsize = 0
552 552 crevcount = 0
553 553 csrcsize = 0
554 554 crawsize = 0
555 555 cdstsize = 0
556 556
557 557 alldatafiles = list(srcrepo.store.walk())
558 558
559 559 # Perform a pass to collect metadata. This validates we can open all
560 560 # source files and allows a unified progress bar to be displayed.
561 561 for unencoded, encoded, size in alldatafiles:
562 562 if unencoded.endswith('.d'):
563 563 continue
564 564
565 565 rl = _revlogfrompath(srcrepo, unencoded)
566 566
567 567 info = rl.storageinfo(exclusivefiles=True, revisionscount=True,
568 568 trackedsize=True, storedsize=True)
569 569
570 570 revcount += info['revisionscount'] or 0
571 571 datasize = info['storedsize'] or 0
572 572 rawsize = info['trackedsize'] or 0
573 573
574 574 srcsize += datasize
575 575 srcrawsize += rawsize
576 576
577 577 # This is for the separate progress bars.
578 578 if isinstance(rl, changelog.changelog):
579 579 crevcount += len(rl)
580 580 csrcsize += datasize
581 581 crawsize += rawsize
582 582 elif isinstance(rl, manifest.manifestrevlog):
583 583 mcount += 1
584 584 mrevcount += len(rl)
585 585 msrcsize += datasize
586 586 mrawsize += rawsize
587 587 elif isinstance(rl, filelog.filelog):
588 588 fcount += 1
589 589 frevcount += len(rl)
590 590 fsrcsize += datasize
591 591 frawsize += rawsize
592 592 else:
593 593 error.ProgrammingError('unknown revlog type')
594 594
595 595 if not revcount:
596 596 return
597 597
598 598 ui.write(_('migrating %d total revisions (%d in filelogs, %d in manifests, '
599 599 '%d in changelog)\n') %
600 600 (revcount, frevcount, mrevcount, crevcount))
601 601 ui.write(_('migrating %s in store; %s tracked data\n') % (
602 602 (util.bytecount(srcsize), util.bytecount(srcrawsize))))
603 603
604 604 # Used to keep track of progress.
605 605 progress = None
606 606 def oncopiedrevision(rl, rev, node):
607 607 progress.increment()
608 608
609 609 # Do the actual copying.
610 610 # FUTURE this operation can be farmed off to worker processes.
611 611 seen = set()
612 612 for unencoded, encoded, size in alldatafiles:
613 613 if unencoded.endswith('.d'):
614 614 continue
615 615
616 616 oldrl = _revlogfrompath(srcrepo, unencoded)
617 617 newrl = _revlogfrompath(dstrepo, unencoded)
618 618
619 619 if isinstance(oldrl, changelog.changelog) and 'c' not in seen:
620 620 ui.write(_('finished migrating %d manifest revisions across %d '
621 621 'manifests; change in size: %s\n') %
622 622 (mrevcount, mcount, util.bytecount(mdstsize - msrcsize)))
623 623
624 624 ui.write(_('migrating changelog containing %d revisions '
625 625 '(%s in store; %s tracked data)\n') %
626 626 (crevcount, util.bytecount(csrcsize),
627 627 util.bytecount(crawsize)))
628 628 seen.add('c')
629 629 progress = srcrepo.ui.makeprogress(_('changelog revisions'),
630 630 total=crevcount)
631 631 elif isinstance(oldrl, manifest.manifestrevlog) and 'm' not in seen:
632 632 ui.write(_('finished migrating %d filelog revisions across %d '
633 633 'filelogs; change in size: %s\n') %
634 634 (frevcount, fcount, util.bytecount(fdstsize - fsrcsize)))
635 635
636 636 ui.write(_('migrating %d manifests containing %d revisions '
637 637 '(%s in store; %s tracked data)\n') %
638 638 (mcount, mrevcount, util.bytecount(msrcsize),
639 639 util.bytecount(mrawsize)))
640 640 seen.add('m')
641 641 if progress:
642 642 progress.complete()
643 643 progress = srcrepo.ui.makeprogress(_('manifest revisions'),
644 644 total=mrevcount)
645 645 elif 'f' not in seen:
646 646 ui.write(_('migrating %d filelogs containing %d revisions '
647 647 '(%s in store; %s tracked data)\n') %
648 648 (fcount, frevcount, util.bytecount(fsrcsize),
649 649 util.bytecount(frawsize)))
650 650 seen.add('f')
651 651 if progress:
652 652 progress.complete()
653 653 progress = srcrepo.ui.makeprogress(_('file revisions'),
654 654 total=frevcount)
655 655
656 656
657 657 ui.note(_('cloning %d revisions from %s\n') % (len(oldrl), unencoded))
658 658 oldrl.clone(tr, newrl, addrevisioncb=oncopiedrevision,
659 659 deltareuse=deltareuse,
660 660 forcedeltabothparents=forcedeltabothparents)
661 661
662 662 info = newrl.storageinfo(storedsize=True)
663 663 datasize = info['storedsize'] or 0
664 664
665 665 dstsize += datasize
666 666
667 667 if isinstance(newrl, changelog.changelog):
668 668 cdstsize += datasize
669 669 elif isinstance(newrl, manifest.manifestrevlog):
670 670 mdstsize += datasize
671 671 else:
672 672 fdstsize += datasize
673 673
674 674 progress.complete()
675 675
676 676 ui.write(_('finished migrating %d changelog revisions; change in size: '
677 677 '%s\n') % (crevcount, util.bytecount(cdstsize - csrcsize)))
678 678
679 679 ui.write(_('finished migrating %d total revisions; total change in store '
680 680 'size: %s\n') % (revcount, util.bytecount(dstsize - srcsize)))
681 681
682 682 def _filterstorefile(srcrepo, dstrepo, requirements, path, mode, st):
683 683 """Determine whether to copy a store file during upgrade.
684 684
685 685 This function is called when migrating store files from ``srcrepo`` to
686 686 ``dstrepo`` as part of upgrading a repository.
687 687
688 688 Args:
689 689 srcrepo: repo we are copying from
690 690 dstrepo: repo we are copying to
691 691 requirements: set of requirements for ``dstrepo``
692 692 path: store file being examined
693 693 mode: the ``ST_MODE`` file type of ``path``
694 694 st: ``stat`` data structure for ``path``
695 695
696 696 Function should return ``True`` if the file is to be copied.
697 697 """
698 698 # Skip revlogs.
699 699 if path.endswith(('.i', '.d')):
700 700 return False
701 701 # Skip transaction related files.
702 702 if path.startswith('undo'):
703 703 return False
704 704 # Only copy regular files.
705 705 if mode != stat.S_IFREG:
706 706 return False
707 707 # Skip other skipped files.
708 708 if path in ('lock', 'fncache'):
709 709 return False
710 710
711 711 return True
712 712
713 713 def _finishdatamigration(ui, srcrepo, dstrepo, requirements):
714 714 """Hook point for extensions to perform additional actions during upgrade.
715 715
716 716 This function is called after revlogs and store files have been copied but
717 717 before the new store is swapped into the original location.
718 718 """
719 719
720 720 def _upgraderepo(ui, srcrepo, dstrepo, requirements, actions):
721 721 """Do the low-level work of upgrading a repository.
722 722
723 723 The upgrade is effectively performed as a copy between a source
724 724 repository and a temporary destination repository.
725 725
726 726 The source repository is unmodified for as long as possible so the
727 727 upgrade can abort at any time without causing loss of service for
728 728 readers and without corrupting the source repository.
729 729 """
730 730 assert srcrepo.currentwlock()
731 731 assert dstrepo.currentwlock()
732 732
733 733 ui.write(_('(it is safe to interrupt this process any time before '
734 734 'data migration completes)\n'))
735 735
736 736 if 're-delta-all' in actions:
737 737 deltareuse = revlog.revlog.DELTAREUSENEVER
738 738 elif 're-delta-parent' in actions:
739 739 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
740 740 elif 're-delta-multibase' in actions:
741 741 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
742 742 elif 're-delta-fulladd' in actions:
743 743 deltareuse = revlog.revlog.DELTAREUSEFULLADD
744 744 else:
745 745 deltareuse = revlog.revlog.DELTAREUSEALWAYS
746 746
747 747 with dstrepo.transaction('upgrade') as tr:
748 _copyrevlogs(ui, srcrepo, dstrepo, tr, deltareuse,
748 _clonerevlogs(ui, srcrepo, dstrepo, tr, deltareuse,
749 749 're-delta-multibase' in actions)
750 750
751 751 # Now copy other files in the store directory.
752 752 # The sorted() makes execution deterministic.
753 753 for p, kind, st in sorted(srcrepo.store.vfs.readdir('', stat=True)):
754 754 if not _filterstorefile(srcrepo, dstrepo, requirements,
755 755 p, kind, st):
756 756 continue
757 757
758 758 srcrepo.ui.write(_('copying %s\n') % p)
759 759 src = srcrepo.store.rawvfs.join(p)
760 760 dst = dstrepo.store.rawvfs.join(p)
761 761 util.copyfile(src, dst, copystat=True)
762 762
763 763 _finishdatamigration(ui, srcrepo, dstrepo, requirements)
764 764
765 765 ui.write(_('data fully migrated to temporary repository\n'))
766 766
767 767 backuppath = pycompat.mkdtemp(prefix='upgradebackup.', dir=srcrepo.path)
768 768 backupvfs = vfsmod.vfs(backuppath)
769 769
770 770 # Make a backup of requires file first, as it is the first to be modified.
771 771 util.copyfile(srcrepo.vfs.join('requires'), backupvfs.join('requires'))
772 772
773 773 # We install an arbitrary requirement that clients must not support
774 774 # as a mechanism to lock out new clients during the data swap. This is
775 775 # better than allowing a client to continue while the repository is in
776 776 # an inconsistent state.
777 777 ui.write(_('marking source repository as being upgraded; clients will be '
778 778 'unable to read from repository\n'))
779 779 scmutil.writerequires(srcrepo.vfs,
780 780 srcrepo.requirements | {'upgradeinprogress'})
781 781
782 782 ui.write(_('starting in-place swap of repository data\n'))
783 783 ui.write(_('replaced files will be backed up at %s\n') %
784 784 backuppath)
785 785
786 786 # Now swap in the new store directory. Doing it as a rename should make
787 787 # the operation nearly instantaneous and atomic (at least in well-behaved
788 788 # environments).
789 789 ui.write(_('replacing store...\n'))
790 790 tstart = util.timer()
791 791 util.rename(srcrepo.spath, backupvfs.join('store'))
792 792 util.rename(dstrepo.spath, srcrepo.spath)
793 793 elapsed = util.timer() - tstart
794 794 ui.write(_('store replacement complete; repository was inconsistent for '
795 795 '%0.1fs\n') % elapsed)
796 796
797 797 # We first write the requirements file. Any new requirements will lock
798 798 # out legacy clients.
799 799 ui.write(_('finalizing requirements file and making repository readable '
800 800 'again\n'))
801 801 scmutil.writerequires(srcrepo.vfs, requirements)
802 802
803 803 # The lock file from the old store won't be removed because nothing has a
804 804 # reference to its new location. So clean it up manually. Alternatively, we
805 805 # could update srcrepo.svfs and other variables to point to the new
806 806 # location. This is simpler.
807 807 backupvfs.unlink('store/lock')
808 808
809 809 return backuppath
810 810
811 811 def upgraderepo(ui, repo, run=False, optimize=None, backup=True):
812 812 """Upgrade a repository in place."""
813 813 if optimize is None:
814 814 optimize = []
815 815 optimize = set(legacy_opts_map.get(o, o) for o in optimize)
816 816 repo = repo.unfiltered()
817 817
818 818 # Ensure the repository can be upgraded.
819 819 missingreqs = requiredsourcerequirements(repo) - repo.requirements
820 820 if missingreqs:
821 821 raise error.Abort(_('cannot upgrade repository; requirement '
822 822 'missing: %s') % _(', ').join(sorted(missingreqs)))
823 823
824 824 blockedreqs = blocksourcerequirements(repo) & repo.requirements
825 825 if blockedreqs:
826 826 raise error.Abort(_('cannot upgrade repository; unsupported source '
827 827 'requirement: %s') %
828 828 _(', ').join(sorted(blockedreqs)))
829 829
830 830 # FUTURE there is potentially a need to control the wanted requirements via
831 831 # command arguments or via an extension hook point.
832 832 newreqs = localrepo.newreporequirements(
833 833 repo.ui, localrepo.defaultcreateopts(repo.ui))
834 834 newreqs.update(preservedrequirements(repo))
835 835
836 836 noremovereqs = (repo.requirements - newreqs -
837 837 supportremovedrequirements(repo))
838 838 if noremovereqs:
839 839 raise error.Abort(_('cannot upgrade repository; requirement would be '
840 840 'removed: %s') % _(', ').join(sorted(noremovereqs)))
841 841
842 842 noaddreqs = (newreqs - repo.requirements -
843 843 allowednewrequirements(repo))
844 844 if noaddreqs:
845 845 raise error.Abort(_('cannot upgrade repository; do not support adding '
846 846 'requirement: %s') %
847 847 _(', ').join(sorted(noaddreqs)))
848 848
849 849 unsupportedreqs = newreqs - supporteddestrequirements(repo)
850 850 if unsupportedreqs:
851 851 raise error.Abort(_('cannot upgrade repository; do not support '
852 852 'destination requirement: %s') %
853 853 _(', ').join(sorted(unsupportedreqs)))
854 854
855 855 # Find and validate all improvements that can be made.
856 856 alloptimizations = findoptimizations(repo)
857 857
858 858 # Apply and Validate arguments.
859 859 optimizations = []
860 860 for o in alloptimizations:
861 861 if o.name in optimize:
862 862 optimizations.append(o)
863 863 optimize.discard(o.name)
864 864
865 865 if optimize: # anything left is unknown
866 866 raise error.Abort(_('unknown optimization action requested: %s') %
867 867 ', '.join(sorted(optimize)),
868 868 hint=_('run without arguments to see valid '
869 869 'optimizations'))
870 870
871 871 deficiencies = finddeficiencies(repo)
872 872 actions = determineactions(repo, deficiencies, repo.requirements, newreqs)
873 873 actions.extend(o for o in sorted(optimizations)
874 874 # determineactions could have added optimisation
875 875 if o not in actions)
876 876
877 877 def printrequirements():
878 878 ui.write(_('requirements\n'))
879 879 ui.write(_(' preserved: %s\n') %
880 880 _(', ').join(sorted(newreqs & repo.requirements)))
881 881
882 882 if repo.requirements - newreqs:
883 883 ui.write(_(' removed: %s\n') %
884 884 _(', ').join(sorted(repo.requirements - newreqs)))
885 885
886 886 if newreqs - repo.requirements:
887 887 ui.write(_(' added: %s\n') %
888 888 _(', ').join(sorted(newreqs - repo.requirements)))
889 889
890 890 ui.write('\n')
891 891
892 892 def printupgradeactions():
893 893 for a in actions:
894 894 ui.write('%s\n %s\n\n' % (a.name, a.upgrademessage))
895 895
896 896 if not run:
897 897 fromconfig = []
898 898 onlydefault = []
899 899
900 900 for d in deficiencies:
901 901 if d.fromconfig(repo):
902 902 fromconfig.append(d)
903 903 elif d.default:
904 904 onlydefault.append(d)
905 905
906 906 if fromconfig or onlydefault:
907 907
908 908 if fromconfig:
909 909 ui.write(_('repository lacks features recommended by '
910 910 'current config options:\n\n'))
911 911 for i in fromconfig:
912 912 ui.write('%s\n %s\n\n' % (i.name, i.description))
913 913
914 914 if onlydefault:
915 915 ui.write(_('repository lacks features used by the default '
916 916 'config options:\n\n'))
917 917 for i in onlydefault:
918 918 ui.write('%s\n %s\n\n' % (i.name, i.description))
919 919
920 920 ui.write('\n')
921 921 else:
922 922 ui.write(_('(no feature deficiencies found in existing '
923 923 'repository)\n'))
924 924
925 925 ui.write(_('performing an upgrade with "--run" will make the following '
926 926 'changes:\n\n'))
927 927
928 928 printrequirements()
929 929 printupgradeactions()
930 930
931 931 unusedoptimize = [i for i in alloptimizations if i not in actions]
932 932
933 933 if unusedoptimize:
934 934 ui.write(_('additional optimizations are available by specifying '
935 935 '"--optimize <name>":\n\n'))
936 936 for i in unusedoptimize:
937 937 ui.write(_('%s\n %s\n\n') % (i.name, i.description))
938 938 return
939 939
940 940 # Else we're in the run=true case.
941 941 ui.write(_('upgrade will perform the following actions:\n\n'))
942 942 printrequirements()
943 943 printupgradeactions()
944 944
945 945 upgradeactions = [a.name for a in actions]
946 946
947 947 ui.write(_('beginning upgrade...\n'))
948 948 with repo.wlock(), repo.lock():
949 949 ui.write(_('repository locked and read-only\n'))
950 950 # Our strategy for upgrading the repository is to create a new,
951 951 # temporary repository, write data to it, then do a swap of the
952 952 # data. There are less heavyweight ways to do this, but it is easier
953 953 # to create a new repo object than to instantiate all the components
954 954 # (like the store) separately.
955 955 tmppath = pycompat.mkdtemp(prefix='upgrade.', dir=repo.path)
956 956 backuppath = None
957 957 try:
958 958 ui.write(_('creating temporary repository to stage migrated '
959 959 'data: %s\n') % tmppath)
960 960
961 961 # clone ui without using ui.copy because repo.ui is protected
962 962 repoui = repo.ui.__class__(repo.ui)
963 963 dstrepo = hg.repository(repoui, path=tmppath, create=True)
964 964
965 965 with dstrepo.wlock(), dstrepo.lock():
966 966 backuppath = _upgraderepo(ui, repo, dstrepo, newreqs,
967 967 upgradeactions)
968 968 if not (backup or backuppath is None):
969 969 ui.write(_('removing old repository content%s\n') % backuppath)
970 970 repo.vfs.rmtree(backuppath, forcibly=True)
971 971 backuppath = None
972 972
973 973 finally:
974 974 ui.write(_('removing temporary repository %s\n') % tmppath)
975 975 repo.vfs.rmtree(tmppath, forcibly=True)
976 976
977 977 if backuppath:
978 978 ui.warn(_('copy of old repository backed up at %s\n') %
979 979 backuppath)
980 980 ui.warn(_('the old repository will not be deleted; remove '
981 981 'it to free up disk space once the upgraded '
982 982 'repository is verified\n'))
General Comments 0
You need to be logged in to leave comments. Login now