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