##// END OF EJS Templates
upgrade-repo: colorize some of the output...
marmoute -
r44257:ad84fc97 default
parent child Browse files
Show More
@@ -1,574 +1,577 b''
1 # utility for color output for Mercurial commands
1 # utility for color output for Mercurial commands
2 #
2 #
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> and other
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> and other
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from .pycompat import getattr
13 from .pycompat import getattr
14
14
15 from . import (
15 from . import (
16 encoding,
16 encoding,
17 pycompat,
17 pycompat,
18 )
18 )
19
19
20 from .utils import stringutil
20 from .utils import stringutil
21
21
22 try:
22 try:
23 import curses
23 import curses
24
24
25 # Mapping from effect name to terminfo attribute name (or raw code) or
25 # Mapping from effect name to terminfo attribute name (or raw code) or
26 # color number. This will also force-load the curses module.
26 # color number. This will also force-load the curses module.
27 _baseterminfoparams = {
27 _baseterminfoparams = {
28 b'none': (True, b'sgr0', b''),
28 b'none': (True, b'sgr0', b''),
29 b'standout': (True, b'smso', b''),
29 b'standout': (True, b'smso', b''),
30 b'underline': (True, b'smul', b''),
30 b'underline': (True, b'smul', b''),
31 b'reverse': (True, b'rev', b''),
31 b'reverse': (True, b'rev', b''),
32 b'inverse': (True, b'rev', b''),
32 b'inverse': (True, b'rev', b''),
33 b'blink': (True, b'blink', b''),
33 b'blink': (True, b'blink', b''),
34 b'dim': (True, b'dim', b''),
34 b'dim': (True, b'dim', b''),
35 b'bold': (True, b'bold', b''),
35 b'bold': (True, b'bold', b''),
36 b'invisible': (True, b'invis', b''),
36 b'invisible': (True, b'invis', b''),
37 b'italic': (True, b'sitm', b''),
37 b'italic': (True, b'sitm', b''),
38 b'black': (False, curses.COLOR_BLACK, b''),
38 b'black': (False, curses.COLOR_BLACK, b''),
39 b'red': (False, curses.COLOR_RED, b''),
39 b'red': (False, curses.COLOR_RED, b''),
40 b'green': (False, curses.COLOR_GREEN, b''),
40 b'green': (False, curses.COLOR_GREEN, b''),
41 b'yellow': (False, curses.COLOR_YELLOW, b''),
41 b'yellow': (False, curses.COLOR_YELLOW, b''),
42 b'blue': (False, curses.COLOR_BLUE, b''),
42 b'blue': (False, curses.COLOR_BLUE, b''),
43 b'magenta': (False, curses.COLOR_MAGENTA, b''),
43 b'magenta': (False, curses.COLOR_MAGENTA, b''),
44 b'cyan': (False, curses.COLOR_CYAN, b''),
44 b'cyan': (False, curses.COLOR_CYAN, b''),
45 b'white': (False, curses.COLOR_WHITE, b''),
45 b'white': (False, curses.COLOR_WHITE, b''),
46 }
46 }
47 except ImportError:
47 except ImportError:
48 curses = None
48 curses = None
49 _baseterminfoparams = {}
49 _baseterminfoparams = {}
50
50
51 # start and stop parameters for effects
51 # start and stop parameters for effects
52 _effects = {
52 _effects = {
53 b'none': 0,
53 b'none': 0,
54 b'black': 30,
54 b'black': 30,
55 b'red': 31,
55 b'red': 31,
56 b'green': 32,
56 b'green': 32,
57 b'yellow': 33,
57 b'yellow': 33,
58 b'blue': 34,
58 b'blue': 34,
59 b'magenta': 35,
59 b'magenta': 35,
60 b'cyan': 36,
60 b'cyan': 36,
61 b'white': 37,
61 b'white': 37,
62 b'bold': 1,
62 b'bold': 1,
63 b'italic': 3,
63 b'italic': 3,
64 b'underline': 4,
64 b'underline': 4,
65 b'inverse': 7,
65 b'inverse': 7,
66 b'dim': 2,
66 b'dim': 2,
67 b'black_background': 40,
67 b'black_background': 40,
68 b'red_background': 41,
68 b'red_background': 41,
69 b'green_background': 42,
69 b'green_background': 42,
70 b'yellow_background': 43,
70 b'yellow_background': 43,
71 b'blue_background': 44,
71 b'blue_background': 44,
72 b'purple_background': 45,
72 b'purple_background': 45,
73 b'cyan_background': 46,
73 b'cyan_background': 46,
74 b'white_background': 47,
74 b'white_background': 47,
75 }
75 }
76
76
77 _defaultstyles = {
77 _defaultstyles = {
78 b'grep.match': b'red bold',
78 b'grep.match': b'red bold',
79 b'grep.linenumber': b'green',
79 b'grep.linenumber': b'green',
80 b'grep.rev': b'blue',
80 b'grep.rev': b'blue',
81 b'grep.sep': b'cyan',
81 b'grep.sep': b'cyan',
82 b'grep.filename': b'magenta',
82 b'grep.filename': b'magenta',
83 b'grep.user': b'magenta',
83 b'grep.user': b'magenta',
84 b'grep.date': b'magenta',
84 b'grep.date': b'magenta',
85 b'grep.inserted': b'green bold',
85 b'grep.inserted': b'green bold',
86 b'grep.deleted': b'red bold',
86 b'grep.deleted': b'red bold',
87 b'bookmarks.active': b'green',
87 b'bookmarks.active': b'green',
88 b'branches.active': b'none',
88 b'branches.active': b'none',
89 b'branches.closed': b'black bold',
89 b'branches.closed': b'black bold',
90 b'branches.current': b'green',
90 b'branches.current': b'green',
91 b'branches.inactive': b'none',
91 b'branches.inactive': b'none',
92 b'diff.changed': b'white',
92 b'diff.changed': b'white',
93 b'diff.deleted': b'red',
93 b'diff.deleted': b'red',
94 b'diff.deleted.changed': b'red bold underline',
94 b'diff.deleted.changed': b'red bold underline',
95 b'diff.deleted.unchanged': b'red',
95 b'diff.deleted.unchanged': b'red',
96 b'diff.diffline': b'bold',
96 b'diff.diffline': b'bold',
97 b'diff.extended': b'cyan bold',
97 b'diff.extended': b'cyan bold',
98 b'diff.file_a': b'red bold',
98 b'diff.file_a': b'red bold',
99 b'diff.file_b': b'green bold',
99 b'diff.file_b': b'green bold',
100 b'diff.hunk': b'magenta',
100 b'diff.hunk': b'magenta',
101 b'diff.inserted': b'green',
101 b'diff.inserted': b'green',
102 b'diff.inserted.changed': b'green bold underline',
102 b'diff.inserted.changed': b'green bold underline',
103 b'diff.inserted.unchanged': b'green',
103 b'diff.inserted.unchanged': b'green',
104 b'diff.tab': b'',
104 b'diff.tab': b'',
105 b'diff.trailingwhitespace': b'bold red_background',
105 b'diff.trailingwhitespace': b'bold red_background',
106 b'changeset.public': b'',
106 b'changeset.public': b'',
107 b'changeset.draft': b'',
107 b'changeset.draft': b'',
108 b'changeset.secret': b'',
108 b'changeset.secret': b'',
109 b'diffstat.deleted': b'red',
109 b'diffstat.deleted': b'red',
110 b'diffstat.inserted': b'green',
110 b'diffstat.inserted': b'green',
111 b'formatvariant.name.mismatchconfig': b'red',
111 b'formatvariant.name.mismatchconfig': b'red',
112 b'formatvariant.name.mismatchdefault': b'yellow',
112 b'formatvariant.name.mismatchdefault': b'yellow',
113 b'formatvariant.name.uptodate': b'green',
113 b'formatvariant.name.uptodate': b'green',
114 b'formatvariant.repo.mismatchconfig': b'red',
114 b'formatvariant.repo.mismatchconfig': b'red',
115 b'formatvariant.repo.mismatchdefault': b'yellow',
115 b'formatvariant.repo.mismatchdefault': b'yellow',
116 b'formatvariant.repo.uptodate': b'green',
116 b'formatvariant.repo.uptodate': b'green',
117 b'formatvariant.config.special': b'yellow',
117 b'formatvariant.config.special': b'yellow',
118 b'formatvariant.config.default': b'green',
118 b'formatvariant.config.default': b'green',
119 b'formatvariant.default': b'',
119 b'formatvariant.default': b'',
120 b'histedit.remaining': b'red bold',
120 b'histedit.remaining': b'red bold',
121 b'ui.addremove.added': b'green',
121 b'ui.addremove.added': b'green',
122 b'ui.addremove.removed': b'red',
122 b'ui.addremove.removed': b'red',
123 b'ui.error': b'red',
123 b'ui.error': b'red',
124 b'ui.prompt': b'yellow',
124 b'ui.prompt': b'yellow',
125 b'log.changeset': b'yellow',
125 b'log.changeset': b'yellow',
126 b'patchbomb.finalsummary': b'',
126 b'patchbomb.finalsummary': b'',
127 b'patchbomb.from': b'magenta',
127 b'patchbomb.from': b'magenta',
128 b'patchbomb.to': b'cyan',
128 b'patchbomb.to': b'cyan',
129 b'patchbomb.subject': b'green',
129 b'patchbomb.subject': b'green',
130 b'patchbomb.diffstats': b'',
130 b'patchbomb.diffstats': b'',
131 b'rebase.rebased': b'blue',
131 b'rebase.rebased': b'blue',
132 b'rebase.remaining': b'red bold',
132 b'rebase.remaining': b'red bold',
133 b'resolve.resolved': b'green bold',
133 b'resolve.resolved': b'green bold',
134 b'resolve.unresolved': b'red bold',
134 b'resolve.unresolved': b'red bold',
135 b'shelve.age': b'cyan',
135 b'shelve.age': b'cyan',
136 b'shelve.newest': b'green bold',
136 b'shelve.newest': b'green bold',
137 b'shelve.name': b'blue bold',
137 b'shelve.name': b'blue bold',
138 b'status.added': b'green bold',
138 b'status.added': b'green bold',
139 b'status.clean': b'none',
139 b'status.clean': b'none',
140 b'status.copied': b'none',
140 b'status.copied': b'none',
141 b'status.deleted': b'cyan bold underline',
141 b'status.deleted': b'cyan bold underline',
142 b'status.ignored': b'black bold',
142 b'status.ignored': b'black bold',
143 b'status.modified': b'blue bold',
143 b'status.modified': b'blue bold',
144 b'status.removed': b'red bold',
144 b'status.removed': b'red bold',
145 b'status.unknown': b'magenta bold underline',
145 b'status.unknown': b'magenta bold underline',
146 b'tags.normal': b'green',
146 b'tags.normal': b'green',
147 b'tags.local': b'black bold',
147 b'tags.local': b'black bold',
148 b'upgrade-repo.requirement.preserved': b'cyan',
149 b'upgrade-repo.requirement.added': b'green',
150 b'upgrade-repo.requirement.removed': b'red',
148 }
151 }
149
152
150
153
151 def loadcolortable(ui, extname, colortable):
154 def loadcolortable(ui, extname, colortable):
152 _defaultstyles.update(colortable)
155 _defaultstyles.update(colortable)
153
156
154
157
155 def _terminfosetup(ui, mode, formatted):
158 def _terminfosetup(ui, mode, formatted):
156 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
159 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
157
160
158 # If we failed to load curses, we go ahead and return.
161 # If we failed to load curses, we go ahead and return.
159 if curses is None:
162 if curses is None:
160 return
163 return
161 # Otherwise, see what the config file says.
164 # Otherwise, see what the config file says.
162 if mode not in (b'auto', b'terminfo'):
165 if mode not in (b'auto', b'terminfo'):
163 return
166 return
164 ui._terminfoparams.update(_baseterminfoparams)
167 ui._terminfoparams.update(_baseterminfoparams)
165
168
166 for key, val in ui.configitems(b'color'):
169 for key, val in ui.configitems(b'color'):
167 if key.startswith(b'color.'):
170 if key.startswith(b'color.'):
168 newval = (False, int(val), b'')
171 newval = (False, int(val), b'')
169 ui._terminfoparams[key[6:]] = newval
172 ui._terminfoparams[key[6:]] = newval
170 elif key.startswith(b'terminfo.'):
173 elif key.startswith(b'terminfo.'):
171 newval = (True, b'', val.replace(b'\\E', b'\x1b'))
174 newval = (True, b'', val.replace(b'\\E', b'\x1b'))
172 ui._terminfoparams[key[9:]] = newval
175 ui._terminfoparams[key[9:]] = newval
173 try:
176 try:
174 curses.setupterm()
177 curses.setupterm()
175 except curses.error:
178 except curses.error:
176 ui._terminfoparams.clear()
179 ui._terminfoparams.clear()
177 return
180 return
178
181
179 for key, (b, e, c) in ui._terminfoparams.copy().items():
182 for key, (b, e, c) in ui._terminfoparams.copy().items():
180 if not b:
183 if not b:
181 continue
184 continue
182 if not c and not curses.tigetstr(pycompat.sysstr(e)):
185 if not c and not curses.tigetstr(pycompat.sysstr(e)):
183 # Most terminals don't support dim, invis, etc, so don't be
186 # Most terminals don't support dim, invis, etc, so don't be
184 # noisy and use ui.debug().
187 # noisy and use ui.debug().
185 ui.debug(b"no terminfo entry for %s\n" % e)
188 ui.debug(b"no terminfo entry for %s\n" % e)
186 del ui._terminfoparams[key]
189 del ui._terminfoparams[key]
187 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
190 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
188 # Only warn about missing terminfo entries if we explicitly asked for
191 # Only warn about missing terminfo entries if we explicitly asked for
189 # terminfo mode and we're in a formatted terminal.
192 # terminfo mode and we're in a formatted terminal.
190 if mode == b"terminfo" and formatted:
193 if mode == b"terminfo" and formatted:
191 ui.warn(
194 ui.warn(
192 _(
195 _(
193 b"no terminfo entry for setab/setaf: reverting to "
196 b"no terminfo entry for setab/setaf: reverting to "
194 b"ECMA-48 color\n"
197 b"ECMA-48 color\n"
195 )
198 )
196 )
199 )
197 ui._terminfoparams.clear()
200 ui._terminfoparams.clear()
198
201
199
202
200 def setup(ui):
203 def setup(ui):
201 """configure color on a ui
204 """configure color on a ui
202
205
203 That function both set the colormode for the ui object and read
206 That function both set the colormode for the ui object and read
204 the configuration looking for custom colors and effect definitions."""
207 the configuration looking for custom colors and effect definitions."""
205 mode = _modesetup(ui)
208 mode = _modesetup(ui)
206 ui._colormode = mode
209 ui._colormode = mode
207 if mode and mode != b'debug':
210 if mode and mode != b'debug':
208 configstyles(ui)
211 configstyles(ui)
209
212
210
213
211 def _modesetup(ui):
214 def _modesetup(ui):
212 if ui.plain(b'color'):
215 if ui.plain(b'color'):
213 return None
216 return None
214 config = ui.config(b'ui', b'color')
217 config = ui.config(b'ui', b'color')
215 if config == b'debug':
218 if config == b'debug':
216 return b'debug'
219 return b'debug'
217
220
218 auto = config == b'auto'
221 auto = config == b'auto'
219 always = False
222 always = False
220 if not auto and stringutil.parsebool(config):
223 if not auto and stringutil.parsebool(config):
221 # We want the config to behave like a boolean, "on" is actually auto,
224 # We want the config to behave like a boolean, "on" is actually auto,
222 # but "always" value is treated as a special case to reduce confusion.
225 # but "always" value is treated as a special case to reduce confusion.
223 if (
226 if (
224 ui.configsource(b'ui', b'color') == b'--color'
227 ui.configsource(b'ui', b'color') == b'--color'
225 or config == b'always'
228 or config == b'always'
226 ):
229 ):
227 always = True
230 always = True
228 else:
231 else:
229 auto = True
232 auto = True
230
233
231 if not always and not auto:
234 if not always and not auto:
232 return None
235 return None
233
236
234 formatted = always or (
237 formatted = always or (
235 encoding.environ.get(b'TERM') != b'dumb' and ui.formatted()
238 encoding.environ.get(b'TERM') != b'dumb' and ui.formatted()
236 )
239 )
237
240
238 mode = ui.config(b'color', b'mode')
241 mode = ui.config(b'color', b'mode')
239
242
240 # If pager is active, color.pagermode overrides color.mode.
243 # If pager is active, color.pagermode overrides color.mode.
241 if getattr(ui, 'pageractive', False):
244 if getattr(ui, 'pageractive', False):
242 mode = ui.config(b'color', b'pagermode', mode)
245 mode = ui.config(b'color', b'pagermode', mode)
243
246
244 realmode = mode
247 realmode = mode
245 if pycompat.iswindows:
248 if pycompat.iswindows:
246 from . import win32
249 from . import win32
247
250
248 term = encoding.environ.get(b'TERM')
251 term = encoding.environ.get(b'TERM')
249 # TERM won't be defined in a vanilla cmd.exe environment.
252 # TERM won't be defined in a vanilla cmd.exe environment.
250
253
251 # UNIX-like environments on Windows such as Cygwin and MSYS will
254 # UNIX-like environments on Windows such as Cygwin and MSYS will
252 # set TERM. They appear to make a best effort attempt at setting it
255 # set TERM. They appear to make a best effort attempt at setting it
253 # to something appropriate. However, not all environments with TERM
256 # to something appropriate. However, not all environments with TERM
254 # defined support ANSI.
257 # defined support ANSI.
255 ansienviron = term and b'xterm' in term
258 ansienviron = term and b'xterm' in term
256
259
257 if mode == b'auto':
260 if mode == b'auto':
258 # Since "ansi" could result in terminal gibberish, we error on the
261 # Since "ansi" could result in terminal gibberish, we error on the
259 # side of selecting "win32". However, if w32effects is not defined,
262 # side of selecting "win32". However, if w32effects is not defined,
260 # we almost certainly don't support "win32", so don't even try.
263 # we almost certainly don't support "win32", so don't even try.
261 # w32effects is not populated when stdout is redirected, so checking
264 # w32effects is not populated when stdout is redirected, so checking
262 # it first avoids win32 calls in a state known to error out.
265 # it first avoids win32 calls in a state known to error out.
263 if ansienviron or not w32effects or win32.enablevtmode():
266 if ansienviron or not w32effects or win32.enablevtmode():
264 realmode = b'ansi'
267 realmode = b'ansi'
265 else:
268 else:
266 realmode = b'win32'
269 realmode = b'win32'
267 # An empty w32effects is a clue that stdout is redirected, and thus
270 # An empty w32effects is a clue that stdout is redirected, and thus
268 # cannot enable VT mode.
271 # cannot enable VT mode.
269 elif mode == b'ansi' and w32effects and not ansienviron:
272 elif mode == b'ansi' and w32effects and not ansienviron:
270 win32.enablevtmode()
273 win32.enablevtmode()
271 elif mode == b'auto':
274 elif mode == b'auto':
272 realmode = b'ansi'
275 realmode = b'ansi'
273
276
274 def modewarn():
277 def modewarn():
275 # only warn if color.mode was explicitly set and we're in
278 # only warn if color.mode was explicitly set and we're in
276 # a formatted terminal
279 # a formatted terminal
277 if mode == realmode and formatted:
280 if mode == realmode and formatted:
278 ui.warn(_(b'warning: failed to set color mode to %s\n') % mode)
281 ui.warn(_(b'warning: failed to set color mode to %s\n') % mode)
279
282
280 if realmode == b'win32':
283 if realmode == b'win32':
281 ui._terminfoparams.clear()
284 ui._terminfoparams.clear()
282 if not w32effects:
285 if not w32effects:
283 modewarn()
286 modewarn()
284 return None
287 return None
285 elif realmode == b'ansi':
288 elif realmode == b'ansi':
286 ui._terminfoparams.clear()
289 ui._terminfoparams.clear()
287 elif realmode == b'terminfo':
290 elif realmode == b'terminfo':
288 _terminfosetup(ui, mode, formatted)
291 _terminfosetup(ui, mode, formatted)
289 if not ui._terminfoparams:
292 if not ui._terminfoparams:
290 ## FIXME Shouldn't we return None in this case too?
293 ## FIXME Shouldn't we return None in this case too?
291 modewarn()
294 modewarn()
292 realmode = b'ansi'
295 realmode = b'ansi'
293 else:
296 else:
294 return None
297 return None
295
298
296 if always or (auto and formatted):
299 if always or (auto and formatted):
297 return realmode
300 return realmode
298 return None
301 return None
299
302
300
303
301 def configstyles(ui):
304 def configstyles(ui):
302 ui._styles.update(_defaultstyles)
305 ui._styles.update(_defaultstyles)
303 for status, cfgeffects in ui.configitems(b'color'):
306 for status, cfgeffects in ui.configitems(b'color'):
304 if b'.' not in status or status.startswith((b'color.', b'terminfo.')):
307 if b'.' not in status or status.startswith((b'color.', b'terminfo.')):
305 continue
308 continue
306 cfgeffects = ui.configlist(b'color', status)
309 cfgeffects = ui.configlist(b'color', status)
307 if cfgeffects:
310 if cfgeffects:
308 good = []
311 good = []
309 for e in cfgeffects:
312 for e in cfgeffects:
310 if valideffect(ui, e):
313 if valideffect(ui, e):
311 good.append(e)
314 good.append(e)
312 else:
315 else:
313 ui.warn(
316 ui.warn(
314 _(
317 _(
315 b"ignoring unknown color/effect %s "
318 b"ignoring unknown color/effect %s "
316 b"(configured in color.%s)\n"
319 b"(configured in color.%s)\n"
317 )
320 )
318 % (stringutil.pprint(e), status)
321 % (stringutil.pprint(e), status)
319 )
322 )
320 ui._styles[status] = b' '.join(good)
323 ui._styles[status] = b' '.join(good)
321
324
322
325
323 def _activeeffects(ui):
326 def _activeeffects(ui):
324 '''Return the effects map for the color mode set on the ui.'''
327 '''Return the effects map for the color mode set on the ui.'''
325 if ui._colormode == b'win32':
328 if ui._colormode == b'win32':
326 return w32effects
329 return w32effects
327 elif ui._colormode is not None:
330 elif ui._colormode is not None:
328 return _effects
331 return _effects
329 return {}
332 return {}
330
333
331
334
332 def valideffect(ui, effect):
335 def valideffect(ui, effect):
333 """Determine if the effect is valid or not."""
336 """Determine if the effect is valid or not."""
334 return (not ui._terminfoparams and effect in _activeeffects(ui)) or (
337 return (not ui._terminfoparams and effect in _activeeffects(ui)) or (
335 effect in ui._terminfoparams or effect[:-11] in ui._terminfoparams
338 effect in ui._terminfoparams or effect[:-11] in ui._terminfoparams
336 )
339 )
337
340
338
341
339 def _effect_str(ui, effect):
342 def _effect_str(ui, effect):
340 '''Helper function for render_effects().'''
343 '''Helper function for render_effects().'''
341
344
342 bg = False
345 bg = False
343 if effect.endswith(b'_background'):
346 if effect.endswith(b'_background'):
344 bg = True
347 bg = True
345 effect = effect[:-11]
348 effect = effect[:-11]
346 try:
349 try:
347 attr, val, termcode = ui._terminfoparams[effect]
350 attr, val, termcode = ui._terminfoparams[effect]
348 except KeyError:
351 except KeyError:
349 return b''
352 return b''
350 if attr:
353 if attr:
351 if termcode:
354 if termcode:
352 return termcode
355 return termcode
353 else:
356 else:
354 return curses.tigetstr(pycompat.sysstr(val))
357 return curses.tigetstr(pycompat.sysstr(val))
355 elif bg:
358 elif bg:
356 return curses.tparm(curses.tigetstr('setab'), val)
359 return curses.tparm(curses.tigetstr('setab'), val)
357 else:
360 else:
358 return curses.tparm(curses.tigetstr('setaf'), val)
361 return curses.tparm(curses.tigetstr('setaf'), val)
359
362
360
363
361 def _mergeeffects(text, start, stop):
364 def _mergeeffects(text, start, stop):
362 """Insert start sequence at every occurrence of stop sequence
365 """Insert start sequence at every occurrence of stop sequence
363
366
364 >>> s = _mergeeffects(b'cyan', b'[C]', b'|')
367 >>> s = _mergeeffects(b'cyan', b'[C]', b'|')
365 >>> s = _mergeeffects(s + b'yellow', b'[Y]', b'|')
368 >>> s = _mergeeffects(s + b'yellow', b'[Y]', b'|')
366 >>> s = _mergeeffects(b'ma' + s + b'genta', b'[M]', b'|')
369 >>> s = _mergeeffects(b'ma' + s + b'genta', b'[M]', b'|')
367 >>> s = _mergeeffects(b'red' + s, b'[R]', b'|')
370 >>> s = _mergeeffects(b'red' + s, b'[R]', b'|')
368 >>> s
371 >>> s
369 '[R]red[M]ma[Y][C]cyan|[R][M][Y]yellow|[R][M]genta|'
372 '[R]red[M]ma[Y][C]cyan|[R][M][Y]yellow|[R][M]genta|'
370 """
373 """
371 parts = []
374 parts = []
372 for t in text.split(stop):
375 for t in text.split(stop):
373 if not t:
376 if not t:
374 continue
377 continue
375 parts.extend([start, t, stop])
378 parts.extend([start, t, stop])
376 return b''.join(parts)
379 return b''.join(parts)
377
380
378
381
379 def _render_effects(ui, text, effects):
382 def _render_effects(ui, text, effects):
380 """Wrap text in commands to turn on each effect."""
383 """Wrap text in commands to turn on each effect."""
381 if not text:
384 if not text:
382 return text
385 return text
383 if ui._terminfoparams:
386 if ui._terminfoparams:
384 start = b''.join(
387 start = b''.join(
385 _effect_str(ui, effect) for effect in [b'none'] + effects.split()
388 _effect_str(ui, effect) for effect in [b'none'] + effects.split()
386 )
389 )
387 stop = _effect_str(ui, b'none')
390 stop = _effect_str(ui, b'none')
388 else:
391 else:
389 activeeffects = _activeeffects(ui)
392 activeeffects = _activeeffects(ui)
390 start = [
393 start = [
391 pycompat.bytestr(activeeffects[e])
394 pycompat.bytestr(activeeffects[e])
392 for e in [b'none'] + effects.split()
395 for e in [b'none'] + effects.split()
393 ]
396 ]
394 start = b'\033[' + b';'.join(start) + b'm'
397 start = b'\033[' + b';'.join(start) + b'm'
395 stop = b'\033[' + pycompat.bytestr(activeeffects[b'none']) + b'm'
398 stop = b'\033[' + pycompat.bytestr(activeeffects[b'none']) + b'm'
396 return _mergeeffects(text, start, stop)
399 return _mergeeffects(text, start, stop)
397
400
398
401
399 _ansieffectre = re.compile(br'\x1b\[[0-9;]*m')
402 _ansieffectre = re.compile(br'\x1b\[[0-9;]*m')
400
403
401
404
402 def stripeffects(text):
405 def stripeffects(text):
403 """Strip ANSI control codes which could be inserted by colorlabel()"""
406 """Strip ANSI control codes which could be inserted by colorlabel()"""
404 return _ansieffectre.sub(b'', text)
407 return _ansieffectre.sub(b'', text)
405
408
406
409
407 def colorlabel(ui, msg, label):
410 def colorlabel(ui, msg, label):
408 """add color control code according to the mode"""
411 """add color control code according to the mode"""
409 if ui._colormode == b'debug':
412 if ui._colormode == b'debug':
410 if label and msg:
413 if label and msg:
411 if msg.endswith(b'\n'):
414 if msg.endswith(b'\n'):
412 msg = b"[%s|%s]\n" % (label, msg[:-1])
415 msg = b"[%s|%s]\n" % (label, msg[:-1])
413 else:
416 else:
414 msg = b"[%s|%s]" % (label, msg)
417 msg = b"[%s|%s]" % (label, msg)
415 elif ui._colormode is not None:
418 elif ui._colormode is not None:
416 effects = []
419 effects = []
417 for l in label.split():
420 for l in label.split():
418 s = ui._styles.get(l, b'')
421 s = ui._styles.get(l, b'')
419 if s:
422 if s:
420 effects.append(s)
423 effects.append(s)
421 elif valideffect(ui, l):
424 elif valideffect(ui, l):
422 effects.append(l)
425 effects.append(l)
423 effects = b' '.join(effects)
426 effects = b' '.join(effects)
424 if effects:
427 if effects:
425 msg = b'\n'.join(
428 msg = b'\n'.join(
426 [
429 [
427 _render_effects(ui, line, effects)
430 _render_effects(ui, line, effects)
428 for line in msg.split(b'\n')
431 for line in msg.split(b'\n')
429 ]
432 ]
430 )
433 )
431 return msg
434 return msg
432
435
433
436
434 w32effects = None
437 w32effects = None
435 if pycompat.iswindows:
438 if pycompat.iswindows:
436 import ctypes
439 import ctypes
437
440
438 _kernel32 = ctypes.windll.kernel32 # pytype: disable=module-attr
441 _kernel32 = ctypes.windll.kernel32 # pytype: disable=module-attr
439
442
440 _WORD = ctypes.c_ushort
443 _WORD = ctypes.c_ushort
441
444
442 _INVALID_HANDLE_VALUE = -1
445 _INVALID_HANDLE_VALUE = -1
443
446
444 class _COORD(ctypes.Structure):
447 class _COORD(ctypes.Structure):
445 _fields_ = [('X', ctypes.c_short), ('Y', ctypes.c_short)]
448 _fields_ = [('X', ctypes.c_short), ('Y', ctypes.c_short)]
446
449
447 class _SMALL_RECT(ctypes.Structure):
450 class _SMALL_RECT(ctypes.Structure):
448 _fields_ = [
451 _fields_ = [
449 ('Left', ctypes.c_short),
452 ('Left', ctypes.c_short),
450 ('Top', ctypes.c_short),
453 ('Top', ctypes.c_short),
451 ('Right', ctypes.c_short),
454 ('Right', ctypes.c_short),
452 ('Bottom', ctypes.c_short),
455 ('Bottom', ctypes.c_short),
453 ]
456 ]
454
457
455 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
458 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
456 _fields_ = [
459 _fields_ = [
457 ('dwSize', _COORD),
460 ('dwSize', _COORD),
458 ('dwCursorPosition', _COORD),
461 ('dwCursorPosition', _COORD),
459 ('wAttributes', _WORD),
462 ('wAttributes', _WORD),
460 ('srWindow', _SMALL_RECT),
463 ('srWindow', _SMALL_RECT),
461 ('dwMaximumWindowSize', _COORD),
464 ('dwMaximumWindowSize', _COORD),
462 ]
465 ]
463
466
464 _STD_OUTPUT_HANDLE = 0xFFFFFFF5 # (DWORD)-11
467 _STD_OUTPUT_HANDLE = 0xFFFFFFF5 # (DWORD)-11
465 _STD_ERROR_HANDLE = 0xFFFFFFF4 # (DWORD)-12
468 _STD_ERROR_HANDLE = 0xFFFFFFF4 # (DWORD)-12
466
469
467 _FOREGROUND_BLUE = 0x0001
470 _FOREGROUND_BLUE = 0x0001
468 _FOREGROUND_GREEN = 0x0002
471 _FOREGROUND_GREEN = 0x0002
469 _FOREGROUND_RED = 0x0004
472 _FOREGROUND_RED = 0x0004
470 _FOREGROUND_INTENSITY = 0x0008
473 _FOREGROUND_INTENSITY = 0x0008
471
474
472 _BACKGROUND_BLUE = 0x0010
475 _BACKGROUND_BLUE = 0x0010
473 _BACKGROUND_GREEN = 0x0020
476 _BACKGROUND_GREEN = 0x0020
474 _BACKGROUND_RED = 0x0040
477 _BACKGROUND_RED = 0x0040
475 _BACKGROUND_INTENSITY = 0x0080
478 _BACKGROUND_INTENSITY = 0x0080
476
479
477 _COMMON_LVB_REVERSE_VIDEO = 0x4000
480 _COMMON_LVB_REVERSE_VIDEO = 0x4000
478 _COMMON_LVB_UNDERSCORE = 0x8000
481 _COMMON_LVB_UNDERSCORE = 0x8000
479
482
480 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
483 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
481 w32effects = {
484 w32effects = {
482 b'none': -1,
485 b'none': -1,
483 b'black': 0,
486 b'black': 0,
484 b'red': _FOREGROUND_RED,
487 b'red': _FOREGROUND_RED,
485 b'green': _FOREGROUND_GREEN,
488 b'green': _FOREGROUND_GREEN,
486 b'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
489 b'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
487 b'blue': _FOREGROUND_BLUE,
490 b'blue': _FOREGROUND_BLUE,
488 b'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
491 b'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
489 b'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
492 b'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
490 b'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
493 b'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
491 b'bold': _FOREGROUND_INTENSITY,
494 b'bold': _FOREGROUND_INTENSITY,
492 b'black_background': 0x100, # unused value > 0x0f
495 b'black_background': 0x100, # unused value > 0x0f
493 b'red_background': _BACKGROUND_RED,
496 b'red_background': _BACKGROUND_RED,
494 b'green_background': _BACKGROUND_GREEN,
497 b'green_background': _BACKGROUND_GREEN,
495 b'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
498 b'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
496 b'blue_background': _BACKGROUND_BLUE,
499 b'blue_background': _BACKGROUND_BLUE,
497 b'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
500 b'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
498 b'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
501 b'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
499 b'white_background': (
502 b'white_background': (
500 _BACKGROUND_RED | _BACKGROUND_GREEN | _BACKGROUND_BLUE
503 _BACKGROUND_RED | _BACKGROUND_GREEN | _BACKGROUND_BLUE
501 ),
504 ),
502 b'bold_background': _BACKGROUND_INTENSITY,
505 b'bold_background': _BACKGROUND_INTENSITY,
503 b'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
506 b'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
504 b'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
507 b'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
505 }
508 }
506
509
507 passthrough = {
510 passthrough = {
508 _FOREGROUND_INTENSITY,
511 _FOREGROUND_INTENSITY,
509 _BACKGROUND_INTENSITY,
512 _BACKGROUND_INTENSITY,
510 _COMMON_LVB_UNDERSCORE,
513 _COMMON_LVB_UNDERSCORE,
511 _COMMON_LVB_REVERSE_VIDEO,
514 _COMMON_LVB_REVERSE_VIDEO,
512 }
515 }
513
516
514 stdout = _kernel32.GetStdHandle(
517 stdout = _kernel32.GetStdHandle(
515 _STD_OUTPUT_HANDLE
518 _STD_OUTPUT_HANDLE
516 ) # don't close the handle returned
519 ) # don't close the handle returned
517 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
520 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
518 w32effects = None
521 w32effects = None
519 else:
522 else:
520 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
523 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
521 if not _kernel32.GetConsoleScreenBufferInfo(stdout, ctypes.byref(csbi)):
524 if not _kernel32.GetConsoleScreenBufferInfo(stdout, ctypes.byref(csbi)):
522 # stdout may not support GetConsoleScreenBufferInfo()
525 # stdout may not support GetConsoleScreenBufferInfo()
523 # when called from subprocess or redirected
526 # when called from subprocess or redirected
524 w32effects = None
527 w32effects = None
525 else:
528 else:
526 origattr = csbi.wAttributes
529 origattr = csbi.wAttributes
527 ansire = re.compile(
530 ansire = re.compile(
528 br'\033\[([^m]*)m([^\033]*)(.*)', re.MULTILINE | re.DOTALL
531 br'\033\[([^m]*)m([^\033]*)(.*)', re.MULTILINE | re.DOTALL
529 )
532 )
530
533
531 def win32print(ui, writefunc, text, **opts):
534 def win32print(ui, writefunc, text, **opts):
532 label = opts.get('label', b'')
535 label = opts.get('label', b'')
533 attr = origattr
536 attr = origattr
534
537
535 def mapcolor(val, attr):
538 def mapcolor(val, attr):
536 if val == -1:
539 if val == -1:
537 return origattr
540 return origattr
538 elif val in passthrough:
541 elif val in passthrough:
539 return attr | val
542 return attr | val
540 elif val > 0x0F:
543 elif val > 0x0F:
541 return (val & 0x70) | (attr & 0x8F)
544 return (val & 0x70) | (attr & 0x8F)
542 else:
545 else:
543 return (val & 0x07) | (attr & 0xF8)
546 return (val & 0x07) | (attr & 0xF8)
544
547
545 # determine console attributes based on labels
548 # determine console attributes based on labels
546 for l in label.split():
549 for l in label.split():
547 style = ui._styles.get(l, b'')
550 style = ui._styles.get(l, b'')
548 for effect in style.split():
551 for effect in style.split():
549 try:
552 try:
550 attr = mapcolor(w32effects[effect], attr)
553 attr = mapcolor(w32effects[effect], attr)
551 except KeyError:
554 except KeyError:
552 # w32effects could not have certain attributes so we skip
555 # w32effects could not have certain attributes so we skip
553 # them if not found
556 # them if not found
554 pass
557 pass
555 # hack to ensure regexp finds data
558 # hack to ensure regexp finds data
556 if not text.startswith(b'\033['):
559 if not text.startswith(b'\033['):
557 text = b'\033[m' + text
560 text = b'\033[m' + text
558
561
559 # Look for ANSI-like codes embedded in text
562 # Look for ANSI-like codes embedded in text
560 m = re.match(ansire, text)
563 m = re.match(ansire, text)
561
564
562 try:
565 try:
563 while m:
566 while m:
564 for sattr in m.group(1).split(b';'):
567 for sattr in m.group(1).split(b';'):
565 if sattr:
568 if sattr:
566 attr = mapcolor(int(sattr), attr)
569 attr = mapcolor(int(sattr), attr)
567 ui.flush()
570 ui.flush()
568 _kernel32.SetConsoleTextAttribute(stdout, attr)
571 _kernel32.SetConsoleTextAttribute(stdout, attr)
569 writefunc(m.group(2))
572 writefunc(m.group(2))
570 m = re.match(ansire, m.group(3))
573 m = re.match(ansire, m.group(3))
571 finally:
574 finally:
572 # Explicitly reset original attributes
575 # Explicitly reset original attributes
573 ui.flush()
576 ui.flush()
574 _kernel32.SetConsoleTextAttribute(stdout, origattr)
577 _kernel32.SetConsoleTextAttribute(stdout, origattr)
@@ -1,1387 +1,1393 b''
1 # upgrade.py - functions for in place upgrade of Mercurial repository
1 # upgrade.py - functions for in place upgrade of Mercurial repository
2 #
2 #
3 # Copyright (c) 2016-present, Gregory Szorc
3 # Copyright (c) 2016-present, Gregory Szorc
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import stat
10 import stat
11
11
12 from .i18n import _
12 from .i18n import _
13 from .pycompat import getattr
13 from .pycompat import getattr
14 from . import (
14 from . import (
15 changelog,
15 changelog,
16 copies,
16 copies,
17 error,
17 error,
18 filelog,
18 filelog,
19 hg,
19 hg,
20 localrepo,
20 localrepo,
21 manifest,
21 manifest,
22 pycompat,
22 pycompat,
23 revlog,
23 revlog,
24 scmutil,
24 scmutil,
25 util,
25 util,
26 vfs as vfsmod,
26 vfs as vfsmod,
27 )
27 )
28
28
29 from .utils import compression
29 from .utils import compression
30
30
31 # list of requirements that request a clone of all revlog if added/removed
31 # list of requirements that request a clone of all revlog if added/removed
32 RECLONES_REQUIREMENTS = {
32 RECLONES_REQUIREMENTS = {
33 b'generaldelta',
33 b'generaldelta',
34 localrepo.SPARSEREVLOG_REQUIREMENT,
34 localrepo.SPARSEREVLOG_REQUIREMENT,
35 }
35 }
36
36
37
37
38 def requiredsourcerequirements(repo):
38 def requiredsourcerequirements(repo):
39 """Obtain requirements required to be present to upgrade a repo.
39 """Obtain requirements required to be present to upgrade a repo.
40
40
41 An upgrade will not be allowed if the repository doesn't have the
41 An upgrade will not be allowed if the repository doesn't have the
42 requirements returned by this function.
42 requirements returned by this function.
43 """
43 """
44 return {
44 return {
45 # Introduced in Mercurial 0.9.2.
45 # Introduced in Mercurial 0.9.2.
46 b'revlogv1',
46 b'revlogv1',
47 # Introduced in Mercurial 0.9.2.
47 # Introduced in Mercurial 0.9.2.
48 b'store',
48 b'store',
49 }
49 }
50
50
51
51
52 def blocksourcerequirements(repo):
52 def blocksourcerequirements(repo):
53 """Obtain requirements that will prevent an upgrade from occurring.
53 """Obtain requirements that will prevent an upgrade from occurring.
54
54
55 An upgrade cannot be performed if the source repository contains a
55 An upgrade cannot be performed if the source repository contains a
56 requirements in the returned set.
56 requirements in the returned set.
57 """
57 """
58 return {
58 return {
59 # The upgrade code does not yet support these experimental features.
59 # The upgrade code does not yet support these experimental features.
60 # This is an artificial limitation.
60 # This is an artificial limitation.
61 b'treemanifest',
61 b'treemanifest',
62 # This was a precursor to generaldelta and was never enabled by default.
62 # This was a precursor to generaldelta and was never enabled by default.
63 # It should (hopefully) not exist in the wild.
63 # It should (hopefully) not exist in the wild.
64 b'parentdelta',
64 b'parentdelta',
65 # Upgrade should operate on the actual store, not the shared link.
65 # Upgrade should operate on the actual store, not the shared link.
66 b'shared',
66 b'shared',
67 }
67 }
68
68
69
69
70 def supportremovedrequirements(repo):
70 def supportremovedrequirements(repo):
71 """Obtain requirements that can be removed during an upgrade.
71 """Obtain requirements that can be removed during an upgrade.
72
72
73 If an upgrade were to create a repository that dropped a requirement,
73 If an upgrade were to create a repository that dropped a requirement,
74 the dropped requirement must appear in the returned set for the upgrade
74 the dropped requirement must appear in the returned set for the upgrade
75 to be allowed.
75 to be allowed.
76 """
76 """
77 supported = {
77 supported = {
78 localrepo.SPARSEREVLOG_REQUIREMENT,
78 localrepo.SPARSEREVLOG_REQUIREMENT,
79 localrepo.SIDEDATA_REQUIREMENT,
79 localrepo.SIDEDATA_REQUIREMENT,
80 localrepo.COPIESSDC_REQUIREMENT,
80 localrepo.COPIESSDC_REQUIREMENT,
81 }
81 }
82 for name in compression.compengines:
82 for name in compression.compengines:
83 engine = compression.compengines[name]
83 engine = compression.compengines[name]
84 if engine.available() and engine.revlogheader():
84 if engine.available() and engine.revlogheader():
85 supported.add(b'exp-compression-%s' % name)
85 supported.add(b'exp-compression-%s' % name)
86 if engine.name() == b'zstd':
86 if engine.name() == b'zstd':
87 supported.add(b'revlog-compression-zstd')
87 supported.add(b'revlog-compression-zstd')
88 return supported
88 return supported
89
89
90
90
91 def supporteddestrequirements(repo):
91 def supporteddestrequirements(repo):
92 """Obtain requirements that upgrade supports in the destination.
92 """Obtain requirements that upgrade supports in the destination.
93
93
94 If the result of the upgrade would create requirements not in this set,
94 If the result of the upgrade would create requirements not in this set,
95 the upgrade is disallowed.
95 the upgrade is disallowed.
96
96
97 Extensions should monkeypatch this to add their custom requirements.
97 Extensions should monkeypatch this to add their custom requirements.
98 """
98 """
99 supported = {
99 supported = {
100 b'dotencode',
100 b'dotencode',
101 b'fncache',
101 b'fncache',
102 b'generaldelta',
102 b'generaldelta',
103 b'revlogv1',
103 b'revlogv1',
104 b'store',
104 b'store',
105 localrepo.SPARSEREVLOG_REQUIREMENT,
105 localrepo.SPARSEREVLOG_REQUIREMENT,
106 localrepo.SIDEDATA_REQUIREMENT,
106 localrepo.SIDEDATA_REQUIREMENT,
107 localrepo.COPIESSDC_REQUIREMENT,
107 localrepo.COPIESSDC_REQUIREMENT,
108 }
108 }
109 for name in compression.compengines:
109 for name in compression.compengines:
110 engine = compression.compengines[name]
110 engine = compression.compengines[name]
111 if engine.available() and engine.revlogheader():
111 if engine.available() and engine.revlogheader():
112 supported.add(b'exp-compression-%s' % name)
112 supported.add(b'exp-compression-%s' % name)
113 if engine.name() == b'zstd':
113 if engine.name() == b'zstd':
114 supported.add(b'revlog-compression-zstd')
114 supported.add(b'revlog-compression-zstd')
115 return supported
115 return supported
116
116
117
117
118 def allowednewrequirements(repo):
118 def allowednewrequirements(repo):
119 """Obtain requirements that can be added to a repository during upgrade.
119 """Obtain requirements that can be added to a repository during upgrade.
120
120
121 This is used to disallow proposed requirements from being added when
121 This is used to disallow proposed requirements from being added when
122 they weren't present before.
122 they weren't present before.
123
123
124 We use a list of allowed requirement additions instead of a list of known
124 We use a list of allowed requirement additions instead of a list of known
125 bad additions because the whitelist approach is safer and will prevent
125 bad additions because the whitelist approach is safer and will prevent
126 future, unknown requirements from accidentally being added.
126 future, unknown requirements from accidentally being added.
127 """
127 """
128 supported = {
128 supported = {
129 b'dotencode',
129 b'dotencode',
130 b'fncache',
130 b'fncache',
131 b'generaldelta',
131 b'generaldelta',
132 localrepo.SPARSEREVLOG_REQUIREMENT,
132 localrepo.SPARSEREVLOG_REQUIREMENT,
133 localrepo.SIDEDATA_REQUIREMENT,
133 localrepo.SIDEDATA_REQUIREMENT,
134 localrepo.COPIESSDC_REQUIREMENT,
134 localrepo.COPIESSDC_REQUIREMENT,
135 }
135 }
136 for name in compression.compengines:
136 for name in compression.compengines:
137 engine = compression.compengines[name]
137 engine = compression.compengines[name]
138 if engine.available() and engine.revlogheader():
138 if engine.available() and engine.revlogheader():
139 supported.add(b'exp-compression-%s' % name)
139 supported.add(b'exp-compression-%s' % name)
140 if engine.name() == b'zstd':
140 if engine.name() == b'zstd':
141 supported.add(b'revlog-compression-zstd')
141 supported.add(b'revlog-compression-zstd')
142 return supported
142 return supported
143
143
144
144
145 def preservedrequirements(repo):
145 def preservedrequirements(repo):
146 return set()
146 return set()
147
147
148
148
149 deficiency = b'deficiency'
149 deficiency = b'deficiency'
150 optimisation = b'optimization'
150 optimisation = b'optimization'
151
151
152
152
153 class improvement(object):
153 class improvement(object):
154 """Represents an improvement that can be made as part of an upgrade.
154 """Represents an improvement that can be made as part of an upgrade.
155
155
156 The following attributes are defined on each instance:
156 The following attributes are defined on each instance:
157
157
158 name
158 name
159 Machine-readable string uniquely identifying this improvement. It
159 Machine-readable string uniquely identifying this improvement. It
160 will be mapped to an action later in the upgrade process.
160 will be mapped to an action later in the upgrade process.
161
161
162 type
162 type
163 Either ``deficiency`` or ``optimisation``. A deficiency is an obvious
163 Either ``deficiency`` or ``optimisation``. A deficiency is an obvious
164 problem. An optimization is an action (sometimes optional) that
164 problem. An optimization is an action (sometimes optional) that
165 can be taken to further improve the state of the repository.
165 can be taken to further improve the state of the repository.
166
166
167 description
167 description
168 Message intended for humans explaining the improvement in more detail,
168 Message intended for humans explaining the improvement in more detail,
169 including the implications of it. For ``deficiency`` types, should be
169 including the implications of it. For ``deficiency`` types, should be
170 worded in the present tense. For ``optimisation`` types, should be
170 worded in the present tense. For ``optimisation`` types, should be
171 worded in the future tense.
171 worded in the future tense.
172
172
173 upgrademessage
173 upgrademessage
174 Message intended for humans explaining what an upgrade addressing this
174 Message intended for humans explaining what an upgrade addressing this
175 issue will do. Should be worded in the future tense.
175 issue will do. Should be worded in the future tense.
176 """
176 """
177
177
178 def __init__(self, name, type, description, upgrademessage):
178 def __init__(self, name, type, description, upgrademessage):
179 self.name = name
179 self.name = name
180 self.type = type
180 self.type = type
181 self.description = description
181 self.description = description
182 self.upgrademessage = upgrademessage
182 self.upgrademessage = upgrademessage
183
183
184 def __eq__(self, other):
184 def __eq__(self, other):
185 if not isinstance(other, improvement):
185 if not isinstance(other, improvement):
186 # This is what python tell use to do
186 # This is what python tell use to do
187 return NotImplemented
187 return NotImplemented
188 return self.name == other.name
188 return self.name == other.name
189
189
190 def __ne__(self, other):
190 def __ne__(self, other):
191 return not (self == other)
191 return not (self == other)
192
192
193 def __hash__(self):
193 def __hash__(self):
194 return hash(self.name)
194 return hash(self.name)
195
195
196
196
197 allformatvariant = []
197 allformatvariant = []
198
198
199
199
200 def registerformatvariant(cls):
200 def registerformatvariant(cls):
201 allformatvariant.append(cls)
201 allformatvariant.append(cls)
202 return cls
202 return cls
203
203
204
204
205 class formatvariant(improvement):
205 class formatvariant(improvement):
206 """an improvement subclass dedicated to repository format"""
206 """an improvement subclass dedicated to repository format"""
207
207
208 type = deficiency
208 type = deficiency
209 ### The following attributes should be defined for each class:
209 ### The following attributes should be defined for each class:
210
210
211 # machine-readable string uniquely identifying this improvement. it will be
211 # machine-readable string uniquely identifying this improvement. it will be
212 # mapped to an action later in the upgrade process.
212 # mapped to an action later in the upgrade process.
213 name = None
213 name = None
214
214
215 # message intended for humans explaining the improvement in more detail,
215 # message intended for humans explaining the improvement in more detail,
216 # including the implications of it ``deficiency`` types, should be worded
216 # including the implications of it ``deficiency`` types, should be worded
217 # in the present tense.
217 # in the present tense.
218 description = None
218 description = None
219
219
220 # message intended for humans explaining what an upgrade addressing this
220 # message intended for humans explaining what an upgrade addressing this
221 # issue will do. should be worded in the future tense.
221 # issue will do. should be worded in the future tense.
222 upgrademessage = None
222 upgrademessage = None
223
223
224 # value of current Mercurial default for new repository
224 # value of current Mercurial default for new repository
225 default = None
225 default = None
226
226
227 def __init__(self):
227 def __init__(self):
228 raise NotImplementedError()
228 raise NotImplementedError()
229
229
230 @staticmethod
230 @staticmethod
231 def fromrepo(repo):
231 def fromrepo(repo):
232 """current value of the variant in the repository"""
232 """current value of the variant in the repository"""
233 raise NotImplementedError()
233 raise NotImplementedError()
234
234
235 @staticmethod
235 @staticmethod
236 def fromconfig(repo):
236 def fromconfig(repo):
237 """current value of the variant in the configuration"""
237 """current value of the variant in the configuration"""
238 raise NotImplementedError()
238 raise NotImplementedError()
239
239
240
240
241 class requirementformatvariant(formatvariant):
241 class requirementformatvariant(formatvariant):
242 """formatvariant based on a 'requirement' name.
242 """formatvariant based on a 'requirement' name.
243
243
244 Many format variant are controlled by a 'requirement'. We define a small
244 Many format variant are controlled by a 'requirement'. We define a small
245 subclass to factor the code.
245 subclass to factor the code.
246 """
246 """
247
247
248 # the requirement that control this format variant
248 # the requirement that control this format variant
249 _requirement = None
249 _requirement = None
250
250
251 @staticmethod
251 @staticmethod
252 def _newreporequirements(ui):
252 def _newreporequirements(ui):
253 return localrepo.newreporequirements(
253 return localrepo.newreporequirements(
254 ui, localrepo.defaultcreateopts(ui)
254 ui, localrepo.defaultcreateopts(ui)
255 )
255 )
256
256
257 @classmethod
257 @classmethod
258 def fromrepo(cls, repo):
258 def fromrepo(cls, repo):
259 assert cls._requirement is not None
259 assert cls._requirement is not None
260 return cls._requirement in repo.requirements
260 return cls._requirement in repo.requirements
261
261
262 @classmethod
262 @classmethod
263 def fromconfig(cls, repo):
263 def fromconfig(cls, repo):
264 assert cls._requirement is not None
264 assert cls._requirement is not None
265 return cls._requirement in cls._newreporequirements(repo.ui)
265 return cls._requirement in cls._newreporequirements(repo.ui)
266
266
267
267
268 @registerformatvariant
268 @registerformatvariant
269 class fncache(requirementformatvariant):
269 class fncache(requirementformatvariant):
270 name = b'fncache'
270 name = b'fncache'
271
271
272 _requirement = b'fncache'
272 _requirement = b'fncache'
273
273
274 default = True
274 default = True
275
275
276 description = _(
276 description = _(
277 b'long and reserved filenames may not work correctly; '
277 b'long and reserved filenames may not work correctly; '
278 b'repository performance is sub-optimal'
278 b'repository performance is sub-optimal'
279 )
279 )
280
280
281 upgrademessage = _(
281 upgrademessage = _(
282 b'repository will be more resilient to storing '
282 b'repository will be more resilient to storing '
283 b'certain paths and performance of certain '
283 b'certain paths and performance of certain '
284 b'operations should be improved'
284 b'operations should be improved'
285 )
285 )
286
286
287
287
288 @registerformatvariant
288 @registerformatvariant
289 class dotencode(requirementformatvariant):
289 class dotencode(requirementformatvariant):
290 name = b'dotencode'
290 name = b'dotencode'
291
291
292 _requirement = b'dotencode'
292 _requirement = b'dotencode'
293
293
294 default = True
294 default = True
295
295
296 description = _(
296 description = _(
297 b'storage of filenames beginning with a period or '
297 b'storage of filenames beginning with a period or '
298 b'space may not work correctly'
298 b'space may not work correctly'
299 )
299 )
300
300
301 upgrademessage = _(
301 upgrademessage = _(
302 b'repository will be better able to store files '
302 b'repository will be better able to store files '
303 b'beginning with a space or period'
303 b'beginning with a space or period'
304 )
304 )
305
305
306
306
307 @registerformatvariant
307 @registerformatvariant
308 class generaldelta(requirementformatvariant):
308 class generaldelta(requirementformatvariant):
309 name = b'generaldelta'
309 name = b'generaldelta'
310
310
311 _requirement = b'generaldelta'
311 _requirement = b'generaldelta'
312
312
313 default = True
313 default = True
314
314
315 description = _(
315 description = _(
316 b'deltas within internal storage are unable to '
316 b'deltas within internal storage are unable to '
317 b'choose optimal revisions; repository is larger and '
317 b'choose optimal revisions; repository is larger and '
318 b'slower than it could be; interaction with other '
318 b'slower than it could be; interaction with other '
319 b'repositories may require extra network and CPU '
319 b'repositories may require extra network and CPU '
320 b'resources, making "hg push" and "hg pull" slower'
320 b'resources, making "hg push" and "hg pull" slower'
321 )
321 )
322
322
323 upgrademessage = _(
323 upgrademessage = _(
324 b'repository storage will be able to create '
324 b'repository storage will be able to create '
325 b'optimal deltas; new repository data will be '
325 b'optimal deltas; new repository data will be '
326 b'smaller and read times should decrease; '
326 b'smaller and read times should decrease; '
327 b'interacting with other repositories using this '
327 b'interacting with other repositories using this '
328 b'storage model should require less network and '
328 b'storage model should require less network and '
329 b'CPU resources, making "hg push" and "hg pull" '
329 b'CPU resources, making "hg push" and "hg pull" '
330 b'faster'
330 b'faster'
331 )
331 )
332
332
333
333
334 @registerformatvariant
334 @registerformatvariant
335 class sparserevlog(requirementformatvariant):
335 class sparserevlog(requirementformatvariant):
336 name = b'sparserevlog'
336 name = b'sparserevlog'
337
337
338 _requirement = localrepo.SPARSEREVLOG_REQUIREMENT
338 _requirement = localrepo.SPARSEREVLOG_REQUIREMENT
339
339
340 default = True
340 default = True
341
341
342 description = _(
342 description = _(
343 b'in order to limit disk reading and memory usage on older '
343 b'in order to limit disk reading and memory usage on older '
344 b'version, the span of a delta chain from its root to its '
344 b'version, the span of a delta chain from its root to its '
345 b'end is limited, whatever the relevant data in this span. '
345 b'end is limited, whatever the relevant data in this span. '
346 b'This can severly limit Mercurial ability to build good '
346 b'This can severly limit Mercurial ability to build good '
347 b'chain of delta resulting is much more storage space being '
347 b'chain of delta resulting is much more storage space being '
348 b'taken and limit reusability of on disk delta during '
348 b'taken and limit reusability of on disk delta during '
349 b'exchange.'
349 b'exchange.'
350 )
350 )
351
351
352 upgrademessage = _(
352 upgrademessage = _(
353 b'Revlog supports delta chain with more unused data '
353 b'Revlog supports delta chain with more unused data '
354 b'between payload. These gaps will be skipped at read '
354 b'between payload. These gaps will be skipped at read '
355 b'time. This allows for better delta chains, making a '
355 b'time. This allows for better delta chains, making a '
356 b'better compression and faster exchange with server.'
356 b'better compression and faster exchange with server.'
357 )
357 )
358
358
359
359
360 @registerformatvariant
360 @registerformatvariant
361 class sidedata(requirementformatvariant):
361 class sidedata(requirementformatvariant):
362 name = b'sidedata'
362 name = b'sidedata'
363
363
364 _requirement = localrepo.SIDEDATA_REQUIREMENT
364 _requirement = localrepo.SIDEDATA_REQUIREMENT
365
365
366 default = False
366 default = False
367
367
368 description = _(
368 description = _(
369 b'Allows storage of extra data alongside a revision, '
369 b'Allows storage of extra data alongside a revision, '
370 b'unlocking various caching options.'
370 b'unlocking various caching options.'
371 )
371 )
372
372
373 upgrademessage = _(b'Allows storage of extra data alongside a revision.')
373 upgrademessage = _(b'Allows storage of extra data alongside a revision.')
374
374
375
375
376 @registerformatvariant
376 @registerformatvariant
377 class copiessdc(requirementformatvariant):
377 class copiessdc(requirementformatvariant):
378 name = b'copies-sdc'
378 name = b'copies-sdc'
379
379
380 _requirement = localrepo.COPIESSDC_REQUIREMENT
380 _requirement = localrepo.COPIESSDC_REQUIREMENT
381
381
382 default = False
382 default = False
383
383
384 description = _(b'Stores copies information alongside changesets.')
384 description = _(b'Stores copies information alongside changesets.')
385
385
386 upgrademessage = _(
386 upgrademessage = _(
387 b'Allows to use more efficient algorithm to deal with ' b'copy tracing.'
387 b'Allows to use more efficient algorithm to deal with ' b'copy tracing.'
388 )
388 )
389
389
390
390
391 @registerformatvariant
391 @registerformatvariant
392 class removecldeltachain(formatvariant):
392 class removecldeltachain(formatvariant):
393 name = b'plain-cl-delta'
393 name = b'plain-cl-delta'
394
394
395 default = True
395 default = True
396
396
397 description = _(
397 description = _(
398 b'changelog storage is using deltas instead of '
398 b'changelog storage is using deltas instead of '
399 b'raw entries; changelog reading and any '
399 b'raw entries; changelog reading and any '
400 b'operation relying on changelog data are slower '
400 b'operation relying on changelog data are slower '
401 b'than they could be'
401 b'than they could be'
402 )
402 )
403
403
404 upgrademessage = _(
404 upgrademessage = _(
405 b'changelog storage will be reformated to '
405 b'changelog storage will be reformated to '
406 b'store raw entries; changelog reading will be '
406 b'store raw entries; changelog reading will be '
407 b'faster; changelog size may be reduced'
407 b'faster; changelog size may be reduced'
408 )
408 )
409
409
410 @staticmethod
410 @staticmethod
411 def fromrepo(repo):
411 def fromrepo(repo):
412 # Mercurial 4.0 changed changelogs to not use delta chains. Search for
412 # Mercurial 4.0 changed changelogs to not use delta chains. Search for
413 # changelogs with deltas.
413 # changelogs with deltas.
414 cl = repo.changelog
414 cl = repo.changelog
415 chainbase = cl.chainbase
415 chainbase = cl.chainbase
416 return all(rev == chainbase(rev) for rev in cl)
416 return all(rev == chainbase(rev) for rev in cl)
417
417
418 @staticmethod
418 @staticmethod
419 def fromconfig(repo):
419 def fromconfig(repo):
420 return True
420 return True
421
421
422
422
423 @registerformatvariant
423 @registerformatvariant
424 class compressionengine(formatvariant):
424 class compressionengine(formatvariant):
425 name = b'compression'
425 name = b'compression'
426 default = b'zlib'
426 default = b'zlib'
427
427
428 description = _(
428 description = _(
429 b'Compresion algorithm used to compress data. '
429 b'Compresion algorithm used to compress data. '
430 b'Some engine are faster than other'
430 b'Some engine are faster than other'
431 )
431 )
432
432
433 upgrademessage = _(
433 upgrademessage = _(
434 b'revlog content will be recompressed with the new algorithm.'
434 b'revlog content will be recompressed with the new algorithm.'
435 )
435 )
436
436
437 @classmethod
437 @classmethod
438 def fromrepo(cls, repo):
438 def fromrepo(cls, repo):
439 # we allow multiple compression engine requirement to co-exist because
439 # we allow multiple compression engine requirement to co-exist because
440 # strickly speaking, revlog seems to support mixed compression style.
440 # strickly speaking, revlog seems to support mixed compression style.
441 #
441 #
442 # The compression used for new entries will be "the last one"
442 # The compression used for new entries will be "the last one"
443 compression = b'zlib'
443 compression = b'zlib'
444 for req in repo.requirements:
444 for req in repo.requirements:
445 prefix = req.startswith
445 prefix = req.startswith
446 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
446 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
447 compression = req.split(b'-', 2)[2]
447 compression = req.split(b'-', 2)[2]
448 return compression
448 return compression
449
449
450 @classmethod
450 @classmethod
451 def fromconfig(cls, repo):
451 def fromconfig(cls, repo):
452 return repo.ui.config(b'format', b'revlog-compression')
452 return repo.ui.config(b'format', b'revlog-compression')
453
453
454
454
455 @registerformatvariant
455 @registerformatvariant
456 class compressionlevel(formatvariant):
456 class compressionlevel(formatvariant):
457 name = b'compression-level'
457 name = b'compression-level'
458 default = b'default'
458 default = b'default'
459
459
460 description = _(b'compression level')
460 description = _(b'compression level')
461
461
462 upgrademessage = _(b'revlog content will be recompressed')
462 upgrademessage = _(b'revlog content will be recompressed')
463
463
464 @classmethod
464 @classmethod
465 def fromrepo(cls, repo):
465 def fromrepo(cls, repo):
466 comp = compressionengine.fromrepo(repo)
466 comp = compressionengine.fromrepo(repo)
467 level = None
467 level = None
468 if comp == b'zlib':
468 if comp == b'zlib':
469 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
469 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
470 elif comp == b'zstd':
470 elif comp == b'zstd':
471 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
471 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
472 if level is None:
472 if level is None:
473 return b'default'
473 return b'default'
474 return bytes(level)
474 return bytes(level)
475
475
476 @classmethod
476 @classmethod
477 def fromconfig(cls, repo):
477 def fromconfig(cls, repo):
478 comp = compressionengine.fromconfig(repo)
478 comp = compressionengine.fromconfig(repo)
479 level = None
479 level = None
480 if comp == b'zlib':
480 if comp == b'zlib':
481 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
481 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
482 elif comp == b'zstd':
482 elif comp == b'zstd':
483 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
483 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
484 if level is None:
484 if level is None:
485 return b'default'
485 return b'default'
486 return bytes(level)
486 return bytes(level)
487
487
488
488
489 def finddeficiencies(repo):
489 def finddeficiencies(repo):
490 """returns a list of deficiencies that the repo suffer from"""
490 """returns a list of deficiencies that the repo suffer from"""
491 deficiencies = []
491 deficiencies = []
492
492
493 # We could detect lack of revlogv1 and store here, but they were added
493 # We could detect lack of revlogv1 and store here, but they were added
494 # in 0.9.2 and we don't support upgrading repos without these
494 # in 0.9.2 and we don't support upgrading repos without these
495 # requirements, so let's not bother.
495 # requirements, so let's not bother.
496
496
497 for fv in allformatvariant:
497 for fv in allformatvariant:
498 if not fv.fromrepo(repo):
498 if not fv.fromrepo(repo):
499 deficiencies.append(fv)
499 deficiencies.append(fv)
500
500
501 return deficiencies
501 return deficiencies
502
502
503
503
504 # search without '-' to support older form on newer client.
504 # search without '-' to support older form on newer client.
505 #
505 #
506 # We don't enforce backward compatibility for debug command so this
506 # We don't enforce backward compatibility for debug command so this
507 # might eventually be dropped. However, having to use two different
507 # might eventually be dropped. However, having to use two different
508 # forms in script when comparing result is anoying enough to add
508 # forms in script when comparing result is anoying enough to add
509 # backward compatibility for a while.
509 # backward compatibility for a while.
510 legacy_opts_map = {
510 legacy_opts_map = {
511 b'redeltaparent': b're-delta-parent',
511 b'redeltaparent': b're-delta-parent',
512 b'redeltamultibase': b're-delta-multibase',
512 b'redeltamultibase': b're-delta-multibase',
513 b'redeltaall': b're-delta-all',
513 b'redeltaall': b're-delta-all',
514 b'redeltafulladd': b're-delta-fulladd',
514 b'redeltafulladd': b're-delta-fulladd',
515 }
515 }
516
516
517
517
518 def findoptimizations(repo):
518 def findoptimizations(repo):
519 """Determine optimisation that could be used during upgrade"""
519 """Determine optimisation that could be used during upgrade"""
520 # These are unconditionally added. There is logic later that figures out
520 # These are unconditionally added. There is logic later that figures out
521 # which ones to apply.
521 # which ones to apply.
522 optimizations = []
522 optimizations = []
523
523
524 optimizations.append(
524 optimizations.append(
525 improvement(
525 improvement(
526 name=b're-delta-parent',
526 name=b're-delta-parent',
527 type=optimisation,
527 type=optimisation,
528 description=_(
528 description=_(
529 b'deltas within internal storage will be recalculated to '
529 b'deltas within internal storage will be recalculated to '
530 b'choose an optimal base revision where this was not '
530 b'choose an optimal base revision where this was not '
531 b'already done; the size of the repository may shrink and '
531 b'already done; the size of the repository may shrink and '
532 b'various operations may become faster; the first time '
532 b'various operations may become faster; the first time '
533 b'this optimization is performed could slow down upgrade '
533 b'this optimization is performed could slow down upgrade '
534 b'execution considerably; subsequent invocations should '
534 b'execution considerably; subsequent invocations should '
535 b'not run noticeably slower'
535 b'not run noticeably slower'
536 ),
536 ),
537 upgrademessage=_(
537 upgrademessage=_(
538 b'deltas within internal storage will choose a new '
538 b'deltas within internal storage will choose a new '
539 b'base revision if needed'
539 b'base revision if needed'
540 ),
540 ),
541 )
541 )
542 )
542 )
543
543
544 optimizations.append(
544 optimizations.append(
545 improvement(
545 improvement(
546 name=b're-delta-multibase',
546 name=b're-delta-multibase',
547 type=optimisation,
547 type=optimisation,
548 description=_(
548 description=_(
549 b'deltas within internal storage will be recalculated '
549 b'deltas within internal storage will be recalculated '
550 b'against multiple base revision and the smallest '
550 b'against multiple base revision and the smallest '
551 b'difference will be used; the size of the repository may '
551 b'difference will be used; the size of the repository may '
552 b'shrink significantly when there are many merges; this '
552 b'shrink significantly when there are many merges; this '
553 b'optimization will slow down execution in proportion to '
553 b'optimization will slow down execution in proportion to '
554 b'the number of merges in the repository and the amount '
554 b'the number of merges in the repository and the amount '
555 b'of files in the repository; this slow down should not '
555 b'of files in the repository; this slow down should not '
556 b'be significant unless there are tens of thousands of '
556 b'be significant unless there are tens of thousands of '
557 b'files and thousands of merges'
557 b'files and thousands of merges'
558 ),
558 ),
559 upgrademessage=_(
559 upgrademessage=_(
560 b'deltas within internal storage will choose an '
560 b'deltas within internal storage will choose an '
561 b'optimal delta by computing deltas against multiple '
561 b'optimal delta by computing deltas against multiple '
562 b'parents; may slow down execution time '
562 b'parents; may slow down execution time '
563 b'significantly'
563 b'significantly'
564 ),
564 ),
565 )
565 )
566 )
566 )
567
567
568 optimizations.append(
568 optimizations.append(
569 improvement(
569 improvement(
570 name=b're-delta-all',
570 name=b're-delta-all',
571 type=optimisation,
571 type=optimisation,
572 description=_(
572 description=_(
573 b'deltas within internal storage will always be '
573 b'deltas within internal storage will always be '
574 b'recalculated without reusing prior deltas; this will '
574 b'recalculated without reusing prior deltas; this will '
575 b'likely make execution run several times slower; this '
575 b'likely make execution run several times slower; this '
576 b'optimization is typically not needed'
576 b'optimization is typically not needed'
577 ),
577 ),
578 upgrademessage=_(
578 upgrademessage=_(
579 b'deltas within internal storage will be fully '
579 b'deltas within internal storage will be fully '
580 b'recomputed; this will likely drastically slow down '
580 b'recomputed; this will likely drastically slow down '
581 b'execution time'
581 b'execution time'
582 ),
582 ),
583 )
583 )
584 )
584 )
585
585
586 optimizations.append(
586 optimizations.append(
587 improvement(
587 improvement(
588 name=b're-delta-fulladd',
588 name=b're-delta-fulladd',
589 type=optimisation,
589 type=optimisation,
590 description=_(
590 description=_(
591 b'every revision will be re-added as if it was new '
591 b'every revision will be re-added as if it was new '
592 b'content. It will go through the full storage '
592 b'content. It will go through the full storage '
593 b'mechanism giving extensions a chance to process it '
593 b'mechanism giving extensions a chance to process it '
594 b'(eg. lfs). This is similar to "re-delta-all" but even '
594 b'(eg. lfs). This is similar to "re-delta-all" but even '
595 b'slower since more logic is involved.'
595 b'slower since more logic is involved.'
596 ),
596 ),
597 upgrademessage=_(
597 upgrademessage=_(
598 b'each revision will be added as new content to the '
598 b'each revision will be added as new content to the '
599 b'internal storage; this will likely drastically slow '
599 b'internal storage; this will likely drastically slow '
600 b'down execution time, but some extensions might need '
600 b'down execution time, but some extensions might need '
601 b'it'
601 b'it'
602 ),
602 ),
603 )
603 )
604 )
604 )
605
605
606 return optimizations
606 return optimizations
607
607
608
608
609 def determineactions(repo, deficiencies, sourcereqs, destreqs):
609 def determineactions(repo, deficiencies, sourcereqs, destreqs):
610 """Determine upgrade actions that will be performed.
610 """Determine upgrade actions that will be performed.
611
611
612 Given a list of improvements as returned by ``finddeficiencies`` and
612 Given a list of improvements as returned by ``finddeficiencies`` and
613 ``findoptimizations``, determine the list of upgrade actions that
613 ``findoptimizations``, determine the list of upgrade actions that
614 will be performed.
614 will be performed.
615
615
616 The role of this function is to filter improvements if needed, apply
616 The role of this function is to filter improvements if needed, apply
617 recommended optimizations from the improvements list that make sense,
617 recommended optimizations from the improvements list that make sense,
618 etc.
618 etc.
619
619
620 Returns a list of action names.
620 Returns a list of action names.
621 """
621 """
622 newactions = []
622 newactions = []
623
623
624 knownreqs = supporteddestrequirements(repo)
624 knownreqs = supporteddestrequirements(repo)
625
625
626 for d in deficiencies:
626 for d in deficiencies:
627 name = d.name
627 name = d.name
628
628
629 # If the action is a requirement that doesn't show up in the
629 # If the action is a requirement that doesn't show up in the
630 # destination requirements, prune the action.
630 # destination requirements, prune the action.
631 if name in knownreqs and name not in destreqs:
631 if name in knownreqs and name not in destreqs:
632 continue
632 continue
633
633
634 newactions.append(d)
634 newactions.append(d)
635
635
636 # FUTURE consider adding some optimizations here for certain transitions.
636 # FUTURE consider adding some optimizations here for certain transitions.
637 # e.g. adding generaldelta could schedule parent redeltas.
637 # e.g. adding generaldelta could schedule parent redeltas.
638
638
639 return newactions
639 return newactions
640
640
641
641
642 def _revlogfrompath(repo, path):
642 def _revlogfrompath(repo, path):
643 """Obtain a revlog from a repo path.
643 """Obtain a revlog from a repo path.
644
644
645 An instance of the appropriate class is returned.
645 An instance of the appropriate class is returned.
646 """
646 """
647 if path == b'00changelog.i':
647 if path == b'00changelog.i':
648 return changelog.changelog(repo.svfs)
648 return changelog.changelog(repo.svfs)
649 elif path.endswith(b'00manifest.i'):
649 elif path.endswith(b'00manifest.i'):
650 mandir = path[: -len(b'00manifest.i')]
650 mandir = path[: -len(b'00manifest.i')]
651 return manifest.manifestrevlog(repo.svfs, tree=mandir)
651 return manifest.manifestrevlog(repo.svfs, tree=mandir)
652 else:
652 else:
653 # reverse of "/".join(("data", path + ".i"))
653 # reverse of "/".join(("data", path + ".i"))
654 return filelog.filelog(repo.svfs, path[5:-2])
654 return filelog.filelog(repo.svfs, path[5:-2])
655
655
656
656
657 def _copyrevlog(tr, destrepo, oldrl, unencodedname):
657 def _copyrevlog(tr, destrepo, oldrl, unencodedname):
658 """copy all relevant files for `oldrl` into `destrepo` store
658 """copy all relevant files for `oldrl` into `destrepo` store
659
659
660 Files are copied "as is" without any transformation. The copy is performed
660 Files are copied "as is" without any transformation. The copy is performed
661 without extra checks. Callers are responsible for making sure the copied
661 without extra checks. Callers are responsible for making sure the copied
662 content is compatible with format of the destination repository.
662 content is compatible with format of the destination repository.
663 """
663 """
664 oldrl = getattr(oldrl, '_revlog', oldrl)
664 oldrl = getattr(oldrl, '_revlog', oldrl)
665 newrl = _revlogfrompath(destrepo, unencodedname)
665 newrl = _revlogfrompath(destrepo, unencodedname)
666 newrl = getattr(newrl, '_revlog', newrl)
666 newrl = getattr(newrl, '_revlog', newrl)
667
667
668 oldvfs = oldrl.opener
668 oldvfs = oldrl.opener
669 newvfs = newrl.opener
669 newvfs = newrl.opener
670 oldindex = oldvfs.join(oldrl.indexfile)
670 oldindex = oldvfs.join(oldrl.indexfile)
671 newindex = newvfs.join(newrl.indexfile)
671 newindex = newvfs.join(newrl.indexfile)
672 olddata = oldvfs.join(oldrl.datafile)
672 olddata = oldvfs.join(oldrl.datafile)
673 newdata = newvfs.join(newrl.datafile)
673 newdata = newvfs.join(newrl.datafile)
674
674
675 with newvfs(newrl.indexfile, b'w'):
675 with newvfs(newrl.indexfile, b'w'):
676 pass # create all the directories
676 pass # create all the directories
677
677
678 util.copyfile(oldindex, newindex)
678 util.copyfile(oldindex, newindex)
679 copydata = oldrl.opener.exists(oldrl.datafile)
679 copydata = oldrl.opener.exists(oldrl.datafile)
680 if copydata:
680 if copydata:
681 util.copyfile(olddata, newdata)
681 util.copyfile(olddata, newdata)
682
682
683 if not (
683 if not (
684 unencodedname.endswith(b'00changelog.i')
684 unencodedname.endswith(b'00changelog.i')
685 or unencodedname.endswith(b'00manifest.i')
685 or unencodedname.endswith(b'00manifest.i')
686 ):
686 ):
687 destrepo.svfs.fncache.add(unencodedname)
687 destrepo.svfs.fncache.add(unencodedname)
688 if copydata:
688 if copydata:
689 destrepo.svfs.fncache.add(unencodedname[:-2] + b'.d')
689 destrepo.svfs.fncache.add(unencodedname[:-2] + b'.d')
690
690
691
691
692 UPGRADE_CHANGELOG = object()
692 UPGRADE_CHANGELOG = object()
693 UPGRADE_MANIFEST = object()
693 UPGRADE_MANIFEST = object()
694 UPGRADE_FILELOG = object()
694 UPGRADE_FILELOG = object()
695
695
696 UPGRADE_ALL_REVLOGS = frozenset(
696 UPGRADE_ALL_REVLOGS = frozenset(
697 [UPGRADE_CHANGELOG, UPGRADE_MANIFEST, UPGRADE_FILELOG]
697 [UPGRADE_CHANGELOG, UPGRADE_MANIFEST, UPGRADE_FILELOG]
698 )
698 )
699
699
700
700
701 def getsidedatacompanion(srcrepo, dstrepo):
701 def getsidedatacompanion(srcrepo, dstrepo):
702 sidedatacompanion = None
702 sidedatacompanion = None
703 removedreqs = srcrepo.requirements - dstrepo.requirements
703 removedreqs = srcrepo.requirements - dstrepo.requirements
704 addedreqs = dstrepo.requirements - srcrepo.requirements
704 addedreqs = dstrepo.requirements - srcrepo.requirements
705 if localrepo.SIDEDATA_REQUIREMENT in removedreqs:
705 if localrepo.SIDEDATA_REQUIREMENT in removedreqs:
706
706
707 def sidedatacompanion(rl, rev):
707 def sidedatacompanion(rl, rev):
708 rl = getattr(rl, '_revlog', rl)
708 rl = getattr(rl, '_revlog', rl)
709 if rl.flags(rev) & revlog.REVIDX_SIDEDATA:
709 if rl.flags(rev) & revlog.REVIDX_SIDEDATA:
710 return True, (), {}
710 return True, (), {}
711 return False, (), {}
711 return False, (), {}
712
712
713 elif localrepo.COPIESSDC_REQUIREMENT in addedreqs:
713 elif localrepo.COPIESSDC_REQUIREMENT in addedreqs:
714 sidedatacompanion = copies.getsidedataadder(srcrepo, dstrepo)
714 sidedatacompanion = copies.getsidedataadder(srcrepo, dstrepo)
715 elif localrepo.COPIESSDC_REQUIREMENT in removedreqs:
715 elif localrepo.COPIESSDC_REQUIREMENT in removedreqs:
716 sidedatacompanion = copies.getsidedataremover(srcrepo, dstrepo)
716 sidedatacompanion = copies.getsidedataremover(srcrepo, dstrepo)
717 return sidedatacompanion
717 return sidedatacompanion
718
718
719
719
720 def matchrevlog(revlogfilter, entry):
720 def matchrevlog(revlogfilter, entry):
721 """check is a revlog is selected for cloning
721 """check is a revlog is selected for cloning
722
722
723 The store entry is checked against the passed filter"""
723 The store entry is checked against the passed filter"""
724 if entry.endswith(b'00changelog.i'):
724 if entry.endswith(b'00changelog.i'):
725 return UPGRADE_CHANGELOG in revlogfilter
725 return UPGRADE_CHANGELOG in revlogfilter
726 elif entry.endswith(b'00manifest.i'):
726 elif entry.endswith(b'00manifest.i'):
727 return UPGRADE_MANIFEST in revlogfilter
727 return UPGRADE_MANIFEST in revlogfilter
728 return UPGRADE_FILELOG in revlogfilter
728 return UPGRADE_FILELOG in revlogfilter
729
729
730
730
731 def _clonerevlogs(
731 def _clonerevlogs(
732 ui,
732 ui,
733 srcrepo,
733 srcrepo,
734 dstrepo,
734 dstrepo,
735 tr,
735 tr,
736 deltareuse,
736 deltareuse,
737 forcedeltabothparents,
737 forcedeltabothparents,
738 revlogs=UPGRADE_ALL_REVLOGS,
738 revlogs=UPGRADE_ALL_REVLOGS,
739 ):
739 ):
740 """Copy revlogs between 2 repos."""
740 """Copy revlogs between 2 repos."""
741 revcount = 0
741 revcount = 0
742 srcsize = 0
742 srcsize = 0
743 srcrawsize = 0
743 srcrawsize = 0
744 dstsize = 0
744 dstsize = 0
745 fcount = 0
745 fcount = 0
746 frevcount = 0
746 frevcount = 0
747 fsrcsize = 0
747 fsrcsize = 0
748 frawsize = 0
748 frawsize = 0
749 fdstsize = 0
749 fdstsize = 0
750 mcount = 0
750 mcount = 0
751 mrevcount = 0
751 mrevcount = 0
752 msrcsize = 0
752 msrcsize = 0
753 mrawsize = 0
753 mrawsize = 0
754 mdstsize = 0
754 mdstsize = 0
755 crevcount = 0
755 crevcount = 0
756 csrcsize = 0
756 csrcsize = 0
757 crawsize = 0
757 crawsize = 0
758 cdstsize = 0
758 cdstsize = 0
759
759
760 alldatafiles = list(srcrepo.store.walk())
760 alldatafiles = list(srcrepo.store.walk())
761
761
762 # Perform a pass to collect metadata. This validates we can open all
762 # Perform a pass to collect metadata. This validates we can open all
763 # source files and allows a unified progress bar to be displayed.
763 # source files and allows a unified progress bar to be displayed.
764 for unencoded, encoded, size in alldatafiles:
764 for unencoded, encoded, size in alldatafiles:
765 if unencoded.endswith(b'.d'):
765 if unencoded.endswith(b'.d'):
766 continue
766 continue
767
767
768 rl = _revlogfrompath(srcrepo, unencoded)
768 rl = _revlogfrompath(srcrepo, unencoded)
769
769
770 info = rl.storageinfo(
770 info = rl.storageinfo(
771 exclusivefiles=True,
771 exclusivefiles=True,
772 revisionscount=True,
772 revisionscount=True,
773 trackedsize=True,
773 trackedsize=True,
774 storedsize=True,
774 storedsize=True,
775 )
775 )
776
776
777 revcount += info[b'revisionscount'] or 0
777 revcount += info[b'revisionscount'] or 0
778 datasize = info[b'storedsize'] or 0
778 datasize = info[b'storedsize'] or 0
779 rawsize = info[b'trackedsize'] or 0
779 rawsize = info[b'trackedsize'] or 0
780
780
781 srcsize += datasize
781 srcsize += datasize
782 srcrawsize += rawsize
782 srcrawsize += rawsize
783
783
784 # This is for the separate progress bars.
784 # This is for the separate progress bars.
785 if isinstance(rl, changelog.changelog):
785 if isinstance(rl, changelog.changelog):
786 crevcount += len(rl)
786 crevcount += len(rl)
787 csrcsize += datasize
787 csrcsize += datasize
788 crawsize += rawsize
788 crawsize += rawsize
789 elif isinstance(rl, manifest.manifestrevlog):
789 elif isinstance(rl, manifest.manifestrevlog):
790 mcount += 1
790 mcount += 1
791 mrevcount += len(rl)
791 mrevcount += len(rl)
792 msrcsize += datasize
792 msrcsize += datasize
793 mrawsize += rawsize
793 mrawsize += rawsize
794 elif isinstance(rl, filelog.filelog):
794 elif isinstance(rl, filelog.filelog):
795 fcount += 1
795 fcount += 1
796 frevcount += len(rl)
796 frevcount += len(rl)
797 fsrcsize += datasize
797 fsrcsize += datasize
798 frawsize += rawsize
798 frawsize += rawsize
799 else:
799 else:
800 error.ProgrammingError(b'unknown revlog type')
800 error.ProgrammingError(b'unknown revlog type')
801
801
802 if not revcount:
802 if not revcount:
803 return
803 return
804
804
805 ui.write(
805 ui.write(
806 _(
806 _(
807 b'migrating %d total revisions (%d in filelogs, %d in manifests, '
807 b'migrating %d total revisions (%d in filelogs, %d in manifests, '
808 b'%d in changelog)\n'
808 b'%d in changelog)\n'
809 )
809 )
810 % (revcount, frevcount, mrevcount, crevcount)
810 % (revcount, frevcount, mrevcount, crevcount)
811 )
811 )
812 ui.write(
812 ui.write(
813 _(b'migrating %s in store; %s tracked data\n')
813 _(b'migrating %s in store; %s tracked data\n')
814 % ((util.bytecount(srcsize), util.bytecount(srcrawsize)))
814 % ((util.bytecount(srcsize), util.bytecount(srcrawsize)))
815 )
815 )
816
816
817 # Used to keep track of progress.
817 # Used to keep track of progress.
818 progress = None
818 progress = None
819
819
820 def oncopiedrevision(rl, rev, node):
820 def oncopiedrevision(rl, rev, node):
821 progress.increment()
821 progress.increment()
822
822
823 sidedatacompanion = getsidedatacompanion(srcrepo, dstrepo)
823 sidedatacompanion = getsidedatacompanion(srcrepo, dstrepo)
824
824
825 # Do the actual copying.
825 # Do the actual copying.
826 # FUTURE this operation can be farmed off to worker processes.
826 # FUTURE this operation can be farmed off to worker processes.
827 seen = set()
827 seen = set()
828 for unencoded, encoded, size in alldatafiles:
828 for unencoded, encoded, size in alldatafiles:
829 if unencoded.endswith(b'.d'):
829 if unencoded.endswith(b'.d'):
830 continue
830 continue
831
831
832 oldrl = _revlogfrompath(srcrepo, unencoded)
832 oldrl = _revlogfrompath(srcrepo, unencoded)
833
833
834 if isinstance(oldrl, changelog.changelog) and b'c' not in seen:
834 if isinstance(oldrl, changelog.changelog) and b'c' not in seen:
835 ui.write(
835 ui.write(
836 _(
836 _(
837 b'finished migrating %d manifest revisions across %d '
837 b'finished migrating %d manifest revisions across %d '
838 b'manifests; change in size: %s\n'
838 b'manifests; change in size: %s\n'
839 )
839 )
840 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
840 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
841 )
841 )
842
842
843 ui.write(
843 ui.write(
844 _(
844 _(
845 b'migrating changelog containing %d revisions '
845 b'migrating changelog containing %d revisions '
846 b'(%s in store; %s tracked data)\n'
846 b'(%s in store; %s tracked data)\n'
847 )
847 )
848 % (
848 % (
849 crevcount,
849 crevcount,
850 util.bytecount(csrcsize),
850 util.bytecount(csrcsize),
851 util.bytecount(crawsize),
851 util.bytecount(crawsize),
852 )
852 )
853 )
853 )
854 seen.add(b'c')
854 seen.add(b'c')
855 progress = srcrepo.ui.makeprogress(
855 progress = srcrepo.ui.makeprogress(
856 _(b'changelog revisions'), total=crevcount
856 _(b'changelog revisions'), total=crevcount
857 )
857 )
858 elif isinstance(oldrl, manifest.manifestrevlog) and b'm' not in seen:
858 elif isinstance(oldrl, manifest.manifestrevlog) and b'm' not in seen:
859 ui.write(
859 ui.write(
860 _(
860 _(
861 b'finished migrating %d filelog revisions across %d '
861 b'finished migrating %d filelog revisions across %d '
862 b'filelogs; change in size: %s\n'
862 b'filelogs; change in size: %s\n'
863 )
863 )
864 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
864 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
865 )
865 )
866
866
867 ui.write(
867 ui.write(
868 _(
868 _(
869 b'migrating %d manifests containing %d revisions '
869 b'migrating %d manifests containing %d revisions '
870 b'(%s in store; %s tracked data)\n'
870 b'(%s in store; %s tracked data)\n'
871 )
871 )
872 % (
872 % (
873 mcount,
873 mcount,
874 mrevcount,
874 mrevcount,
875 util.bytecount(msrcsize),
875 util.bytecount(msrcsize),
876 util.bytecount(mrawsize),
876 util.bytecount(mrawsize),
877 )
877 )
878 )
878 )
879 seen.add(b'm')
879 seen.add(b'm')
880 if progress:
880 if progress:
881 progress.complete()
881 progress.complete()
882 progress = srcrepo.ui.makeprogress(
882 progress = srcrepo.ui.makeprogress(
883 _(b'manifest revisions'), total=mrevcount
883 _(b'manifest revisions'), total=mrevcount
884 )
884 )
885 elif b'f' not in seen:
885 elif b'f' not in seen:
886 ui.write(
886 ui.write(
887 _(
887 _(
888 b'migrating %d filelogs containing %d revisions '
888 b'migrating %d filelogs containing %d revisions '
889 b'(%s in store; %s tracked data)\n'
889 b'(%s in store; %s tracked data)\n'
890 )
890 )
891 % (
891 % (
892 fcount,
892 fcount,
893 frevcount,
893 frevcount,
894 util.bytecount(fsrcsize),
894 util.bytecount(fsrcsize),
895 util.bytecount(frawsize),
895 util.bytecount(frawsize),
896 )
896 )
897 )
897 )
898 seen.add(b'f')
898 seen.add(b'f')
899 if progress:
899 if progress:
900 progress.complete()
900 progress.complete()
901 progress = srcrepo.ui.makeprogress(
901 progress = srcrepo.ui.makeprogress(
902 _(b'file revisions'), total=frevcount
902 _(b'file revisions'), total=frevcount
903 )
903 )
904
904
905 if matchrevlog(revlogs, unencoded):
905 if matchrevlog(revlogs, unencoded):
906 ui.note(
906 ui.note(
907 _(b'cloning %d revisions from %s\n') % (len(oldrl), unencoded)
907 _(b'cloning %d revisions from %s\n') % (len(oldrl), unencoded)
908 )
908 )
909 newrl = _revlogfrompath(dstrepo, unencoded)
909 newrl = _revlogfrompath(dstrepo, unencoded)
910 oldrl.clone(
910 oldrl.clone(
911 tr,
911 tr,
912 newrl,
912 newrl,
913 addrevisioncb=oncopiedrevision,
913 addrevisioncb=oncopiedrevision,
914 deltareuse=deltareuse,
914 deltareuse=deltareuse,
915 forcedeltabothparents=forcedeltabothparents,
915 forcedeltabothparents=forcedeltabothparents,
916 sidedatacompanion=sidedatacompanion,
916 sidedatacompanion=sidedatacompanion,
917 )
917 )
918 else:
918 else:
919 msg = _(b'blindly copying %s containing %i revisions\n')
919 msg = _(b'blindly copying %s containing %i revisions\n')
920 ui.note(msg % (unencoded, len(oldrl)))
920 ui.note(msg % (unencoded, len(oldrl)))
921 _copyrevlog(tr, dstrepo, oldrl, unencoded)
921 _copyrevlog(tr, dstrepo, oldrl, unencoded)
922
922
923 newrl = _revlogfrompath(dstrepo, unencoded)
923 newrl = _revlogfrompath(dstrepo, unencoded)
924
924
925 info = newrl.storageinfo(storedsize=True)
925 info = newrl.storageinfo(storedsize=True)
926 datasize = info[b'storedsize'] or 0
926 datasize = info[b'storedsize'] or 0
927
927
928 dstsize += datasize
928 dstsize += datasize
929
929
930 if isinstance(newrl, changelog.changelog):
930 if isinstance(newrl, changelog.changelog):
931 cdstsize += datasize
931 cdstsize += datasize
932 elif isinstance(newrl, manifest.manifestrevlog):
932 elif isinstance(newrl, manifest.manifestrevlog):
933 mdstsize += datasize
933 mdstsize += datasize
934 else:
934 else:
935 fdstsize += datasize
935 fdstsize += datasize
936
936
937 progress.complete()
937 progress.complete()
938
938
939 ui.write(
939 ui.write(
940 _(
940 _(
941 b'finished migrating %d changelog revisions; change in size: '
941 b'finished migrating %d changelog revisions; change in size: '
942 b'%s\n'
942 b'%s\n'
943 )
943 )
944 % (crevcount, util.bytecount(cdstsize - csrcsize))
944 % (crevcount, util.bytecount(cdstsize - csrcsize))
945 )
945 )
946
946
947 ui.write(
947 ui.write(
948 _(
948 _(
949 b'finished migrating %d total revisions; total change in store '
949 b'finished migrating %d total revisions; total change in store '
950 b'size: %s\n'
950 b'size: %s\n'
951 )
951 )
952 % (revcount, util.bytecount(dstsize - srcsize))
952 % (revcount, util.bytecount(dstsize - srcsize))
953 )
953 )
954
954
955
955
956 def _filterstorefile(srcrepo, dstrepo, requirements, path, mode, st):
956 def _filterstorefile(srcrepo, dstrepo, requirements, path, mode, st):
957 """Determine whether to copy a store file during upgrade.
957 """Determine whether to copy a store file during upgrade.
958
958
959 This function is called when migrating store files from ``srcrepo`` to
959 This function is called when migrating store files from ``srcrepo`` to
960 ``dstrepo`` as part of upgrading a repository.
960 ``dstrepo`` as part of upgrading a repository.
961
961
962 Args:
962 Args:
963 srcrepo: repo we are copying from
963 srcrepo: repo we are copying from
964 dstrepo: repo we are copying to
964 dstrepo: repo we are copying to
965 requirements: set of requirements for ``dstrepo``
965 requirements: set of requirements for ``dstrepo``
966 path: store file being examined
966 path: store file being examined
967 mode: the ``ST_MODE`` file type of ``path``
967 mode: the ``ST_MODE`` file type of ``path``
968 st: ``stat`` data structure for ``path``
968 st: ``stat`` data structure for ``path``
969
969
970 Function should return ``True`` if the file is to be copied.
970 Function should return ``True`` if the file is to be copied.
971 """
971 """
972 # Skip revlogs.
972 # Skip revlogs.
973 if path.endswith((b'.i', b'.d')):
973 if path.endswith((b'.i', b'.d')):
974 return False
974 return False
975 # Skip transaction related files.
975 # Skip transaction related files.
976 if path.startswith(b'undo'):
976 if path.startswith(b'undo'):
977 return False
977 return False
978 # Only copy regular files.
978 # Only copy regular files.
979 if mode != stat.S_IFREG:
979 if mode != stat.S_IFREG:
980 return False
980 return False
981 # Skip other skipped files.
981 # Skip other skipped files.
982 if path in (b'lock', b'fncache'):
982 if path in (b'lock', b'fncache'):
983 return False
983 return False
984
984
985 return True
985 return True
986
986
987
987
988 def _finishdatamigration(ui, srcrepo, dstrepo, requirements):
988 def _finishdatamigration(ui, srcrepo, dstrepo, requirements):
989 """Hook point for extensions to perform additional actions during upgrade.
989 """Hook point for extensions to perform additional actions during upgrade.
990
990
991 This function is called after revlogs and store files have been copied but
991 This function is called after revlogs and store files have been copied but
992 before the new store is swapped into the original location.
992 before the new store is swapped into the original location.
993 """
993 """
994
994
995
995
996 def _upgraderepo(
996 def _upgraderepo(
997 ui, srcrepo, dstrepo, requirements, actions, revlogs=UPGRADE_ALL_REVLOGS
997 ui, srcrepo, dstrepo, requirements, actions, revlogs=UPGRADE_ALL_REVLOGS
998 ):
998 ):
999 """Do the low-level work of upgrading a repository.
999 """Do the low-level work of upgrading a repository.
1000
1000
1001 The upgrade is effectively performed as a copy between a source
1001 The upgrade is effectively performed as a copy between a source
1002 repository and a temporary destination repository.
1002 repository and a temporary destination repository.
1003
1003
1004 The source repository is unmodified for as long as possible so the
1004 The source repository is unmodified for as long as possible so the
1005 upgrade can abort at any time without causing loss of service for
1005 upgrade can abort at any time without causing loss of service for
1006 readers and without corrupting the source repository.
1006 readers and without corrupting the source repository.
1007 """
1007 """
1008 assert srcrepo.currentwlock()
1008 assert srcrepo.currentwlock()
1009 assert dstrepo.currentwlock()
1009 assert dstrepo.currentwlock()
1010
1010
1011 ui.write(
1011 ui.write(
1012 _(
1012 _(
1013 b'(it is safe to interrupt this process any time before '
1013 b'(it is safe to interrupt this process any time before '
1014 b'data migration completes)\n'
1014 b'data migration completes)\n'
1015 )
1015 )
1016 )
1016 )
1017
1017
1018 if b're-delta-all' in actions:
1018 if b're-delta-all' in actions:
1019 deltareuse = revlog.revlog.DELTAREUSENEVER
1019 deltareuse = revlog.revlog.DELTAREUSENEVER
1020 elif b're-delta-parent' in actions:
1020 elif b're-delta-parent' in actions:
1021 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
1021 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
1022 elif b're-delta-multibase' in actions:
1022 elif b're-delta-multibase' in actions:
1023 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
1023 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
1024 elif b're-delta-fulladd' in actions:
1024 elif b're-delta-fulladd' in actions:
1025 deltareuse = revlog.revlog.DELTAREUSEFULLADD
1025 deltareuse = revlog.revlog.DELTAREUSEFULLADD
1026 else:
1026 else:
1027 deltareuse = revlog.revlog.DELTAREUSEALWAYS
1027 deltareuse = revlog.revlog.DELTAREUSEALWAYS
1028
1028
1029 with dstrepo.transaction(b'upgrade') as tr:
1029 with dstrepo.transaction(b'upgrade') as tr:
1030 _clonerevlogs(
1030 _clonerevlogs(
1031 ui,
1031 ui,
1032 srcrepo,
1032 srcrepo,
1033 dstrepo,
1033 dstrepo,
1034 tr,
1034 tr,
1035 deltareuse,
1035 deltareuse,
1036 b're-delta-multibase' in actions,
1036 b're-delta-multibase' in actions,
1037 revlogs=revlogs,
1037 revlogs=revlogs,
1038 )
1038 )
1039
1039
1040 # Now copy other files in the store directory.
1040 # Now copy other files in the store directory.
1041 # The sorted() makes execution deterministic.
1041 # The sorted() makes execution deterministic.
1042 for p, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)):
1042 for p, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)):
1043 if not _filterstorefile(srcrepo, dstrepo, requirements, p, kind, st):
1043 if not _filterstorefile(srcrepo, dstrepo, requirements, p, kind, st):
1044 continue
1044 continue
1045
1045
1046 srcrepo.ui.write(_(b'copying %s\n') % p)
1046 srcrepo.ui.write(_(b'copying %s\n') % p)
1047 src = srcrepo.store.rawvfs.join(p)
1047 src = srcrepo.store.rawvfs.join(p)
1048 dst = dstrepo.store.rawvfs.join(p)
1048 dst = dstrepo.store.rawvfs.join(p)
1049 util.copyfile(src, dst, copystat=True)
1049 util.copyfile(src, dst, copystat=True)
1050
1050
1051 _finishdatamigration(ui, srcrepo, dstrepo, requirements)
1051 _finishdatamigration(ui, srcrepo, dstrepo, requirements)
1052
1052
1053 ui.write(_(b'data fully migrated to temporary repository\n'))
1053 ui.write(_(b'data fully migrated to temporary repository\n'))
1054
1054
1055 backuppath = pycompat.mkdtemp(prefix=b'upgradebackup.', dir=srcrepo.path)
1055 backuppath = pycompat.mkdtemp(prefix=b'upgradebackup.', dir=srcrepo.path)
1056 backupvfs = vfsmod.vfs(backuppath)
1056 backupvfs = vfsmod.vfs(backuppath)
1057
1057
1058 # Make a backup of requires file first, as it is the first to be modified.
1058 # Make a backup of requires file first, as it is the first to be modified.
1059 util.copyfile(srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires'))
1059 util.copyfile(srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires'))
1060
1060
1061 # We install an arbitrary requirement that clients must not support
1061 # We install an arbitrary requirement that clients must not support
1062 # as a mechanism to lock out new clients during the data swap. This is
1062 # as a mechanism to lock out new clients during the data swap. This is
1063 # better than allowing a client to continue while the repository is in
1063 # better than allowing a client to continue while the repository is in
1064 # an inconsistent state.
1064 # an inconsistent state.
1065 ui.write(
1065 ui.write(
1066 _(
1066 _(
1067 b'marking source repository as being upgraded; clients will be '
1067 b'marking source repository as being upgraded; clients will be '
1068 b'unable to read from repository\n'
1068 b'unable to read from repository\n'
1069 )
1069 )
1070 )
1070 )
1071 scmutil.writerequires(
1071 scmutil.writerequires(
1072 srcrepo.vfs, srcrepo.requirements | {b'upgradeinprogress'}
1072 srcrepo.vfs, srcrepo.requirements | {b'upgradeinprogress'}
1073 )
1073 )
1074
1074
1075 ui.write(_(b'starting in-place swap of repository data\n'))
1075 ui.write(_(b'starting in-place swap of repository data\n'))
1076 ui.write(_(b'replaced files will be backed up at %s\n') % backuppath)
1076 ui.write(_(b'replaced files will be backed up at %s\n') % backuppath)
1077
1077
1078 # Now swap in the new store directory. Doing it as a rename should make
1078 # Now swap in the new store directory. Doing it as a rename should make
1079 # the operation nearly instantaneous and atomic (at least in well-behaved
1079 # the operation nearly instantaneous and atomic (at least in well-behaved
1080 # environments).
1080 # environments).
1081 ui.write(_(b'replacing store...\n'))
1081 ui.write(_(b'replacing store...\n'))
1082 tstart = util.timer()
1082 tstart = util.timer()
1083 util.rename(srcrepo.spath, backupvfs.join(b'store'))
1083 util.rename(srcrepo.spath, backupvfs.join(b'store'))
1084 util.rename(dstrepo.spath, srcrepo.spath)
1084 util.rename(dstrepo.spath, srcrepo.spath)
1085 elapsed = util.timer() - tstart
1085 elapsed = util.timer() - tstart
1086 ui.write(
1086 ui.write(
1087 _(
1087 _(
1088 b'store replacement complete; repository was inconsistent for '
1088 b'store replacement complete; repository was inconsistent for '
1089 b'%0.1fs\n'
1089 b'%0.1fs\n'
1090 )
1090 )
1091 % elapsed
1091 % elapsed
1092 )
1092 )
1093
1093
1094 # We first write the requirements file. Any new requirements will lock
1094 # We first write the requirements file. Any new requirements will lock
1095 # out legacy clients.
1095 # out legacy clients.
1096 ui.write(
1096 ui.write(
1097 _(
1097 _(
1098 b'finalizing requirements file and making repository readable '
1098 b'finalizing requirements file and making repository readable '
1099 b'again\n'
1099 b'again\n'
1100 )
1100 )
1101 )
1101 )
1102 scmutil.writerequires(srcrepo.vfs, requirements)
1102 scmutil.writerequires(srcrepo.vfs, requirements)
1103
1103
1104 # The lock file from the old store won't be removed because nothing has a
1104 # The lock file from the old store won't be removed because nothing has a
1105 # reference to its new location. So clean it up manually. Alternatively, we
1105 # reference to its new location. So clean it up manually. Alternatively, we
1106 # could update srcrepo.svfs and other variables to point to the new
1106 # could update srcrepo.svfs and other variables to point to the new
1107 # location. This is simpler.
1107 # location. This is simpler.
1108 backupvfs.unlink(b'store/lock')
1108 backupvfs.unlink(b'store/lock')
1109
1109
1110 return backuppath
1110 return backuppath
1111
1111
1112
1112
1113 def upgraderepo(
1113 def upgraderepo(
1114 ui,
1114 ui,
1115 repo,
1115 repo,
1116 run=False,
1116 run=False,
1117 optimize=None,
1117 optimize=None,
1118 backup=True,
1118 backup=True,
1119 manifest=None,
1119 manifest=None,
1120 changelog=None,
1120 changelog=None,
1121 ):
1121 ):
1122 """Upgrade a repository in place."""
1122 """Upgrade a repository in place."""
1123 if optimize is None:
1123 if optimize is None:
1124 optimize = []
1124 optimize = []
1125 optimize = set(legacy_opts_map.get(o, o) for o in optimize)
1125 optimize = set(legacy_opts_map.get(o, o) for o in optimize)
1126 repo = repo.unfiltered()
1126 repo = repo.unfiltered()
1127
1127
1128 revlogs = set(UPGRADE_ALL_REVLOGS)
1128 revlogs = set(UPGRADE_ALL_REVLOGS)
1129 specentries = ((b'c', changelog), (b'm', manifest))
1129 specentries = ((b'c', changelog), (b'm', manifest))
1130 specified = [(y, x) for (y, x) in specentries if x is not None]
1130 specified = [(y, x) for (y, x) in specentries if x is not None]
1131 if specified:
1131 if specified:
1132 # we have some limitation on revlogs to be recloned
1132 # we have some limitation on revlogs to be recloned
1133 if any(x for y, x in specified):
1133 if any(x for y, x in specified):
1134 revlogs = set()
1134 revlogs = set()
1135 for r, enabled in specified:
1135 for r, enabled in specified:
1136 if enabled:
1136 if enabled:
1137 if r == b'c':
1137 if r == b'c':
1138 revlogs.add(UPGRADE_CHANGELOG)
1138 revlogs.add(UPGRADE_CHANGELOG)
1139 elif r == b'm':
1139 elif r == b'm':
1140 revlogs.add(UPGRADE_MANIFEST)
1140 revlogs.add(UPGRADE_MANIFEST)
1141 else:
1141 else:
1142 # none are enabled
1142 # none are enabled
1143 for r, __ in specified:
1143 for r, __ in specified:
1144 if r == b'c':
1144 if r == b'c':
1145 revlogs.discard(UPGRADE_CHANGELOG)
1145 revlogs.discard(UPGRADE_CHANGELOG)
1146 elif r == b'm':
1146 elif r == b'm':
1147 revlogs.discard(UPGRADE_MANIFEST)
1147 revlogs.discard(UPGRADE_MANIFEST)
1148
1148
1149 # Ensure the repository can be upgraded.
1149 # Ensure the repository can be upgraded.
1150 missingreqs = requiredsourcerequirements(repo) - repo.requirements
1150 missingreqs = requiredsourcerequirements(repo) - repo.requirements
1151 if missingreqs:
1151 if missingreqs:
1152 raise error.Abort(
1152 raise error.Abort(
1153 _(b'cannot upgrade repository; requirement missing: %s')
1153 _(b'cannot upgrade repository; requirement missing: %s')
1154 % _(b', ').join(sorted(missingreqs))
1154 % _(b', ').join(sorted(missingreqs))
1155 )
1155 )
1156
1156
1157 blockedreqs = blocksourcerequirements(repo) & repo.requirements
1157 blockedreqs = blocksourcerequirements(repo) & repo.requirements
1158 if blockedreqs:
1158 if blockedreqs:
1159 raise error.Abort(
1159 raise error.Abort(
1160 _(
1160 _(
1161 b'cannot upgrade repository; unsupported source '
1161 b'cannot upgrade repository; unsupported source '
1162 b'requirement: %s'
1162 b'requirement: %s'
1163 )
1163 )
1164 % _(b', ').join(sorted(blockedreqs))
1164 % _(b', ').join(sorted(blockedreqs))
1165 )
1165 )
1166
1166
1167 # FUTURE there is potentially a need to control the wanted requirements via
1167 # FUTURE there is potentially a need to control the wanted requirements via
1168 # command arguments or via an extension hook point.
1168 # command arguments or via an extension hook point.
1169 newreqs = localrepo.newreporequirements(
1169 newreqs = localrepo.newreporequirements(
1170 repo.ui, localrepo.defaultcreateopts(repo.ui)
1170 repo.ui, localrepo.defaultcreateopts(repo.ui)
1171 )
1171 )
1172 newreqs.update(preservedrequirements(repo))
1172 newreqs.update(preservedrequirements(repo))
1173
1173
1174 noremovereqs = (
1174 noremovereqs = (
1175 repo.requirements - newreqs - supportremovedrequirements(repo)
1175 repo.requirements - newreqs - supportremovedrequirements(repo)
1176 )
1176 )
1177 if noremovereqs:
1177 if noremovereqs:
1178 raise error.Abort(
1178 raise error.Abort(
1179 _(
1179 _(
1180 b'cannot upgrade repository; requirement would be '
1180 b'cannot upgrade repository; requirement would be '
1181 b'removed: %s'
1181 b'removed: %s'
1182 )
1182 )
1183 % _(b', ').join(sorted(noremovereqs))
1183 % _(b', ').join(sorted(noremovereqs))
1184 )
1184 )
1185
1185
1186 noaddreqs = newreqs - repo.requirements - allowednewrequirements(repo)
1186 noaddreqs = newreqs - repo.requirements - allowednewrequirements(repo)
1187 if noaddreqs:
1187 if noaddreqs:
1188 raise error.Abort(
1188 raise error.Abort(
1189 _(
1189 _(
1190 b'cannot upgrade repository; do not support adding '
1190 b'cannot upgrade repository; do not support adding '
1191 b'requirement: %s'
1191 b'requirement: %s'
1192 )
1192 )
1193 % _(b', ').join(sorted(noaddreqs))
1193 % _(b', ').join(sorted(noaddreqs))
1194 )
1194 )
1195
1195
1196 unsupportedreqs = newreqs - supporteddestrequirements(repo)
1196 unsupportedreqs = newreqs - supporteddestrequirements(repo)
1197 if unsupportedreqs:
1197 if unsupportedreqs:
1198 raise error.Abort(
1198 raise error.Abort(
1199 _(
1199 _(
1200 b'cannot upgrade repository; do not support '
1200 b'cannot upgrade repository; do not support '
1201 b'destination requirement: %s'
1201 b'destination requirement: %s'
1202 )
1202 )
1203 % _(b', ').join(sorted(unsupportedreqs))
1203 % _(b', ').join(sorted(unsupportedreqs))
1204 )
1204 )
1205
1205
1206 # Find and validate all improvements that can be made.
1206 # Find and validate all improvements that can be made.
1207 alloptimizations = findoptimizations(repo)
1207 alloptimizations = findoptimizations(repo)
1208
1208
1209 # Apply and Validate arguments.
1209 # Apply and Validate arguments.
1210 optimizations = []
1210 optimizations = []
1211 for o in alloptimizations:
1211 for o in alloptimizations:
1212 if o.name in optimize:
1212 if o.name in optimize:
1213 optimizations.append(o)
1213 optimizations.append(o)
1214 optimize.discard(o.name)
1214 optimize.discard(o.name)
1215
1215
1216 if optimize: # anything left is unknown
1216 if optimize: # anything left is unknown
1217 raise error.Abort(
1217 raise error.Abort(
1218 _(b'unknown optimization action requested: %s')
1218 _(b'unknown optimization action requested: %s')
1219 % b', '.join(sorted(optimize)),
1219 % b', '.join(sorted(optimize)),
1220 hint=_(b'run without arguments to see valid optimizations'),
1220 hint=_(b'run without arguments to see valid optimizations'),
1221 )
1221 )
1222
1222
1223 deficiencies = finddeficiencies(repo)
1223 deficiencies = finddeficiencies(repo)
1224 actions = determineactions(repo, deficiencies, repo.requirements, newreqs)
1224 actions = determineactions(repo, deficiencies, repo.requirements, newreqs)
1225 actions.extend(
1225 actions.extend(
1226 o
1226 o
1227 for o in sorted(optimizations)
1227 for o in sorted(optimizations)
1228 # determineactions could have added optimisation
1228 # determineactions could have added optimisation
1229 if o not in actions
1229 if o not in actions
1230 )
1230 )
1231
1231
1232 removedreqs = repo.requirements - newreqs
1232 removedreqs = repo.requirements - newreqs
1233 addedreqs = newreqs - repo.requirements
1233 addedreqs = newreqs - repo.requirements
1234
1234
1235 if revlogs != UPGRADE_ALL_REVLOGS:
1235 if revlogs != UPGRADE_ALL_REVLOGS:
1236 incompatible = RECLONES_REQUIREMENTS & (removedreqs | addedreqs)
1236 incompatible = RECLONES_REQUIREMENTS & (removedreqs | addedreqs)
1237 if incompatible:
1237 if incompatible:
1238 msg = _(
1238 msg = _(
1239 b'ignoring revlogs selection flags, format requirements '
1239 b'ignoring revlogs selection flags, format requirements '
1240 b'change: %s\n'
1240 b'change: %s\n'
1241 )
1241 )
1242 ui.warn(msg % b', '.join(sorted(incompatible)))
1242 ui.warn(msg % b', '.join(sorted(incompatible)))
1243 revlogs = UPGRADE_ALL_REVLOGS
1243 revlogs = UPGRADE_ALL_REVLOGS
1244
1244
1245 def write_labeled(l, label):
1246 first = True
1247 for r in sorted(l):
1248 if not first:
1249 ui.write(b', ')
1250 ui.write(r, label=label)
1251 first = False
1252
1245 def printrequirements():
1253 def printrequirements():
1246 ui.write(_(b'requirements\n'))
1254 ui.write(_(b'requirements\n'))
1247 ui.write(
1255 ui.write(_(b' preserved: '))
1248 _(b' preserved: %s\n')
1256 write_labeled(
1249 % _(b', ').join(sorted(newreqs & repo.requirements))
1257 newreqs & repo.requirements, "upgrade-repo.requirement.preserved"
1250 )
1258 )
1251
1259 ui.write((b'\n'))
1260 removed = repo.requirements - newreqs
1252 if repo.requirements - newreqs:
1261 if repo.requirements - newreqs:
1253 ui.write(
1262 ui.write(_(b' removed: '))
1254 _(b' removed: %s\n')
1263 write_labeled(removed, "upgrade-repo.requirement.removed")
1255 % _(b', ').join(sorted(repo.requirements - newreqs))
1264 ui.write((b'\n'))
1256 )
1265 added = newreqs - repo.requirements
1257
1266 if added:
1258 if newreqs - repo.requirements:
1267 ui.write(_(b' added: '))
1259 ui.write(
1268 write_labeled(added, "upgrade-repo.requirement.added")
1260 _(b' added: %s\n')
1269 ui.write((b'\n'))
1261 % _(b', ').join(sorted(newreqs - repo.requirements))
1262 )
1263
1264 ui.write(b'\n')
1270 ui.write(b'\n')
1265
1271
1266 def printupgradeactions():
1272 def printupgradeactions():
1267 for a in actions:
1273 for a in actions:
1268 ui.write(b'%s\n %s\n\n' % (a.name, a.upgrademessage))
1274 ui.write(b'%s\n %s\n\n' % (a.name, a.upgrademessage))
1269
1275
1270 if not run:
1276 if not run:
1271 fromconfig = []
1277 fromconfig = []
1272 onlydefault = []
1278 onlydefault = []
1273
1279
1274 for d in deficiencies:
1280 for d in deficiencies:
1275 if d.fromconfig(repo):
1281 if d.fromconfig(repo):
1276 fromconfig.append(d)
1282 fromconfig.append(d)
1277 elif d.default:
1283 elif d.default:
1278 onlydefault.append(d)
1284 onlydefault.append(d)
1279
1285
1280 if fromconfig or onlydefault:
1286 if fromconfig or onlydefault:
1281
1287
1282 if fromconfig:
1288 if fromconfig:
1283 ui.write(
1289 ui.write(
1284 _(
1290 _(
1285 b'repository lacks features recommended by '
1291 b'repository lacks features recommended by '
1286 b'current config options:\n\n'
1292 b'current config options:\n\n'
1287 )
1293 )
1288 )
1294 )
1289 for i in fromconfig:
1295 for i in fromconfig:
1290 ui.write(b'%s\n %s\n\n' % (i.name, i.description))
1296 ui.write(b'%s\n %s\n\n' % (i.name, i.description))
1291
1297
1292 if onlydefault:
1298 if onlydefault:
1293 ui.write(
1299 ui.write(
1294 _(
1300 _(
1295 b'repository lacks features used by the default '
1301 b'repository lacks features used by the default '
1296 b'config options:\n\n'
1302 b'config options:\n\n'
1297 )
1303 )
1298 )
1304 )
1299 for i in onlydefault:
1305 for i in onlydefault:
1300 ui.write(b'%s\n %s\n\n' % (i.name, i.description))
1306 ui.write(b'%s\n %s\n\n' % (i.name, i.description))
1301
1307
1302 ui.write(b'\n')
1308 ui.write(b'\n')
1303 else:
1309 else:
1304 ui.write(
1310 ui.write(
1305 _(
1311 _(
1306 b'(no feature deficiencies found in existing '
1312 b'(no feature deficiencies found in existing '
1307 b'repository)\n'
1313 b'repository)\n'
1308 )
1314 )
1309 )
1315 )
1310
1316
1311 ui.write(
1317 ui.write(
1312 _(
1318 _(
1313 b'performing an upgrade with "--run" will make the following '
1319 b'performing an upgrade with "--run" will make the following '
1314 b'changes:\n\n'
1320 b'changes:\n\n'
1315 )
1321 )
1316 )
1322 )
1317
1323
1318 printrequirements()
1324 printrequirements()
1319 printupgradeactions()
1325 printupgradeactions()
1320
1326
1321 unusedoptimize = [i for i in alloptimizations if i not in actions]
1327 unusedoptimize = [i for i in alloptimizations if i not in actions]
1322
1328
1323 if unusedoptimize:
1329 if unusedoptimize:
1324 ui.write(
1330 ui.write(
1325 _(
1331 _(
1326 b'additional optimizations are available by specifying '
1332 b'additional optimizations are available by specifying '
1327 b'"--optimize <name>":\n\n'
1333 b'"--optimize <name>":\n\n'
1328 )
1334 )
1329 )
1335 )
1330 for i in unusedoptimize:
1336 for i in unusedoptimize:
1331 ui.write(_(b'%s\n %s\n\n') % (i.name, i.description))
1337 ui.write(_(b'%s\n %s\n\n') % (i.name, i.description))
1332 return
1338 return
1333
1339
1334 # Else we're in the run=true case.
1340 # Else we're in the run=true case.
1335 ui.write(_(b'upgrade will perform the following actions:\n\n'))
1341 ui.write(_(b'upgrade will perform the following actions:\n\n'))
1336 printrequirements()
1342 printrequirements()
1337 printupgradeactions()
1343 printupgradeactions()
1338
1344
1339 upgradeactions = [a.name for a in actions]
1345 upgradeactions = [a.name for a in actions]
1340
1346
1341 ui.write(_(b'beginning upgrade...\n'))
1347 ui.write(_(b'beginning upgrade...\n'))
1342 with repo.wlock(), repo.lock():
1348 with repo.wlock(), repo.lock():
1343 ui.write(_(b'repository locked and read-only\n'))
1349 ui.write(_(b'repository locked and read-only\n'))
1344 # Our strategy for upgrading the repository is to create a new,
1350 # Our strategy for upgrading the repository is to create a new,
1345 # temporary repository, write data to it, then do a swap of the
1351 # temporary repository, write data to it, then do a swap of the
1346 # data. There are less heavyweight ways to do this, but it is easier
1352 # data. There are less heavyweight ways to do this, but it is easier
1347 # to create a new repo object than to instantiate all the components
1353 # to create a new repo object than to instantiate all the components
1348 # (like the store) separately.
1354 # (like the store) separately.
1349 tmppath = pycompat.mkdtemp(prefix=b'upgrade.', dir=repo.path)
1355 tmppath = pycompat.mkdtemp(prefix=b'upgrade.', dir=repo.path)
1350 backuppath = None
1356 backuppath = None
1351 try:
1357 try:
1352 ui.write(
1358 ui.write(
1353 _(
1359 _(
1354 b'creating temporary repository to stage migrated '
1360 b'creating temporary repository to stage migrated '
1355 b'data: %s\n'
1361 b'data: %s\n'
1356 )
1362 )
1357 % tmppath
1363 % tmppath
1358 )
1364 )
1359
1365
1360 # clone ui without using ui.copy because repo.ui is protected
1366 # clone ui without using ui.copy because repo.ui is protected
1361 repoui = repo.ui.__class__(repo.ui)
1367 repoui = repo.ui.__class__(repo.ui)
1362 dstrepo = hg.repository(repoui, path=tmppath, create=True)
1368 dstrepo = hg.repository(repoui, path=tmppath, create=True)
1363
1369
1364 with dstrepo.wlock(), dstrepo.lock():
1370 with dstrepo.wlock(), dstrepo.lock():
1365 backuppath = _upgraderepo(
1371 backuppath = _upgraderepo(
1366 ui, repo, dstrepo, newreqs, upgradeactions, revlogs=revlogs
1372 ui, repo, dstrepo, newreqs, upgradeactions, revlogs=revlogs
1367 )
1373 )
1368 if not (backup or backuppath is None):
1374 if not (backup or backuppath is None):
1369 ui.write(_(b'removing old repository content%s\n') % backuppath)
1375 ui.write(_(b'removing old repository content%s\n') % backuppath)
1370 repo.vfs.rmtree(backuppath, forcibly=True)
1376 repo.vfs.rmtree(backuppath, forcibly=True)
1371 backuppath = None
1377 backuppath = None
1372
1378
1373 finally:
1379 finally:
1374 ui.write(_(b'removing temporary repository %s\n') % tmppath)
1380 ui.write(_(b'removing temporary repository %s\n') % tmppath)
1375 repo.vfs.rmtree(tmppath, forcibly=True)
1381 repo.vfs.rmtree(tmppath, forcibly=True)
1376
1382
1377 if backuppath:
1383 if backuppath:
1378 ui.warn(
1384 ui.warn(
1379 _(b'copy of old repository backed up at %s\n') % backuppath
1385 _(b'copy of old repository backed up at %s\n') % backuppath
1380 )
1386 )
1381 ui.warn(
1387 ui.warn(
1382 _(
1388 _(
1383 b'the old repository will not be deleted; remove '
1389 b'the old repository will not be deleted; remove '
1384 b'it to free up disk space once the upgraded '
1390 b'it to free up disk space once the upgraded '
1385 b'repository is verified\n'
1391 b'repository is verified\n'
1386 )
1392 )
1387 )
1393 )
General Comments 0
You need to be logged in to leave comments. Login now