##// END OF EJS Templates
upgrade: extract the checking of target requirements change...
marmoute -
r46666:72b7b4bf default
parent child Browse files
Show More
@@ -1,357 +1,320 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 from .i18n import _
11 11 from . import (
12 12 error,
13 13 hg,
14 14 localrepo,
15 15 pycompat,
16 16 )
17 17
18 18 from .upgrade_utils import (
19 19 actions as upgrade_actions,
20 20 engine as upgrade_engine,
21 21 )
22 22
23 23 allformatvariant = upgrade_actions.allformatvariant
24 24
25 25 # search without '-' to support older form on newer client.
26 26 #
27 27 # We don't enforce backward compatibility for debug command so this
28 28 # might eventually be dropped. However, having to use two different
29 29 # forms in script when comparing result is anoying enough to add
30 30 # backward compatibility for a while.
31 31 legacy_opts_map = {
32 32 b'redeltaparent': b're-delta-parent',
33 33 b'redeltamultibase': b're-delta-multibase',
34 34 b'redeltaall': b're-delta-all',
35 35 b'redeltafulladd': b're-delta-fulladd',
36 36 }
37 37
38 38
39 39 def upgraderepo(
40 40 ui,
41 41 repo,
42 42 run=False,
43 43 optimize=None,
44 44 backup=True,
45 45 manifest=None,
46 46 changelog=None,
47 47 filelogs=None,
48 48 ):
49 49 """Upgrade a repository in place."""
50 50 if optimize is None:
51 51 optimize = []
52 52 optimize = {legacy_opts_map.get(o, o) for o in optimize}
53 53 repo = repo.unfiltered()
54 54
55 55 revlogs = set(upgrade_engine.UPGRADE_ALL_REVLOGS)
56 56 specentries = (
57 57 (upgrade_engine.UPGRADE_CHANGELOG, changelog),
58 58 (upgrade_engine.UPGRADE_MANIFEST, manifest),
59 59 (upgrade_engine.UPGRADE_FILELOGS, filelogs),
60 60 )
61 61 specified = [(y, x) for (y, x) in specentries if x is not None]
62 62 if specified:
63 63 # we have some limitation on revlogs to be recloned
64 64 if any(x for y, x in specified):
65 65 revlogs = set()
66 66 for upgrade, enabled in specified:
67 67 if enabled:
68 68 revlogs.add(upgrade)
69 69 else:
70 70 # none are enabled
71 71 for upgrade, __ in specified:
72 72 revlogs.discard(upgrade)
73 73
74 74 # Ensure the repository can be upgraded.
75 75 upgrade_actions.check_source_requirements(repo)
76 76
77 newreqs = localrepo.newreporequirements(
78 repo.ui, localrepo.defaultcreateopts(repo.ui)
79 )
77 default_options = localrepo.defaultcreateopts(repo.ui)
78 newreqs = localrepo.newreporequirements(repo.ui, default_options)
80 79 newreqs.update(upgrade_actions.preservedrequirements(repo))
81 80
82 noremovereqs = (
83 repo.requirements
84 - newreqs
85 - upgrade_actions.supportremovedrequirements(repo)
86 )
87 if noremovereqs:
88 raise error.Abort(
89 _(
90 b'cannot upgrade repository; requirement would be '
91 b'removed: %s'
92 )
93 % _(b', ').join(sorted(noremovereqs))
94 )
95
96 noaddreqs = (
97 newreqs
98 - repo.requirements
99 - upgrade_actions.allowednewrequirements(repo)
100 )
101 if noaddreqs:
102 raise error.Abort(
103 _(
104 b'cannot upgrade repository; do not support adding '
105 b'requirement: %s'
106 )
107 % _(b', ').join(sorted(noaddreqs))
108 )
109
110 unsupportedreqs = newreqs - upgrade_actions.supporteddestrequirements(repo)
111 if unsupportedreqs:
112 raise error.Abort(
113 _(
114 b'cannot upgrade repository; do not support '
115 b'destination requirement: %s'
116 )
117 % _(b', ').join(sorted(unsupportedreqs))
118 )
81 upgrade_actions.check_requirements_changes(repo, newreqs)
119 82
120 83 # Find and validate all improvements that can be made.
121 84 alloptimizations = upgrade_actions.findoptimizations(repo)
122 85
123 86 # Apply and Validate arguments.
124 87 optimizations = []
125 88 for o in alloptimizations:
126 89 if o.name in optimize:
127 90 optimizations.append(o)
128 91 optimize.discard(o.name)
129 92
130 93 if optimize: # anything left is unknown
131 94 raise error.Abort(
132 95 _(b'unknown optimization action requested: %s')
133 96 % b', '.join(sorted(optimize)),
134 97 hint=_(b'run without arguments to see valid optimizations'),
135 98 )
136 99
137 100 deficiencies = upgrade_actions.finddeficiencies(repo)
138 101 actions = upgrade_actions.determineactions(
139 102 repo, deficiencies, repo.requirements, newreqs
140 103 )
141 104 actions.extend(
142 105 o
143 106 for o in sorted(optimizations)
144 107 # determineactions could have added optimisation
145 108 if o not in actions
146 109 )
147 110
148 111 removedreqs = repo.requirements - newreqs
149 112 addedreqs = newreqs - repo.requirements
150 113
151 114 if revlogs != upgrade_engine.UPGRADE_ALL_REVLOGS:
152 115 incompatible = upgrade_actions.RECLONES_REQUIREMENTS & (
153 116 removedreqs | addedreqs
154 117 )
155 118 if incompatible:
156 119 msg = _(
157 120 b'ignoring revlogs selection flags, format requirements '
158 121 b'change: %s\n'
159 122 )
160 123 ui.warn(msg % b', '.join(sorted(incompatible)))
161 124 revlogs = upgrade_engine.UPGRADE_ALL_REVLOGS
162 125
163 126 def write_labeled(l, label):
164 127 first = True
165 128 for r in sorted(l):
166 129 if not first:
167 130 ui.write(b', ')
168 131 ui.write(r, label=label)
169 132 first = False
170 133
171 134 def printrequirements():
172 135 ui.write(_(b'requirements\n'))
173 136 ui.write(_(b' preserved: '))
174 137 write_labeled(
175 138 newreqs & repo.requirements, "upgrade-repo.requirement.preserved"
176 139 )
177 140 ui.write((b'\n'))
178 141 removed = repo.requirements - newreqs
179 142 if repo.requirements - newreqs:
180 143 ui.write(_(b' removed: '))
181 144 write_labeled(removed, "upgrade-repo.requirement.removed")
182 145 ui.write((b'\n'))
183 146 added = newreqs - repo.requirements
184 147 if added:
185 148 ui.write(_(b' added: '))
186 149 write_labeled(added, "upgrade-repo.requirement.added")
187 150 ui.write((b'\n'))
188 151 ui.write(b'\n')
189 152
190 153 def printoptimisations():
191 154 optimisations = [
192 155 a for a in actions if a.type == upgrade_actions.OPTIMISATION
193 156 ]
194 157 optimisations.sort(key=lambda a: a.name)
195 158 if optimisations:
196 159 ui.write(_(b'optimisations: '))
197 160 write_labeled(
198 161 [a.name for a in optimisations],
199 162 "upgrade-repo.optimisation.performed",
200 163 )
201 164 ui.write(b'\n\n')
202 165
203 166 def printupgradeactions():
204 167 for a in actions:
205 168 ui.status(b'%s\n %s\n\n' % (a.name, a.upgrademessage))
206 169
207 170 def print_affected_revlogs():
208 171 if not revlogs:
209 172 ui.write((b'no revlogs to process\n'))
210 173 else:
211 174 ui.write((b'processed revlogs:\n'))
212 175 for r in sorted(revlogs):
213 176 ui.write((b' - %s\n' % r))
214 177 ui.write((b'\n'))
215 178
216 179 if not run:
217 180 fromconfig = []
218 181 onlydefault = []
219 182
220 183 for d in deficiencies:
221 184 if d.fromconfig(repo):
222 185 fromconfig.append(d)
223 186 elif d.default:
224 187 onlydefault.append(d)
225 188
226 189 if fromconfig or onlydefault:
227 190
228 191 if fromconfig:
229 192 ui.status(
230 193 _(
231 194 b'repository lacks features recommended by '
232 195 b'current config options:\n\n'
233 196 )
234 197 )
235 198 for i in fromconfig:
236 199 ui.status(b'%s\n %s\n\n' % (i.name, i.description))
237 200
238 201 if onlydefault:
239 202 ui.status(
240 203 _(
241 204 b'repository lacks features used by the default '
242 205 b'config options:\n\n'
243 206 )
244 207 )
245 208 for i in onlydefault:
246 209 ui.status(b'%s\n %s\n\n' % (i.name, i.description))
247 210
248 211 ui.status(b'\n')
249 212 else:
250 213 ui.status(
251 214 _(
252 215 b'(no feature deficiencies found in existing '
253 216 b'repository)\n'
254 217 )
255 218 )
256 219
257 220 ui.status(
258 221 _(
259 222 b'performing an upgrade with "--run" will make the following '
260 223 b'changes:\n\n'
261 224 )
262 225 )
263 226
264 227 printrequirements()
265 228 printoptimisations()
266 229 printupgradeactions()
267 230 print_affected_revlogs()
268 231
269 232 unusedoptimize = [i for i in alloptimizations if i not in actions]
270 233
271 234 if unusedoptimize:
272 235 ui.status(
273 236 _(
274 237 b'additional optimizations are available by specifying '
275 238 b'"--optimize <name>":\n\n'
276 239 )
277 240 )
278 241 for i in unusedoptimize:
279 242 ui.status(_(b'%s\n %s\n\n') % (i.name, i.description))
280 243 return
281 244
282 245 # Else we're in the run=true case.
283 246 ui.write(_(b'upgrade will perform the following actions:\n\n'))
284 247 printrequirements()
285 248 printoptimisations()
286 249 printupgradeactions()
287 250 print_affected_revlogs()
288 251
289 252 upgradeactions = [a.name for a in actions]
290 253
291 254 ui.status(_(b'beginning upgrade...\n'))
292 255 with repo.wlock(), repo.lock():
293 256 ui.status(_(b'repository locked and read-only\n'))
294 257 # Our strategy for upgrading the repository is to create a new,
295 258 # temporary repository, write data to it, then do a swap of the
296 259 # data. There are less heavyweight ways to do this, but it is easier
297 260 # to create a new repo object than to instantiate all the components
298 261 # (like the store) separately.
299 262 tmppath = pycompat.mkdtemp(prefix=b'upgrade.', dir=repo.path)
300 263 backuppath = None
301 264 try:
302 265 ui.status(
303 266 _(
304 267 b'creating temporary repository to stage migrated '
305 268 b'data: %s\n'
306 269 )
307 270 % tmppath
308 271 )
309 272
310 273 # clone ui without using ui.copy because repo.ui is protected
311 274 repoui = repo.ui.__class__(repo.ui)
312 275 dstrepo = hg.repository(repoui, path=tmppath, create=True)
313 276
314 277 with dstrepo.wlock(), dstrepo.lock():
315 278 backuppath = upgrade_engine.upgrade(
316 279 ui, repo, dstrepo, newreqs, upgradeactions, revlogs=revlogs
317 280 )
318 281 if not (backup or backuppath is None):
319 282 ui.status(
320 283 _(b'removing old repository content%s\n') % backuppath
321 284 )
322 285 repo.vfs.rmtree(backuppath, forcibly=True)
323 286 backuppath = None
324 287
325 288 finally:
326 289 ui.status(_(b'removing temporary repository %s\n') % tmppath)
327 290 repo.vfs.rmtree(tmppath, forcibly=True)
328 291
329 292 if backuppath and not ui.quiet:
330 293 ui.warn(
331 294 _(b'copy of old repository backed up at %s\n') % backuppath
332 295 )
333 296 ui.warn(
334 297 _(
335 298 b'the old repository will not be deleted; remove '
336 299 b'it to free up disk space once the upgraded '
337 300 b'repository is verified\n'
338 301 )
339 302 )
340 303
341 304 if upgrade_actions.sharesafe.name in addedreqs:
342 305 ui.warn(
343 306 _(
344 307 b'repository upgraded to share safe mode, existing'
345 308 b' shares will still work in old non-safe mode. '
346 309 b'Re-share existing shares to use them in safe mode'
347 310 b' New shares will be created in safe mode.\n'
348 311 )
349 312 )
350 313 if upgrade_actions.sharesafe.name in removedreqs:
351 314 ui.warn(
352 315 _(
353 316 b'repository downgraded to not use share safe mode, '
354 317 b'existing shares will not work and needs to'
355 318 b' be reshared.\n'
356 319 )
357 320 )
@@ -1,688 +1,719 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 from ..i18n import _
11 11 from .. import (
12 12 error,
13 13 localrepo,
14 14 requirements,
15 15 util,
16 16 )
17 17
18 18 from ..utils import compression
19 19
20 20 # list of requirements that request a clone of all revlog if added/removed
21 21 RECLONES_REQUIREMENTS = {
22 22 b'generaldelta',
23 23 requirements.SPARSEREVLOG_REQUIREMENT,
24 24 }
25 25
26 26
27 27 def supportremovedrequirements(repo):
28 28 """Obtain requirements that can be removed during an upgrade.
29 29
30 30 If an upgrade were to create a repository that dropped a requirement,
31 31 the dropped requirement must appear in the returned set for the upgrade
32 32 to be allowed.
33 33 """
34 34 supported = {
35 35 requirements.SPARSEREVLOG_REQUIREMENT,
36 36 requirements.SIDEDATA_REQUIREMENT,
37 37 requirements.COPIESSDC_REQUIREMENT,
38 38 requirements.NODEMAP_REQUIREMENT,
39 39 requirements.SHARESAFE_REQUIREMENT,
40 40 }
41 41 for name in compression.compengines:
42 42 engine = compression.compengines[name]
43 43 if engine.available() and engine.revlogheader():
44 44 supported.add(b'exp-compression-%s' % name)
45 45 if engine.name() == b'zstd':
46 46 supported.add(b'revlog-compression-zstd')
47 47 return supported
48 48
49 49
50 50 def supporteddestrequirements(repo):
51 51 """Obtain requirements that upgrade supports in the destination.
52 52
53 53 If the result of the upgrade would create requirements not in this set,
54 54 the upgrade is disallowed.
55 55
56 56 Extensions should monkeypatch this to add their custom requirements.
57 57 """
58 58 supported = {
59 59 b'dotencode',
60 60 b'fncache',
61 61 b'generaldelta',
62 62 b'revlogv1',
63 63 b'store',
64 64 requirements.SPARSEREVLOG_REQUIREMENT,
65 65 requirements.SIDEDATA_REQUIREMENT,
66 66 requirements.COPIESSDC_REQUIREMENT,
67 67 requirements.NODEMAP_REQUIREMENT,
68 68 requirements.SHARESAFE_REQUIREMENT,
69 69 }
70 70 for name in compression.compengines:
71 71 engine = compression.compengines[name]
72 72 if engine.available() and engine.revlogheader():
73 73 supported.add(b'exp-compression-%s' % name)
74 74 if engine.name() == b'zstd':
75 75 supported.add(b'revlog-compression-zstd')
76 76 return supported
77 77
78 78
79 79 def allowednewrequirements(repo):
80 80 """Obtain requirements that can be added to a repository during upgrade.
81 81
82 82 This is used to disallow proposed requirements from being added when
83 83 they weren't present before.
84 84
85 85 We use a list of allowed requirement additions instead of a list of known
86 86 bad additions because the whitelist approach is safer and will prevent
87 87 future, unknown requirements from accidentally being added.
88 88 """
89 89 supported = {
90 90 b'dotencode',
91 91 b'fncache',
92 92 b'generaldelta',
93 93 requirements.SPARSEREVLOG_REQUIREMENT,
94 94 requirements.SIDEDATA_REQUIREMENT,
95 95 requirements.COPIESSDC_REQUIREMENT,
96 96 requirements.NODEMAP_REQUIREMENT,
97 97 requirements.SHARESAFE_REQUIREMENT,
98 98 }
99 99 for name in compression.compengines:
100 100 engine = compression.compengines[name]
101 101 if engine.available() and engine.revlogheader():
102 102 supported.add(b'exp-compression-%s' % name)
103 103 if engine.name() == b'zstd':
104 104 supported.add(b'revlog-compression-zstd')
105 105 return supported
106 106
107 107
108 108 def preservedrequirements(repo):
109 109 return set()
110 110
111 111
112 112 DEFICIENCY = b'deficiency'
113 113 OPTIMISATION = b'optimization'
114 114
115 115
116 116 class improvement(object):
117 117 """Represents an improvement that can be made as part of an upgrade.
118 118
119 119 The following attributes are defined on each instance:
120 120
121 121 name
122 122 Machine-readable string uniquely identifying this improvement. It
123 123 will be mapped to an action later in the upgrade process.
124 124
125 125 type
126 126 Either ``DEFICIENCY`` or ``OPTIMISATION``. A deficiency is an obvious
127 127 problem. An optimization is an action (sometimes optional) that
128 128 can be taken to further improve the state of the repository.
129 129
130 130 description
131 131 Message intended for humans explaining the improvement in more detail,
132 132 including the implications of it. For ``DEFICIENCY`` types, should be
133 133 worded in the present tense. For ``OPTIMISATION`` types, should be
134 134 worded in the future tense.
135 135
136 136 upgrademessage
137 137 Message intended for humans explaining what an upgrade addressing this
138 138 issue will do. Should be worded in the future tense.
139 139 """
140 140
141 141 def __init__(self, name, type, description, upgrademessage):
142 142 self.name = name
143 143 self.type = type
144 144 self.description = description
145 145 self.upgrademessage = upgrademessage
146 146
147 147 def __eq__(self, other):
148 148 if not isinstance(other, improvement):
149 149 # This is what python tell use to do
150 150 return NotImplemented
151 151 return self.name == other.name
152 152
153 153 def __ne__(self, other):
154 154 return not (self == other)
155 155
156 156 def __hash__(self):
157 157 return hash(self.name)
158 158
159 159
160 160 allformatvariant = []
161 161
162 162
163 163 def registerformatvariant(cls):
164 164 allformatvariant.append(cls)
165 165 return cls
166 166
167 167
168 168 class formatvariant(improvement):
169 169 """an improvement subclass dedicated to repository format"""
170 170
171 171 type = DEFICIENCY
172 172 ### The following attributes should be defined for each class:
173 173
174 174 # machine-readable string uniquely identifying this improvement. it will be
175 175 # mapped to an action later in the upgrade process.
176 176 name = None
177 177
178 178 # message intended for humans explaining the improvement in more detail,
179 179 # including the implications of it ``DEFICIENCY`` types, should be worded
180 180 # in the present tense.
181 181 description = None
182 182
183 183 # message intended for humans explaining what an upgrade addressing this
184 184 # issue will do. should be worded in the future tense.
185 185 upgrademessage = None
186 186
187 187 # value of current Mercurial default for new repository
188 188 default = None
189 189
190 190 def __init__(self):
191 191 raise NotImplementedError()
192 192
193 193 @staticmethod
194 194 def fromrepo(repo):
195 195 """current value of the variant in the repository"""
196 196 raise NotImplementedError()
197 197
198 198 @staticmethod
199 199 def fromconfig(repo):
200 200 """current value of the variant in the configuration"""
201 201 raise NotImplementedError()
202 202
203 203
204 204 class requirementformatvariant(formatvariant):
205 205 """formatvariant based on a 'requirement' name.
206 206
207 207 Many format variant are controlled by a 'requirement'. We define a small
208 208 subclass to factor the code.
209 209 """
210 210
211 211 # the requirement that control this format variant
212 212 _requirement = None
213 213
214 214 @staticmethod
215 215 def _newreporequirements(ui):
216 216 return localrepo.newreporequirements(
217 217 ui, localrepo.defaultcreateopts(ui)
218 218 )
219 219
220 220 @classmethod
221 221 def fromrepo(cls, repo):
222 222 assert cls._requirement is not None
223 223 return cls._requirement in repo.requirements
224 224
225 225 @classmethod
226 226 def fromconfig(cls, repo):
227 227 assert cls._requirement is not None
228 228 return cls._requirement in cls._newreporequirements(repo.ui)
229 229
230 230
231 231 @registerformatvariant
232 232 class fncache(requirementformatvariant):
233 233 name = b'fncache'
234 234
235 235 _requirement = b'fncache'
236 236
237 237 default = True
238 238
239 239 description = _(
240 240 b'long and reserved filenames may not work correctly; '
241 241 b'repository performance is sub-optimal'
242 242 )
243 243
244 244 upgrademessage = _(
245 245 b'repository will be more resilient to storing '
246 246 b'certain paths and performance of certain '
247 247 b'operations should be improved'
248 248 )
249 249
250 250
251 251 @registerformatvariant
252 252 class dotencode(requirementformatvariant):
253 253 name = b'dotencode'
254 254
255 255 _requirement = b'dotencode'
256 256
257 257 default = True
258 258
259 259 description = _(
260 260 b'storage of filenames beginning with a period or '
261 261 b'space may not work correctly'
262 262 )
263 263
264 264 upgrademessage = _(
265 265 b'repository will be better able to store files '
266 266 b'beginning with a space or period'
267 267 )
268 268
269 269
270 270 @registerformatvariant
271 271 class generaldelta(requirementformatvariant):
272 272 name = b'generaldelta'
273 273
274 274 _requirement = b'generaldelta'
275 275
276 276 default = True
277 277
278 278 description = _(
279 279 b'deltas within internal storage are unable to '
280 280 b'choose optimal revisions; repository is larger and '
281 281 b'slower than it could be; interaction with other '
282 282 b'repositories may require extra network and CPU '
283 283 b'resources, making "hg push" and "hg pull" slower'
284 284 )
285 285
286 286 upgrademessage = _(
287 287 b'repository storage will be able to create '
288 288 b'optimal deltas; new repository data will be '
289 289 b'smaller and read times should decrease; '
290 290 b'interacting with other repositories using this '
291 291 b'storage model should require less network and '
292 292 b'CPU resources, making "hg push" and "hg pull" '
293 293 b'faster'
294 294 )
295 295
296 296
297 297 @registerformatvariant
298 298 class sharesafe(requirementformatvariant):
299 299 name = b'exp-sharesafe'
300 300 _requirement = requirements.SHARESAFE_REQUIREMENT
301 301
302 302 default = False
303 303
304 304 description = _(
305 305 b'old shared repositories do not share source repository '
306 306 b'requirements and config. This leads to various problems '
307 307 b'when the source repository format is upgraded or some new '
308 308 b'extensions are enabled.'
309 309 )
310 310
311 311 upgrademessage = _(
312 312 b'Upgrades a repository to share-safe format so that future '
313 313 b'shares of this repository share its requirements and configs.'
314 314 )
315 315
316 316
317 317 @registerformatvariant
318 318 class sparserevlog(requirementformatvariant):
319 319 name = b'sparserevlog'
320 320
321 321 _requirement = requirements.SPARSEREVLOG_REQUIREMENT
322 322
323 323 default = True
324 324
325 325 description = _(
326 326 b'in order to limit disk reading and memory usage on older '
327 327 b'version, the span of a delta chain from its root to its '
328 328 b'end is limited, whatever the relevant data in this span. '
329 329 b'This can severly limit Mercurial ability to build good '
330 330 b'chain of delta resulting is much more storage space being '
331 331 b'taken and limit reusability of on disk delta during '
332 332 b'exchange.'
333 333 )
334 334
335 335 upgrademessage = _(
336 336 b'Revlog supports delta chain with more unused data '
337 337 b'between payload. These gaps will be skipped at read '
338 338 b'time. This allows for better delta chains, making a '
339 339 b'better compression and faster exchange with server.'
340 340 )
341 341
342 342
343 343 @registerformatvariant
344 344 class sidedata(requirementformatvariant):
345 345 name = b'sidedata'
346 346
347 347 _requirement = requirements.SIDEDATA_REQUIREMENT
348 348
349 349 default = False
350 350
351 351 description = _(
352 352 b'Allows storage of extra data alongside a revision, '
353 353 b'unlocking various caching options.'
354 354 )
355 355
356 356 upgrademessage = _(b'Allows storage of extra data alongside a revision.')
357 357
358 358
359 359 @registerformatvariant
360 360 class persistentnodemap(requirementformatvariant):
361 361 name = b'persistent-nodemap'
362 362
363 363 _requirement = requirements.NODEMAP_REQUIREMENT
364 364
365 365 default = False
366 366
367 367 description = _(
368 368 b'persist the node -> rev mapping on disk to speedup lookup'
369 369 )
370 370
371 371 upgrademessage = _(b'Speedup revision lookup by node id.')
372 372
373 373
374 374 @registerformatvariant
375 375 class copiessdc(requirementformatvariant):
376 376 name = b'copies-sdc'
377 377
378 378 _requirement = requirements.COPIESSDC_REQUIREMENT
379 379
380 380 default = False
381 381
382 382 description = _(b'Stores copies information alongside changesets.')
383 383
384 384 upgrademessage = _(
385 385 b'Allows to use more efficient algorithm to deal with ' b'copy tracing.'
386 386 )
387 387
388 388
389 389 @registerformatvariant
390 390 class removecldeltachain(formatvariant):
391 391 name = b'plain-cl-delta'
392 392
393 393 default = True
394 394
395 395 description = _(
396 396 b'changelog storage is using deltas instead of '
397 397 b'raw entries; changelog reading and any '
398 398 b'operation relying on changelog data are slower '
399 399 b'than they could be'
400 400 )
401 401
402 402 upgrademessage = _(
403 403 b'changelog storage will be reformated to '
404 404 b'store raw entries; changelog reading will be '
405 405 b'faster; changelog size may be reduced'
406 406 )
407 407
408 408 @staticmethod
409 409 def fromrepo(repo):
410 410 # Mercurial 4.0 changed changelogs to not use delta chains. Search for
411 411 # changelogs with deltas.
412 412 cl = repo.changelog
413 413 chainbase = cl.chainbase
414 414 return all(rev == chainbase(rev) for rev in cl)
415 415
416 416 @staticmethod
417 417 def fromconfig(repo):
418 418 return True
419 419
420 420
421 421 @registerformatvariant
422 422 class compressionengine(formatvariant):
423 423 name = b'compression'
424 424 default = b'zlib'
425 425
426 426 description = _(
427 427 b'Compresion algorithm used to compress data. '
428 428 b'Some engine are faster than other'
429 429 )
430 430
431 431 upgrademessage = _(
432 432 b'revlog content will be recompressed with the new algorithm.'
433 433 )
434 434
435 435 @classmethod
436 436 def fromrepo(cls, repo):
437 437 # we allow multiple compression engine requirement to co-exist because
438 438 # strickly speaking, revlog seems to support mixed compression style.
439 439 #
440 440 # The compression used for new entries will be "the last one"
441 441 compression = b'zlib'
442 442 for req in repo.requirements:
443 443 prefix = req.startswith
444 444 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
445 445 compression = req.split(b'-', 2)[2]
446 446 return compression
447 447
448 448 @classmethod
449 449 def fromconfig(cls, repo):
450 450 compengines = repo.ui.configlist(b'format', b'revlog-compression')
451 451 # return the first valid value as the selection code would do
452 452 for comp in compengines:
453 453 if comp in util.compengines:
454 454 return comp
455 455
456 456 # no valide compression found lets display it all for clarity
457 457 return b','.join(compengines)
458 458
459 459
460 460 @registerformatvariant
461 461 class compressionlevel(formatvariant):
462 462 name = b'compression-level'
463 463 default = b'default'
464 464
465 465 description = _(b'compression level')
466 466
467 467 upgrademessage = _(b'revlog content will be recompressed')
468 468
469 469 @classmethod
470 470 def fromrepo(cls, repo):
471 471 comp = compressionengine.fromrepo(repo)
472 472 level = None
473 473 if comp == b'zlib':
474 474 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
475 475 elif comp == b'zstd':
476 476 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
477 477 if level is None:
478 478 return b'default'
479 479 return bytes(level)
480 480
481 481 @classmethod
482 482 def fromconfig(cls, repo):
483 483 comp = compressionengine.fromconfig(repo)
484 484 level = None
485 485 if comp == b'zlib':
486 486 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
487 487 elif comp == b'zstd':
488 488 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
489 489 if level is None:
490 490 return b'default'
491 491 return bytes(level)
492 492
493 493
494 494 def finddeficiencies(repo):
495 495 """returns a list of deficiencies that the repo suffer from"""
496 496 deficiencies = []
497 497
498 498 # We could detect lack of revlogv1 and store here, but they were added
499 499 # in 0.9.2 and we don't support upgrading repos without these
500 500 # requirements, so let's not bother.
501 501
502 502 for fv in allformatvariant:
503 503 if not fv.fromrepo(repo):
504 504 deficiencies.append(fv)
505 505
506 506 return deficiencies
507 507
508 508
509 509 ALL_OPTIMISATIONS = []
510 510
511 511
512 512 def register_optimization(obj):
513 513 ALL_OPTIMISATIONS.append(obj)
514 514 return obj
515 515
516 516
517 517 register_optimization(
518 518 improvement(
519 519 name=b're-delta-parent',
520 520 type=OPTIMISATION,
521 521 description=_(
522 522 b'deltas within internal storage will be recalculated to '
523 523 b'choose an optimal base revision where this was not '
524 524 b'already done; the size of the repository may shrink and '
525 525 b'various operations may become faster; the first time '
526 526 b'this optimization is performed could slow down upgrade '
527 527 b'execution considerably; subsequent invocations should '
528 528 b'not run noticeably slower'
529 529 ),
530 530 upgrademessage=_(
531 531 b'deltas within internal storage will choose a new '
532 532 b'base revision if needed'
533 533 ),
534 534 )
535 535 )
536 536
537 537 register_optimization(
538 538 improvement(
539 539 name=b're-delta-multibase',
540 540 type=OPTIMISATION,
541 541 description=_(
542 542 b'deltas within internal storage will be recalculated '
543 543 b'against multiple base revision and the smallest '
544 544 b'difference will be used; the size of the repository may '
545 545 b'shrink significantly when there are many merges; this '
546 546 b'optimization will slow down execution in proportion to '
547 547 b'the number of merges in the repository and the amount '
548 548 b'of files in the repository; this slow down should not '
549 549 b'be significant unless there are tens of thousands of '
550 550 b'files and thousands of merges'
551 551 ),
552 552 upgrademessage=_(
553 553 b'deltas within internal storage will choose an '
554 554 b'optimal delta by computing deltas against multiple '
555 555 b'parents; may slow down execution time '
556 556 b'significantly'
557 557 ),
558 558 )
559 559 )
560 560
561 561 register_optimization(
562 562 improvement(
563 563 name=b're-delta-all',
564 564 type=OPTIMISATION,
565 565 description=_(
566 566 b'deltas within internal storage will always be '
567 567 b'recalculated without reusing prior deltas; this will '
568 568 b'likely make execution run several times slower; this '
569 569 b'optimization is typically not needed'
570 570 ),
571 571 upgrademessage=_(
572 572 b'deltas within internal storage will be fully '
573 573 b'recomputed; this will likely drastically slow down '
574 574 b'execution time'
575 575 ),
576 576 )
577 577 )
578 578
579 579 register_optimization(
580 580 improvement(
581 581 name=b're-delta-fulladd',
582 582 type=OPTIMISATION,
583 583 description=_(
584 584 b'every revision will be re-added as if it was new '
585 585 b'content. It will go through the full storage '
586 586 b'mechanism giving extensions a chance to process it '
587 587 b'(eg. lfs). This is similar to "re-delta-all" but even '
588 588 b'slower since more logic is involved.'
589 589 ),
590 590 upgrademessage=_(
591 591 b'each revision will be added as new content to the '
592 592 b'internal storage; this will likely drastically slow '
593 593 b'down execution time, but some extensions might need '
594 594 b'it'
595 595 ),
596 596 )
597 597 )
598 598
599 599
600 600 def findoptimizations(repo):
601 601 """Determine optimisation that could be used during upgrade"""
602 602 # These are unconditionally added. There is logic later that figures out
603 603 # which ones to apply.
604 604 return list(ALL_OPTIMISATIONS)
605 605
606 606
607 607 def determineactions(repo, deficiencies, sourcereqs, destreqs):
608 608 """Determine upgrade actions that will be performed.
609 609
610 610 Given a list of improvements as returned by ``finddeficiencies`` and
611 611 ``findoptimizations``, determine the list of upgrade actions that
612 612 will be performed.
613 613
614 614 The role of this function is to filter improvements if needed, apply
615 615 recommended optimizations from the improvements list that make sense,
616 616 etc.
617 617
618 618 Returns a list of action names.
619 619 """
620 620 newactions = []
621 621
622 622 for d in deficiencies:
623 623 name = d._requirement
624 624
625 625 # If the action is a requirement that doesn't show up in the
626 626 # destination requirements, prune the action.
627 627 if name is not None and name not in destreqs:
628 628 continue
629 629
630 630 newactions.append(d)
631 631
632 632 # FUTURE consider adding some optimizations here for certain transitions.
633 633 # e.g. adding generaldelta could schedule parent redeltas.
634 634
635 635 return newactions
636 636
637 637
638 638 ### Code checking if a repository can got through the upgrade process at all. #
639 639
640 640
641 641 def requiredsourcerequirements(repo):
642 642 """Obtain requirements required to be present to upgrade a repo.
643 643
644 644 An upgrade will not be allowed if the repository doesn't have the
645 645 requirements returned by this function.
646 646 """
647 647 return {
648 648 # Introduced in Mercurial 0.9.2.
649 649 b'revlogv1',
650 650 # Introduced in Mercurial 0.9.2.
651 651 b'store',
652 652 }
653 653
654 654
655 655 def blocksourcerequirements(repo):
656 656 """Obtain requirements that will prevent an upgrade from occurring.
657 657
658 658 An upgrade cannot be performed if the source repository contains a
659 659 requirements in the returned set.
660 660 """
661 661 return {
662 662 # The upgrade code does not yet support these experimental features.
663 663 # This is an artificial limitation.
664 664 requirements.TREEMANIFEST_REQUIREMENT,
665 665 # This was a precursor to generaldelta and was never enabled by default.
666 666 # It should (hopefully) not exist in the wild.
667 667 b'parentdelta',
668 668 # Upgrade should operate on the actual store, not the shared link.
669 669 requirements.SHARED_REQUIREMENT,
670 670 }
671 671
672 672
673 673 def check_source_requirements(repo):
674 674 """Ensure that no existing requirements prevent the repository upgrade"""
675 675
676 676 required = requiredsourcerequirements(repo)
677 677 missingreqs = required - repo.requirements
678 678 if missingreqs:
679 679 msg = _(b'cannot upgrade repository; requirement missing: %s')
680 680 missingreqs = b', '.join(sorted(missingreqs))
681 681 raise error.Abort(msg % missingreqs)
682 682
683 683 blocking = blocksourcerequirements(repo)
684 684 blockingreqs = blocking & repo.requirements
685 685 if blockingreqs:
686 686 m = _(b'cannot upgrade repository; unsupported source requirement: %s')
687 687 blockingreqs = b', '.join(sorted(blockingreqs))
688 688 raise error.Abort(m % blockingreqs)
689
690
691 ### Verify the validity of the planned requirement changes ####################
692
693
694 def check_requirements_changes(repo, new_reqs):
695 old_reqs = repo.requirements
696
697 support_removal = supportremovedrequirements(repo)
698 no_remove_reqs = old_reqs - new_reqs - support_removal
699 if no_remove_reqs:
700 msg = _(b'cannot upgrade repository; requirement would be removed: %s')
701 no_remove_reqs = b', '.join(sorted(no_remove_reqs))
702 raise error.Abort(msg % no_remove_reqs)
703
704 support_addition = allowednewrequirements(repo)
705 no_add_reqs = new_reqs - old_reqs - support_addition
706 if no_add_reqs:
707 m = _(b'cannot upgrade repository; do not support adding requirement: ')
708 no_add_reqs = b', '.join(sorted(no_add_reqs))
709 raise error.Abort(m + no_add_reqs)
710
711 supported = supporteddestrequirements(repo)
712 unsupported_reqs = new_reqs - supported
713 if unsupported_reqs:
714 msg = _(
715 b'cannot upgrade repository; do not support destination '
716 b'requirement: %s'
717 )
718 unsupported_reqs = b', '.join(sorted(unsupported_reqs))
719 raise error.Abort(msg % unsupported_reqs)
General Comments 0
You need to be logged in to leave comments. Login now