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