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 |
'b |
|
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'b |
|
27 | b'breezy.transactions', | |
26 |
b'b |
|
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 b |
|
35 | import breezy.bzr.bzrdir | |
34 |
import b |
|
36 | import breezy.errors | |
35 |
import b |
|
37 | import breezy.revision | |
36 |
import b |
|
38 | import breezy.revisionspec | |
37 |
|
39 | |||
38 |
bzrdir = b |
|
40 | bzrdir = breezy.bzr.bzrdir | |
39 |
errors = b |
|
41 | errors = breezy.errors | |
40 |
revision = b |
|
42 | revision = breezy.revision | |
41 |
revisionspec = b |
|
43 | revisionspec = breezy.revisionspec | |
42 | revisionspec.RevisionSpec |
|
44 | revisionspec.RevisionSpec | |
43 | except ImportError: |
|
45 | except ImportError: | |
44 | pass |
|
46 | pass | |
45 |
|
47 | |||
46 |
supportedkinds = ( |
|
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 b |
|
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 |
|
|
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. |
|
93 | and tree.controldir.root_transport.base | |
91 |
!= branch. |
|
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 = |
|
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 == |
|
160 | if kind == 'symlink': | |
155 |
target = revtree.get_symlink_target( |
|
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( |
|
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 = |
|
193 | branch = rev.properties.get('branch-nick', 'default') | |
188 |
if branch == |
|
194 | if branch == 'trunk': | |
189 |
branch = |
|
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 |
change |
|
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] == |
|
262 | if kind[1] == 'directory': | |
260 |
if kind[0] not in (None, |
|
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] == |
|
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 == |
|
276 | if entry.kind == 'directory': | |
274 | continue |
|
277 | continue | |
275 |
frompath = self.recode(paths[0] + |
|
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] + |
|
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 == |
|
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 == |
|
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 b |
|
177 | import breezy | |
178 |
import b |
|
178 | import breezy.bzr.bzrdir | |
179 |
import b |
|
179 | import breezy.errors | |
180 |
import b |
|
180 | import breezy.revision | |
|
181 | import breezy.revisionspec | |||
181 |
|
182 | |||
182 |
b |
|
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 |
|
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 |
$ b |
|
12 | $ brz init -q source | |
13 | $ cd source |
|
13 | $ cd source | |
14 | $ echo d > d |
|
14 | $ echo d > d | |
15 |
$ b |
|
15 | $ brz add -q d | |
16 |
$ b |
|
16 | $ brz commit -q -m 'add d file' | |
17 | $ rm d |
|
17 | $ rm d | |
18 | $ mkdir d |
|
18 | $ mkdir d | |
19 |
$ b |
|
19 | $ brz add -q d | |
20 |
$ b |
|
20 | $ brz commit -q -m 'replace with d dir' | |
21 | $ echo a > d/a |
|
21 | $ echo a > d/a | |
22 |
$ b |
|
22 | $ brz add -q d/a | |
23 |
$ b |
|
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 |
$ b |
|
12 | $ brz init -q source | |
13 | $ cd source |
|
13 | $ cd source | |
14 | $ echo content > a |
|
14 | $ echo content > a | |
15 |
$ b |
|
15 | $ brz add -q a | |
16 |
$ b |
|
16 | $ brz commit -q -m 'Initial add' | |
17 | $ mkdir empty |
|
17 | $ mkdir empty | |
18 |
$ b |
|
18 | $ brz add -q empty | |
19 |
$ b |
|
19 | $ brz commit -q -m 'Empty directory added' | |
20 | $ echo content > empty/something |
|
20 | $ echo content > empty/something | |
21 |
$ b |
|
21 | $ brz add -q empty/something | |
22 |
$ b |
|
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 |
$ b |
|
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 |
$ b |
|
49 | $ brz add -q tpyo | |
50 |
$ b |
|
50 | $ brz commit -q -m 'Added directory' | |
51 |
$ b |
|
51 | $ brz mv tpyo typo | |
52 | tpyo => typo |
|
52 | tpyo => typo | |
53 |
$ b |
|
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 |
$ b |
|
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 |
$ b |
|
79 | $ brz add -q firstlevel | |
80 |
$ b |
|
80 | $ brz commit -q -m 'Added nested directories' | |
81 |
$ b |
|
81 | $ brz mv firstlevel/secondlevel secondlevel | |
82 | firstlevel/secondlevel => secondlevel |
|
82 | firstlevel/secondlevel => secondlevel | |
83 |
$ b |
|
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 |
$ b |
|
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 |
$ b |
|
106 | $ brz add -q src | |
107 |
$ b |
|
107 | $ brz commit -q -m 'Added directory' | |
108 |
$ b |
|
108 | $ brz rm -q src | |
109 |
$ b |
|
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 |
$ b |
|
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 |
$ b |
|
135 | $ brz add -q first second | |
136 |
$ b |
|
136 | $ brz commit -q -m 'Initial layout' | |
137 |
$ b |
|
137 | $ brz mv first/file second/file | |
138 | first/file => second/file |
|
138 | first/file => second/file | |
139 |
$ b |
|
139 | $ brz mv first third | |
140 | first => third |
|
140 | first => third | |
141 |
$ b |
|
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 |
$ b |
|
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 |
$ b |
|
166 | $ brz add -q a | |
167 |
$ b |
|
167 | $ brz commit -q -m 'Initial layout' | |
168 |
$ b |
|
168 | $ brz mv a b | |
169 | a => b |
|
169 | a => b | |
170 | $ mkdir a |
|
170 | $ mkdir a | |
171 |
$ b |
|
171 | $ brz add a | |
172 | add(ed|ing) a (re) |
|
172 | add(ed|ing) a (re) | |
173 |
$ b |
|
173 | $ brz mv b/c a/c | |
174 | b/c => a/c |
|
174 | b/c => a/c | |
175 |
$ b |
|
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 |
$ b |
|
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 b |
|
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 |
$ b |
|
19 | $ brz init -q source | |
19 | $ cd source |
|
20 | $ cd source | |
20 | $ echo content > somefile |
|
21 | $ echo content > somefile | |
21 |
$ b |
|
22 | $ brz add -q somefile | |
22 |
$ b |
|
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 |
$ b |
|
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 |
$ b |
|
17 | $ brz add -q file rename_me | |
18 |
$ b |
|
18 | $ brz commit -q -m 'Initial add' '--commit-time=2009-10-10 08:00:00 +0100' | |
19 | $ cd .. |
|
19 | $ cd .. | |
20 |
$ b |
|
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 |
$ b |
|
24 | $ brz add -q file-branch1 | |
25 |
$ b |
|
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 |
$ b |
|
29 | $ brz add -q file-parent | |
30 |
$ b |
|
30 | $ brz commit -q -m 'Added parent file' '--commit-time=2009-10-10 08:00:02 +0100' | |
31 | $ cd .. |
|
31 | $ cd .. | |
32 |
$ b |
|
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 |
$ b |
|
35 | $ brz add -q file-branch2 | |
36 |
$ b |
|
36 | $ brz mv -q rename_me renamed | |
37 | $ echo change > renamed |
|
37 | $ echo change > renamed | |
38 |
$ b |
|
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 |
$ b |
|
41 | $ brz merge -q ../source-branch1 | |
42 |
$ b |
|
42 | $ brz merge -q --force ../source-branch2 | |
43 |
$ b |
|
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 b |
|
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 |
$ b |
|
19 | $ brz init -q source | |
19 | $ cd source |
|
20 | $ cd source | |
20 | $ echo content > file |
|
21 | $ echo content > file | |
21 |
$ b |
|
22 | $ brz add -q file | |
22 |
$ b |
|
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 |
$ b |
|
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 |
$ b |
|
25 | $ brz add -q a c e | |
26 |
$ b |
|
26 | $ brz commit -q -m 'Initial add: a, c, e' | |
27 |
$ b |
|
27 | $ brz mv a b | |
28 | a => b |
|
28 | a => b | |
29 |
$ b |
|
29 | $ brz mv c d | |
30 | c => d |
|
30 | c => d | |
31 |
$ b |
|
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 |
$ b |
|
35 | $ brz add -q a e | |
36 |
$ b |
|
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 |
$ b |
|
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 |
$ b |
|
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 b |
|
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 |
$ b |
|
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 |
$ b |
|
127 | $ brz add -q a b | |
127 |
$ b |
|
128 | $ brz commit -q -m 'Initial add' | |
128 | $ cd .. |
|
129 | $ cd .. | |
129 |
$ b |
|
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 |
$ b |
|
138 | $ brz merge -q ../source-improve | |
138 |
$ b |
|
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 |
$ b |
|
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 |
$ b |
|
175 | $ brz add -q altname program syma d/a | |
175 |
$ b |
|
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 |
$ b |
|
182 | $ brz add -q newprog | |
182 |
$ b |
|
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 |
$ b |
|
219 | $ brz init-repo -q --no-trees repo | |
219 |
$ b |
|
220 | $ brz init -q repo/trunk | |
220 |
$ b |
|
221 | $ brz co repo/trunk repo-trunk | |
221 | $ cd repo-trunk |
|
222 | $ cd repo-trunk | |
222 | $ echo a > a |
|
223 | $ echo a > a | |
223 |
$ b |
|
224 | $ brz add -q a | |
224 |
$ b |
|
225 | $ brz ci -qm adda | |
225 |
$ b |
|
226 | $ brz tag trunk-tag | |
226 | Created tag trunk-tag. |
|
227 | Created tag trunk-tag. | |
227 |
$ b |
|
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 |
$ b |
|
232 | $ brz add -q b | |
233 |
$ b |
|
233 | $ brz ci -qm addb | |
234 |
$ b |
|
234 | $ brz tag branch-tag | |
235 | Created tag branch-tag. |
|
235 | Created tag branch-tag. | |
236 |
$ b |
|
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 |
$ b |
|
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 |
$ b |
|
271 | $ brz init-repo -q --no-trees repo/inner | |
273 |
$ b |
|
272 | $ brz init -q repo/inner/trunk | |
274 |
$ b |
|
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 |
$ b |
|
276 | $ brz add -q b | |
278 |
$ b |
|
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