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