##// END OF EJS Templates
convert-bazaar: use breezy package instead of old bzr one...
Raphaël Gomès -
r48168:26127236 default
parent child Browse files
Show More
@@ -1,821 +1,821 b''
1 1 #!/usr/bin/env python3
2 2
3 3 from __future__ import absolute_import, print_function
4 4
5 5 import ast
6 6 import collections
7 7 import io
8 8 import os
9 9 import sys
10 10
11 11 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
12 12 # to work when run from a virtualenv. The modules were chosen empirically
13 13 # so that the return value matches the return value without virtualenv.
14 14 if True: # disable lexical sorting checks
15 15 try:
16 16 import BaseHTTPServer as basehttpserver
17 17 except ImportError:
18 18 basehttpserver = None
19 19 import zlib
20 20
21 21 import testparseutil
22 22
23 23 # Whitelist of modules that symbols can be directly imported from.
24 24 allowsymbolimports = (
25 25 '__future__',
26 'bzrlib',
26 'breezy',
27 27 'hgclient',
28 28 'mercurial',
29 29 'mercurial.hgweb.common',
30 30 'mercurial.hgweb.request',
31 31 'mercurial.i18n',
32 32 'mercurial.interfaces',
33 33 'mercurial.node',
34 34 'mercurial.pycompat',
35 35 # for revlog to re-export constant to extensions
36 36 'mercurial.revlogutils.constants',
37 37 'mercurial.revlogutils.flagutil',
38 38 # for cffi modules to re-export pure functions
39 39 'mercurial.pure.base85',
40 40 'mercurial.pure.bdiff',
41 41 'mercurial.pure.mpatch',
42 42 'mercurial.pure.osutil',
43 43 'mercurial.pure.parsers',
44 44 # third-party imports should be directly imported
45 45 'mercurial.thirdparty',
46 46 'mercurial.thirdparty.attr',
47 47 'mercurial.thirdparty.zope',
48 48 'mercurial.thirdparty.zope.interface',
49 49 )
50 50
51 51 # Whitelist of symbols that can be directly imported.
52 52 directsymbols = ('demandimport',)
53 53
54 54 # Modules that must be aliased because they are commonly confused with
55 55 # common variables and can create aliasing and readability issues.
56 56 requirealias = {
57 57 'ui': 'uimod',
58 58 }
59 59
60 60
61 61 def usingabsolute(root):
62 62 """Whether absolute imports are being used."""
63 63 if sys.version_info[0] >= 3:
64 64 return True
65 65
66 66 for node in ast.walk(root):
67 67 if isinstance(node, ast.ImportFrom):
68 68 if node.module == '__future__':
69 69 for n in node.names:
70 70 if n.name == 'absolute_import':
71 71 return True
72 72
73 73 return False
74 74
75 75
76 76 def walklocal(root):
77 77 """Recursively yield all descendant nodes but not in a different scope"""
78 78 todo = collections.deque(ast.iter_child_nodes(root))
79 79 yield root, False
80 80 while todo:
81 81 node = todo.popleft()
82 82 newscope = isinstance(node, ast.FunctionDef)
83 83 if not newscope:
84 84 todo.extend(ast.iter_child_nodes(node))
85 85 yield node, newscope
86 86
87 87
88 88 def dotted_name_of_path(path):
89 89 """Given a relative path to a source file, return its dotted module name.
90 90
91 91 >>> dotted_name_of_path('mercurial/error.py')
92 92 'mercurial.error'
93 93 >>> dotted_name_of_path('zlibmodule.so')
94 94 'zlib'
95 95 """
96 96 parts = path.replace(os.sep, '/').split('/')
97 97 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
98 98 if parts[-1].endswith('module'):
99 99 parts[-1] = parts[-1][:-6]
100 100 return '.'.join(parts)
101 101
102 102
103 103 def fromlocalfunc(modulename, localmods):
104 104 """Get a function to examine which locally defined module the
105 105 target source imports via a specified name.
106 106
107 107 `modulename` is an `dotted_name_of_path()`-ed source file path,
108 108 which may have `.__init__` at the end of it, of the target source.
109 109
110 110 `localmods` is a set of absolute `dotted_name_of_path()`-ed source file
111 111 paths of locally defined (= Mercurial specific) modules.
112 112
113 113 This function assumes that module names not existing in
114 114 `localmods` are from the Python standard library.
115 115
116 116 This function returns the function, which takes `name` argument,
117 117 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
118 118 matches against locally defined module. Otherwise, it returns
119 119 False.
120 120
121 121 It is assumed that `name` doesn't have `.__init__`.
122 122
123 123 `absname` is an absolute module name of specified `name`
124 124 (e.g. "hgext.convert"). This can be used to compose prefix for sub
125 125 modules or so.
126 126
127 127 `dottedpath` is a `dotted_name_of_path()`-ed source file path
128 128 (e.g. "hgext.convert.__init__") of `name`. This is used to look
129 129 module up in `localmods` again.
130 130
131 131 `hassubmod` is whether it may have sub modules under it (for
132 132 convenient, even though this is also equivalent to "absname !=
133 133 dottednpath")
134 134
135 135 >>> localmods = {'foo.__init__', 'foo.foo1',
136 136 ... 'foo.bar.__init__', 'foo.bar.bar1',
137 137 ... 'baz.__init__', 'baz.baz1'}
138 138 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
139 139 >>> # relative
140 140 >>> fromlocal('foo1')
141 141 ('foo.foo1', 'foo.foo1', False)
142 142 >>> fromlocal('bar')
143 143 ('foo.bar', 'foo.bar.__init__', True)
144 144 >>> fromlocal('bar.bar1')
145 145 ('foo.bar.bar1', 'foo.bar.bar1', False)
146 146 >>> # absolute
147 147 >>> fromlocal('baz')
148 148 ('baz', 'baz.__init__', True)
149 149 >>> fromlocal('baz.baz1')
150 150 ('baz.baz1', 'baz.baz1', False)
151 151 >>> # unknown = maybe standard library
152 152 >>> fromlocal('os')
153 153 False
154 154 >>> fromlocal(None, 1)
155 155 ('foo', 'foo.__init__', True)
156 156 >>> fromlocal('foo1', 1)
157 157 ('foo.foo1', 'foo.foo1', False)
158 158 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
159 159 >>> fromlocal2(None, 2)
160 160 ('foo', 'foo.__init__', True)
161 161 >>> fromlocal2('bar2', 1)
162 162 False
163 163 >>> fromlocal2('bar', 2)
164 164 ('foo.bar', 'foo.bar.__init__', True)
165 165 """
166 166 if not isinstance(modulename, str):
167 167 modulename = modulename.decode('ascii')
168 168 prefix = '.'.join(modulename.split('.')[:-1])
169 169 if prefix:
170 170 prefix += '.'
171 171
172 172 def fromlocal(name, level=0):
173 173 # name is false value when relative imports are used.
174 174 if not name:
175 175 # If relative imports are used, level must not be absolute.
176 176 assert level > 0
177 177 candidates = ['.'.join(modulename.split('.')[:-level])]
178 178 else:
179 179 if not level:
180 180 # Check relative name first.
181 181 candidates = [prefix + name, name]
182 182 else:
183 183 candidates = [
184 184 '.'.join(modulename.split('.')[:-level]) + '.' + name
185 185 ]
186 186
187 187 for n in candidates:
188 188 if n in localmods:
189 189 return (n, n, False)
190 190 dottedpath = n + '.__init__'
191 191 if dottedpath in localmods:
192 192 return (n, dottedpath, True)
193 193 return False
194 194
195 195 return fromlocal
196 196
197 197
198 198 def populateextmods(localmods):
199 199 """Populate C extension modules based on pure modules"""
200 200 newlocalmods = set(localmods)
201 201 for n in localmods:
202 202 if n.startswith('mercurial.pure.'):
203 203 m = n[len('mercurial.pure.') :]
204 204 newlocalmods.add('mercurial.cext.' + m)
205 205 newlocalmods.add('mercurial.cffi._' + m)
206 206 return newlocalmods
207 207
208 208
209 209 def list_stdlib_modules():
210 210 """List the modules present in the stdlib.
211 211
212 212 >>> py3 = sys.version_info[0] >= 3
213 213 >>> mods = set(list_stdlib_modules())
214 214 >>> 'BaseHTTPServer' in mods or py3
215 215 True
216 216
217 217 os.path isn't really a module, so it's missing:
218 218
219 219 >>> 'os.path' in mods
220 220 False
221 221
222 222 sys requires special treatment, because it's baked into the
223 223 interpreter, but it should still appear:
224 224
225 225 >>> 'sys' in mods
226 226 True
227 227
228 228 >>> 'collections' in mods
229 229 True
230 230
231 231 >>> 'cStringIO' in mods or py3
232 232 True
233 233
234 234 >>> 'cffi' in mods
235 235 True
236 236 """
237 237 for m in sys.builtin_module_names:
238 238 yield m
239 239 # These modules only exist on windows, but we should always
240 240 # consider them stdlib.
241 241 for m in ['msvcrt', '_winreg']:
242 242 yield m
243 243 yield '__builtin__'
244 244 yield 'builtins' # python3 only
245 245 yield 'importlib.abc' # python3 only
246 246 yield 'importlib.machinery' # python3 only
247 247 yield 'importlib.util' # python3 only
248 248 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
249 249 yield m
250 250 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
251 251 yield m
252 252 for m in ['cffi']:
253 253 yield m
254 254 stdlib_prefixes = {sys.prefix, sys.exec_prefix}
255 255 # We need to supplement the list of prefixes for the search to work
256 256 # when run from within a virtualenv.
257 257 for mod in (basehttpserver, zlib):
258 258 if mod is None:
259 259 continue
260 260 try:
261 261 # Not all module objects have a __file__ attribute.
262 262 filename = mod.__file__
263 263 except AttributeError:
264 264 continue
265 265 dirname = os.path.dirname(filename)
266 266 for prefix in stdlib_prefixes:
267 267 if dirname.startswith(prefix):
268 268 # Then this directory is redundant.
269 269 break
270 270 else:
271 271 stdlib_prefixes.add(dirname)
272 272 sourceroot = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
273 273 for libpath in sys.path:
274 274 # We want to walk everything in sys.path that starts with something in
275 275 # stdlib_prefixes, but not directories from the hg sources.
276 276 if os.path.abspath(libpath).startswith(sourceroot) or not any(
277 277 libpath.startswith(p) for p in stdlib_prefixes
278 278 ):
279 279 continue
280 280 for top, dirs, files in os.walk(libpath):
281 281 for i, d in reversed(list(enumerate(dirs))):
282 282 if (
283 283 not os.path.exists(os.path.join(top, d, '__init__.py'))
284 284 or top == libpath
285 285 and d in ('hgdemandimport', 'hgext', 'mercurial')
286 286 ):
287 287 del dirs[i]
288 288 for name in files:
289 289 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
290 290 continue
291 291 if name.startswith('__init__.py'):
292 292 full_path = top
293 293 else:
294 294 full_path = os.path.join(top, name)
295 295 rel_path = full_path[len(libpath) + 1 :]
296 296 mod = dotted_name_of_path(rel_path)
297 297 yield mod
298 298
299 299
300 300 stdlib_modules = set(list_stdlib_modules())
301 301
302 302
303 303 def imported_modules(source, modulename, f, localmods, ignore_nested=False):
304 304 """Given the source of a file as a string, yield the names
305 305 imported by that file.
306 306
307 307 Args:
308 308 source: The python source to examine as a string.
309 309 modulename: of specified python source (may have `__init__`)
310 310 localmods: set of locally defined module names (may have `__init__`)
311 311 ignore_nested: If true, import statements that do not start in
312 312 column zero will be ignored.
313 313
314 314 Returns:
315 315 A list of absolute module names imported by the given source.
316 316
317 317 >>> f = 'foo/xxx.py'
318 318 >>> modulename = 'foo.xxx'
319 319 >>> localmods = {'foo.__init__': True,
320 320 ... 'foo.foo1': True, 'foo.foo2': True,
321 321 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
322 322 ... 'baz.__init__': True, 'baz.baz1': True }
323 323 >>> # standard library (= not locally defined ones)
324 324 >>> sorted(imported_modules(
325 325 ... 'from stdlib1 import foo, bar; import stdlib2',
326 326 ... modulename, f, localmods))
327 327 []
328 328 >>> # relative importing
329 329 >>> sorted(imported_modules(
330 330 ... 'import foo1; from bar import bar1',
331 331 ... modulename, f, localmods))
332 332 ['foo.bar.bar1', 'foo.foo1']
333 333 >>> sorted(imported_modules(
334 334 ... 'from bar.bar1 import name1, name2, name3',
335 335 ... modulename, f, localmods))
336 336 ['foo.bar.bar1']
337 337 >>> # absolute importing
338 338 >>> sorted(imported_modules(
339 339 ... 'from baz import baz1, name1',
340 340 ... modulename, f, localmods))
341 341 ['baz.__init__', 'baz.baz1']
342 342 >>> # mixed importing, even though it shouldn't be recommended
343 343 >>> sorted(imported_modules(
344 344 ... 'import stdlib, foo1, baz',
345 345 ... modulename, f, localmods))
346 346 ['baz.__init__', 'foo.foo1']
347 347 >>> # ignore_nested
348 348 >>> sorted(imported_modules(
349 349 ... '''import foo
350 350 ... def wat():
351 351 ... import bar
352 352 ... ''', modulename, f, localmods))
353 353 ['foo.__init__', 'foo.bar.__init__']
354 354 >>> sorted(imported_modules(
355 355 ... '''import foo
356 356 ... def wat():
357 357 ... import bar
358 358 ... ''', modulename, f, localmods, ignore_nested=True))
359 359 ['foo.__init__']
360 360 """
361 361 fromlocal = fromlocalfunc(modulename, localmods)
362 362 for node in ast.walk(ast.parse(source, f)):
363 363 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
364 364 continue
365 365 if isinstance(node, ast.Import):
366 366 for n in node.names:
367 367 found = fromlocal(n.name)
368 368 if not found:
369 369 # this should import standard library
370 370 continue
371 371 yield found[1]
372 372 elif isinstance(node, ast.ImportFrom):
373 373 found = fromlocal(node.module, node.level)
374 374 if not found:
375 375 # this should import standard library
376 376 continue
377 377
378 378 absname, dottedpath, hassubmod = found
379 379 if not hassubmod:
380 380 # "dottedpath" is not a package; must be imported
381 381 yield dottedpath
382 382 # examination of "node.names" should be redundant
383 383 # e.g.: from mercurial.node import nullid, nullrev
384 384 continue
385 385
386 386 modnotfound = False
387 387 prefix = absname + '.'
388 388 for n in node.names:
389 389 found = fromlocal(prefix + n.name)
390 390 if not found:
391 391 # this should be a function or a property of "node.module"
392 392 modnotfound = True
393 393 continue
394 394 yield found[1]
395 395 if modnotfound and dottedpath != modulename:
396 396 # "dottedpath" is a package, but imported because of non-module
397 397 # lookup
398 398 # specifically allow "from . import foo" from __init__.py
399 399 yield dottedpath
400 400
401 401
402 402 def verify_import_convention(module, source, localmods):
403 403 """Verify imports match our established coding convention.
404 404
405 405 We have 2 conventions: legacy and modern. The modern convention is in
406 406 effect when using absolute imports.
407 407
408 408 The legacy convention only looks for mixed imports. The modern convention
409 409 is much more thorough.
410 410 """
411 411 root = ast.parse(source)
412 412 absolute = usingabsolute(root)
413 413
414 414 if absolute:
415 415 return verify_modern_convention(module, root, localmods)
416 416 else:
417 417 return verify_stdlib_on_own_line(root)
418 418
419 419
420 420 def verify_modern_convention(module, root, localmods, root_col_offset=0):
421 421 """Verify a file conforms to the modern import convention rules.
422 422
423 423 The rules of the modern convention are:
424 424
425 425 * Ordering is stdlib followed by local imports. Each group is lexically
426 426 sorted.
427 427 * Importing multiple modules via "import X, Y" is not allowed: use
428 428 separate import statements.
429 429 * Importing multiple modules via "from X import ..." is allowed if using
430 430 parenthesis and one entry per line.
431 431 * Only 1 relative import statement per import level ("from .", "from ..")
432 432 is allowed.
433 433 * Relative imports from higher levels must occur before lower levels. e.g.
434 434 "from .." must be before "from .".
435 435 * Imports from peer packages should use relative import (e.g. do not
436 436 "import mercurial.foo" from a "mercurial.*" module).
437 437 * Symbols can only be imported from specific modules (see
438 438 `allowsymbolimports`). For other modules, first import the module then
439 439 assign the symbol to a module-level variable. In addition, these imports
440 440 must be performed before other local imports. This rule only
441 441 applies to import statements outside of any blocks.
442 442 * Relative imports from the standard library are not allowed, unless that
443 443 library is also a local module.
444 444 * Certain modules must be aliased to alternate names to avoid aliasing
445 445 and readability problems. See `requirealias`.
446 446 """
447 447 if not isinstance(module, str):
448 448 module = module.decode('ascii')
449 449 topmodule = module.split('.')[0]
450 450 fromlocal = fromlocalfunc(module, localmods)
451 451
452 452 # Whether a local/non-stdlib import has been performed.
453 453 seenlocal = None
454 454 # Whether a local/non-stdlib, non-symbol import has been seen.
455 455 seennonsymbollocal = False
456 456 # The last name to be imported (for sorting).
457 457 lastname = None
458 458 laststdlib = None
459 459 # Relative import levels encountered so far.
460 460 seenlevels = set()
461 461
462 462 for node, newscope in walklocal(root):
463 463
464 464 def msg(fmt, *args):
465 465 return (fmt % args, node.lineno)
466 466
467 467 if newscope:
468 468 # Check for local imports in function
469 469 for r in verify_modern_convention(
470 470 module, node, localmods, node.col_offset + 4
471 471 ):
472 472 yield r
473 473 elif isinstance(node, ast.Import):
474 474 # Disallow "import foo, bar" and require separate imports
475 475 # for each module.
476 476 if len(node.names) > 1:
477 477 yield msg(
478 478 'multiple imported names: %s',
479 479 ', '.join(n.name for n in node.names),
480 480 )
481 481
482 482 name = node.names[0].name
483 483 asname = node.names[0].asname
484 484
485 485 stdlib = name in stdlib_modules
486 486
487 487 # Ignore sorting rules on imports inside blocks.
488 488 if node.col_offset == root_col_offset:
489 489 if lastname and name < lastname and laststdlib == stdlib:
490 490 yield msg(
491 491 'imports not lexically sorted: %s < %s', name, lastname
492 492 )
493 493
494 494 lastname = name
495 495 laststdlib = stdlib
496 496
497 497 # stdlib imports should be before local imports.
498 498 if stdlib and seenlocal and node.col_offset == root_col_offset:
499 499 yield msg(
500 500 'stdlib import "%s" follows local import: %s',
501 501 name,
502 502 seenlocal,
503 503 )
504 504
505 505 if not stdlib:
506 506 seenlocal = name
507 507
508 508 # Import of sibling modules should use relative imports.
509 509 topname = name.split('.')[0]
510 510 if topname == topmodule:
511 511 yield msg('import should be relative: %s', name)
512 512
513 513 if name in requirealias and asname != requirealias[name]:
514 514 yield msg(
515 515 '%s module must be "as" aliased to %s',
516 516 name,
517 517 requirealias[name],
518 518 )
519 519
520 520 elif isinstance(node, ast.ImportFrom):
521 521 # Resolve the full imported module name.
522 522 if node.level > 0:
523 523 fullname = '.'.join(module.split('.')[: -node.level])
524 524 if node.module:
525 525 fullname += '.%s' % node.module
526 526 else:
527 527 assert node.module
528 528 fullname = node.module
529 529
530 530 topname = fullname.split('.')[0]
531 531 if topname == topmodule:
532 532 yield msg('import should be relative: %s', fullname)
533 533
534 534 # __future__ is special since it needs to come first and use
535 535 # symbol import.
536 536 if fullname != '__future__':
537 537 if not fullname or (
538 538 fullname in stdlib_modules
539 539 # allow standard 'from typing import ...' style
540 540 and fullname.startswith('.')
541 541 and fullname not in localmods
542 542 and fullname + '.__init__' not in localmods
543 543 ):
544 544 yield msg('relative import of stdlib module')
545 545 else:
546 546 seenlocal = fullname
547 547
548 548 # Direct symbol import is only allowed from certain modules and
549 549 # must occur before non-symbol imports.
550 550 found = fromlocal(node.module, node.level)
551 551 if found and found[2]: # node.module is a package
552 552 prefix = found[0] + '.'
553 553 symbols = (
554 554 n.name for n in node.names if not fromlocal(prefix + n.name)
555 555 )
556 556 else:
557 557 symbols = (n.name for n in node.names)
558 558 symbols = [sym for sym in symbols if sym not in directsymbols]
559 559 if node.module and node.col_offset == root_col_offset:
560 560 if symbols and fullname not in allowsymbolimports:
561 561 yield msg(
562 562 'direct symbol import %s from %s',
563 563 ', '.join(symbols),
564 564 fullname,
565 565 )
566 566
567 567 if symbols and seennonsymbollocal:
568 568 yield msg(
569 569 'symbol import follows non-symbol import: %s', fullname
570 570 )
571 571 if not symbols and fullname not in stdlib_modules:
572 572 seennonsymbollocal = True
573 573
574 574 if not node.module:
575 575 assert node.level
576 576
577 577 # Only allow 1 group per level.
578 578 if (
579 579 node.level in seenlevels
580 580 and node.col_offset == root_col_offset
581 581 ):
582 582 yield msg(
583 583 'multiple "from %s import" statements', '.' * node.level
584 584 )
585 585
586 586 # Higher-level groups come before lower-level groups.
587 587 if any(node.level > l for l in seenlevels):
588 588 yield msg(
589 589 'higher-level import should come first: %s', fullname
590 590 )
591 591
592 592 seenlevels.add(node.level)
593 593
594 594 # Entries in "from .X import ( ... )" lists must be lexically
595 595 # sorted.
596 596 lastentryname = None
597 597
598 598 for n in node.names:
599 599 if lastentryname and n.name < lastentryname:
600 600 yield msg(
601 601 'imports from %s not lexically sorted: %s < %s',
602 602 fullname,
603 603 n.name,
604 604 lastentryname,
605 605 )
606 606
607 607 lastentryname = n.name
608 608
609 609 if n.name in requirealias and n.asname != requirealias[n.name]:
610 610 yield msg(
611 611 '%s from %s must be "as" aliased to %s',
612 612 n.name,
613 613 fullname,
614 614 requirealias[n.name],
615 615 )
616 616
617 617
618 618 def verify_stdlib_on_own_line(root):
619 619 """Given some python source, verify that stdlib imports are done
620 620 in separate statements from relative local module imports.
621 621
622 622 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
623 623 [('mixed imports\\n stdlib: sys\\n relative: foo', 1)]
624 624 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
625 625 []
626 626 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
627 627 []
628 628 """
629 629 for node in ast.walk(root):
630 630 if isinstance(node, ast.Import):
631 631 from_stdlib = {False: [], True: []}
632 632 for n in node.names:
633 633 from_stdlib[n.name in stdlib_modules].append(n.name)
634 634 if from_stdlib[True] and from_stdlib[False]:
635 635 yield (
636 636 'mixed imports\n stdlib: %s\n relative: %s'
637 637 % (
638 638 ', '.join(sorted(from_stdlib[True])),
639 639 ', '.join(sorted(from_stdlib[False])),
640 640 ),
641 641 node.lineno,
642 642 )
643 643
644 644
645 645 class CircularImport(Exception):
646 646 pass
647 647
648 648
649 649 def checkmod(mod, imports):
650 650 shortest = {}
651 651 visit = [[mod]]
652 652 while visit:
653 653 path = visit.pop(0)
654 654 for i in sorted(imports.get(path[-1], [])):
655 655 if len(path) < shortest.get(i, 1000):
656 656 shortest[i] = len(path)
657 657 if i in path:
658 658 if i == path[0]:
659 659 raise CircularImport(path)
660 660 continue
661 661 visit.append(path + [i])
662 662
663 663
664 664 def rotatecycle(cycle):
665 665 """arrange a cycle so that the lexicographically first module listed first
666 666
667 667 >>> rotatecycle(['foo', 'bar'])
668 668 ['bar', 'foo', 'bar']
669 669 """
670 670 lowest = min(cycle)
671 671 idx = cycle.index(lowest)
672 672 return cycle[idx:] + cycle[:idx] + [lowest]
673 673
674 674
675 675 def find_cycles(imports):
676 676 """Find cycles in an already-loaded import graph.
677 677
678 678 All module names recorded in `imports` should be absolute one.
679 679
680 680 >>> from __future__ import print_function
681 681 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
682 682 ... 'top.bar': ['top.baz', 'sys'],
683 683 ... 'top.baz': ['top.foo'],
684 684 ... 'top.qux': ['top.foo']}
685 685 >>> print('\\n'.join(sorted(find_cycles(imports))))
686 686 top.bar -> top.baz -> top.foo -> top.bar
687 687 top.foo -> top.qux -> top.foo
688 688 """
689 689 cycles = set()
690 690 for mod in sorted(imports.keys()):
691 691 try:
692 692 checkmod(mod, imports)
693 693 except CircularImport as e:
694 694 cycle = e.args[0]
695 695 cycles.add(" -> ".join(rotatecycle(cycle)))
696 696 return cycles
697 697
698 698
699 699 def _cycle_sortkey(c):
700 700 return len(c), c
701 701
702 702
703 703 def embedded(f, modname, src):
704 704 """Extract embedded python code
705 705
706 706 >>> def _forcestr(thing):
707 707 ... if not isinstance(thing, str):
708 708 ... return thing.decode('ascii')
709 709 ... return thing
710 710 >>> def test(fn, lines):
711 711 ... for s, m, f, l in embedded(fn, b"example", lines):
712 712 ... print("%s %s %d" % (_forcestr(m), _forcestr(f), l))
713 713 ... print(repr(_forcestr(s)))
714 714 >>> lines = [
715 715 ... 'comment',
716 716 ... ' >>> from __future__ import print_function',
717 717 ... " >>> ' multiline",
718 718 ... " ... string'",
719 719 ... ' ',
720 720 ... 'comment',
721 721 ... ' $ cat > foo.py <<EOF',
722 722 ... ' > from __future__ import print_function',
723 723 ... ' > EOF',
724 724 ... ]
725 725 >>> test(b"example.t", lines)
726 726 example[2] doctest.py 1
727 727 "from __future__ import print_function\\n' multiline\\nstring'\\n\\n"
728 728 example[8] foo.py 7
729 729 'from __future__ import print_function\\n'
730 730 """
731 731 errors = []
732 732 for name, starts, ends, code in testparseutil.pyembedded(f, src, errors):
733 733 if not name:
734 734 # use 'doctest.py', in order to make already existing
735 735 # doctest above pass instantly
736 736 name = 'doctest.py'
737 737 # "starts" is "line number" (1-origin), but embedded() is
738 738 # expected to return "line offset" (0-origin). Therefore, this
739 739 # yields "starts - 1".
740 740 if not isinstance(modname, str):
741 741 modname = modname.decode('utf8')
742 742 yield code, "%s[%d]" % (modname, starts), name, starts - 1
743 743
744 744
745 745 def sources(f, modname):
746 746 """Yields possibly multiple sources from a filepath
747 747
748 748 input: filepath, modulename
749 749 yields: script(string), modulename, filepath, linenumber
750 750
751 751 For embedded scripts, the modulename and filepath will be different
752 752 from the function arguments. linenumber is an offset relative to
753 753 the input file.
754 754 """
755 755 py = False
756 756 if not f.endswith('.t'):
757 757 with open(f, 'rb') as src:
758 758 yield src.read(), modname, f, 0
759 759 py = True
760 760 if py or f.endswith('.t'):
761 761 # Strictly speaking we should sniff for the magic header that denotes
762 762 # Python source file encoding. But in reality we don't use anything
763 763 # other than ASCII (mainly) and UTF-8 (in a few exceptions), so
764 764 # simplicity is fine.
765 765 with io.open(f, 'r', encoding='utf-8') as src:
766 766 for script, modname, t, line in embedded(f, modname, src):
767 767 yield script, modname.encode('utf8'), t, line
768 768
769 769
770 770 def main(argv):
771 771 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
772 772 print('Usage: %s {-|file [file] [file] ...}')
773 773 return 1
774 774 if argv[1] == '-':
775 775 argv = argv[:1]
776 776 argv.extend(l.rstrip() for l in sys.stdin.readlines())
777 777 localmodpaths = {}
778 778 used_imports = {}
779 779 any_errors = False
780 780 for source_path in argv[1:]:
781 781 modname = dotted_name_of_path(source_path)
782 782 localmodpaths[modname] = source_path
783 783 localmods = populateextmods(localmodpaths)
784 784 for localmodname, source_path in sorted(localmodpaths.items()):
785 785 if not isinstance(localmodname, bytes):
786 786 # This is only safe because all hg's files are ascii
787 787 localmodname = localmodname.encode('ascii')
788 788 for src, modname, name, line in sources(source_path, localmodname):
789 789 try:
790 790 used_imports[modname] = sorted(
791 791 imported_modules(
792 792 src, modname, name, localmods, ignore_nested=True
793 793 )
794 794 )
795 795 for error, lineno in verify_import_convention(
796 796 modname, src, localmods
797 797 ):
798 798 any_errors = True
799 799 print('%s:%d: %s' % (source_path, lineno + line, error))
800 800 except SyntaxError as e:
801 801 print(
802 802 '%s:%d: SyntaxError: %s' % (source_path, e.lineno + line, e)
803 803 )
804 804 cycles = find_cycles(used_imports)
805 805 if cycles:
806 806 firstmods = set()
807 807 for c in sorted(cycles, key=_cycle_sortkey):
808 808 first = c.split()[0]
809 809 # As a rough cut, ignore any cycle that starts with the
810 810 # same module as some other cycle. Otherwise we see lots
811 811 # of cycles that are effectively duplicates.
812 812 if first in firstmods:
813 813 continue
814 814 print('Import cycle:', c)
815 815 firstmods.add(first)
816 816 any_errors = True
817 817 return any_errors != 0
818 818
819 819
820 820 if __name__ == '__main__':
821 821 sys.exit(int(main(sys.argv)))
@@ -1,335 +1,338 b''
1 1 # bzr.py - bzr support for the convert extension
2 2 #
3 3 # Copyright 2008, 2009 Marek Kubica <marek@xivilization.net> and others
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 # This module is for handling 'bzr', that was formerly known as Bazaar-NG;
9 # it cannot access 'bar' repositories, but they were never used very much
8 # This module is for handling Breezy imports or `brz`, but it's also compatible
9 # with Bazaar or `bzr`, that was formerly known as Bazaar-NG;
10 # it cannot access `bar` repositories, but they were never used very much.
10 11 from __future__ import absolute_import
11 12
12 13 import os
13 14
14 15 from mercurial.i18n import _
15 16 from mercurial import (
16 17 demandimport,
17 18 error,
18 19 pycompat,
19 20 )
20 21 from . import common
21 22
23
22 24 # these do not work with demandimport, blacklist
23 25 demandimport.IGNORES.update(
24 26 [
25 b'bzrlib.transactions',
26 b'bzrlib.urlutils',
27 b'breezy.transactions',
28 b'breezy.urlutils',
27 29 b'ElementPath',
28 30 ]
29 31 )
30 32
31 33 try:
32 34 # bazaar imports
33 import bzrlib.bzrdir
34 import bzrlib.errors
35 import bzrlib.revision
36 import bzrlib.revisionspec
35 import breezy.bzr.bzrdir
36 import breezy.errors
37 import breezy.revision
38 import breezy.revisionspec
37 39
38 bzrdir = bzrlib.bzrdir
39 errors = bzrlib.errors
40 revision = bzrlib.revision
41 revisionspec = bzrlib.revisionspec
40 bzrdir = breezy.bzr.bzrdir
41 errors = breezy.errors
42 revision = breezy.revision
43 revisionspec = breezy.revisionspec
42 44 revisionspec.RevisionSpec
43 45 except ImportError:
44 46 pass
45 47
46 supportedkinds = (b'file', b'symlink')
48 supportedkinds = ('file', 'symlink')
47 49
48 50
49 51 class bzr_source(common.converter_source):
50 52 """Reads Bazaar repositories by using the Bazaar Python libraries"""
51 53
52 54 def __init__(self, ui, repotype, path, revs=None):
53 55 super(bzr_source, self).__init__(ui, repotype, path, revs=revs)
54 56
55 57 if not os.path.exists(os.path.join(path, b'.bzr')):
56 58 raise common.NoRepo(
57 59 _(b'%s does not look like a Bazaar repository') % path
58 60 )
59 61
60 62 try:
61 # access bzrlib stuff
63 # access breezy stuff
62 64 bzrdir
63 65 except NameError:
64 66 raise common.NoRepo(_(b'Bazaar modules could not be loaded'))
65 67
66 68 path = os.path.abspath(path)
67 69 self._checkrepotype(path)
68 70 try:
69 self.sourcerepo = bzrdir.BzrDir.open(path).open_repository()
71 bzr_dir = bzrdir.BzrDir.open(path.decode())
72 self.sourcerepo = bzr_dir.open_repository()
70 73 except errors.NoRepositoryPresent:
71 74 raise common.NoRepo(
72 75 _(b'%s does not look like a Bazaar repository') % path
73 76 )
74 77 self._parentids = {}
75 78 self._saverev = ui.configbool(b'convert', b'bzr.saverev')
76 79
77 80 def _checkrepotype(self, path):
78 81 # Lightweight checkouts detection is informational but probably
79 82 # fragile at API level. It should not terminate the conversion.
80 83 try:
81 dir = bzrdir.BzrDir.open_containing(path)[0]
84 dir = bzrdir.BzrDir.open_containing(path.decode())[0]
82 85 try:
83 86 tree = dir.open_workingtree(recommend_upgrade=False)
84 87 branch = tree.branch
85 88 except (errors.NoWorkingTree, errors.NotLocalUrl):
86 89 tree = None
87 90 branch = dir.open_branch()
88 91 if (
89 92 tree is not None
90 and tree.bzrdir.root_transport.base
91 != branch.bzrdir.root_transport.base
93 and tree.controldir.root_transport.base
94 != branch.controldir.root_transport.base
92 95 ):
93 96 self.ui.warn(
94 97 _(
95 98 b'warning: lightweight checkouts may cause '
96 99 b'conversion failures, try with a regular '
97 100 b'branch instead.\n'
98 101 )
99 102 )
100 103 except Exception:
101 104 self.ui.note(_(b'bzr source type could not be determined\n'))
102 105
103 106 def before(self):
104 107 """Before the conversion begins, acquire a read lock
105 108 for all the operations that might need it. Fortunately
106 109 read locks don't block other reads or writes to the
107 110 repository, so this shouldn't have any impact on the usage of
108 111 the source repository.
109 112
110 113 The alternative would be locking on every operation that
111 114 needs locks (there are currently two: getting the file and
112 115 getting the parent map) and releasing immediately after,
113 116 but this approach can take even 40% longer."""
114 117 self.sourcerepo.lock_read()
115 118
116 119 def after(self):
117 120 self.sourcerepo.unlock()
118 121
119 122 def _bzrbranches(self):
120 123 return self.sourcerepo.find_branches(using=True)
121 124
122 125 def getheads(self):
123 126 if not self.revs:
124 127 # Set using=True to avoid nested repositories (see issue3254)
125 128 heads = sorted([b.last_revision() for b in self._bzrbranches()])
126 129 else:
127 130 revid = None
128 131 for branch in self._bzrbranches():
129 132 try:
130 r = revisionspec.RevisionSpec.from_string(self.revs[0])
133 revspec = self.revs[0].decode()
134 r = revisionspec.RevisionSpec.from_string(revspec)
131 135 info = r.in_history(branch)
132 136 except errors.BzrError:
133 137 pass
134 138 revid = info.rev_id
135 139 if revid is None:
136 140 raise error.Abort(
137 141 _(b'%s is not a valid revision') % self.revs[0]
138 142 )
139 143 heads = [revid]
140 144 # Empty repositories return 'null:', which cannot be retrieved
141 145 heads = [h for h in heads if h != b'null:']
142 146 return heads
143 147
144 148 def getfile(self, name, rev):
149 name = name.decode()
145 150 revtree = self.sourcerepo.revision_tree(rev)
146 fileid = revtree.path2id(name.decode(self.encoding or b'utf-8'))
147 kind = None
148 if fileid is not None:
149 kind = revtree.kind(fileid)
151
152 try:
153 kind = revtree.kind(name)
154 except breezy.errors.NoSuchFile:
155 return None, None
150 156 if kind not in supportedkinds:
151 157 # the file is not available anymore - was deleted
152 158 return None, None
153 mode = self._modecache[(name, rev)]
154 if kind == b'symlink':
155 target = revtree.get_symlink_target(fileid)
159 mode = self._modecache[(name.encode(), rev)]
160 if kind == 'symlink':
161 target = revtree.get_symlink_target(name)
156 162 if target is None:
157 163 raise error.Abort(
158 164 _(b'%s.%s symlink has no target') % (name, rev)
159 165 )
160 return target, mode
166 return target.encode(), mode
161 167 else:
162 sio = revtree.get_file(fileid)
168 sio = revtree.get_file(name)
163 169 return sio.read(), mode
164 170
165 171 def getchanges(self, version, full):
166 172 if full:
167 173 raise error.Abort(_(b"convert from cvs does not support --full"))
168 174 self._modecache = {}
169 175 self._revtree = self.sourcerepo.revision_tree(version)
170 176 # get the parentids from the cache
171 177 parentids = self._parentids.pop(version)
172 178 # only diff against first parent id
173 179 prevtree = self.sourcerepo.revision_tree(parentids[0])
174 180 files, changes = self._gettreechanges(self._revtree, prevtree)
175 181 return files, changes, set()
176 182
177 183 def getcommit(self, version):
178 184 rev = self.sourcerepo.get_revision(version)
179 185 # populate parent id cache
180 186 if not rev.parent_ids:
181 187 parents = []
182 188 self._parentids[version] = (revision.NULL_REVISION,)
183 189 else:
184 190 parents = self._filterghosts(rev.parent_ids)
185 191 self._parentids[version] = parents
186 192
187 branch = self.recode(rev.properties.get(b'branch-nick', u'default'))
188 if branch == b'trunk':
189 branch = b'default'
193 branch = rev.properties.get('branch-nick', 'default')
194 if branch == 'trunk':
195 branch = 'default'
190 196 return common.commit(
191 197 parents=parents,
192 198 date=b'%d %d' % (rev.timestamp, -rev.timezone),
193 199 author=self.recode(rev.committer),
194 200 desc=self.recode(rev.message),
195 branch=branch,
201 branch=branch.encode('utf8'),
196 202 rev=version,
197 203 saverev=self._saverev,
198 204 )
199 205
200 206 def gettags(self):
201 207 bytetags = {}
202 208 for branch in self._bzrbranches():
203 209 if not branch.supports_tags():
204 210 return {}
205 211 tagdict = branch.tags.get_tag_dict()
206 212 for name, rev in pycompat.iteritems(tagdict):
207 213 bytetags[self.recode(name)] = rev
208 214 return bytetags
209 215
210 216 def getchangedfiles(self, rev, i):
211 217 self._modecache = {}
212 218 curtree = self.sourcerepo.revision_tree(rev)
213 219 if i is not None:
214 220 parentid = self._parentids[rev][i]
215 221 else:
216 222 # no parent id, get the empty revision
217 223 parentid = revision.NULL_REVISION
218 224
219 225 prevtree = self.sourcerepo.revision_tree(parentid)
220 226 changes = [e[0] for e in self._gettreechanges(curtree, prevtree)[0]]
221 227 return changes
222 228
223 229 def _gettreechanges(self, current, origin):
224 230 revid = current._revision_id
225 231 changes = []
226 232 renames = {}
227 233 seen = set()
228 234
229 235 # Fall back to the deprecated attribute for legacy installations.
230 236 try:
231 237 inventory = origin.root_inventory
232 238 except AttributeError:
233 239 inventory = origin.inventory
234 240
235 241 # Process the entries by reverse lexicographic name order to
236 242 # handle nested renames correctly, most specific first.
243
244 def key(c):
245 return c.path[0] or c.path[1] or ""
246
237 247 curchanges = sorted(
238 248 current.iter_changes(origin),
239 key=lambda c: c[1][0] or c[1][1],
249 key=key,
240 250 reverse=True,
241 251 )
242 for (
243 fileid,
244 paths,
245 changed_content,
246 versioned,
247 parent,
248 name,
249 kind,
250 executable,
251 ) in curchanges:
252
252 for change in curchanges:
253 paths = change.path
254 kind = change.kind
255 executable = change.executable
253 256 if paths[0] == u'' or paths[1] == u'':
254 257 # ignore changes to tree root
255 258 continue
256 259
257 260 # bazaar tracks directories, mercurial does not, so
258 261 # we have to rename the directory contents
259 if kind[1] == b'directory':
260 if kind[0] not in (None, b'directory'):
262 if kind[1] == 'directory':
263 if kind[0] not in (None, 'directory'):
261 264 # Replacing 'something' with a directory, record it
262 265 # so it can be removed.
263 266 changes.append((self.recode(paths[0]), revid))
264 267
265 if kind[0] == b'directory' and None not in paths:
268 if kind[0] == 'directory' and None not in paths:
266 269 renaming = paths[0] != paths[1]
267 270 # neither an add nor an delete - a move
268 271 # rename all directory contents manually
269 272 subdir = inventory.path2id(paths[0])
270 273 # get all child-entries of the directory
271 274 for name, entry in inventory.iter_entries(subdir):
272 275 # hg does not track directory renames
273 if entry.kind == b'directory':
276 if entry.kind == 'directory':
274 277 continue
275 frompath = self.recode(paths[0] + b'/' + name)
278 frompath = self.recode(paths[0] + '/' + name)
276 279 if frompath in seen:
277 280 # Already handled by a more specific change entry
278 281 # This is important when you have:
279 282 # a => b
280 283 # a/c => a/c
281 284 # Here a/c must not be renamed into b/c
282 285 continue
283 286 seen.add(frompath)
284 287 if not renaming:
285 288 continue
286 topath = self.recode(paths[1] + b'/' + name)
289 topath = self.recode(paths[1] + '/' + name)
287 290 # register the files as changed
288 291 changes.append((frompath, revid))
289 292 changes.append((topath, revid))
290 293 # add to mode cache
291 294 mode = (
292 295 (entry.executable and b'x')
293 or (entry.kind == b'symlink' and b's')
296 or (entry.kind == 'symlink' and b's')
294 297 or b''
295 298 )
296 299 self._modecache[(topath, revid)] = mode
297 300 # register the change as move
298 301 renames[topath] = frompath
299 302
300 303 # no further changes, go to the next change
301 304 continue
302 305
303 306 # we got unicode paths, need to convert them
304 307 path, topath = paths
305 308 if path is not None:
306 309 path = self.recode(path)
307 310 if topath is not None:
308 311 topath = self.recode(topath)
309 312 seen.add(path or topath)
310 313
311 314 if topath is None:
312 315 # file deleted
313 316 changes.append((path, revid))
314 317 continue
315 318
316 319 # renamed
317 320 if path and path != topath:
318 321 renames[topath] = path
319 322 changes.append((path, revid))
320 323
321 324 # populate the mode cache
322 325 kind, executable = [e[1] for e in (kind, executable)]
323 mode = (executable and b'x') or (kind == b'symlink' and b'l') or b''
326 mode = (executable and b'x') or (kind == 'symlink' and b'l') or b''
324 327 self._modecache[(topath, revid)] = mode
325 328 changes.append((topath, revid))
326 329
327 330 return changes, renames
328 331
329 332 def _filterghosts(self, ids):
330 333 """Filters out ghost revisions which hg does not support, see
331 334 <http://bazaar-vcs.org/GhostRevision>
332 335 """
333 336 parentmap = self.sourcerepo.get_parent_map(ids)
334 337 parents = tuple([parent for parent in ids if parent in parentmap])
335 338 return parents
@@ -1,1139 +1,1129 b''
1 1 from __future__ import absolute_import, print_function
2 2
3 3 import distutils.version
4 4 import os
5 5 import re
6 6 import socket
7 7 import stat
8 8 import subprocess
9 9 import sys
10 10 import tempfile
11 11
12 12 tempprefix = 'hg-hghave-'
13 13
14 14 checks = {
15 15 "true": (lambda: True, "yak shaving"),
16 16 "false": (lambda: False, "nail clipper"),
17 17 "known-bad-output": (lambda: True, "use for currently known bad output"),
18 18 "missing-correct-output": (lambda: False, "use for missing good output"),
19 19 }
20 20
21 21 try:
22 22 import msvcrt
23 23
24 24 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
25 25 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
26 26 except ImportError:
27 27 pass
28 28
29 29 stdout = getattr(sys.stdout, 'buffer', sys.stdout)
30 30 stderr = getattr(sys.stderr, 'buffer', sys.stderr)
31 31
32 32 is_not_python2 = sys.version_info[0] >= 3
33 33 if is_not_python2:
34 34
35 35 def _sys2bytes(p):
36 36 if p is None:
37 37 return p
38 38 return p.encode('utf-8')
39 39
40 40 def _bytes2sys(p):
41 41 if p is None:
42 42 return p
43 43 return p.decode('utf-8')
44 44
45 45
46 46 else:
47 47
48 48 def _sys2bytes(p):
49 49 return p
50 50
51 51 _bytes2sys = _sys2bytes
52 52
53 53
54 54 def check(name, desc):
55 55 """Registers a check function for a feature."""
56 56
57 57 def decorator(func):
58 58 checks[name] = (func, desc)
59 59 return func
60 60
61 61 return decorator
62 62
63 63
64 64 def checkvers(name, desc, vers):
65 65 """Registers a check function for each of a series of versions.
66 66
67 67 vers can be a list or an iterator.
68 68
69 69 Produces a series of feature checks that have the form <name><vers> without
70 70 any punctuation (even if there's punctuation in 'vers'; i.e. this produces
71 71 'py38', not 'py3.8' or 'py-38')."""
72 72
73 73 def decorator(func):
74 74 def funcv(v):
75 75 def f():
76 76 return func(v)
77 77
78 78 return f
79 79
80 80 for v in vers:
81 81 v = str(v)
82 82 f = funcv(v)
83 83 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
84 84 return func
85 85
86 86 return decorator
87 87
88 88
89 89 def checkfeatures(features):
90 90 result = {
91 91 'error': [],
92 92 'missing': [],
93 93 'skipped': [],
94 94 }
95 95
96 96 for feature in features:
97 97 negate = feature.startswith('no-')
98 98 if negate:
99 99 feature = feature[3:]
100 100
101 101 if feature not in checks:
102 102 result['missing'].append(feature)
103 103 continue
104 104
105 105 check, desc = checks[feature]
106 106 try:
107 107 available = check()
108 108 except Exception as e:
109 109 result['error'].append('hghave check %s failed: %r' % (feature, e))
110 110 continue
111 111
112 112 if not negate and not available:
113 113 result['skipped'].append('missing feature: %s' % desc)
114 114 elif negate and available:
115 115 result['skipped'].append('system supports %s' % desc)
116 116
117 117 return result
118 118
119 119
120 120 def require(features):
121 121 """Require that features are available, exiting if not."""
122 122 result = checkfeatures(features)
123 123
124 124 for missing in result['missing']:
125 125 stderr.write(
126 126 ('skipped: unknown feature: %s\n' % missing).encode('utf-8')
127 127 )
128 128 for msg in result['skipped']:
129 129 stderr.write(('skipped: %s\n' % msg).encode('utf-8'))
130 130 for msg in result['error']:
131 131 stderr.write(('%s\n' % msg).encode('utf-8'))
132 132
133 133 if result['missing']:
134 134 sys.exit(2)
135 135
136 136 if result['skipped'] or result['error']:
137 137 sys.exit(1)
138 138
139 139
140 140 def matchoutput(cmd, regexp, ignorestatus=False):
141 141 """Return the match object if cmd executes successfully and its output
142 142 is matched by the supplied regular expression.
143 143 """
144 144
145 145 # Tests on Windows have to fake USERPROFILE to point to the test area so
146 146 # that `~` is properly expanded on py3.8+. However, some tools like black
147 147 # make calls that need the real USERPROFILE in order to run `foo --version`.
148 148 env = os.environ
149 149 if os.name == 'nt':
150 150 env = os.environ.copy()
151 151 env['USERPROFILE'] = env['REALUSERPROFILE']
152 152
153 153 r = re.compile(regexp)
154 154 p = subprocess.Popen(
155 155 cmd,
156 156 shell=True,
157 157 stdout=subprocess.PIPE,
158 158 stderr=subprocess.STDOUT,
159 159 env=env,
160 160 )
161 161 s = p.communicate()[0]
162 162 ret = p.returncode
163 163 return (ignorestatus or not ret) and r.search(s)
164 164
165 165
166 166 @check("baz", "GNU Arch baz client")
167 167 def has_baz():
168 168 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
169 169
170 170
171 @check("bzr", "Canonical's Bazaar client")
171 @check("bzr", "Breezy library and executable version >= 3.1")
172 172 def has_bzr():
173 173 if not is_not_python2:
174 174 return False
175 175 try:
176 import bzrlib
177 import bzrlib.bzrdir
178 import bzrlib.errors
179 import bzrlib.revision
180 import bzrlib.revisionspec
176 # Test the Breezy python lib
177 import breezy
178 import breezy.bzr.bzrdir
179 import breezy.errors
180 import breezy.revision
181 import breezy.revisionspec
181 182
182 bzrlib.revisionspec.RevisionSpec
183 return bzrlib.__doc__ is not None
183 breezy.revisionspec.RevisionSpec
184 if breezy.__doc__ is None or breezy.version_info[:2] < (3, 1):
185 return False
184 186 except (AttributeError, ImportError):
185 187 return False
186
187
188 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
189 def has_bzr_range(v):
190 major, minor = v.split('rc')[0].split('.')[0:2]
191 try:
192 import bzrlib
193
194 return bzrlib.__doc__ is not None and bzrlib.version_info[:2] >= (
195 int(major),
196 int(minor),
197 )
198 except ImportError:
199 return False
188 # Test the executable
189 return matchoutput('brz --version 2>&1', br'Breezy \(brz\) ')
200 190
201 191
202 192 @check("chg", "running with chg")
203 193 def has_chg():
204 194 return 'CHGHG' in os.environ
205 195
206 196
207 197 @check("rhg", "running with rhg as 'hg'")
208 198 def has_rhg():
209 199 return 'RHG_INSTALLED_AS_HG' in os.environ
210 200
211 201
212 202 @check("cvs", "cvs client/server")
213 203 def has_cvs():
214 204 re = br'Concurrent Versions System.*?server'
215 205 return matchoutput('cvs --version 2>&1', re) and not has_msys()
216 206
217 207
218 208 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
219 209 def has_cvs112():
220 210 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
221 211 return matchoutput('cvs --version 2>&1', re) and not has_msys()
222 212
223 213
224 214 @check("cvsnt", "cvsnt client/server")
225 215 def has_cvsnt():
226 216 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
227 217 return matchoutput('cvsnt --version 2>&1', re)
228 218
229 219
230 220 @check("darcs", "darcs client")
231 221 def has_darcs():
232 222 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
233 223
234 224
235 225 @check("mtn", "monotone client (>= 1.0)")
236 226 def has_mtn():
237 227 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
238 228 'mtn --version', br'monotone 0\.', True
239 229 )
240 230
241 231
242 232 @check("eol-in-paths", "end-of-lines in paths")
243 233 def has_eol_in_paths():
244 234 try:
245 235 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
246 236 os.close(fd)
247 237 os.remove(path)
248 238 return True
249 239 except (IOError, OSError):
250 240 return False
251 241
252 242
253 243 @check("execbit", "executable bit")
254 244 def has_executablebit():
255 245 try:
256 246 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
257 247 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
258 248 try:
259 249 os.close(fh)
260 250 m = os.stat(fn).st_mode & 0o777
261 251 new_file_has_exec = m & EXECFLAGS
262 252 os.chmod(fn, m ^ EXECFLAGS)
263 253 exec_flags_cannot_flip = (os.stat(fn).st_mode & 0o777) == m
264 254 finally:
265 255 os.unlink(fn)
266 256 except (IOError, OSError):
267 257 # we don't care, the user probably won't be able to commit anyway
268 258 return False
269 259 return not (new_file_has_exec or exec_flags_cannot_flip)
270 260
271 261
272 262 @check("icasefs", "case insensitive file system")
273 263 def has_icasefs():
274 264 # Stolen from mercurial.util
275 265 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
276 266 os.close(fd)
277 267 try:
278 268 s1 = os.stat(path)
279 269 d, b = os.path.split(path)
280 270 p2 = os.path.join(d, b.upper())
281 271 if path == p2:
282 272 p2 = os.path.join(d, b.lower())
283 273 try:
284 274 s2 = os.stat(p2)
285 275 return s2 == s1
286 276 except OSError:
287 277 return False
288 278 finally:
289 279 os.remove(path)
290 280
291 281
292 282 @check("fifo", "named pipes")
293 283 def has_fifo():
294 284 if getattr(os, "mkfifo", None) is None:
295 285 return False
296 286 name = tempfile.mktemp(dir='.', prefix=tempprefix)
297 287 try:
298 288 os.mkfifo(name)
299 289 os.unlink(name)
300 290 return True
301 291 except OSError:
302 292 return False
303 293
304 294
305 295 @check("killdaemons", 'killdaemons.py support')
306 296 def has_killdaemons():
307 297 return True
308 298
309 299
310 300 @check("cacheable", "cacheable filesystem")
311 301 def has_cacheable_fs():
312 302 from mercurial import util
313 303
314 304 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
315 305 os.close(fd)
316 306 try:
317 307 return util.cachestat(path).cacheable()
318 308 finally:
319 309 os.remove(path)
320 310
321 311
322 312 @check("lsprof", "python lsprof module")
323 313 def has_lsprof():
324 314 try:
325 315 import _lsprof
326 316
327 317 _lsprof.Profiler # silence unused import warning
328 318 return True
329 319 except ImportError:
330 320 return False
331 321
332 322
333 323 def _gethgversion():
334 324 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
335 325 if not m:
336 326 return (0, 0)
337 327 return (int(m.group(1)), int(m.group(2)))
338 328
339 329
340 330 _hgversion = None
341 331
342 332
343 333 def gethgversion():
344 334 global _hgversion
345 335 if _hgversion is None:
346 336 _hgversion = _gethgversion()
347 337 return _hgversion
348 338
349 339
350 340 @checkvers(
351 341 "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)])
352 342 )
353 343 def has_hg_range(v):
354 344 major, minor = v.split('.')[0:2]
355 345 return gethgversion() >= (int(major), int(minor))
356 346
357 347
358 348 @check("rust", "Using the Rust extensions")
359 349 def has_rust():
360 350 """Check is the mercurial currently running is using some rust code"""
361 351 cmd = 'hg debuginstall --quiet 2>&1'
362 352 match = br'checking module policy \(([^)]+)\)'
363 353 policy = matchoutput(cmd, match)
364 354 if not policy:
365 355 return False
366 356 return b'rust' in policy.group(1)
367 357
368 358
369 359 @check("hg08", "Mercurial >= 0.8")
370 360 def has_hg08():
371 361 if checks["hg09"][0]():
372 362 return True
373 363 return matchoutput('hg help annotate 2>&1', '--date')
374 364
375 365
376 366 @check("hg07", "Mercurial >= 0.7")
377 367 def has_hg07():
378 368 if checks["hg08"][0]():
379 369 return True
380 370 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
381 371
382 372
383 373 @check("hg06", "Mercurial >= 0.6")
384 374 def has_hg06():
385 375 if checks["hg07"][0]():
386 376 return True
387 377 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
388 378
389 379
390 380 @check("gettext", "GNU Gettext (msgfmt)")
391 381 def has_gettext():
392 382 return matchoutput('msgfmt --version', br'GNU gettext-tools')
393 383
394 384
395 385 @check("git", "git command line client")
396 386 def has_git():
397 387 return matchoutput('git --version 2>&1', br'^git version')
398 388
399 389
400 390 def getgitversion():
401 391 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
402 392 if not m:
403 393 return (0, 0)
404 394 return (int(m.group(1)), int(m.group(2)))
405 395
406 396
407 397 @check("pygit2", "pygit2 Python library")
408 398 def has_git():
409 399 try:
410 400 import pygit2
411 401
412 402 pygit2.Oid # silence unused import
413 403 return True
414 404 except ImportError:
415 405 return False
416 406
417 407
418 408 # https://github.com/git-lfs/lfs-test-server
419 409 @check("lfs-test-server", "git-lfs test server")
420 410 def has_lfsserver():
421 411 exe = 'lfs-test-server'
422 412 if has_windows():
423 413 exe = 'lfs-test-server.exe'
424 414 return any(
425 415 os.access(os.path.join(path, exe), os.X_OK)
426 416 for path in os.environ["PATH"].split(os.pathsep)
427 417 )
428 418
429 419
430 420 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
431 421 def has_git_range(v):
432 422 major, minor = v.split('.')[0:2]
433 423 return getgitversion() >= (int(major), int(minor))
434 424
435 425
436 426 @check("docutils", "Docutils text processing library")
437 427 def has_docutils():
438 428 try:
439 429 import docutils.core
440 430
441 431 docutils.core.publish_cmdline # silence unused import
442 432 return True
443 433 except ImportError:
444 434 return False
445 435
446 436
447 437 def getsvnversion():
448 438 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
449 439 if not m:
450 440 return (0, 0)
451 441 return (int(m.group(1)), int(m.group(2)))
452 442
453 443
454 444 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
455 445 def has_svn_range(v):
456 446 major, minor = v.split('.')[0:2]
457 447 return getsvnversion() >= (int(major), int(minor))
458 448
459 449
460 450 @check("svn", "subversion client and admin tools")
461 451 def has_svn():
462 452 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
463 453 'svnadmin --version 2>&1', br'^svnadmin, version'
464 454 )
465 455
466 456
467 457 @check("svn-bindings", "subversion python bindings")
468 458 def has_svn_bindings():
469 459 try:
470 460 import svn.core
471 461
472 462 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
473 463 if version < (1, 4):
474 464 return False
475 465 return True
476 466 except ImportError:
477 467 return False
478 468
479 469
480 470 @check("p4", "Perforce server and client")
481 471 def has_p4():
482 472 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
483 473 'p4d -V', br'Rev\. P4D/'
484 474 )
485 475
486 476
487 477 @check("symlink", "symbolic links")
488 478 def has_symlink():
489 479 # mercurial.windows.checklink() is a hard 'no' at the moment
490 480 if os.name == 'nt' or getattr(os, "symlink", None) is None:
491 481 return False
492 482 name = tempfile.mktemp(dir='.', prefix=tempprefix)
493 483 try:
494 484 os.symlink(".", name)
495 485 os.unlink(name)
496 486 return True
497 487 except (OSError, AttributeError):
498 488 return False
499 489
500 490
501 491 @check("hardlink", "hardlinks")
502 492 def has_hardlink():
503 493 from mercurial import util
504 494
505 495 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
506 496 os.close(fh)
507 497 name = tempfile.mktemp(dir='.', prefix=tempprefix)
508 498 try:
509 499 util.oslink(_sys2bytes(fn), _sys2bytes(name))
510 500 os.unlink(name)
511 501 return True
512 502 except OSError:
513 503 return False
514 504 finally:
515 505 os.unlink(fn)
516 506
517 507
518 508 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
519 509 def has_hardlink_whitelisted():
520 510 from mercurial import util
521 511
522 512 try:
523 513 fstype = util.getfstype(b'.')
524 514 except OSError:
525 515 return False
526 516 return fstype in util._hardlinkfswhitelist
527 517
528 518
529 519 @check("rmcwd", "can remove current working directory")
530 520 def has_rmcwd():
531 521 ocwd = os.getcwd()
532 522 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
533 523 try:
534 524 os.chdir(temp)
535 525 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
536 526 # On Solaris and Windows, the cwd can't be removed by any names.
537 527 os.rmdir(os.getcwd())
538 528 return True
539 529 except OSError:
540 530 return False
541 531 finally:
542 532 os.chdir(ocwd)
543 533 # clean up temp dir on platforms where cwd can't be removed
544 534 try:
545 535 os.rmdir(temp)
546 536 except OSError:
547 537 pass
548 538
549 539
550 540 @check("tla", "GNU Arch tla client")
551 541 def has_tla():
552 542 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
553 543
554 544
555 545 @check("gpg", "gpg client")
556 546 def has_gpg():
557 547 return matchoutput('gpg --version 2>&1', br'GnuPG')
558 548
559 549
560 550 @check("gpg2", "gpg client v2")
561 551 def has_gpg2():
562 552 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
563 553
564 554
565 555 @check("gpg21", "gpg client v2.1+")
566 556 def has_gpg21():
567 557 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
568 558
569 559
570 560 @check("unix-permissions", "unix-style permissions")
571 561 def has_unix_permissions():
572 562 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
573 563 try:
574 564 fname = os.path.join(d, 'foo')
575 565 for umask in (0o77, 0o07, 0o22):
576 566 os.umask(umask)
577 567 f = open(fname, 'w')
578 568 f.close()
579 569 mode = os.stat(fname).st_mode
580 570 os.unlink(fname)
581 571 if mode & 0o777 != ~umask & 0o666:
582 572 return False
583 573 return True
584 574 finally:
585 575 os.rmdir(d)
586 576
587 577
588 578 @check("unix-socket", "AF_UNIX socket family")
589 579 def has_unix_socket():
590 580 return getattr(socket, 'AF_UNIX', None) is not None
591 581
592 582
593 583 @check("root", "root permissions")
594 584 def has_root():
595 585 return getattr(os, 'geteuid', None) and os.geteuid() == 0
596 586
597 587
598 588 @check("pyflakes", "Pyflakes python linter")
599 589 def has_pyflakes():
600 590 try:
601 591 import pyflakes
602 592
603 593 pyflakes.__version__
604 594 except ImportError:
605 595 return False
606 596 else:
607 597 return True
608 598
609 599
610 600 @check("pylint", "Pylint python linter")
611 601 def has_pylint():
612 602 return matchoutput("pylint --help", br"Usage:[ ]+pylint", True)
613 603
614 604
615 605 @check("clang-format", "clang-format C code formatter (>= 11)")
616 606 def has_clang_format():
617 607 m = matchoutput('clang-format --version', br'clang-format version (\d+)')
618 608 # style changed somewhere between 10.x and 11.x
619 609 return m and int(m.group(1)) >= 11
620 610
621 611
622 612 @check("jshint", "JSHint static code analysis tool")
623 613 def has_jshint():
624 614 return matchoutput("jshint --version 2>&1", br"jshint v")
625 615
626 616
627 617 @check("pygments", "Pygments source highlighting library")
628 618 def has_pygments():
629 619 try:
630 620 import pygments
631 621
632 622 pygments.highlight # silence unused import warning
633 623 return True
634 624 except ImportError:
635 625 return False
636 626
637 627
638 628 @check("pygments25", "Pygments version >= 2.5")
639 629 def pygments25():
640 630 try:
641 631 import pygments
642 632
643 633 v = pygments.__version__
644 634 except ImportError:
645 635 return False
646 636
647 637 parts = v.split(".")
648 638 major = int(parts[0])
649 639 minor = int(parts[1])
650 640
651 641 return (major, minor) >= (2, 5)
652 642
653 643
654 644 @check("outer-repo", "outer repo")
655 645 def has_outer_repo():
656 646 # failing for other reasons than 'no repo' imply that there is a repo
657 647 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
658 648
659 649
660 650 @check("ssl", "ssl module available")
661 651 def has_ssl():
662 652 try:
663 653 import ssl
664 654
665 655 ssl.CERT_NONE
666 656 return True
667 657 except ImportError:
668 658 return False
669 659
670 660
671 661 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
672 662 def has_defaultcacertsloaded():
673 663 import ssl
674 664 from mercurial import sslutil, ui as uimod
675 665
676 666 ui = uimod.ui.load()
677 667 cafile = sslutil._defaultcacerts(ui)
678 668 ctx = ssl.create_default_context()
679 669 if cafile:
680 670 ctx.load_verify_locations(cafile=cafile)
681 671 else:
682 672 ctx.load_default_certs()
683 673
684 674 return len(ctx.get_ca_certs()) > 0
685 675
686 676
687 677 @check("tls1.2", "TLS 1.2 protocol support")
688 678 def has_tls1_2():
689 679 from mercurial import sslutil
690 680
691 681 return b'tls1.2' in sslutil.supportedprotocols
692 682
693 683
694 684 @check("windows", "Windows")
695 685 def has_windows():
696 686 return os.name == 'nt'
697 687
698 688
699 689 @check("system-sh", "system() uses sh")
700 690 def has_system_sh():
701 691 return os.name != 'nt'
702 692
703 693
704 694 @check("serve", "platform and python can manage 'hg serve -d'")
705 695 def has_serve():
706 696 return True
707 697
708 698
709 699 @check("setprocname", "whether osutil.setprocname is available or not")
710 700 def has_setprocname():
711 701 try:
712 702 from mercurial.utils import procutil
713 703
714 704 procutil.setprocname
715 705 return True
716 706 except AttributeError:
717 707 return False
718 708
719 709
720 710 @check("test-repo", "running tests from repository")
721 711 def has_test_repo():
722 712 t = os.environ["TESTDIR"]
723 713 return os.path.isdir(os.path.join(t, "..", ".hg"))
724 714
725 715
726 716 @check("network-io", "whether tests are allowed to access 3rd party services")
727 717 def has_test_repo():
728 718 t = os.environ.get("HGTESTS_ALLOW_NETIO")
729 719 return t == "1"
730 720
731 721
732 722 @check("curses", "terminfo compiler and curses module")
733 723 def has_curses():
734 724 try:
735 725 import curses
736 726
737 727 curses.COLOR_BLUE
738 728
739 729 # Windows doesn't have a `tic` executable, but the windows_curses
740 730 # package is sufficient to run the tests without it.
741 731 if os.name == 'nt':
742 732 return True
743 733
744 734 return has_tic()
745 735
746 736 except (ImportError, AttributeError):
747 737 return False
748 738
749 739
750 740 @check("tic", "terminfo compiler")
751 741 def has_tic():
752 742 return matchoutput('test -x "`which tic`"', br'')
753 743
754 744
755 745 @check("xz", "xz compression utility")
756 746 def has_xz():
757 747 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
758 748 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
759 749 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
760 750
761 751
762 752 @check("msys", "Windows with MSYS")
763 753 def has_msys():
764 754 return os.getenv('MSYSTEM')
765 755
766 756
767 757 @check("aix", "AIX")
768 758 def has_aix():
769 759 return sys.platform.startswith("aix")
770 760
771 761
772 762 @check("osx", "OS X")
773 763 def has_osx():
774 764 return sys.platform == 'darwin'
775 765
776 766
777 767 @check("osxpackaging", "OS X packaging tools")
778 768 def has_osxpackaging():
779 769 try:
780 770 return (
781 771 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
782 772 and matchoutput(
783 773 'productbuild', br'Usage: productbuild ', ignorestatus=1
784 774 )
785 775 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
786 776 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
787 777 )
788 778 except ImportError:
789 779 return False
790 780
791 781
792 782 @check('linuxormacos', 'Linux or MacOS')
793 783 def has_linuxormacos():
794 784 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
795 785 return sys.platform.startswith(('linux', 'darwin'))
796 786
797 787
798 788 @check("docker", "docker support")
799 789 def has_docker():
800 790 pat = br'A self-sufficient runtime for'
801 791 if matchoutput('docker --help', pat):
802 792 if 'linux' not in sys.platform:
803 793 # TODO: in theory we should be able to test docker-based
804 794 # package creation on non-linux using boot2docker, but in
805 795 # practice that requires extra coordination to make sure
806 796 # $TESTTEMP is going to be visible at the same path to the
807 797 # boot2docker VM. If we figure out how to verify that, we
808 798 # can use the following instead of just saying False:
809 799 # return 'DOCKER_HOST' in os.environ
810 800 return False
811 801
812 802 return True
813 803 return False
814 804
815 805
816 806 @check("debhelper", "debian packaging tools")
817 807 def has_debhelper():
818 808 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
819 809 # quote), so just accept anything in that spot.
820 810 dpkg = matchoutput(
821 811 'dpkg --version', br"Debian .dpkg' package management program"
822 812 )
823 813 dh = matchoutput(
824 814 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
825 815 )
826 816 dh_py2 = matchoutput(
827 817 'dh_python2 --help', br'other supported Python versions'
828 818 )
829 819 # debuild comes from the 'devscripts' package, though you might want
830 820 # the 'build-debs' package instead, which has a dependency on devscripts.
831 821 debuild = matchoutput(
832 822 'debuild --help', br'to run debian/rules with given parameter'
833 823 )
834 824 return dpkg and dh and dh_py2 and debuild
835 825
836 826
837 827 @check(
838 828 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
839 829 )
840 830 def has_debdeps():
841 831 # just check exit status (ignoring output)
842 832 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
843 833 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
844 834
845 835
846 836 @check("demandimport", "demandimport enabled")
847 837 def has_demandimport():
848 838 # chg disables demandimport intentionally for performance wins.
849 839 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
850 840
851 841
852 842 # Add "py27", "py35", ... as possible feature checks. Note that there's no
853 843 # punctuation here.
854 844 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
855 845 def has_python_range(v):
856 846 major, minor = v.split('.')[0:2]
857 847 py_major, py_minor = sys.version_info.major, sys.version_info.minor
858 848
859 849 return (py_major, py_minor) >= (int(major), int(minor))
860 850
861 851
862 852 @check("py3", "running with Python 3.x")
863 853 def has_py3():
864 854 return 3 == sys.version_info[0]
865 855
866 856
867 857 @check("py3exe", "a Python 3.x interpreter is available")
868 858 def has_python3exe():
869 859 py = 'python3'
870 860 if os.name == 'nt':
871 861 py = 'py -3'
872 862 return matchoutput('%s -V' % py, br'^Python 3.(5|6|7|8|9)')
873 863
874 864
875 865 @check("pure", "running with pure Python code")
876 866 def has_pure():
877 867 return any(
878 868 [
879 869 os.environ.get("HGMODULEPOLICY") == "py",
880 870 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
881 871 ]
882 872 )
883 873
884 874
885 875 @check("slow", "allow slow tests (use --allow-slow-tests)")
886 876 def has_slow():
887 877 return os.environ.get('HGTEST_SLOW') == 'slow'
888 878
889 879
890 880 @check("hypothesis", "Hypothesis automated test generation")
891 881 def has_hypothesis():
892 882 try:
893 883 import hypothesis
894 884
895 885 hypothesis.given
896 886 return True
897 887 except ImportError:
898 888 return False
899 889
900 890
901 891 @check("unziplinks", "unzip(1) understands and extracts symlinks")
902 892 def unzip_understands_symlinks():
903 893 return matchoutput('unzip --help', br'Info-ZIP')
904 894
905 895
906 896 @check("zstd", "zstd Python module available")
907 897 def has_zstd():
908 898 try:
909 899 import mercurial.zstd
910 900
911 901 mercurial.zstd.__version__
912 902 return True
913 903 except ImportError:
914 904 return False
915 905
916 906
917 907 @check("devfull", "/dev/full special file")
918 908 def has_dev_full():
919 909 return os.path.exists('/dev/full')
920 910
921 911
922 912 @check("ensurepip", "ensurepip module")
923 913 def has_ensurepip():
924 914 try:
925 915 import ensurepip
926 916
927 917 ensurepip.bootstrap
928 918 return True
929 919 except ImportError:
930 920 return False
931 921
932 922
933 923 @check("virtualenv", "virtualenv support")
934 924 def has_virtualenv():
935 925 try:
936 926 import virtualenv
937 927
938 928 # --no-site-package became the default in 1.7 (Nov 2011), and the
939 929 # argument was removed in 20.0 (Feb 2020). Rather than make the
940 930 # script complicated, just ignore ancient versions.
941 931 return int(virtualenv.__version__.split('.')[0]) > 1
942 932 except (AttributeError, ImportError, IndexError):
943 933 return False
944 934
945 935
946 936 @check("fsmonitor", "running tests with fsmonitor")
947 937 def has_fsmonitor():
948 938 return 'HGFSMONITOR_TESTS' in os.environ
949 939
950 940
951 941 @check("fuzzywuzzy", "Fuzzy string matching library")
952 942 def has_fuzzywuzzy():
953 943 try:
954 944 import fuzzywuzzy
955 945
956 946 fuzzywuzzy.__version__
957 947 return True
958 948 except ImportError:
959 949 return False
960 950
961 951
962 952 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
963 953 def has_clang_libfuzzer():
964 954 mat = matchoutput('clang --version', br'clang version (\d)')
965 955 if mat:
966 956 # libfuzzer is new in clang 6
967 957 return int(mat.group(1)) > 5
968 958 return False
969 959
970 960
971 961 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
972 962 def has_clang60():
973 963 return matchoutput('clang-6.0 --version', br'clang version 6\.')
974 964
975 965
976 966 @check("xdiff", "xdiff algorithm")
977 967 def has_xdiff():
978 968 try:
979 969 from mercurial import policy
980 970
981 971 bdiff = policy.importmod('bdiff')
982 972 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
983 973 except (ImportError, AttributeError):
984 974 return False
985 975
986 976
987 977 @check('extraextensions', 'whether tests are running with extra extensions')
988 978 def has_extraextensions():
989 979 return 'HGTESTEXTRAEXTENSIONS' in os.environ
990 980
991 981
992 982 def getrepofeatures():
993 983 """Obtain set of repository features in use.
994 984
995 985 HGREPOFEATURES can be used to define or remove features. It contains
996 986 a space-delimited list of feature strings. Strings beginning with ``-``
997 987 mean to remove.
998 988 """
999 989 # Default list provided by core.
1000 990 features = {
1001 991 'bundlerepo',
1002 992 'revlogstore',
1003 993 'fncache',
1004 994 }
1005 995
1006 996 # Features that imply other features.
1007 997 implies = {
1008 998 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
1009 999 }
1010 1000
1011 1001 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
1012 1002 if not override:
1013 1003 continue
1014 1004
1015 1005 if override.startswith('-'):
1016 1006 if override[1:] in features:
1017 1007 features.remove(override[1:])
1018 1008 else:
1019 1009 features.add(override)
1020 1010
1021 1011 for imply in implies.get(override, []):
1022 1012 if imply.startswith('-'):
1023 1013 if imply[1:] in features:
1024 1014 features.remove(imply[1:])
1025 1015 else:
1026 1016 features.add(imply)
1027 1017
1028 1018 return features
1029 1019
1030 1020
1031 1021 @check('reporevlogstore', 'repository using the default revlog store')
1032 1022 def has_reporevlogstore():
1033 1023 return 'revlogstore' in getrepofeatures()
1034 1024
1035 1025
1036 1026 @check('reposimplestore', 'repository using simple storage extension')
1037 1027 def has_reposimplestore():
1038 1028 return 'simplestore' in getrepofeatures()
1039 1029
1040 1030
1041 1031 @check('repobundlerepo', 'whether we can open bundle files as repos')
1042 1032 def has_repobundlerepo():
1043 1033 return 'bundlerepo' in getrepofeatures()
1044 1034
1045 1035
1046 1036 @check('repofncache', 'repository has an fncache')
1047 1037 def has_repofncache():
1048 1038 return 'fncache' in getrepofeatures()
1049 1039
1050 1040
1051 1041 @check('dirstate-v2', 'using the v2 format of .hg/dirstate')
1052 1042 def has_dirstate_v2():
1053 1043 # Keep this logic in sync with `newreporequirements()` in `mercurial/localrepo.py`
1054 1044 return has_rust() and matchoutput(
1055 1045 'hg config format.exp-dirstate-v2', b'(?i)1|yes|true|on|always'
1056 1046 )
1057 1047
1058 1048
1059 1049 @check('sqlite', 'sqlite3 module and matching cli is available')
1060 1050 def has_sqlite():
1061 1051 try:
1062 1052 import sqlite3
1063 1053
1064 1054 version = sqlite3.sqlite_version_info
1065 1055 except ImportError:
1066 1056 return False
1067 1057
1068 1058 if version < (3, 8, 3):
1069 1059 # WITH clause not supported
1070 1060 return False
1071 1061
1072 1062 return matchoutput('sqlite3 -version', br'^3\.\d+')
1073 1063
1074 1064
1075 1065 @check('vcr', 'vcr http mocking library (pytest-vcr)')
1076 1066 def has_vcr():
1077 1067 try:
1078 1068 import vcr
1079 1069
1080 1070 vcr.VCR
1081 1071 return True
1082 1072 except (ImportError, AttributeError):
1083 1073 pass
1084 1074 return False
1085 1075
1086 1076
1087 1077 @check('emacs', 'GNU Emacs')
1088 1078 def has_emacs():
1089 1079 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1090 1080 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1091 1081 # 24 release)
1092 1082 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1093 1083
1094 1084
1095 1085 @check('black', 'the black formatter for python (>= 20.8b1)')
1096 1086 def has_black():
1097 1087 blackcmd = 'black --version'
1098 1088 version_regex = b'black, version ([0-9a-b.]+)'
1099 1089 version = matchoutput(blackcmd, version_regex)
1100 1090 sv = distutils.version.StrictVersion
1101 1091 return version and sv(_bytes2sys(version.group(1))) >= sv('20.8b1')
1102 1092
1103 1093
1104 1094 @check('pytype', 'the pytype type checker')
1105 1095 def has_pytype():
1106 1096 pytypecmd = 'pytype --version'
1107 1097 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1108 1098 sv = distutils.version.StrictVersion
1109 1099 return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
1110 1100
1111 1101
1112 1102 @check("rustfmt", "rustfmt tool at version nightly-2020-10-04")
1113 1103 def has_rustfmt():
1114 1104 # We use Nightly's rustfmt due to current unstable config options.
1115 1105 return matchoutput(
1116 1106 '`rustup which --toolchain nightly-2020-10-04 rustfmt` --version',
1117 1107 b'rustfmt',
1118 1108 )
1119 1109
1120 1110
1121 1111 @check("cargo", "cargo tool")
1122 1112 def has_cargo():
1123 1113 return matchoutput('`rustup which cargo` --version', b'cargo')
1124 1114
1125 1115
1126 1116 @check("lzma", "python lzma module")
1127 1117 def has_lzma():
1128 1118 try:
1129 1119 import _lzma
1130 1120
1131 1121 _lzma.FORMAT_XZ
1132 1122 return True
1133 1123 except ImportError:
1134 1124 return False
1135 1125
1136 1126
1137 1127 @check("bash", "bash shell")
1138 1128 def has_bash():
1139 1129 return matchoutput("bash -c 'echo hi'", b'^hi$')
@@ -1,39 +1,39 b''
1 #require bzr bzr114
1 #require bzr
2 2
3 3 $ . "$TESTDIR/bzr-definitions"
4 4
5 5 The file/directory replacement can only be reproduced on
6 6 bzr >= 1.4. Merge it back in test-convert-bzr-directories once
7 7 this version becomes mainstream.
8 8 replace file with dir
9 9
10 10 $ mkdir test-replace-file-with-dir
11 11 $ cd test-replace-file-with-dir
12 $ bzr init -q source
12 $ brz init -q source
13 13 $ cd source
14 14 $ echo d > d
15 $ bzr add -q d
16 $ bzr commit -q -m 'add d file'
15 $ brz add -q d
16 $ brz commit -q -m 'add d file'
17 17 $ rm d
18 18 $ mkdir d
19 $ bzr add -q d
20 $ bzr commit -q -m 'replace with d dir'
19 $ brz add -q d
20 $ brz commit -q -m 'replace with d dir'
21 21 $ echo a > d/a
22 $ bzr add -q d/a
23 $ bzr commit -q -m 'add d/a'
22 $ brz add -q d/a
23 $ brz commit -q -m 'add d/a'
24 24 $ cd ..
25 25 $ hg convert source source-hg
26 26 initializing destination source-hg repository
27 27 scanning source...
28 28 sorting...
29 29 converting...
30 30 2 add d file
31 31 1 replace with d dir
32 32 0 add d/a
33 33 $ manifest source-hg tip
34 34 % manifest of tip
35 35 644 d/a
36 36 $ cd source-hg
37 37 $ hg update
38 38 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
39 39 $ cd ../..
@@ -1,197 +1,197 b''
1 1 #require bzr
2 2
3 3 $ . "$TESTDIR/bzr-definitions"
4 4
5 5 Work around https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=944379
6 6 $ mkdir -p "${HOME}/.config/breezy"
7 7
8 8 empty directory
9 9
10 10 $ mkdir test-empty
11 11 $ cd test-empty
12 $ bzr init -q source
12 $ brz init -q source
13 13 $ cd source
14 14 $ echo content > a
15 $ bzr add -q a
16 $ bzr commit -q -m 'Initial add'
15 $ brz add -q a
16 $ brz commit -q -m 'Initial add'
17 17 $ mkdir empty
18 $ bzr add -q empty
19 $ bzr commit -q -m 'Empty directory added'
18 $ brz add -q empty
19 $ brz commit -q -m 'Empty directory added'
20 20 $ echo content > empty/something
21 $ bzr add -q empty/something
22 $ bzr commit -q -m 'Added file into directory'
21 $ brz add -q empty/something
22 $ brz commit -q -m 'Added file into directory'
23 23 $ cd ..
24 24 $ hg convert source source-hg
25 25 initializing destination source-hg repository
26 26 scanning source...
27 27 sorting...
28 28 converting...
29 29 2 Initial add
30 30 1 Empty directory added
31 31 0 Added file into directory
32 32 $ manifest source-hg 1
33 33 % manifest of 1
34 34 644 a
35 35 $ manifest source-hg tip
36 36 % manifest of tip
37 37 644 a
38 38 644 empty/something
39 39 $ cd ..
40 40
41 41 directory renames
42 42
43 43 $ mkdir test-dir-rename
44 44 $ cd test-dir-rename
45 $ bzr init -q source
45 $ brz init -q source
46 46 $ cd source
47 47 $ mkdir tpyo
48 48 $ echo content > tpyo/something
49 $ bzr add -q tpyo
50 $ bzr commit -q -m 'Added directory'
51 $ bzr mv tpyo typo
49 $ brz add -q tpyo
50 $ brz commit -q -m 'Added directory'
51 $ brz mv tpyo typo
52 52 tpyo => typo
53 $ bzr commit -q -m 'Oops, typo'
53 $ brz commit -q -m 'Oops, typo'
54 54 $ cd ..
55 55 $ hg convert source source-hg
56 56 initializing destination source-hg repository
57 57 scanning source...
58 58 sorting...
59 59 converting...
60 60 1 Added directory
61 61 0 Oops, typo
62 62 $ manifest source-hg 0
63 63 % manifest of 0
64 64 644 tpyo/something
65 65 $ manifest source-hg tip
66 66 % manifest of tip
67 67 644 typo/something
68 68 $ cd ..
69 69
70 70 nested directory renames
71 71
72 72 $ mkdir test-nested-dir-rename
73 73 $ cd test-nested-dir-rename
74 $ bzr init -q source
74 $ brz init -q source
75 75 $ cd source
76 76 $ mkdir -p firstlevel/secondlevel/thirdlevel
77 77 $ echo content > firstlevel/secondlevel/file
78 78 $ echo this_needs_to_be_there_too > firstlevel/secondlevel/thirdlevel/stuff
79 $ bzr add -q firstlevel
80 $ bzr commit -q -m 'Added nested directories'
81 $ bzr mv firstlevel/secondlevel secondlevel
79 $ brz add -q firstlevel
80 $ brz commit -q -m 'Added nested directories'
81 $ brz mv firstlevel/secondlevel secondlevel
82 82 firstlevel/secondlevel => secondlevel
83 $ bzr commit -q -m 'Moved secondlevel one level up'
83 $ brz commit -q -m 'Moved secondlevel one level up'
84 84 $ cd ..
85 85 $ hg convert source source-hg
86 86 initializing destination source-hg repository
87 87 scanning source...
88 88 sorting...
89 89 converting...
90 90 1 Added nested directories
91 91 0 Moved secondlevel one level up
92 92 $ manifest source-hg tip
93 93 % manifest of tip
94 94 644 secondlevel/file
95 95 644 secondlevel/thirdlevel/stuff
96 96 $ cd ..
97 97
98 98 directory remove
99 99
100 100 $ mkdir test-dir-remove
101 101 $ cd test-dir-remove
102 $ bzr init -q source
102 $ brz init -q source
103 103 $ cd source
104 104 $ mkdir src
105 105 $ echo content > src/sourcecode
106 $ bzr add -q src
107 $ bzr commit -q -m 'Added directory'
108 $ bzr rm -q src
109 $ bzr commit -q -m 'Removed directory'
106 $ brz add -q src
107 $ brz commit -q -m 'Added directory'
108 $ brz rm -q src
109 $ brz commit -q -m 'Removed directory'
110 110 $ cd ..
111 111 $ hg convert source source-hg
112 112 initializing destination source-hg repository
113 113 scanning source...
114 114 sorting...
115 115 converting...
116 116 1 Added directory
117 117 0 Removed directory
118 118 $ manifest source-hg 0
119 119 % manifest of 0
120 120 644 src/sourcecode
121 121 $ manifest source-hg tip
122 122 % manifest of tip
123 123 $ cd ..
124 124
125 125 directory replace
126 126
127 127 $ mkdir test-dir-replace
128 128 $ cd test-dir-replace
129 $ bzr init -q source
129 $ brz init -q source
130 130 $ cd source
131 131 $ mkdir first second
132 132 $ echo content > first/file
133 133 $ echo morecontent > first/dummy
134 134 $ echo othercontent > second/something
135 $ bzr add -q first second
136 $ bzr commit -q -m 'Initial layout'
137 $ bzr mv first/file second/file
135 $ brz add -q first second
136 $ brz commit -q -m 'Initial layout'
137 $ brz mv first/file second/file
138 138 first/file => second/file
139 $ bzr mv first third
139 $ brz mv first third
140 140 first => third
141 $ bzr commit -q -m 'Some conflicting moves'
141 $ brz commit -q -m 'Some conflicting moves'
142 142 $ cd ..
143 143 $ hg convert source source-hg
144 144 initializing destination source-hg repository
145 145 scanning source...
146 146 sorting...
147 147 converting...
148 148 1 Initial layout
149 149 0 Some conflicting moves
150 150 $ manifest source-hg tip
151 151 % manifest of tip
152 152 644 second/file
153 153 644 second/something
154 154 644 third/dummy
155 155 $ cd ..
156 156
157 157 divergent nested renames (issue3089)
158 158
159 159 $ mkdir test-divergent-renames
160 160 $ cd test-divergent-renames
161 $ bzr init -q source
161 $ brz init -q source
162 162 $ cd source
163 163 $ mkdir -p a/c
164 164 $ echo a > a/fa
165 165 $ echo c > a/c/fc
166 $ bzr add -q a
167 $ bzr commit -q -m 'Initial layout'
168 $ bzr mv a b
166 $ brz add -q a
167 $ brz commit -q -m 'Initial layout'
168 $ brz mv a b
169 169 a => b
170 170 $ mkdir a
171 $ bzr add a
171 $ brz add a
172 172 add(ed|ing) a (re)
173 $ bzr mv b/c a/c
173 $ brz mv b/c a/c
174 174 b/c => a/c
175 $ bzr status
175 $ brz status
176 176 added:
177 177 a/
178 178 renamed:
179 179 a/? => b/? (re)
180 180 a/c/? => a/c/? (re)
181 $ bzr commit -q -m 'Divergent renames'
181 $ brz commit -q -m 'Divergent renames'
182 182 $ cd ..
183 183 $ hg convert source source-hg
184 184 initializing destination source-hg repository
185 185 scanning source...
186 186 sorting...
187 187 converting...
188 188 1 Initial layout
189 189 0 Divergent renames
190 190 $ hg -R source-hg st -C --change 1
191 191 A b/fa
192 192 a/fa
193 193 R a/fa
194 194 $ hg -R source-hg manifest -r 1
195 195 a/c/fc
196 196 b/fa
197 197 $ cd ..
@@ -1,39 +1,40 b''
1 1 #require bzr
2 2
3 3 $ . "$TESTDIR/bzr-definitions"
4 4 $ cat > ghostcreator.py <<EOF
5 5 > import sys
6 > from bzrlib import workingtree
6 > from breezy import workingtree
7 > import breezy.bzr.bzrdir
7 8 > wt = workingtree.WorkingTree.open('.')
8 9 >
9 10 > message, ghostrev = sys.argv[1:]
10 > wt.set_parent_ids(wt.get_parent_ids() + [ghostrev])
11 > wt.set_parent_ids(wt.get_parent_ids() + [ghostrev.encode()])
11 12 > wt.commit(message)
12 13 > EOF
13 14
14 15 ghost revisions
15 16
16 17 $ mkdir test-ghost-revisions
17 18 $ cd test-ghost-revisions
18 $ bzr init -q source
19 $ brz init -q source
19 20 $ cd source
20 21 $ echo content > somefile
21 $ bzr add -q somefile
22 $ bzr commit -q -m 'Initial layout setup'
22 $ brz add -q somefile
23 $ brz commit -q -m 'Initial layout setup'
23 24 $ echo morecontent >> somefile
24 25 $ "$PYTHON" ../../ghostcreator.py 'Commit with ghost revision' ghostrev
25 26 $ cd ..
26 27 $ hg convert source source-hg
27 28 initializing destination source-hg repository
28 29 scanning source...
29 30 sorting...
30 31 converting...
31 32 1 Initial layout setup
32 33 0 Commit with ghost revision
33 34 $ glog -R source-hg
34 35 o 1@source "Commit with ghost revision" files+: [], files-: [], files: [somefile]
35 36 |
36 37 o 0@source "Initial layout setup" files+: [somefile], files-: [], files: []
37 38
38 39
39 40 $ cd ..
@@ -1,225 +1,225 b''
1 1 #require bzr
2 2
3 3 N.B. bzr 1.13 has a bug that breaks this test. If you see this
4 4 test fail, check your bzr version. Upgrading to bzr 1.13.1
5 5 should fix it.
6 6
7 7 $ . "$TESTDIR/bzr-definitions"
8 8
9 9 test multiple merges at once
10 10
11 11 $ mkdir test-multimerge
12 12 $ cd test-multimerge
13 $ bzr init -q source
13 $ brz init -q source
14 14 $ cd source
15 15 $ echo content > file
16 16 $ echo text > rename_me
17 $ bzr add -q file rename_me
18 $ bzr commit -q -m 'Initial add' '--commit-time=2009-10-10 08:00:00 +0100'
17 $ brz add -q file rename_me
18 $ brz commit -q -m 'Initial add' '--commit-time=2009-10-10 08:00:00 +0100'
19 19 $ cd ..
20 $ bzr branch -q source source-branch1
20 $ brz branch -q source source-branch1
21 21 $ cd source-branch1
22 22 $ echo morecontent >> file
23 23 $ echo evenmorecontent > file-branch1
24 $ bzr add -q file-branch1
25 $ bzr commit -q -m 'Added branch1 file' '--commit-time=2009-10-10 08:00:01 +0100'
24 $ brz add -q file-branch1
25 $ brz commit -q -m 'Added branch1 file' '--commit-time=2009-10-10 08:00:01 +0100'
26 26 $ cd ../source
27 27 $ sleep 1
28 28 $ echo content > file-parent
29 $ bzr add -q file-parent
30 $ bzr commit -q -m 'Added parent file' '--commit-time=2009-10-10 08:00:02 +0100'
29 $ brz add -q file-parent
30 $ brz commit -q -m 'Added parent file' '--commit-time=2009-10-10 08:00:02 +0100'
31 31 $ cd ..
32 $ bzr branch -q source source-branch2
32 $ brz branch -q source source-branch2
33 33 $ cd source-branch2
34 34 $ echo somecontent > file-branch2
35 $ bzr add -q file-branch2
36 $ bzr mv -q rename_me renamed
35 $ brz add -q file-branch2
36 $ brz mv -q rename_me renamed
37 37 $ echo change > renamed
38 $ bzr commit -q -m 'Added brach2 file' '--commit-time=2009-10-10 08:00:03 +0100'
38 $ brz commit -q -m 'Added brach2 file' '--commit-time=2009-10-10 08:00:03 +0100'
39 39 $ sleep 1
40 40 $ cd ../source
41 $ bzr merge -q ../source-branch1
42 $ bzr merge -q --force ../source-branch2
43 $ bzr commit -q -m 'Merged branches' '--commit-time=2009-10-10 08:00:04 +0100'
41 $ brz merge -q ../source-branch1
42 $ brz merge -q --force ../source-branch2
43 $ brz commit -q -m 'Merged branches' '--commit-time=2009-10-10 08:00:04 +0100'
44 44 $ cd ..
45 45
46 46 BUG: file-branch2 should not be added in rev 4, and the rename_me -> renamed
47 47 move should be recorded in the fixup merge.
48 48 $ hg convert --datesort --config convert.bzr.saverev=False source source-hg
49 49 initializing destination source-hg repository
50 50 scanning source...
51 51 sorting...
52 52 converting...
53 53 4 Initial add
54 54 3 Added branch1 file
55 55 2 Added parent file
56 56 1 Added brach2 file
57 57 0 Merged branches
58 58 warning: can't find ancestor for 'renamed' copied from 'rename_me'!
59 59 $ glog -R source-hg
60 60 o 5@source "(octopus merge fixup)" files+: [], files-: [], files: [renamed]
61 61 |\
62 62 | o 4@source "Merged branches" files+: [file-branch2 renamed], files-: [rename_me], files: []
63 63 | |\
64 64 o---+ 3@source-branch2 "Added brach2 file" files+: [file-branch2 renamed], files-: [rename_me], files: []
65 65 / /
66 66 | o 2@source "Added parent file" files+: [file-parent], files-: [], files: []
67 67 | |
68 68 o | 1@source-branch1 "Added branch1 file" files+: [file-branch1], files-: [], files: [file]
69 69 |/
70 70 o 0@source "Initial add" files+: [file rename_me], files-: [], files: []
71 71
72 72 $ manifest source-hg tip
73 73 % manifest of tip
74 74 644 file
75 75 644 file-branch1
76 76 644 file-branch2
77 77 644 file-parent
78 78 644 renamed
79 79
80 80 $ hg convert source-hg hg2hg
81 81 initializing destination hg2hg repository
82 82 scanning source...
83 83 sorting...
84 84 converting...
85 85 5 Initial add
86 86 4 Added branch1 file
87 87 3 Added parent file
88 88 2 Added brach2 file
89 89 1 Merged branches
90 90 0 (octopus merge fixup)
91 91
92 92 BUG: The manifest entries should be the same for matching revisions, and
93 93 nothing should be outgoing
94 94
95 95 $ hg -R source-hg manifest --debug -r tip | grep renamed
96 96 67109fdebf6c556eb0a9d5696dd98c8420520405 644 renamed
97 97 $ hg -R hg2hg manifest --debug -r tip | grep renamed
98 98 27c968376d7c3afd095ecb9c7697919b933448c8 644 renamed
99 99 $ hg -R source-hg manifest --debug -r 'tip^' | grep renamed
100 100 27c968376d7c3afd095ecb9c7697919b933448c8 644 renamed
101 101 $ hg -R hg2hg manifest --debug -r 'tip^' | grep renamed
102 102 27c968376d7c3afd095ecb9c7697919b933448c8 644 renamed
103 103
104 104 BUG: The revisions found should be the same in both repos
105 105
106 106 $ hg --cwd source-hg log -r 'file("renamed")' -G -Tcompact
107 107 o 5[tip]:4,3 6652429c300a 2009-10-10 08:00 +0100 foo
108 108 |\ (octopus merge fixup)
109 109 | |
110 110 | o 4:2,1 e0ae8af3503a 2009-10-10 08:00 +0100 foo
111 111 | |\ Merged branches
112 112 | ~ ~
113 113 o 3 138bed2e14be 2009-10-10 08:00 +0100 foo
114 114 | Added brach2 file
115 115 ~
116 116 $ hg --cwd hg2hg log -r 'file("renamed")' -G -Tcompact
117 117 o 4:2,1 e0ae8af3503a 2009-10-10 08:00 +0100 foo
118 118 |\ Merged branches
119 119 ~ ~
120 120 o 3 138bed2e14be 2009-10-10 08:00 +0100 foo
121 121 | Added brach2 file
122 122 ~
123 123
124 124 BUG(?): The move seems to be recorded in rev 4, so it should probably show up
125 125 there. It's not recorded as a move in rev 5, even in source-hg.
126 126
127 127 $ hg -R source-hg up -q tip
128 128 $ hg -R hg2hg up -q tip
129 129 $ hg --cwd source-hg log -r 'follow("renamed")' -G -Tcompact
130 130 @ 5[tip]:4,3 6652429c300a 2009-10-10 08:00 +0100 foo
131 131 |\ (octopus merge fixup)
132 132 | :
133 133 o : 3 138bed2e14be 2009-10-10 08:00 +0100 foo
134 134 :/ Added brach2 file
135 135 :
136 136 o 0 18b86f5df51b 2009-10-10 08:00 +0100 foo
137 137 Initial add
138 138
139 139 $ hg --cwd hg2hg log -r 'follow("renamed")' -G -Tcompact
140 140 o 3 138bed2e14be 2009-10-10 08:00 +0100 foo
141 141 : Added brach2 file
142 142 :
143 143 o 0 18b86f5df51b 2009-10-10 08:00 +0100 foo
144 144 Initial add
145 145
146 146
147 147 $ hg -R hg2hg out source-hg -T compact
148 148 comparing with source-hg
149 149 searching for changes
150 150 5[tip]:4,3 3be2299ccd31 2009-10-10 08:00 +0100 foo
151 151 (octopus merge fixup)
152 152
153 153
154 154 $ glog -R hg2hg
155 155 @ 5@source "(octopus merge fixup)" files+: [], files-: [], files: []
156 156 |\
157 157 | o 4@source "Merged branches" files+: [file-branch2 renamed], files-: [rename_me], files: []
158 158 | |\
159 159 o---+ 3@source-branch2 "Added brach2 file" files+: [file-branch2 renamed], files-: [rename_me], files: []
160 160 / /
161 161 | o 2@source "Added parent file" files+: [file-parent], files-: [], files: []
162 162 | |
163 163 o | 1@source-branch1 "Added branch1 file" files+: [file-branch1], files-: [], files: [file]
164 164 |/
165 165 o 0@source "Initial add" files+: [file rename_me], files-: [], files: []
166 166
167 167
168 168 $ hg -R source-hg log --debug -r tip
169 169 changeset: 5:6652429c300ab66fdeaf2e730945676a00b53231
170 170 branch: source
171 171 tag: tip
172 172 phase: draft
173 173 parent: 4:e0ae8af3503af9bbffb0b29268a02744cc61a561
174 174 parent: 3:138bed2e14be415a2692b02e41405b2864f758b4
175 175 manifest: 5:1eabd5f5d4b985784cf2c45c717ff053eca14b0d
176 176 user: Foo Bar <foo.bar@example.com>
177 177 date: Sat Oct 10 08:00:04 2009 +0100
178 178 files: renamed
179 179 extra: branch=source
180 180 description:
181 181 (octopus merge fixup)
182 182
183 183
184 184 $ hg -R hg2hg log --debug -r tip
185 185 changeset: 5:3be2299ccd315ff9aab2b49bdb0d14e3244435e8
186 186 branch: source
187 187 tag: tip
188 188 phase: draft
189 189 parent: 4:e0ae8af3503af9bbffb0b29268a02744cc61a561
190 190 parent: 3:138bed2e14be415a2692b02e41405b2864f758b4
191 191 manifest: 4:3ece3c7f2cc6df15b3cbbf3273c69869fc7c3ab0
192 192 user: Foo Bar <foo.bar@example.com>
193 193 date: Sat Oct 10 08:00:04 2009 +0100
194 194 extra: branch=source
195 195 description:
196 196 (octopus merge fixup)
197 197
198 198
199 199 $ hg -R source-hg manifest --debug -r tip
200 200 cdf31ed9242b209cd94697112160e2c5b37a667d 644 file
201 201 5108144f585149b29779d7c7e51d61dd22303ffe 644 file-branch1
202 202 80753c4a9ac3806858405b96b24a907b309e3616 644 file-branch2
203 203 7108421418404a937c684d2479a34a24d2ce4757 644 file-parent
204 204 67109fdebf6c556eb0a9d5696dd98c8420520405 644 renamed
205 205 $ hg -R source-hg manifest --debug -r 'tip^'
206 206 cdf31ed9242b209cd94697112160e2c5b37a667d 644 file
207 207 5108144f585149b29779d7c7e51d61dd22303ffe 644 file-branch1
208 208 80753c4a9ac3806858405b96b24a907b309e3616 644 file-branch2
209 209 7108421418404a937c684d2479a34a24d2ce4757 644 file-parent
210 210 27c968376d7c3afd095ecb9c7697919b933448c8 644 renamed
211 211
212 212 $ hg -R hg2hg manifest --debug -r tip
213 213 cdf31ed9242b209cd94697112160e2c5b37a667d 644 file
214 214 5108144f585149b29779d7c7e51d61dd22303ffe 644 file-branch1
215 215 80753c4a9ac3806858405b96b24a907b309e3616 644 file-branch2
216 216 7108421418404a937c684d2479a34a24d2ce4757 644 file-parent
217 217 27c968376d7c3afd095ecb9c7697919b933448c8 644 renamed
218 218 $ hg -R hg2hg manifest --debug -r 'tip^'
219 219 cdf31ed9242b209cd94697112160e2c5b37a667d 644 file
220 220 5108144f585149b29779d7c7e51d61dd22303ffe 644 file-branch1
221 221 80753c4a9ac3806858405b96b24a907b309e3616 644 file-branch2
222 222 7108421418404a937c684d2479a34a24d2ce4757 644 file-parent
223 223 27c968376d7c3afd095ecb9c7697919b933448c8 644 renamed
224 224
225 225 $ cd ..
@@ -1,36 +1,37 b''
1 1 #require bzr
2 2
3 3 $ . "$TESTDIR/bzr-definitions"
4 4 $ cat > treeset.py <<EOF
5 5 > import sys
6 > from bzrlib import workingtree
6 > from breezy import workingtree
7 > import breezy.bzr.bzrdir
7 8 > wt = workingtree.WorkingTree.open('.')
8 9 >
9 10 > message, rootid = sys.argv[1:]
10 > wt.set_root_id('tree_root-%s' % rootid)
11 > wt.set_root_id(b'tree_root-%s' % rootid.encode())
11 12 > wt.commit(message)
12 13 > EOF
13 14
14 15 change the id of the tree root
15 16
16 17 $ mkdir test-change-treeroot-id
17 18 $ cd test-change-treeroot-id
18 $ bzr init -q source
19 $ brz init -q source
19 20 $ cd source
20 21 $ echo content > file
21 $ bzr add -q file
22 $ bzr commit -q -m 'Initial add'
22 $ brz add -q file
23 $ brz commit -q -m 'Initial add'
23 24 $ "$PYTHON" ../../treeset.py 'Changed root' new
24 25 $ cd ..
25 26 $ hg convert source source-hg
26 27 initializing destination source-hg repository
27 28 scanning source...
28 29 sorting...
29 30 converting...
30 31 1 Initial add
31 32 0 Changed root
32 33 $ manifest source-hg tip
33 34 % manifest of tip
34 35 644 file
35 36
36 37 $ cd ..
@@ -1,288 +1,287 b''
1 1 #require bzr
2 2
3 3 $ . "$TESTDIR/bzr-definitions"
4 4
5 5 create and rename on the same file in the same step
6 6
7 7 $ mkdir test-createandrename
8 8 $ cd test-createandrename
9 $ bzr init -q source
9 $ brz init -q source
10 10
11 11 test empty repo conversion (issue3233)
12 12
13 13 $ hg convert source source-hg
14 14 initializing destination source-hg repository
15 15 scanning source...
16 16 sorting...
17 17 converting...
18 18
19 19 back to the rename stuff
20 20
21 21 $ cd source
22 22 $ echo a > a
23 23 $ echo c > c
24 24 $ echo e > e
25 $ bzr add -q a c e
26 $ bzr commit -q -m 'Initial add: a, c, e'
27 $ bzr mv a b
25 $ brz add -q a c e
26 $ brz commit -q -m 'Initial add: a, c, e'
27 $ brz mv a b
28 28 a => b
29 $ bzr mv c d
29 $ brz mv c d
30 30 c => d
31 $ bzr mv e f
31 $ brz mv e f
32 32 e => f
33 33 $ echo a2 >> a
34 34 $ mkdir e
35 $ bzr add -q a e
36 $ bzr commit -q -m 'rename a into b, create a, rename c into d'
35 $ brz add -q a e
36 $ brz commit -q -m 'rename a into b, create a, rename c into d'
37 37 $ cd ..
38 38 $ hg convert source source-hg
39 39 scanning source...
40 40 sorting...
41 41 converting...
42 42 1 Initial add: a, c, e
43 43 0 rename a into b, create a, rename c into d
44 44 $ glog -R source-hg
45 45 o 1@source "rename a into b, create a, rename c into d" files+: [b d f], files-: [c e], files: [a]
46 46 |
47 47 o 0@source "Initial add: a, c, e" files+: [a c e], files-: [], files: []
48 48
49 49
50 50 manifest
51 51
52 52 $ hg manifest -R source-hg -r tip
53 53 a
54 54 b
55 55 d
56 56 f
57 57
58 58 test --rev option
59 59
60 60 $ hg convert -r 1 source source-1-hg
61 61 initializing destination source-1-hg repository
62 62 scanning source...
63 63 sorting...
64 64 converting...
65 65 0 Initial add: a, c, e
66 66 $ glog -R source-1-hg
67 67 o 0@source "Initial add: a, c, e" files+: [a c e], files-: [], files: []
68 68
69 69
70 70 test with filemap
71 71
72 72 $ cat > filemap <<EOF
73 73 > exclude a
74 74 > EOF
75 75 $ hg convert --filemap filemap source source-filemap-hg
76 76 initializing destination source-filemap-hg repository
77 77 scanning source...
78 78 sorting...
79 79 converting...
80 80 1 Initial add: a, c, e
81 81 0 rename a into b, create a, rename c into d
82 82 $ hg -R source-filemap-hg manifest -r tip
83 83 b
84 84 d
85 85 f
86 86
87 87 convert from lightweight checkout
88 88
89 $ bzr checkout --lightweight source source-light
89 $ brz checkout --lightweight source source-light
90 90 $ hg convert -s bzr source-light source-light-hg
91 91 initializing destination source-light-hg repository
92 92 warning: lightweight checkouts may cause conversion failures, try with a regular branch instead.
93 93 $TESTTMP/test-createandrename/source-light does not look like a Bazaar repository
94 94 abort: source-light: missing or unsupported repository
95 95 [255]
96 96
97 97 extract timestamps that look just like hg's {date|isodate}:
98 98 yyyy-mm-dd HH:MM zzzz (no seconds!)
99 99 compare timestamps
100 100
101 101 $ cd source
102 $ bzr log | \
102 $ brz log | \
103 103 > sed '/timestamp/!d;s/.\{15\}\([0-9: -]\{16\}\):.. \(.[0-9]\{4\}\)/\1 \2/' \
104 104 > > ../bzr-timestamps
105 105 $ cd ..
106 106 $ hg -R source-hg log --template "{date|isodate}\n" > hg-timestamps
107 107 $ cmp bzr-timestamps hg-timestamps || diff -u bzr-timestamps hg-timestamps
108 108 $ cd ..
109 109
110 110 merge
111 111
112 112 $ mkdir test-merge
113 113 $ cd test-merge
114 114 $ cat > helper.py <<EOF
115 115 > import sys
116 > from bzrlib import workingtree
116 > from breezy import workingtree
117 > import breezy.bzr.bzrdir
117 118 > wt = workingtree.WorkingTree.open('.')
118 119 >
119 120 > message, stamp = sys.argv[1:]
120 121 > wt.commit(message, timestamp=int(stamp))
121 122 > EOF
122 $ bzr init -q source
123 $ brz init -q source
123 124 $ cd source
124 125 $ echo content > a
125 126 $ echo content2 > b
126 $ bzr add -q a b
127 $ bzr commit -q -m 'Initial add'
127 $ brz add -q a b
128 $ brz commit -q -m 'Initial add'
128 129 $ cd ..
129 $ bzr branch -q source source-improve
130 $ brz branch -q source source-improve
130 131 $ cd source
131 132 $ echo more >> a
132 133 $ "$PYTHON" ../helper.py 'Editing a' 100
133 134 $ cd ../source-improve
134 135 $ echo content3 >> b
135 136 $ "$PYTHON" ../helper.py 'Editing b' 200
136 137 $ cd ../source
137 $ bzr merge -q ../source-improve
138 $ bzr commit -q -m 'Merged improve branch'
138 $ brz merge -q ../source-improve
139 $ brz commit -q -m 'Merged improve branch'
139 140 $ cd ..
140 141 $ hg convert --datesort source source-hg
141 142 initializing destination source-hg repository
142 143 scanning source...
143 144 sorting...
144 145 converting...
145 146 3 Initial add
146 147 2 Editing a
147 148 1 Editing b
148 149 0 Merged improve branch
149 150 $ glog -R source-hg
150 151 o 3@source "Merged improve branch" files+: [], files-: [], files: []
151 152 |\
152 153 | o 2@source-improve "Editing b" files+: [], files-: [], files: [b]
153 154 | |
154 155 o | 1@source "Editing a" files+: [], files-: [], files: [a]
155 156 |/
156 157 o 0@source "Initial add" files+: [a b], files-: [], files: []
157 158
158 159 $ cd ..
159 160
160 161 #if symlink execbit
161 162
162 163 symlinks and executable files
163 164
164 165 $ mkdir test-symlinks
165 166 $ cd test-symlinks
166 $ bzr init -q source
167 $ brz init -q source
167 168 $ cd source
168 169 $ touch program
169 170 $ chmod +x program
170 171 $ ln -s program altname
171 172 $ mkdir d
172 173 $ echo a > d/a
173 174 $ ln -s a syma
174 $ bzr add -q altname program syma d/a
175 $ bzr commit -q -m 'Initial setup'
175 $ brz add -q altname program syma d/a
176 $ brz commit -q -m 'Initial setup'
176 177 $ touch newprog
177 178 $ chmod +x newprog
178 179 $ rm altname
179 180 $ ln -s newprog altname
180 181 $ chmod -x program
181 $ bzr add -q newprog
182 $ bzr commit -q -m 'Symlink changed, x bits changed'
182 $ brz add -q newprog
183 $ brz commit -q -m 'Symlink changed, x bits changed'
183 184 $ cd ..
184 185 $ hg convert source source-hg
185 186 initializing destination source-hg repository
186 187 scanning source...
187 188 sorting...
188 189 converting...
189 190 1 Initial setup
190 191 0 Symlink changed, x bits changed
191 192 $ manifest source-hg 0
192 193 % manifest of 0
193 194 644 @ altname
194 195 644 d/a
195 196 755 * program
196 197 644 @ syma
197 198 $ manifest source-hg tip
198 199 % manifest of tip
199 200 644 @ altname
200 201 644 d/a
201 202 755 * newprog
202 203 644 program
203 204 644 @ syma
204 205
205 206 test the symlinks can be recreated
206 207
207 208 $ cd source-hg
208 209 $ hg up
209 210 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
210 211 $ hg cat syma; echo
211 212 a
212 213 $ cd ../..
213 214
214 215 #endif
215 216
216 217 Multiple branches
217 218
218 $ bzr init-repo -q --no-trees repo
219 $ bzr init -q repo/trunk
220 $ bzr co repo/trunk repo-trunk
219 $ brz init-repo -q --no-trees repo
220 $ brz init -q repo/trunk
221 $ brz co repo/trunk repo-trunk
221 222 $ cd repo-trunk
222 223 $ echo a > a
223 $ bzr add -q a
224 $ bzr ci -qm adda
225 $ bzr tag trunk-tag
224 $ brz add -q a
225 $ brz ci -qm adda
226 $ brz tag trunk-tag
226 227 Created tag trunk-tag.
227 $ bzr switch -b branch
228 $ brz switch -b branch
228 229 Tree is up to date at revision 1.
229 230 Switched to branch*repo/branch/ (glob)
230 $ sleep 1
231 231 $ echo b > b
232 $ bzr add -q b
233 $ bzr ci -qm addb
234 $ bzr tag branch-tag
232 $ brz add -q b
233 $ brz ci -qm addb
234 $ brz tag branch-tag
235 235 Created tag branch-tag.
236 $ bzr switch --force ../repo/trunk
236 $ brz switch --force ../repo/trunk
237 237 Updated to revision 1.
238 238 Switched to branch*/repo/trunk/ (glob)
239 $ sleep 1
240 239 $ echo a >> a
241 $ bzr ci -qm changea
240 $ brz ci -qm changea
242 241 $ cd ..
243 242 $ hg convert --datesort repo repo-bzr
244 243 initializing destination repo-bzr repository
245 244 scanning source...
246 245 sorting...
247 246 converting...
248 247 2 adda
249 248 1 addb
250 249 0 changea
251 250 updating tags
252 251 $ (cd repo-bzr; glog)
253 252 o 3@default "update tags" files+: [.hgtags], files-: [], files: []
254 253 |
255 254 o 2@default "changea" files+: [], files-: [], files: [a]
256 255 |
257 256 | o 1@branch "addb" files+: [b], files-: [], files: []
258 257 |/
259 258 o 0@default "adda" files+: [a], files-: [], files: []
260 259
261 260
262 261 Test tags (converted identifiers are not stable because bzr ones are
263 262 not and get incorporated in extra fields).
264 263
265 264 $ hg -R repo-bzr tags
266 265 tip 3:* (glob)
267 266 branch-tag 1:* (glob)
268 267 trunk-tag 0:* (glob)
269 268
270 269 Nested repositories (issue3254)
271 270
272 $ bzr init-repo -q --no-trees repo/inner
273 $ bzr init -q repo/inner/trunk
274 $ bzr co repo/inner/trunk inner-trunk
271 $ brz init-repo -q --no-trees repo/inner
272 $ brz init -q repo/inner/trunk
273 $ brz co repo/inner/trunk inner-trunk
275 274 $ cd inner-trunk
276 275 $ echo b > b
277 $ bzr add -q b
278 $ bzr ci -qm addb
276 $ brz add -q b
277 $ brz ci -qm addb
279 278 $ cd ..
280 279 $ hg convert --datesort repo noinner-bzr
281 280 initializing destination noinner-bzr repository
282 281 scanning source...
283 282 sorting...
284 283 converting...
285 284 2 adda
286 285 1 addb
287 286 0 changea
288 287 updating tags
General Comments 0
You need to be logged in to leave comments. Login now