Show More
The requested changes are too big and content was truncated. Show full diff
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
@@ -1,1609 +1,1612 b'' | |||||
1 | # configitems.py - centralized declaration of configuration option |
|
1 | # configitems.py - centralized declaration of configuration option | |
2 | # |
|
2 | # | |
3 | # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net> |
|
3 | # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | from __future__ import absolute_import |
|
8 | from __future__ import absolute_import | |
9 |
|
9 | |||
10 | import functools |
|
10 | import functools | |
11 | import re |
|
11 | import re | |
12 |
|
12 | |||
13 | from . import ( |
|
13 | from . import ( | |
14 | encoding, |
|
14 | encoding, | |
15 | error, |
|
15 | error, | |
16 | ) |
|
16 | ) | |
17 |
|
17 | |||
18 |
|
18 | |||
19 | def loadconfigtable(ui, extname, configtable): |
|
19 | def loadconfigtable(ui, extname, configtable): | |
20 | """update config item known to the ui with the extension ones""" |
|
20 | """update config item known to the ui with the extension ones""" | |
21 | for section, items in sorted(configtable.items()): |
|
21 | for section, items in sorted(configtable.items()): | |
22 | knownitems = ui._knownconfig.setdefault(section, itemregister()) |
|
22 | knownitems = ui._knownconfig.setdefault(section, itemregister()) | |
23 | knownkeys = set(knownitems) |
|
23 | knownkeys = set(knownitems) | |
24 | newkeys = set(items) |
|
24 | newkeys = set(items) | |
25 | for key in sorted(knownkeys & newkeys): |
|
25 | for key in sorted(knownkeys & newkeys): | |
26 | msg = b"extension '%s' overwrite config item '%s.%s'" |
|
26 | msg = b"extension '%s' overwrite config item '%s.%s'" | |
27 | msg %= (extname, section, key) |
|
27 | msg %= (extname, section, key) | |
28 | ui.develwarn(msg, config=b'warn-config') |
|
28 | ui.develwarn(msg, config=b'warn-config') | |
29 |
|
29 | |||
30 | knownitems.update(items) |
|
30 | knownitems.update(items) | |
31 |
|
31 | |||
32 |
|
32 | |||
33 | class configitem(object): |
|
33 | class configitem(object): | |
34 | """represent a known config item |
|
34 | """represent a known config item | |
35 |
|
35 | |||
36 | :section: the official config section where to find this item, |
|
36 | :section: the official config section where to find this item, | |
37 | :name: the official name within the section, |
|
37 | :name: the official name within the section, | |
38 | :default: default value for this item, |
|
38 | :default: default value for this item, | |
39 | :alias: optional list of tuples as alternatives, |
|
39 | :alias: optional list of tuples as alternatives, | |
40 | :generic: this is a generic definition, match name using regular expression. |
|
40 | :generic: this is a generic definition, match name using regular expression. | |
41 | """ |
|
41 | """ | |
42 |
|
42 | |||
43 | def __init__( |
|
43 | def __init__( | |
44 | self, |
|
44 | self, | |
45 | section, |
|
45 | section, | |
46 | name, |
|
46 | name, | |
47 | default=None, |
|
47 | default=None, | |
48 | alias=(), |
|
48 | alias=(), | |
49 | generic=False, |
|
49 | generic=False, | |
50 | priority=0, |
|
50 | priority=0, | |
51 | experimental=False, |
|
51 | experimental=False, | |
52 | ): |
|
52 | ): | |
53 | self.section = section |
|
53 | self.section = section | |
54 | self.name = name |
|
54 | self.name = name | |
55 | self.default = default |
|
55 | self.default = default | |
56 | self.alias = list(alias) |
|
56 | self.alias = list(alias) | |
57 | self.generic = generic |
|
57 | self.generic = generic | |
58 | self.priority = priority |
|
58 | self.priority = priority | |
59 | self.experimental = experimental |
|
59 | self.experimental = experimental | |
60 | self._re = None |
|
60 | self._re = None | |
61 | if generic: |
|
61 | if generic: | |
62 | self._re = re.compile(self.name) |
|
62 | self._re = re.compile(self.name) | |
63 |
|
63 | |||
64 |
|
64 | |||
65 | class itemregister(dict): |
|
65 | class itemregister(dict): | |
66 | """A specialized dictionary that can handle wild-card selection""" |
|
66 | """A specialized dictionary that can handle wild-card selection""" | |
67 |
|
67 | |||
68 | def __init__(self): |
|
68 | def __init__(self): | |
69 | super(itemregister, self).__init__() |
|
69 | super(itemregister, self).__init__() | |
70 | self._generics = set() |
|
70 | self._generics = set() | |
71 |
|
71 | |||
72 | def update(self, other): |
|
72 | def update(self, other): | |
73 | super(itemregister, self).update(other) |
|
73 | super(itemregister, self).update(other) | |
74 | self._generics.update(other._generics) |
|
74 | self._generics.update(other._generics) | |
75 |
|
75 | |||
76 | def __setitem__(self, key, item): |
|
76 | def __setitem__(self, key, item): | |
77 | super(itemregister, self).__setitem__(key, item) |
|
77 | super(itemregister, self).__setitem__(key, item) | |
78 | if item.generic: |
|
78 | if item.generic: | |
79 | self._generics.add(item) |
|
79 | self._generics.add(item) | |
80 |
|
80 | |||
81 | def get(self, key): |
|
81 | def get(self, key): | |
82 | baseitem = super(itemregister, self).get(key) |
|
82 | baseitem = super(itemregister, self).get(key) | |
83 | if baseitem is not None and not baseitem.generic: |
|
83 | if baseitem is not None and not baseitem.generic: | |
84 | return baseitem |
|
84 | return baseitem | |
85 |
|
85 | |||
86 | # search for a matching generic item |
|
86 | # search for a matching generic item | |
87 | generics = sorted(self._generics, key=(lambda x: (x.priority, x.name))) |
|
87 | generics = sorted(self._generics, key=(lambda x: (x.priority, x.name))) | |
88 | for item in generics: |
|
88 | for item in generics: | |
89 | # we use 'match' instead of 'search' to make the matching simpler |
|
89 | # we use 'match' instead of 'search' to make the matching simpler | |
90 | # for people unfamiliar with regular expression. Having the match |
|
90 | # for people unfamiliar with regular expression. Having the match | |
91 | # rooted to the start of the string will produce less surprising |
|
91 | # rooted to the start of the string will produce less surprising | |
92 | # result for user writing simple regex for sub-attribute. |
|
92 | # result for user writing simple regex for sub-attribute. | |
93 | # |
|
93 | # | |
94 | # For example using "color\..*" match produces an unsurprising |
|
94 | # For example using "color\..*" match produces an unsurprising | |
95 | # result, while using search could suddenly match apparently |
|
95 | # result, while using search could suddenly match apparently | |
96 | # unrelated configuration that happens to contains "color." |
|
96 | # unrelated configuration that happens to contains "color." | |
97 | # anywhere. This is a tradeoff where we favor requiring ".*" on |
|
97 | # anywhere. This is a tradeoff where we favor requiring ".*" on | |
98 | # some match to avoid the need to prefix most pattern with "^". |
|
98 | # some match to avoid the need to prefix most pattern with "^". | |
99 | # The "^" seems more error prone. |
|
99 | # The "^" seems more error prone. | |
100 | if item._re.match(key): |
|
100 | if item._re.match(key): | |
101 | return item |
|
101 | return item | |
102 |
|
102 | |||
103 | return None |
|
103 | return None | |
104 |
|
104 | |||
105 |
|
105 | |||
106 | coreitems = {} |
|
106 | coreitems = {} | |
107 |
|
107 | |||
108 |
|
108 | |||
109 | def _register(configtable, *args, **kwargs): |
|
109 | def _register(configtable, *args, **kwargs): | |
110 | item = configitem(*args, **kwargs) |
|
110 | item = configitem(*args, **kwargs) | |
111 | section = configtable.setdefault(item.section, itemregister()) |
|
111 | section = configtable.setdefault(item.section, itemregister()) | |
112 | if item.name in section: |
|
112 | if item.name in section: | |
113 | msg = b"duplicated config item registration for '%s.%s'" |
|
113 | msg = b"duplicated config item registration for '%s.%s'" | |
114 | raise error.ProgrammingError(msg % (item.section, item.name)) |
|
114 | raise error.ProgrammingError(msg % (item.section, item.name)) | |
115 | section[item.name] = item |
|
115 | section[item.name] = item | |
116 |
|
116 | |||
117 |
|
117 | |||
118 | # special value for case where the default is derived from other values |
|
118 | # special value for case where the default is derived from other values | |
119 | dynamicdefault = object() |
|
119 | dynamicdefault = object() | |
120 |
|
120 | |||
121 | # Registering actual config items |
|
121 | # Registering actual config items | |
122 |
|
122 | |||
123 |
|
123 | |||
124 | def getitemregister(configtable): |
|
124 | def getitemregister(configtable): | |
125 | f = functools.partial(_register, configtable) |
|
125 | f = functools.partial(_register, configtable) | |
126 | # export pseudo enum as configitem.* |
|
126 | # export pseudo enum as configitem.* | |
127 | f.dynamicdefault = dynamicdefault |
|
127 | f.dynamicdefault = dynamicdefault | |
128 | return f |
|
128 | return f | |
129 |
|
129 | |||
130 |
|
130 | |||
131 | coreconfigitem = getitemregister(coreitems) |
|
131 | coreconfigitem = getitemregister(coreitems) | |
132 |
|
132 | |||
133 |
|
133 | |||
134 | def _registerdiffopts(section, configprefix=b''): |
|
134 | def _registerdiffopts(section, configprefix=b''): | |
135 | coreconfigitem( |
|
135 | coreconfigitem( | |
136 | section, configprefix + b'nodates', default=False, |
|
136 | section, configprefix + b'nodates', default=False, | |
137 | ) |
|
137 | ) | |
138 | coreconfigitem( |
|
138 | coreconfigitem( | |
139 | section, configprefix + b'showfunc', default=False, |
|
139 | section, configprefix + b'showfunc', default=False, | |
140 | ) |
|
140 | ) | |
141 | coreconfigitem( |
|
141 | coreconfigitem( | |
142 | section, configprefix + b'unified', default=None, |
|
142 | section, configprefix + b'unified', default=None, | |
143 | ) |
|
143 | ) | |
144 | coreconfigitem( |
|
144 | coreconfigitem( | |
145 | section, configprefix + b'git', default=False, |
|
145 | section, configprefix + b'git', default=False, | |
146 | ) |
|
146 | ) | |
147 | coreconfigitem( |
|
147 | coreconfigitem( | |
148 | section, configprefix + b'ignorews', default=False, |
|
148 | section, configprefix + b'ignorews', default=False, | |
149 | ) |
|
149 | ) | |
150 | coreconfigitem( |
|
150 | coreconfigitem( | |
151 | section, configprefix + b'ignorewsamount', default=False, |
|
151 | section, configprefix + b'ignorewsamount', default=False, | |
152 | ) |
|
152 | ) | |
153 | coreconfigitem( |
|
153 | coreconfigitem( | |
154 | section, configprefix + b'ignoreblanklines', default=False, |
|
154 | section, configprefix + b'ignoreblanklines', default=False, | |
155 | ) |
|
155 | ) | |
156 | coreconfigitem( |
|
156 | coreconfigitem( | |
157 | section, configprefix + b'ignorewseol', default=False, |
|
157 | section, configprefix + b'ignorewseol', default=False, | |
158 | ) |
|
158 | ) | |
159 | coreconfigitem( |
|
159 | coreconfigitem( | |
160 | section, configprefix + b'nobinary', default=False, |
|
160 | section, configprefix + b'nobinary', default=False, | |
161 | ) |
|
161 | ) | |
162 | coreconfigitem( |
|
162 | coreconfigitem( | |
163 | section, configprefix + b'noprefix', default=False, |
|
163 | section, configprefix + b'noprefix', default=False, | |
164 | ) |
|
164 | ) | |
165 | coreconfigitem( |
|
165 | coreconfigitem( | |
166 | section, configprefix + b'word-diff', default=False, |
|
166 | section, configprefix + b'word-diff', default=False, | |
167 | ) |
|
167 | ) | |
168 |
|
168 | |||
169 |
|
169 | |||
170 | coreconfigitem( |
|
170 | coreconfigitem( | |
171 | b'alias', b'.*', default=dynamicdefault, generic=True, |
|
171 | b'alias', b'.*', default=dynamicdefault, generic=True, | |
172 | ) |
|
172 | ) | |
173 | coreconfigitem( |
|
173 | coreconfigitem( | |
174 | b'auth', b'cookiefile', default=None, |
|
174 | b'auth', b'cookiefile', default=None, | |
175 | ) |
|
175 | ) | |
176 | _registerdiffopts(section=b'annotate') |
|
176 | _registerdiffopts(section=b'annotate') | |
177 | # bookmarks.pushing: internal hack for discovery |
|
177 | # bookmarks.pushing: internal hack for discovery | |
178 | coreconfigitem( |
|
178 | coreconfigitem( | |
179 | b'bookmarks', b'pushing', default=list, |
|
179 | b'bookmarks', b'pushing', default=list, | |
180 | ) |
|
180 | ) | |
181 | # bundle.mainreporoot: internal hack for bundlerepo |
|
181 | # bundle.mainreporoot: internal hack for bundlerepo | |
182 | coreconfigitem( |
|
182 | coreconfigitem( | |
183 | b'bundle', b'mainreporoot', default=b'', |
|
183 | b'bundle', b'mainreporoot', default=b'', | |
184 | ) |
|
184 | ) | |
185 | coreconfigitem( |
|
185 | coreconfigitem( | |
186 | b'censor', b'policy', default=b'abort', experimental=True, |
|
186 | b'censor', b'policy', default=b'abort', experimental=True, | |
187 | ) |
|
187 | ) | |
188 | coreconfigitem( |
|
188 | coreconfigitem( | |
189 | b'chgserver', b'idletimeout', default=3600, |
|
189 | b'chgserver', b'idletimeout', default=3600, | |
190 | ) |
|
190 | ) | |
191 | coreconfigitem( |
|
191 | coreconfigitem( | |
192 | b'chgserver', b'skiphash', default=False, |
|
192 | b'chgserver', b'skiphash', default=False, | |
193 | ) |
|
193 | ) | |
194 | coreconfigitem( |
|
194 | coreconfigitem( | |
195 | b'cmdserver', b'log', default=None, |
|
195 | b'cmdserver', b'log', default=None, | |
196 | ) |
|
196 | ) | |
197 | coreconfigitem( |
|
197 | coreconfigitem( | |
198 | b'cmdserver', b'max-log-files', default=7, |
|
198 | b'cmdserver', b'max-log-files', default=7, | |
199 | ) |
|
199 | ) | |
200 | coreconfigitem( |
|
200 | coreconfigitem( | |
201 | b'cmdserver', b'max-log-size', default=b'1 MB', |
|
201 | b'cmdserver', b'max-log-size', default=b'1 MB', | |
202 | ) |
|
202 | ) | |
203 | coreconfigitem( |
|
203 | coreconfigitem( | |
204 | b'cmdserver', b'max-repo-cache', default=0, experimental=True, |
|
204 | b'cmdserver', b'max-repo-cache', default=0, experimental=True, | |
205 | ) |
|
205 | ) | |
206 | coreconfigitem( |
|
206 | coreconfigitem( | |
207 | b'cmdserver', b'message-encodings', default=list, |
|
207 | b'cmdserver', b'message-encodings', default=list, | |
208 | ) |
|
208 | ) | |
209 | coreconfigitem( |
|
209 | coreconfigitem( | |
210 | b'cmdserver', |
|
210 | b'cmdserver', | |
211 | b'track-log', |
|
211 | b'track-log', | |
212 | default=lambda: [b'chgserver', b'cmdserver', b'repocache'], |
|
212 | default=lambda: [b'chgserver', b'cmdserver', b'repocache'], | |
213 | ) |
|
213 | ) | |
214 | coreconfigitem( |
|
214 | coreconfigitem( | |
215 | b'cmdserver', b'shutdown-on-interrupt', default=True, |
|
215 | b'cmdserver', b'shutdown-on-interrupt', default=True, | |
216 | ) |
|
216 | ) | |
217 | coreconfigitem( |
|
217 | coreconfigitem( | |
218 | b'color', b'.*', default=None, generic=True, |
|
218 | b'color', b'.*', default=None, generic=True, | |
219 | ) |
|
219 | ) | |
220 | coreconfigitem( |
|
220 | coreconfigitem( | |
221 | b'color', b'mode', default=b'auto', |
|
221 | b'color', b'mode', default=b'auto', | |
222 | ) |
|
222 | ) | |
223 | coreconfigitem( |
|
223 | coreconfigitem( | |
224 | b'color', b'pagermode', default=dynamicdefault, |
|
224 | b'color', b'pagermode', default=dynamicdefault, | |
225 | ) |
|
225 | ) | |
226 | coreconfigitem( |
|
226 | coreconfigitem( | |
227 | b'command-templates', |
|
227 | b'command-templates', | |
228 | b'graphnode', |
|
228 | b'graphnode', | |
229 | default=None, |
|
229 | default=None, | |
230 | alias=[(b'ui', b'graphnodetemplate')], |
|
230 | alias=[(b'ui', b'graphnodetemplate')], | |
231 | ) |
|
231 | ) | |
232 | coreconfigitem( |
|
232 | coreconfigitem( | |
233 | b'command-templates', b'log', default=None, alias=[(b'ui', b'logtemplate')], |
|
233 | b'command-templates', b'log', default=None, alias=[(b'ui', b'logtemplate')], | |
234 | ) |
|
234 | ) | |
235 | coreconfigitem( |
|
235 | coreconfigitem( | |
236 | b'command-templates', |
|
236 | b'command-templates', | |
237 | b'mergemarker', |
|
237 | b'mergemarker', | |
238 | default=( |
|
238 | default=( | |
239 | b'{node|short} ' |
|
239 | b'{node|short} ' | |
240 | b'{ifeq(tags, "tip", "", ' |
|
240 | b'{ifeq(tags, "tip", "", ' | |
241 | b'ifeq(tags, "", "", "{tags} "))}' |
|
241 | b'ifeq(tags, "", "", "{tags} "))}' | |
242 | b'{if(bookmarks, "{bookmarks} ")}' |
|
242 | b'{if(bookmarks, "{bookmarks} ")}' | |
243 | b'{ifeq(branch, "default", "", "{branch} ")}' |
|
243 | b'{ifeq(branch, "default", "", "{branch} ")}' | |
244 | b'- {author|user}: {desc|firstline}' |
|
244 | b'- {author|user}: {desc|firstline}' | |
245 | ), |
|
245 | ), | |
246 | alias=[(b'ui', b'mergemarkertemplate')], |
|
246 | alias=[(b'ui', b'mergemarkertemplate')], | |
247 | ) |
|
247 | ) | |
248 | coreconfigitem( |
|
248 | coreconfigitem( | |
249 | b'command-templates', |
|
249 | b'command-templates', | |
250 | b'pre-merge-tool-output', |
|
250 | b'pre-merge-tool-output', | |
251 | default=None, |
|
251 | default=None, | |
252 | alias=[(b'ui', b'pre-merge-tool-output-template')], |
|
252 | alias=[(b'ui', b'pre-merge-tool-output-template')], | |
253 | ) |
|
253 | ) | |
254 | coreconfigitem( |
|
254 | coreconfigitem( | |
255 | b'command-templates', b'oneline-summary', default=None, |
|
255 | b'command-templates', b'oneline-summary', default=None, | |
256 | ) |
|
256 | ) | |
257 | coreconfigitem( |
|
257 | coreconfigitem( | |
258 | b'command-templates', |
|
258 | b'command-templates', | |
259 | b'oneline-summary.*', |
|
259 | b'oneline-summary.*', | |
260 | default=dynamicdefault, |
|
260 | default=dynamicdefault, | |
261 | generic=True, |
|
261 | generic=True, | |
262 | ) |
|
262 | ) | |
263 | _registerdiffopts(section=b'commands', configprefix=b'commit.interactive.') |
|
263 | _registerdiffopts(section=b'commands', configprefix=b'commit.interactive.') | |
264 | coreconfigitem( |
|
264 | coreconfigitem( | |
265 | b'commands', b'commit.post-status', default=False, |
|
265 | b'commands', b'commit.post-status', default=False, | |
266 | ) |
|
266 | ) | |
267 | coreconfigitem( |
|
267 | coreconfigitem( | |
268 | b'commands', b'grep.all-files', default=False, experimental=True, |
|
268 | b'commands', b'grep.all-files', default=False, experimental=True, | |
269 | ) |
|
269 | ) | |
270 | coreconfigitem( |
|
270 | coreconfigitem( | |
271 | b'commands', b'merge.require-rev', default=False, |
|
271 | b'commands', b'merge.require-rev', default=False, | |
272 | ) |
|
272 | ) | |
273 | coreconfigitem( |
|
273 | coreconfigitem( | |
274 | b'commands', b'push.require-revs', default=False, |
|
274 | b'commands', b'push.require-revs', default=False, | |
275 | ) |
|
275 | ) | |
276 | coreconfigitem( |
|
276 | coreconfigitem( | |
277 | b'commands', b'resolve.confirm', default=False, |
|
277 | b'commands', b'resolve.confirm', default=False, | |
278 | ) |
|
278 | ) | |
279 | coreconfigitem( |
|
279 | coreconfigitem( | |
280 | b'commands', b'resolve.explicit-re-merge', default=False, |
|
280 | b'commands', b'resolve.explicit-re-merge', default=False, | |
281 | ) |
|
281 | ) | |
282 | coreconfigitem( |
|
282 | coreconfigitem( | |
283 | b'commands', b'resolve.mark-check', default=b'none', |
|
283 | b'commands', b'resolve.mark-check', default=b'none', | |
284 | ) |
|
284 | ) | |
285 | _registerdiffopts(section=b'commands', configprefix=b'revert.interactive.') |
|
285 | _registerdiffopts(section=b'commands', configprefix=b'revert.interactive.') | |
286 | coreconfigitem( |
|
286 | coreconfigitem( | |
287 | b'commands', b'show.aliasprefix', default=list, |
|
287 | b'commands', b'show.aliasprefix', default=list, | |
288 | ) |
|
288 | ) | |
289 | coreconfigitem( |
|
289 | coreconfigitem( | |
290 | b'commands', b'status.relative', default=False, |
|
290 | b'commands', b'status.relative', default=False, | |
291 | ) |
|
291 | ) | |
292 | coreconfigitem( |
|
292 | coreconfigitem( | |
293 | b'commands', b'status.skipstates', default=[], experimental=True, |
|
293 | b'commands', b'status.skipstates', default=[], experimental=True, | |
294 | ) |
|
294 | ) | |
295 | coreconfigitem( |
|
295 | coreconfigitem( | |
296 | b'commands', b'status.terse', default=b'', |
|
296 | b'commands', b'status.terse', default=b'', | |
297 | ) |
|
297 | ) | |
298 | coreconfigitem( |
|
298 | coreconfigitem( | |
299 | b'commands', b'status.verbose', default=False, |
|
299 | b'commands', b'status.verbose', default=False, | |
300 | ) |
|
300 | ) | |
301 | coreconfigitem( |
|
301 | coreconfigitem( | |
302 | b'commands', b'update.check', default=None, |
|
302 | b'commands', b'update.check', default=None, | |
303 | ) |
|
303 | ) | |
304 | coreconfigitem( |
|
304 | coreconfigitem( | |
305 | b'commands', b'update.requiredest', default=False, |
|
305 | b'commands', b'update.requiredest', default=False, | |
306 | ) |
|
306 | ) | |
307 | coreconfigitem( |
|
307 | coreconfigitem( | |
308 | b'committemplate', b'.*', default=None, generic=True, |
|
308 | b'committemplate', b'.*', default=None, generic=True, | |
309 | ) |
|
309 | ) | |
310 | coreconfigitem( |
|
310 | coreconfigitem( | |
311 | b'convert', b'bzr.saverev', default=True, |
|
311 | b'convert', b'bzr.saverev', default=True, | |
312 | ) |
|
312 | ) | |
313 | coreconfigitem( |
|
313 | coreconfigitem( | |
314 | b'convert', b'cvsps.cache', default=True, |
|
314 | b'convert', b'cvsps.cache', default=True, | |
315 | ) |
|
315 | ) | |
316 | coreconfigitem( |
|
316 | coreconfigitem( | |
317 | b'convert', b'cvsps.fuzz', default=60, |
|
317 | b'convert', b'cvsps.fuzz', default=60, | |
318 | ) |
|
318 | ) | |
319 | coreconfigitem( |
|
319 | coreconfigitem( | |
320 | b'convert', b'cvsps.logencoding', default=None, |
|
320 | b'convert', b'cvsps.logencoding', default=None, | |
321 | ) |
|
321 | ) | |
322 | coreconfigitem( |
|
322 | coreconfigitem( | |
323 | b'convert', b'cvsps.mergefrom', default=None, |
|
323 | b'convert', b'cvsps.mergefrom', default=None, | |
324 | ) |
|
324 | ) | |
325 | coreconfigitem( |
|
325 | coreconfigitem( | |
326 | b'convert', b'cvsps.mergeto', default=None, |
|
326 | b'convert', b'cvsps.mergeto', default=None, | |
327 | ) |
|
327 | ) | |
328 | coreconfigitem( |
|
328 | coreconfigitem( | |
329 | b'convert', b'git.committeractions', default=lambda: [b'messagedifferent'], |
|
329 | b'convert', b'git.committeractions', default=lambda: [b'messagedifferent'], | |
330 | ) |
|
330 | ) | |
331 | coreconfigitem( |
|
331 | coreconfigitem( | |
332 | b'convert', b'git.extrakeys', default=list, |
|
332 | b'convert', b'git.extrakeys', default=list, | |
333 | ) |
|
333 | ) | |
334 | coreconfigitem( |
|
334 | coreconfigitem( | |
335 | b'convert', b'git.findcopiesharder', default=False, |
|
335 | b'convert', b'git.findcopiesharder', default=False, | |
336 | ) |
|
336 | ) | |
337 | coreconfigitem( |
|
337 | coreconfigitem( | |
338 | b'convert', b'git.remoteprefix', default=b'remote', |
|
338 | b'convert', b'git.remoteprefix', default=b'remote', | |
339 | ) |
|
339 | ) | |
340 | coreconfigitem( |
|
340 | coreconfigitem( | |
341 | b'convert', b'git.renamelimit', default=400, |
|
341 | b'convert', b'git.renamelimit', default=400, | |
342 | ) |
|
342 | ) | |
343 | coreconfigitem( |
|
343 | coreconfigitem( | |
344 | b'convert', b'git.saverev', default=True, |
|
344 | b'convert', b'git.saverev', default=True, | |
345 | ) |
|
345 | ) | |
346 | coreconfigitem( |
|
346 | coreconfigitem( | |
347 | b'convert', b'git.similarity', default=50, |
|
347 | b'convert', b'git.similarity', default=50, | |
348 | ) |
|
348 | ) | |
349 | coreconfigitem( |
|
349 | coreconfigitem( | |
350 | b'convert', b'git.skipsubmodules', default=False, |
|
350 | b'convert', b'git.skipsubmodules', default=False, | |
351 | ) |
|
351 | ) | |
352 | coreconfigitem( |
|
352 | coreconfigitem( | |
353 | b'convert', b'hg.clonebranches', default=False, |
|
353 | b'convert', b'hg.clonebranches', default=False, | |
354 | ) |
|
354 | ) | |
355 | coreconfigitem( |
|
355 | coreconfigitem( | |
356 | b'convert', b'hg.ignoreerrors', default=False, |
|
356 | b'convert', b'hg.ignoreerrors', default=False, | |
357 | ) |
|
357 | ) | |
358 | coreconfigitem( |
|
358 | coreconfigitem( | |
359 | b'convert', b'hg.preserve-hash', default=False, |
|
359 | b'convert', b'hg.preserve-hash', default=False, | |
360 | ) |
|
360 | ) | |
361 | coreconfigitem( |
|
361 | coreconfigitem( | |
362 | b'convert', b'hg.revs', default=None, |
|
362 | b'convert', b'hg.revs', default=None, | |
363 | ) |
|
363 | ) | |
364 | coreconfigitem( |
|
364 | coreconfigitem( | |
365 | b'convert', b'hg.saverev', default=False, |
|
365 | b'convert', b'hg.saverev', default=False, | |
366 | ) |
|
366 | ) | |
367 | coreconfigitem( |
|
367 | coreconfigitem( | |
368 | b'convert', b'hg.sourcename', default=None, |
|
368 | b'convert', b'hg.sourcename', default=None, | |
369 | ) |
|
369 | ) | |
370 | coreconfigitem( |
|
370 | coreconfigitem( | |
371 | b'convert', b'hg.startrev', default=None, |
|
371 | b'convert', b'hg.startrev', default=None, | |
372 | ) |
|
372 | ) | |
373 | coreconfigitem( |
|
373 | coreconfigitem( | |
374 | b'convert', b'hg.tagsbranch', default=b'default', |
|
374 | b'convert', b'hg.tagsbranch', default=b'default', | |
375 | ) |
|
375 | ) | |
376 | coreconfigitem( |
|
376 | coreconfigitem( | |
377 | b'convert', b'hg.usebranchnames', default=True, |
|
377 | b'convert', b'hg.usebranchnames', default=True, | |
378 | ) |
|
378 | ) | |
379 | coreconfigitem( |
|
379 | coreconfigitem( | |
380 | b'convert', b'ignoreancestorcheck', default=False, experimental=True, |
|
380 | b'convert', b'ignoreancestorcheck', default=False, experimental=True, | |
381 | ) |
|
381 | ) | |
382 | coreconfigitem( |
|
382 | coreconfigitem( | |
383 | b'convert', b'localtimezone', default=False, |
|
383 | b'convert', b'localtimezone', default=False, | |
384 | ) |
|
384 | ) | |
385 | coreconfigitem( |
|
385 | coreconfigitem( | |
386 | b'convert', b'p4.encoding', default=dynamicdefault, |
|
386 | b'convert', b'p4.encoding', default=dynamicdefault, | |
387 | ) |
|
387 | ) | |
388 | coreconfigitem( |
|
388 | coreconfigitem( | |
389 | b'convert', b'p4.startrev', default=0, |
|
389 | b'convert', b'p4.startrev', default=0, | |
390 | ) |
|
390 | ) | |
391 | coreconfigitem( |
|
391 | coreconfigitem( | |
392 | b'convert', b'skiptags', default=False, |
|
392 | b'convert', b'skiptags', default=False, | |
393 | ) |
|
393 | ) | |
394 | coreconfigitem( |
|
394 | coreconfigitem( | |
395 | b'convert', b'svn.debugsvnlog', default=True, |
|
395 | b'convert', b'svn.debugsvnlog', default=True, | |
396 | ) |
|
396 | ) | |
397 | coreconfigitem( |
|
397 | coreconfigitem( | |
398 | b'convert', b'svn.trunk', default=None, |
|
398 | b'convert', b'svn.trunk', default=None, | |
399 | ) |
|
399 | ) | |
400 | coreconfigitem( |
|
400 | coreconfigitem( | |
401 | b'convert', b'svn.tags', default=None, |
|
401 | b'convert', b'svn.tags', default=None, | |
402 | ) |
|
402 | ) | |
403 | coreconfigitem( |
|
403 | coreconfigitem( | |
404 | b'convert', b'svn.branches', default=None, |
|
404 | b'convert', b'svn.branches', default=None, | |
405 | ) |
|
405 | ) | |
406 | coreconfigitem( |
|
406 | coreconfigitem( | |
407 | b'convert', b'svn.startrev', default=0, |
|
407 | b'convert', b'svn.startrev', default=0, | |
408 | ) |
|
408 | ) | |
409 | coreconfigitem( |
|
409 | coreconfigitem( | |
410 | b'debug', b'dirstate.delaywrite', default=0, |
|
410 | b'debug', b'dirstate.delaywrite', default=0, | |
411 | ) |
|
411 | ) | |
412 | coreconfigitem( |
|
412 | coreconfigitem( | |
413 | b'defaults', b'.*', default=None, generic=True, |
|
413 | b'defaults', b'.*', default=None, generic=True, | |
414 | ) |
|
414 | ) | |
415 | coreconfigitem( |
|
415 | coreconfigitem( | |
416 | b'devel', b'all-warnings', default=False, |
|
416 | b'devel', b'all-warnings', default=False, | |
417 | ) |
|
417 | ) | |
418 | coreconfigitem( |
|
418 | coreconfigitem( | |
419 | b'devel', b'bundle2.debug', default=False, |
|
419 | b'devel', b'bundle2.debug', default=False, | |
420 | ) |
|
420 | ) | |
421 | coreconfigitem( |
|
421 | coreconfigitem( | |
422 | b'devel', b'bundle.delta', default=b'', |
|
422 | b'devel', b'bundle.delta', default=b'', | |
423 | ) |
|
423 | ) | |
424 | coreconfigitem( |
|
424 | coreconfigitem( | |
425 | b'devel', b'cache-vfs', default=None, |
|
425 | b'devel', b'cache-vfs', default=None, | |
426 | ) |
|
426 | ) | |
427 | coreconfigitem( |
|
427 | coreconfigitem( | |
428 | b'devel', b'check-locks', default=False, |
|
428 | b'devel', b'check-locks', default=False, | |
429 | ) |
|
429 | ) | |
430 | coreconfigitem( |
|
430 | coreconfigitem( | |
431 | b'devel', b'check-relroot', default=False, |
|
431 | b'devel', b'check-relroot', default=False, | |
432 | ) |
|
432 | ) | |
433 | coreconfigitem( |
|
433 | coreconfigitem( | |
434 | b'devel', b'default-date', default=None, |
|
434 | b'devel', b'default-date', default=None, | |
435 | ) |
|
435 | ) | |
436 | coreconfigitem( |
|
436 | coreconfigitem( | |
437 | b'devel', b'deprec-warn', default=False, |
|
437 | b'devel', b'deprec-warn', default=False, | |
438 | ) |
|
438 | ) | |
439 | coreconfigitem( |
|
439 | coreconfigitem( | |
440 | b'devel', b'disableloaddefaultcerts', default=False, |
|
440 | b'devel', b'disableloaddefaultcerts', default=False, | |
441 | ) |
|
441 | ) | |
442 | coreconfigitem( |
|
442 | coreconfigitem( | |
443 | b'devel', b'warn-empty-changegroup', default=False, |
|
443 | b'devel', b'warn-empty-changegroup', default=False, | |
444 | ) |
|
444 | ) | |
445 | coreconfigitem( |
|
445 | coreconfigitem( | |
446 | b'devel', b'legacy.exchange', default=list, |
|
446 | b'devel', b'legacy.exchange', default=list, | |
447 | ) |
|
447 | ) | |
448 | coreconfigitem( |
|
448 | coreconfigitem( | |
449 | b'devel', b'persistent-nodemap', default=False, |
|
449 | b'devel', b'persistent-nodemap', default=False, | |
450 | ) |
|
450 | ) | |
451 | coreconfigitem( |
|
451 | coreconfigitem( | |
452 | b'devel', b'servercafile', default=b'', |
|
452 | b'devel', b'servercafile', default=b'', | |
453 | ) |
|
453 | ) | |
454 | coreconfigitem( |
|
454 | coreconfigitem( | |
455 | b'devel', b'serverexactprotocol', default=b'', |
|
455 | b'devel', b'serverexactprotocol', default=b'', | |
456 | ) |
|
456 | ) | |
457 | coreconfigitem( |
|
457 | coreconfigitem( | |
458 | b'devel', b'serverrequirecert', default=False, |
|
458 | b'devel', b'serverrequirecert', default=False, | |
459 | ) |
|
459 | ) | |
460 | coreconfigitem( |
|
460 | coreconfigitem( | |
461 | b'devel', b'strip-obsmarkers', default=True, |
|
461 | b'devel', b'strip-obsmarkers', default=True, | |
462 | ) |
|
462 | ) | |
463 | coreconfigitem( |
|
463 | coreconfigitem( | |
464 | b'devel', b'warn-config', default=None, |
|
464 | b'devel', b'warn-config', default=None, | |
465 | ) |
|
465 | ) | |
466 | coreconfigitem( |
|
466 | coreconfigitem( | |
467 | b'devel', b'warn-config-default', default=None, |
|
467 | b'devel', b'warn-config-default', default=None, | |
468 | ) |
|
468 | ) | |
469 | coreconfigitem( |
|
469 | coreconfigitem( | |
470 | b'devel', b'user.obsmarker', default=None, |
|
470 | b'devel', b'user.obsmarker', default=None, | |
471 | ) |
|
471 | ) | |
472 | coreconfigitem( |
|
472 | coreconfigitem( | |
473 | b'devel', b'warn-config-unknown', default=None, |
|
473 | b'devel', b'warn-config-unknown', default=None, | |
474 | ) |
|
474 | ) | |
475 | coreconfigitem( |
|
475 | coreconfigitem( | |
476 | b'devel', b'debug.copies', default=False, |
|
476 | b'devel', b'debug.copies', default=False, | |
477 | ) |
|
477 | ) | |
478 | coreconfigitem( |
|
478 | coreconfigitem( | |
479 | b'devel', b'debug.extensions', default=False, |
|
479 | b'devel', b'debug.extensions', default=False, | |
480 | ) |
|
480 | ) | |
481 | coreconfigitem( |
|
481 | coreconfigitem( | |
482 | b'devel', b'debug.repo-filters', default=False, |
|
482 | b'devel', b'debug.repo-filters', default=False, | |
483 | ) |
|
483 | ) | |
484 | coreconfigitem( |
|
484 | coreconfigitem( | |
485 | b'devel', b'debug.peer-request', default=False, |
|
485 | b'devel', b'debug.peer-request', default=False, | |
486 | ) |
|
486 | ) | |
487 | coreconfigitem( |
|
487 | coreconfigitem( | |
488 | b'devel', b'discovery.randomize', default=True, |
|
488 | b'devel', b'discovery.randomize', default=True, | |
489 | ) |
|
489 | ) | |
490 | _registerdiffopts(section=b'diff') |
|
490 | _registerdiffopts(section=b'diff') | |
491 | coreconfigitem( |
|
491 | coreconfigitem( | |
492 | b'email', b'bcc', default=None, |
|
492 | b'email', b'bcc', default=None, | |
493 | ) |
|
493 | ) | |
494 | coreconfigitem( |
|
494 | coreconfigitem( | |
495 | b'email', b'cc', default=None, |
|
495 | b'email', b'cc', default=None, | |
496 | ) |
|
496 | ) | |
497 | coreconfigitem( |
|
497 | coreconfigitem( | |
498 | b'email', b'charsets', default=list, |
|
498 | b'email', b'charsets', default=list, | |
499 | ) |
|
499 | ) | |
500 | coreconfigitem( |
|
500 | coreconfigitem( | |
501 | b'email', b'from', default=None, |
|
501 | b'email', b'from', default=None, | |
502 | ) |
|
502 | ) | |
503 | coreconfigitem( |
|
503 | coreconfigitem( | |
504 | b'email', b'method', default=b'smtp', |
|
504 | b'email', b'method', default=b'smtp', | |
505 | ) |
|
505 | ) | |
506 | coreconfigitem( |
|
506 | coreconfigitem( | |
507 | b'email', b'reply-to', default=None, |
|
507 | b'email', b'reply-to', default=None, | |
508 | ) |
|
508 | ) | |
509 | coreconfigitem( |
|
509 | coreconfigitem( | |
510 | b'email', b'to', default=None, |
|
510 | b'email', b'to', default=None, | |
511 | ) |
|
511 | ) | |
512 | coreconfigitem( |
|
512 | coreconfigitem( | |
513 | b'experimental', b'archivemetatemplate', default=dynamicdefault, |
|
513 | b'experimental', b'archivemetatemplate', default=dynamicdefault, | |
514 | ) |
|
514 | ) | |
515 | coreconfigitem( |
|
515 | coreconfigitem( | |
516 | b'experimental', b'auto-publish', default=b'publish', |
|
516 | b'experimental', b'auto-publish', default=b'publish', | |
517 | ) |
|
517 | ) | |
518 | coreconfigitem( |
|
518 | coreconfigitem( | |
519 | b'experimental', b'bundle-phases', default=False, |
|
519 | b'experimental', b'bundle-phases', default=False, | |
520 | ) |
|
520 | ) | |
521 | coreconfigitem( |
|
521 | coreconfigitem( | |
522 | b'experimental', b'bundle2-advertise', default=True, |
|
522 | b'experimental', b'bundle2-advertise', default=True, | |
523 | ) |
|
523 | ) | |
524 | coreconfigitem( |
|
524 | coreconfigitem( | |
525 | b'experimental', b'bundle2-output-capture', default=False, |
|
525 | b'experimental', b'bundle2-output-capture', default=False, | |
526 | ) |
|
526 | ) | |
527 | coreconfigitem( |
|
527 | coreconfigitem( | |
528 | b'experimental', b'bundle2.pushback', default=False, |
|
528 | b'experimental', b'bundle2.pushback', default=False, | |
529 | ) |
|
529 | ) | |
530 | coreconfigitem( |
|
530 | coreconfigitem( | |
531 | b'experimental', b'bundle2lazylocking', default=False, |
|
531 | b'experimental', b'bundle2lazylocking', default=False, | |
532 | ) |
|
532 | ) | |
533 | coreconfigitem( |
|
533 | coreconfigitem( | |
534 | b'experimental', b'bundlecomplevel', default=None, |
|
534 | b'experimental', b'bundlecomplevel', default=None, | |
535 | ) |
|
535 | ) | |
536 | coreconfigitem( |
|
536 | coreconfigitem( | |
537 | b'experimental', b'bundlecomplevel.bzip2', default=None, |
|
537 | b'experimental', b'bundlecomplevel.bzip2', default=None, | |
538 | ) |
|
538 | ) | |
539 | coreconfigitem( |
|
539 | coreconfigitem( | |
540 | b'experimental', b'bundlecomplevel.gzip', default=None, |
|
540 | b'experimental', b'bundlecomplevel.gzip', default=None, | |
541 | ) |
|
541 | ) | |
542 | coreconfigitem( |
|
542 | coreconfigitem( | |
543 | b'experimental', b'bundlecomplevel.none', default=None, |
|
543 | b'experimental', b'bundlecomplevel.none', default=None, | |
544 | ) |
|
544 | ) | |
545 | coreconfigitem( |
|
545 | coreconfigitem( | |
546 | b'experimental', b'bundlecomplevel.zstd', default=None, |
|
546 | b'experimental', b'bundlecomplevel.zstd', default=None, | |
547 | ) |
|
547 | ) | |
548 | coreconfigitem( |
|
548 | coreconfigitem( | |
549 | b'experimental', b'changegroup3', default=False, |
|
549 | b'experimental', b'changegroup3', default=False, | |
550 | ) |
|
550 | ) | |
551 | coreconfigitem( |
|
551 | coreconfigitem( | |
552 | b'experimental', b'cleanup-as-archived', default=False, |
|
552 | b'experimental', b'cleanup-as-archived', default=False, | |
553 | ) |
|
553 | ) | |
554 | coreconfigitem( |
|
554 | coreconfigitem( | |
555 | b'experimental', b'clientcompressionengines', default=list, |
|
555 | b'experimental', b'clientcompressionengines', default=list, | |
556 | ) |
|
556 | ) | |
557 | coreconfigitem( |
|
557 | coreconfigitem( | |
558 | b'experimental', b'copytrace', default=b'on', |
|
558 | b'experimental', b'copytrace', default=b'on', | |
559 | ) |
|
559 | ) | |
560 | coreconfigitem( |
|
560 | coreconfigitem( | |
561 | b'experimental', b'copytrace.movecandidateslimit', default=100, |
|
561 | b'experimental', b'copytrace.movecandidateslimit', default=100, | |
562 | ) |
|
562 | ) | |
563 | coreconfigitem( |
|
563 | coreconfigitem( | |
564 | b'experimental', b'copytrace.sourcecommitlimit', default=100, |
|
564 | b'experimental', b'copytrace.sourcecommitlimit', default=100, | |
565 | ) |
|
565 | ) | |
566 | coreconfigitem( |
|
566 | coreconfigitem( | |
567 | b'experimental', b'copies.read-from', default=b"filelog-only", |
|
567 | b'experimental', b'copies.read-from', default=b"filelog-only", | |
568 | ) |
|
568 | ) | |
569 | coreconfigitem( |
|
569 | coreconfigitem( | |
570 | b'experimental', b'copies.write-to', default=b'filelog-only', |
|
570 | b'experimental', b'copies.write-to', default=b'filelog-only', | |
571 | ) |
|
571 | ) | |
572 | coreconfigitem( |
|
572 | coreconfigitem( | |
573 | b'experimental', b'crecordtest', default=None, |
|
573 | b'experimental', b'crecordtest', default=None, | |
574 | ) |
|
574 | ) | |
575 | coreconfigitem( |
|
575 | coreconfigitem( | |
576 | b'experimental', b'directaccess', default=False, |
|
576 | b'experimental', b'directaccess', default=False, | |
577 | ) |
|
577 | ) | |
578 | coreconfigitem( |
|
578 | coreconfigitem( | |
579 | b'experimental', b'directaccess.revnums', default=False, |
|
579 | b'experimental', b'directaccess.revnums', default=False, | |
580 | ) |
|
580 | ) | |
581 | coreconfigitem( |
|
581 | coreconfigitem( | |
582 | b'experimental', b'editortmpinhg', default=False, |
|
582 | b'experimental', b'editortmpinhg', default=False, | |
583 | ) |
|
583 | ) | |
584 | coreconfigitem( |
|
584 | coreconfigitem( | |
585 | b'experimental', b'evolution', default=list, |
|
585 | b'experimental', b'evolution', default=list, | |
586 | ) |
|
586 | ) | |
587 | coreconfigitem( |
|
587 | coreconfigitem( | |
588 | b'experimental', |
|
588 | b'experimental', | |
589 | b'evolution.allowdivergence', |
|
589 | b'evolution.allowdivergence', | |
590 | default=False, |
|
590 | default=False, | |
591 | alias=[(b'experimental', b'allowdivergence')], |
|
591 | alias=[(b'experimental', b'allowdivergence')], | |
592 | ) |
|
592 | ) | |
593 | coreconfigitem( |
|
593 | coreconfigitem( | |
594 | b'experimental', b'evolution.allowunstable', default=None, |
|
594 | b'experimental', b'evolution.allowunstable', default=None, | |
595 | ) |
|
595 | ) | |
596 | coreconfigitem( |
|
596 | coreconfigitem( | |
597 | b'experimental', b'evolution.createmarkers', default=None, |
|
597 | b'experimental', b'evolution.createmarkers', default=None, | |
598 | ) |
|
598 | ) | |
599 | coreconfigitem( |
|
599 | coreconfigitem( | |
600 | b'experimental', |
|
600 | b'experimental', | |
601 | b'evolution.effect-flags', |
|
601 | b'evolution.effect-flags', | |
602 | default=True, |
|
602 | default=True, | |
603 | alias=[(b'experimental', b'effect-flags')], |
|
603 | alias=[(b'experimental', b'effect-flags')], | |
604 | ) |
|
604 | ) | |
605 | coreconfigitem( |
|
605 | coreconfigitem( | |
606 | b'experimental', b'evolution.exchange', default=None, |
|
606 | b'experimental', b'evolution.exchange', default=None, | |
607 | ) |
|
607 | ) | |
608 | coreconfigitem( |
|
608 | coreconfigitem( | |
609 | b'experimental', b'evolution.bundle-obsmarker', default=False, |
|
609 | b'experimental', b'evolution.bundle-obsmarker', default=False, | |
610 | ) |
|
610 | ) | |
611 | coreconfigitem( |
|
611 | coreconfigitem( | |
612 | b'experimental', b'log.topo', default=False, |
|
612 | b'experimental', b'log.topo', default=False, | |
613 | ) |
|
613 | ) | |
614 | coreconfigitem( |
|
614 | coreconfigitem( | |
615 | b'experimental', b'evolution.report-instabilities', default=True, |
|
615 | b'experimental', b'evolution.report-instabilities', default=True, | |
616 | ) |
|
616 | ) | |
617 | coreconfigitem( |
|
617 | coreconfigitem( | |
618 | b'experimental', b'evolution.track-operation', default=True, |
|
618 | b'experimental', b'evolution.track-operation', default=True, | |
619 | ) |
|
619 | ) | |
620 | # repo-level config to exclude a revset visibility |
|
620 | # repo-level config to exclude a revset visibility | |
621 | # |
|
621 | # | |
622 | # The target use case is to use `share` to expose different subset of the same |
|
622 | # The target use case is to use `share` to expose different subset of the same | |
623 | # repository, especially server side. See also `server.view`. |
|
623 | # repository, especially server side. See also `server.view`. | |
624 | coreconfigitem( |
|
624 | coreconfigitem( | |
625 | b'experimental', b'extra-filter-revs', default=None, |
|
625 | b'experimental', b'extra-filter-revs', default=None, | |
626 | ) |
|
626 | ) | |
627 | coreconfigitem( |
|
627 | coreconfigitem( | |
628 | b'experimental', b'maxdeltachainspan', default=-1, |
|
628 | b'experimental', b'maxdeltachainspan', default=-1, | |
629 | ) |
|
629 | ) | |
630 | # tracks files which were undeleted (merge might delete them but we explicitly |
|
630 | # tracks files which were undeleted (merge might delete them but we explicitly | |
631 | # kept/undeleted them) and creates new filenodes for them |
|
631 | # kept/undeleted them) and creates new filenodes for them | |
632 | coreconfigitem( |
|
632 | coreconfigitem( | |
633 | b'experimental', b'merge-track-salvaged', default=False, |
|
633 | b'experimental', b'merge-track-salvaged', default=False, | |
634 | ) |
|
634 | ) | |
635 | coreconfigitem( |
|
635 | coreconfigitem( | |
636 | b'experimental', b'mergetempdirprefix', default=None, |
|
636 | b'experimental', b'mergetempdirprefix', default=None, | |
637 | ) |
|
637 | ) | |
638 | coreconfigitem( |
|
638 | coreconfigitem( | |
639 | b'experimental', b'mmapindexthreshold', default=None, |
|
639 | b'experimental', b'mmapindexthreshold', default=None, | |
640 | ) |
|
640 | ) | |
641 | coreconfigitem( |
|
641 | coreconfigitem( | |
642 | b'experimental', b'narrow', default=False, |
|
642 | b'experimental', b'narrow', default=False, | |
643 | ) |
|
643 | ) | |
644 | coreconfigitem( |
|
644 | coreconfigitem( | |
645 | b'experimental', b'nonnormalparanoidcheck', default=False, |
|
645 | b'experimental', b'nonnormalparanoidcheck', default=False, | |
646 | ) |
|
646 | ) | |
647 | coreconfigitem( |
|
647 | coreconfigitem( | |
648 | b'experimental', b'exportableenviron', default=list, |
|
648 | b'experimental', b'exportableenviron', default=list, | |
649 | ) |
|
649 | ) | |
650 | coreconfigitem( |
|
650 | coreconfigitem( | |
651 | b'experimental', b'extendedheader.index', default=None, |
|
651 | b'experimental', b'extendedheader.index', default=None, | |
652 | ) |
|
652 | ) | |
653 | coreconfigitem( |
|
653 | coreconfigitem( | |
654 | b'experimental', b'extendedheader.similarity', default=False, |
|
654 | b'experimental', b'extendedheader.similarity', default=False, | |
655 | ) |
|
655 | ) | |
656 | coreconfigitem( |
|
656 | coreconfigitem( | |
657 | b'experimental', b'graphshorten', default=False, |
|
657 | b'experimental', b'graphshorten', default=False, | |
658 | ) |
|
658 | ) | |
659 | coreconfigitem( |
|
659 | coreconfigitem( | |
660 | b'experimental', b'graphstyle.parent', default=dynamicdefault, |
|
660 | b'experimental', b'graphstyle.parent', default=dynamicdefault, | |
661 | ) |
|
661 | ) | |
662 | coreconfigitem( |
|
662 | coreconfigitem( | |
663 | b'experimental', b'graphstyle.missing', default=dynamicdefault, |
|
663 | b'experimental', b'graphstyle.missing', default=dynamicdefault, | |
664 | ) |
|
664 | ) | |
665 | coreconfigitem( |
|
665 | coreconfigitem( | |
666 | b'experimental', b'graphstyle.grandparent', default=dynamicdefault, |
|
666 | b'experimental', b'graphstyle.grandparent', default=dynamicdefault, | |
667 | ) |
|
667 | ) | |
668 | coreconfigitem( |
|
668 | coreconfigitem( | |
669 | b'experimental', b'hook-track-tags', default=False, |
|
669 | b'experimental', b'hook-track-tags', default=False, | |
670 | ) |
|
670 | ) | |
671 | coreconfigitem( |
|
671 | coreconfigitem( | |
672 | b'experimental', b'httppeer.advertise-v2', default=False, |
|
672 | b'experimental', b'httppeer.advertise-v2', default=False, | |
673 | ) |
|
673 | ) | |
674 | coreconfigitem( |
|
674 | coreconfigitem( | |
675 | b'experimental', b'httppeer.v2-encoder-order', default=None, |
|
675 | b'experimental', b'httppeer.v2-encoder-order', default=None, | |
676 | ) |
|
676 | ) | |
677 | coreconfigitem( |
|
677 | coreconfigitem( | |
678 | b'experimental', b'httppostargs', default=False, |
|
678 | b'experimental', b'httppostargs', default=False, | |
679 | ) |
|
679 | ) | |
680 | coreconfigitem(b'experimental', b'nointerrupt', default=False) |
|
680 | coreconfigitem(b'experimental', b'nointerrupt', default=False) | |
681 | coreconfigitem(b'experimental', b'nointerrupt-interactiveonly', default=True) |
|
681 | coreconfigitem(b'experimental', b'nointerrupt-interactiveonly', default=True) | |
682 |
|
682 | |||
683 | coreconfigitem( |
|
683 | coreconfigitem( | |
684 | b'experimental', b'obsmarkers-exchange-debug', default=False, |
|
684 | b'experimental', b'obsmarkers-exchange-debug', default=False, | |
685 | ) |
|
685 | ) | |
686 | coreconfigitem( |
|
686 | coreconfigitem( | |
687 | b'experimental', b'remotenames', default=False, |
|
687 | b'experimental', b'remotenames', default=False, | |
688 | ) |
|
688 | ) | |
689 | coreconfigitem( |
|
689 | coreconfigitem( | |
690 | b'experimental', b'removeemptydirs', default=True, |
|
690 | b'experimental', b'removeemptydirs', default=True, | |
691 | ) |
|
691 | ) | |
692 | coreconfigitem( |
|
692 | coreconfigitem( | |
693 | b'experimental', b'revert.interactive.select-to-keep', default=False, |
|
693 | b'experimental', b'revert.interactive.select-to-keep', default=False, | |
694 | ) |
|
694 | ) | |
695 | coreconfigitem( |
|
695 | coreconfigitem( | |
696 | b'experimental', b'revisions.prefixhexnode', default=False, |
|
696 | b'experimental', b'revisions.prefixhexnode', default=False, | |
697 | ) |
|
697 | ) | |
698 | coreconfigitem( |
|
698 | coreconfigitem( | |
699 | b'experimental', b'revlogv2', default=None, |
|
699 | b'experimental', b'revlogv2', default=None, | |
700 | ) |
|
700 | ) | |
701 | coreconfigitem( |
|
701 | coreconfigitem( | |
702 | b'experimental', b'revisions.disambiguatewithin', default=None, |
|
702 | b'experimental', b'revisions.disambiguatewithin', default=None, | |
703 | ) |
|
703 | ) | |
704 | coreconfigitem( |
|
704 | coreconfigitem( | |
705 | b'experimental', b'rust.index', default=False, |
|
705 | b'experimental', b'rust.index', default=False, | |
706 | ) |
|
706 | ) | |
707 | coreconfigitem( |
|
707 | coreconfigitem( | |
708 | b'experimental', b'server.filesdata.recommended-batch-size', default=50000, |
|
708 | b'experimental', b'server.filesdata.recommended-batch-size', default=50000, | |
709 | ) |
|
709 | ) | |
710 | coreconfigitem( |
|
710 | coreconfigitem( | |
711 | b'experimental', |
|
711 | b'experimental', | |
712 | b'server.manifestdata.recommended-batch-size', |
|
712 | b'server.manifestdata.recommended-batch-size', | |
713 | default=100000, |
|
713 | default=100000, | |
714 | ) |
|
714 | ) | |
715 | coreconfigitem( |
|
715 | coreconfigitem( | |
716 | b'experimental', b'server.stream-narrow-clones', default=False, |
|
716 | b'experimental', b'server.stream-narrow-clones', default=False, | |
717 | ) |
|
717 | ) | |
718 | coreconfigitem( |
|
718 | coreconfigitem( | |
719 | b'experimental', b'single-head-per-branch', default=False, |
|
719 | b'experimental', b'single-head-per-branch', default=False, | |
720 | ) |
|
720 | ) | |
721 | coreconfigitem( |
|
721 | coreconfigitem( | |
722 | b'experimental', |
|
722 | b'experimental', | |
723 | b'single-head-per-branch:account-closed-heads', |
|
723 | b'single-head-per-branch:account-closed-heads', | |
724 | default=False, |
|
724 | default=False, | |
725 | ) |
|
725 | ) | |
726 | coreconfigitem( |
|
726 | coreconfigitem( | |
727 | b'experimental', b'sshserver.support-v2', default=False, |
|
727 | b'experimental', b'sshserver.support-v2', default=False, | |
728 | ) |
|
728 | ) | |
729 | coreconfigitem( |
|
729 | coreconfigitem( | |
730 | b'experimental', b'sparse-read', default=False, |
|
730 | b'experimental', b'sparse-read', default=False, | |
731 | ) |
|
731 | ) | |
732 | coreconfigitem( |
|
732 | coreconfigitem( | |
733 | b'experimental', b'sparse-read.density-threshold', default=0.50, |
|
733 | b'experimental', b'sparse-read.density-threshold', default=0.50, | |
734 | ) |
|
734 | ) | |
735 | coreconfigitem( |
|
735 | coreconfigitem( | |
736 | b'experimental', b'sparse-read.min-gap-size', default=b'65K', |
|
736 | b'experimental', b'sparse-read.min-gap-size', default=b'65K', | |
737 | ) |
|
737 | ) | |
738 | coreconfigitem( |
|
738 | coreconfigitem( | |
739 | b'experimental', b'treemanifest', default=False, |
|
739 | b'experimental', b'treemanifest', default=False, | |
740 | ) |
|
740 | ) | |
741 | coreconfigitem( |
|
741 | coreconfigitem( | |
742 | b'experimental', b'update.atomic-file', default=False, |
|
742 | b'experimental', b'update.atomic-file', default=False, | |
743 | ) |
|
743 | ) | |
744 | coreconfigitem( |
|
744 | coreconfigitem( | |
745 | b'experimental', b'sshpeer.advertise-v2', default=False, |
|
745 | b'experimental', b'sshpeer.advertise-v2', default=False, | |
746 | ) |
|
746 | ) | |
747 | coreconfigitem( |
|
747 | coreconfigitem( | |
748 | b'experimental', b'web.apiserver', default=False, |
|
748 | b'experimental', b'web.apiserver', default=False, | |
749 | ) |
|
749 | ) | |
750 | coreconfigitem( |
|
750 | coreconfigitem( | |
751 | b'experimental', b'web.api.http-v2', default=False, |
|
751 | b'experimental', b'web.api.http-v2', default=False, | |
752 | ) |
|
752 | ) | |
753 | coreconfigitem( |
|
753 | coreconfigitem( | |
754 | b'experimental', b'web.api.debugreflect', default=False, |
|
754 | b'experimental', b'web.api.debugreflect', default=False, | |
755 | ) |
|
755 | ) | |
756 | coreconfigitem( |
|
756 | coreconfigitem( | |
757 | b'experimental', b'worker.wdir-get-thread-safe', default=False, |
|
757 | b'experimental', b'worker.wdir-get-thread-safe', default=False, | |
758 | ) |
|
758 | ) | |
759 | coreconfigitem( |
|
759 | coreconfigitem( | |
760 | b'experimental', b'worker.repository-upgrade', default=False, |
|
760 | b'experimental', b'worker.repository-upgrade', default=False, | |
761 | ) |
|
761 | ) | |
762 | coreconfigitem( |
|
762 | coreconfigitem( | |
763 | b'experimental', b'xdiff', default=False, |
|
763 | b'experimental', b'xdiff', default=False, | |
764 | ) |
|
764 | ) | |
765 | coreconfigitem( |
|
765 | coreconfigitem( | |
766 | b'extensions', b'.*', default=None, generic=True, |
|
766 | b'extensions', b'.*', default=None, generic=True, | |
767 | ) |
|
767 | ) | |
768 | coreconfigitem( |
|
768 | coreconfigitem( | |
769 | b'extdata', b'.*', default=None, generic=True, |
|
769 | b'extdata', b'.*', default=None, generic=True, | |
770 | ) |
|
770 | ) | |
771 | coreconfigitem( |
|
771 | coreconfigitem( | |
772 | b'format', b'bookmarks-in-store', default=False, |
|
772 | b'format', b'bookmarks-in-store', default=False, | |
773 | ) |
|
773 | ) | |
774 | coreconfigitem( |
|
774 | coreconfigitem( | |
775 | b'format', b'chunkcachesize', default=None, experimental=True, |
|
775 | b'format', b'chunkcachesize', default=None, experimental=True, | |
776 | ) |
|
776 | ) | |
777 | coreconfigitem( |
|
777 | coreconfigitem( | |
778 | b'format', b'dotencode', default=True, |
|
778 | b'format', b'dotencode', default=True, | |
779 | ) |
|
779 | ) | |
780 | coreconfigitem( |
|
780 | coreconfigitem( | |
781 | b'format', b'generaldelta', default=False, experimental=True, |
|
781 | b'format', b'generaldelta', default=False, experimental=True, | |
782 | ) |
|
782 | ) | |
783 | coreconfigitem( |
|
783 | coreconfigitem( | |
784 | b'format', b'manifestcachesize', default=None, experimental=True, |
|
784 | b'format', b'manifestcachesize', default=None, experimental=True, | |
785 | ) |
|
785 | ) | |
786 | coreconfigitem( |
|
786 | coreconfigitem( | |
787 | b'format', b'maxchainlen', default=dynamicdefault, experimental=True, |
|
787 | b'format', b'maxchainlen', default=dynamicdefault, experimental=True, | |
788 | ) |
|
788 | ) | |
789 | coreconfigitem( |
|
789 | coreconfigitem( | |
790 | b'format', b'obsstore-version', default=None, |
|
790 | b'format', b'obsstore-version', default=None, | |
791 | ) |
|
791 | ) | |
792 | coreconfigitem( |
|
792 | coreconfigitem( | |
793 | b'format', b'sparse-revlog', default=True, |
|
793 | b'format', b'sparse-revlog', default=True, | |
794 | ) |
|
794 | ) | |
795 | coreconfigitem( |
|
795 | coreconfigitem( | |
796 | b'format', |
|
796 | b'format', | |
797 | b'revlog-compression', |
|
797 | b'revlog-compression', | |
798 | default=lambda: [b'zlib'], |
|
798 | default=lambda: [b'zlib'], | |
799 | alias=[(b'experimental', b'format.compression')], |
|
799 | alias=[(b'experimental', b'format.compression')], | |
800 | ) |
|
800 | ) | |
801 | coreconfigitem( |
|
801 | coreconfigitem( | |
802 | b'format', b'usefncache', default=True, |
|
802 | b'format', b'usefncache', default=True, | |
803 | ) |
|
803 | ) | |
804 | coreconfigitem( |
|
804 | coreconfigitem( | |
805 | b'format', b'usegeneraldelta', default=True, |
|
805 | b'format', b'usegeneraldelta', default=True, | |
806 | ) |
|
806 | ) | |
807 | coreconfigitem( |
|
807 | coreconfigitem( | |
808 | b'format', b'usestore', default=True, |
|
808 | b'format', b'usestore', default=True, | |
809 | ) |
|
809 | ) | |
810 | # Right now, the only efficient implement of the nodemap logic is in Rust, so |
|
810 | # Right now, the only efficient implement of the nodemap logic is in Rust, so | |
811 | # the persistent nodemap feature needs to stay experimental as long as the Rust |
|
811 | # the persistent nodemap feature needs to stay experimental as long as the Rust | |
812 | # extensions are an experimental feature. |
|
812 | # extensions are an experimental feature. | |
813 | coreconfigitem( |
|
813 | coreconfigitem( | |
814 | b'format', b'use-persistent-nodemap', default=False, experimental=True |
|
814 | b'format', b'use-persistent-nodemap', default=False, experimental=True | |
815 | ) |
|
815 | ) | |
816 | coreconfigitem( |
|
816 | coreconfigitem( | |
817 | b'format', |
|
817 | b'format', | |
818 | b'exp-use-copies-side-data-changeset', |
|
818 | b'exp-use-copies-side-data-changeset', | |
819 | default=False, |
|
819 | default=False, | |
820 | experimental=True, |
|
820 | experimental=True, | |
821 | ) |
|
821 | ) | |
822 | coreconfigitem( |
|
822 | coreconfigitem( | |
823 | b'format', b'exp-use-side-data', default=False, experimental=True, |
|
823 | b'format', b'exp-use-side-data', default=False, experimental=True, | |
824 | ) |
|
824 | ) | |
825 | coreconfigitem( |
|
825 | coreconfigitem( | |
826 | b'format', b'exp-share-safe', default=False, experimental=True, |
|
826 | b'format', b'exp-share-safe', default=False, experimental=True, | |
827 | ) |
|
827 | ) | |
828 | coreconfigitem( |
|
828 | coreconfigitem( | |
829 | b'format', b'internal-phase', default=False, experimental=True, |
|
829 | b'format', b'internal-phase', default=False, experimental=True, | |
830 | ) |
|
830 | ) | |
831 | coreconfigitem( |
|
831 | coreconfigitem( | |
832 | b'fsmonitor', b'warn_when_unused', default=True, |
|
832 | b'fsmonitor', b'warn_when_unused', default=True, | |
833 | ) |
|
833 | ) | |
834 | coreconfigitem( |
|
834 | coreconfigitem( | |
835 | b'fsmonitor', b'warn_update_file_count', default=50000, |
|
835 | b'fsmonitor', b'warn_update_file_count', default=50000, | |
836 | ) |
|
836 | ) | |
837 | coreconfigitem( |
|
837 | coreconfigitem( | |
838 | b'fsmonitor', b'warn_update_file_count_rust', default=400000, |
|
838 | b'fsmonitor', b'warn_update_file_count_rust', default=400000, | |
839 | ) |
|
839 | ) | |
840 | coreconfigitem( |
|
840 | coreconfigitem( | |
841 | b'help', br'hidden-command\..*', default=False, generic=True, |
|
841 | b'help', br'hidden-command\..*', default=False, generic=True, | |
842 | ) |
|
842 | ) | |
843 | coreconfigitem( |
|
843 | coreconfigitem( | |
844 | b'help', br'hidden-topic\..*', default=False, generic=True, |
|
844 | b'help', br'hidden-topic\..*', default=False, generic=True, | |
845 | ) |
|
845 | ) | |
846 | coreconfigitem( |
|
846 | coreconfigitem( | |
847 | b'hooks', b'.*', default=dynamicdefault, generic=True, |
|
847 | b'hooks', b'.*', default=dynamicdefault, generic=True, | |
848 | ) |
|
848 | ) | |
849 | coreconfigitem( |
|
849 | coreconfigitem( | |
850 | b'hgweb-paths', b'.*', default=list, generic=True, |
|
850 | b'hgweb-paths', b'.*', default=list, generic=True, | |
851 | ) |
|
851 | ) | |
852 | coreconfigitem( |
|
852 | coreconfigitem( | |
853 | b'hostfingerprints', b'.*', default=list, generic=True, |
|
853 | b'hostfingerprints', b'.*', default=list, generic=True, | |
854 | ) |
|
854 | ) | |
855 | coreconfigitem( |
|
855 | coreconfigitem( | |
856 | b'hostsecurity', b'ciphers', default=None, |
|
856 | b'hostsecurity', b'ciphers', default=None, | |
857 | ) |
|
857 | ) | |
858 | coreconfigitem( |
|
858 | coreconfigitem( | |
859 | b'hostsecurity', b'minimumprotocol', default=dynamicdefault, |
|
859 | b'hostsecurity', b'minimumprotocol', default=dynamicdefault, | |
860 | ) |
|
860 | ) | |
861 | coreconfigitem( |
|
861 | coreconfigitem( | |
862 | b'hostsecurity', |
|
862 | b'hostsecurity', | |
863 | b'.*:minimumprotocol$', |
|
863 | b'.*:minimumprotocol$', | |
864 | default=dynamicdefault, |
|
864 | default=dynamicdefault, | |
865 | generic=True, |
|
865 | generic=True, | |
866 | ) |
|
866 | ) | |
867 | coreconfigitem( |
|
867 | coreconfigitem( | |
868 | b'hostsecurity', b'.*:ciphers$', default=dynamicdefault, generic=True, |
|
868 | b'hostsecurity', b'.*:ciphers$', default=dynamicdefault, generic=True, | |
869 | ) |
|
869 | ) | |
870 | coreconfigitem( |
|
870 | coreconfigitem( | |
871 | b'hostsecurity', b'.*:fingerprints$', default=list, generic=True, |
|
871 | b'hostsecurity', b'.*:fingerprints$', default=list, generic=True, | |
872 | ) |
|
872 | ) | |
873 | coreconfigitem( |
|
873 | coreconfigitem( | |
874 | b'hostsecurity', b'.*:verifycertsfile$', default=None, generic=True, |
|
874 | b'hostsecurity', b'.*:verifycertsfile$', default=None, generic=True, | |
875 | ) |
|
875 | ) | |
876 |
|
876 | |||
877 | coreconfigitem( |
|
877 | coreconfigitem( | |
878 | b'http_proxy', b'always', default=False, |
|
878 | b'http_proxy', b'always', default=False, | |
879 | ) |
|
879 | ) | |
880 | coreconfigitem( |
|
880 | coreconfigitem( | |
881 | b'http_proxy', b'host', default=None, |
|
881 | b'http_proxy', b'host', default=None, | |
882 | ) |
|
882 | ) | |
883 | coreconfigitem( |
|
883 | coreconfigitem( | |
884 | b'http_proxy', b'no', default=list, |
|
884 | b'http_proxy', b'no', default=list, | |
885 | ) |
|
885 | ) | |
886 | coreconfigitem( |
|
886 | coreconfigitem( | |
887 | b'http_proxy', b'passwd', default=None, |
|
887 | b'http_proxy', b'passwd', default=None, | |
888 | ) |
|
888 | ) | |
889 | coreconfigitem( |
|
889 | coreconfigitem( | |
890 | b'http_proxy', b'user', default=None, |
|
890 | b'http_proxy', b'user', default=None, | |
891 | ) |
|
891 | ) | |
892 |
|
892 | |||
893 | coreconfigitem( |
|
893 | coreconfigitem( | |
894 | b'http', b'timeout', default=None, |
|
894 | b'http', b'timeout', default=None, | |
895 | ) |
|
895 | ) | |
896 |
|
896 | |||
897 | coreconfigitem( |
|
897 | coreconfigitem( | |
898 | b'logtoprocess', b'commandexception', default=None, |
|
898 | b'logtoprocess', b'commandexception', default=None, | |
899 | ) |
|
899 | ) | |
900 | coreconfigitem( |
|
900 | coreconfigitem( | |
901 | b'logtoprocess', b'commandfinish', default=None, |
|
901 | b'logtoprocess', b'commandfinish', default=None, | |
902 | ) |
|
902 | ) | |
903 | coreconfigitem( |
|
903 | coreconfigitem( | |
904 | b'logtoprocess', b'command', default=None, |
|
904 | b'logtoprocess', b'command', default=None, | |
905 | ) |
|
905 | ) | |
906 | coreconfigitem( |
|
906 | coreconfigitem( | |
907 | b'logtoprocess', b'develwarn', default=None, |
|
907 | b'logtoprocess', b'develwarn', default=None, | |
908 | ) |
|
908 | ) | |
909 | coreconfigitem( |
|
909 | coreconfigitem( | |
910 | b'logtoprocess', b'uiblocked', default=None, |
|
910 | b'logtoprocess', b'uiblocked', default=None, | |
911 | ) |
|
911 | ) | |
912 | coreconfigitem( |
|
912 | coreconfigitem( | |
913 | b'merge', b'checkunknown', default=b'abort', |
|
913 | b'merge', b'checkunknown', default=b'abort', | |
914 | ) |
|
914 | ) | |
915 | coreconfigitem( |
|
915 | coreconfigitem( | |
916 | b'merge', b'checkignored', default=b'abort', |
|
916 | b'merge', b'checkignored', default=b'abort', | |
917 | ) |
|
917 | ) | |
918 | coreconfigitem( |
|
918 | coreconfigitem( | |
919 | b'experimental', b'merge.checkpathconflicts', default=False, |
|
919 | b'experimental', b'merge.checkpathconflicts', default=False, | |
920 | ) |
|
920 | ) | |
921 | coreconfigitem( |
|
921 | coreconfigitem( | |
922 | b'merge', b'followcopies', default=True, |
|
922 | b'merge', b'followcopies', default=True, | |
923 | ) |
|
923 | ) | |
924 | coreconfigitem( |
|
924 | coreconfigitem( | |
925 | b'merge', b'on-failure', default=b'continue', |
|
925 | b'merge', b'on-failure', default=b'continue', | |
926 | ) |
|
926 | ) | |
927 | coreconfigitem( |
|
927 | coreconfigitem( | |
928 | b'merge', b'preferancestor', default=lambda: [b'*'], experimental=True, |
|
928 | b'merge', b'preferancestor', default=lambda: [b'*'], experimental=True, | |
929 | ) |
|
929 | ) | |
930 | coreconfigitem( |
|
930 | coreconfigitem( | |
931 | b'merge', b'strict-capability-check', default=False, |
|
931 | b'merge', b'strict-capability-check', default=False, | |
932 | ) |
|
932 | ) | |
933 | coreconfigitem( |
|
933 | coreconfigitem( | |
934 | b'merge-tools', b'.*', default=None, generic=True, |
|
934 | b'merge-tools', b'.*', default=None, generic=True, | |
935 | ) |
|
935 | ) | |
936 | coreconfigitem( |
|
936 | coreconfigitem( | |
937 | b'merge-tools', |
|
937 | b'merge-tools', | |
938 | br'.*\.args$', |
|
938 | br'.*\.args$', | |
939 | default=b"$local $base $other", |
|
939 | default=b"$local $base $other", | |
940 | generic=True, |
|
940 | generic=True, | |
941 | priority=-1, |
|
941 | priority=-1, | |
942 | ) |
|
942 | ) | |
943 | coreconfigitem( |
|
943 | coreconfigitem( | |
944 | b'merge-tools', br'.*\.binary$', default=False, generic=True, priority=-1, |
|
944 | b'merge-tools', br'.*\.binary$', default=False, generic=True, priority=-1, | |
945 | ) |
|
945 | ) | |
946 | coreconfigitem( |
|
946 | coreconfigitem( | |
947 | b'merge-tools', br'.*\.check$', default=list, generic=True, priority=-1, |
|
947 | b'merge-tools', br'.*\.check$', default=list, generic=True, priority=-1, | |
948 | ) |
|
948 | ) | |
949 | coreconfigitem( |
|
949 | coreconfigitem( | |
950 | b'merge-tools', |
|
950 | b'merge-tools', | |
951 | br'.*\.checkchanged$', |
|
951 | br'.*\.checkchanged$', | |
952 | default=False, |
|
952 | default=False, | |
953 | generic=True, |
|
953 | generic=True, | |
954 | priority=-1, |
|
954 | priority=-1, | |
955 | ) |
|
955 | ) | |
956 | coreconfigitem( |
|
956 | coreconfigitem( | |
957 | b'merge-tools', |
|
957 | b'merge-tools', | |
958 | br'.*\.executable$', |
|
958 | br'.*\.executable$', | |
959 | default=dynamicdefault, |
|
959 | default=dynamicdefault, | |
960 | generic=True, |
|
960 | generic=True, | |
961 | priority=-1, |
|
961 | priority=-1, | |
962 | ) |
|
962 | ) | |
963 | coreconfigitem( |
|
963 | coreconfigitem( | |
964 | b'merge-tools', br'.*\.fixeol$', default=False, generic=True, priority=-1, |
|
964 | b'merge-tools', br'.*\.fixeol$', default=False, generic=True, priority=-1, | |
965 | ) |
|
965 | ) | |
966 | coreconfigitem( |
|
966 | coreconfigitem( | |
967 | b'merge-tools', br'.*\.gui$', default=False, generic=True, priority=-1, |
|
967 | b'merge-tools', br'.*\.gui$', default=False, generic=True, priority=-1, | |
968 | ) |
|
968 | ) | |
969 | coreconfigitem( |
|
969 | coreconfigitem( | |
970 | b'merge-tools', |
|
970 | b'merge-tools', | |
971 | br'.*\.mergemarkers$', |
|
971 | br'.*\.mergemarkers$', | |
972 | default=b'basic', |
|
972 | default=b'basic', | |
973 | generic=True, |
|
973 | generic=True, | |
974 | priority=-1, |
|
974 | priority=-1, | |
975 | ) |
|
975 | ) | |
976 | coreconfigitem( |
|
976 | coreconfigitem( | |
977 | b'merge-tools', |
|
977 | b'merge-tools', | |
978 | br'.*\.mergemarkertemplate$', |
|
978 | br'.*\.mergemarkertemplate$', | |
979 | default=dynamicdefault, # take from command-templates.mergemarker |
|
979 | default=dynamicdefault, # take from command-templates.mergemarker | |
980 | generic=True, |
|
980 | generic=True, | |
981 | priority=-1, |
|
981 | priority=-1, | |
982 | ) |
|
982 | ) | |
983 | coreconfigitem( |
|
983 | coreconfigitem( | |
984 | b'merge-tools', br'.*\.priority$', default=0, generic=True, priority=-1, |
|
984 | b'merge-tools', br'.*\.priority$', default=0, generic=True, priority=-1, | |
985 | ) |
|
985 | ) | |
986 | coreconfigitem( |
|
986 | coreconfigitem( | |
987 | b'merge-tools', |
|
987 | b'merge-tools', | |
988 | br'.*\.premerge$', |
|
988 | br'.*\.premerge$', | |
989 | default=dynamicdefault, |
|
989 | default=dynamicdefault, | |
990 | generic=True, |
|
990 | generic=True, | |
991 | priority=-1, |
|
991 | priority=-1, | |
992 | ) |
|
992 | ) | |
993 | coreconfigitem( |
|
993 | coreconfigitem( | |
994 | b'merge-tools', br'.*\.symlink$', default=False, generic=True, priority=-1, |
|
994 | b'merge-tools', br'.*\.symlink$', default=False, generic=True, priority=-1, | |
995 | ) |
|
995 | ) | |
996 | coreconfigitem( |
|
996 | coreconfigitem( | |
997 | b'pager', b'attend-.*', default=dynamicdefault, generic=True, |
|
997 | b'pager', b'attend-.*', default=dynamicdefault, generic=True, | |
998 | ) |
|
998 | ) | |
999 | coreconfigitem( |
|
999 | coreconfigitem( | |
1000 | b'pager', b'ignore', default=list, |
|
1000 | b'pager', b'ignore', default=list, | |
1001 | ) |
|
1001 | ) | |
1002 | coreconfigitem( |
|
1002 | coreconfigitem( | |
1003 | b'pager', b'pager', default=dynamicdefault, |
|
1003 | b'pager', b'pager', default=dynamicdefault, | |
1004 | ) |
|
1004 | ) | |
1005 | coreconfigitem( |
|
1005 | coreconfigitem( | |
1006 | b'patch', b'eol', default=b'strict', |
|
1006 | b'patch', b'eol', default=b'strict', | |
1007 | ) |
|
1007 | ) | |
1008 | coreconfigitem( |
|
1008 | coreconfigitem( | |
1009 | b'patch', b'fuzz', default=2, |
|
1009 | b'patch', b'fuzz', default=2, | |
1010 | ) |
|
1010 | ) | |
1011 | coreconfigitem( |
|
1011 | coreconfigitem( | |
1012 | b'paths', b'default', default=None, |
|
1012 | b'paths', b'default', default=None, | |
1013 | ) |
|
1013 | ) | |
1014 | coreconfigitem( |
|
1014 | coreconfigitem( | |
1015 | b'paths', b'default-push', default=None, |
|
1015 | b'paths', b'default-push', default=None, | |
1016 | ) |
|
1016 | ) | |
1017 | coreconfigitem( |
|
1017 | coreconfigitem( | |
1018 | b'paths', b'.*', default=None, generic=True, |
|
1018 | b'paths', b'.*', default=None, generic=True, | |
1019 | ) |
|
1019 | ) | |
1020 | coreconfigitem( |
|
1020 | coreconfigitem( | |
1021 | b'phases', b'checksubrepos', default=b'follow', |
|
1021 | b'phases', b'checksubrepos', default=b'follow', | |
1022 | ) |
|
1022 | ) | |
1023 | coreconfigitem( |
|
1023 | coreconfigitem( | |
1024 | b'phases', b'new-commit', default=b'draft', |
|
1024 | b'phases', b'new-commit', default=b'draft', | |
1025 | ) |
|
1025 | ) | |
1026 | coreconfigitem( |
|
1026 | coreconfigitem( | |
1027 | b'phases', b'publish', default=True, |
|
1027 | b'phases', b'publish', default=True, | |
1028 | ) |
|
1028 | ) | |
1029 | coreconfigitem( |
|
1029 | coreconfigitem( | |
1030 | b'profiling', b'enabled', default=False, |
|
1030 | b'profiling', b'enabled', default=False, | |
1031 | ) |
|
1031 | ) | |
1032 | coreconfigitem( |
|
1032 | coreconfigitem( | |
1033 | b'profiling', b'format', default=b'text', |
|
1033 | b'profiling', b'format', default=b'text', | |
1034 | ) |
|
1034 | ) | |
1035 | coreconfigitem( |
|
1035 | coreconfigitem( | |
1036 | b'profiling', b'freq', default=1000, |
|
1036 | b'profiling', b'freq', default=1000, | |
1037 | ) |
|
1037 | ) | |
1038 | coreconfigitem( |
|
1038 | coreconfigitem( | |
1039 | b'profiling', b'limit', default=30, |
|
1039 | b'profiling', b'limit', default=30, | |
1040 | ) |
|
1040 | ) | |
1041 | coreconfigitem( |
|
1041 | coreconfigitem( | |
1042 | b'profiling', b'nested', default=0, |
|
1042 | b'profiling', b'nested', default=0, | |
1043 | ) |
|
1043 | ) | |
1044 | coreconfigitem( |
|
1044 | coreconfigitem( | |
1045 | b'profiling', b'output', default=None, |
|
1045 | b'profiling', b'output', default=None, | |
1046 | ) |
|
1046 | ) | |
1047 | coreconfigitem( |
|
1047 | coreconfigitem( | |
1048 | b'profiling', b'showmax', default=0.999, |
|
1048 | b'profiling', b'showmax', default=0.999, | |
1049 | ) |
|
1049 | ) | |
1050 | coreconfigitem( |
|
1050 | coreconfigitem( | |
1051 | b'profiling', b'showmin', default=dynamicdefault, |
|
1051 | b'profiling', b'showmin', default=dynamicdefault, | |
1052 | ) |
|
1052 | ) | |
1053 | coreconfigitem( |
|
1053 | coreconfigitem( | |
1054 | b'profiling', b'showtime', default=True, |
|
1054 | b'profiling', b'showtime', default=True, | |
1055 | ) |
|
1055 | ) | |
1056 | coreconfigitem( |
|
1056 | coreconfigitem( | |
1057 | b'profiling', b'sort', default=b'inlinetime', |
|
1057 | b'profiling', b'sort', default=b'inlinetime', | |
1058 | ) |
|
1058 | ) | |
1059 | coreconfigitem( |
|
1059 | coreconfigitem( | |
1060 | b'profiling', b'statformat', default=b'hotpath', |
|
1060 | b'profiling', b'statformat', default=b'hotpath', | |
1061 | ) |
|
1061 | ) | |
1062 | coreconfigitem( |
|
1062 | coreconfigitem( | |
1063 | b'profiling', b'time-track', default=dynamicdefault, |
|
1063 | b'profiling', b'time-track', default=dynamicdefault, | |
1064 | ) |
|
1064 | ) | |
1065 | coreconfigitem( |
|
1065 | coreconfigitem( | |
1066 | b'profiling', b'type', default=b'stat', |
|
1066 | b'profiling', b'type', default=b'stat', | |
1067 | ) |
|
1067 | ) | |
1068 | coreconfigitem( |
|
1068 | coreconfigitem( | |
1069 | b'progress', b'assume-tty', default=False, |
|
1069 | b'progress', b'assume-tty', default=False, | |
1070 | ) |
|
1070 | ) | |
1071 | coreconfigitem( |
|
1071 | coreconfigitem( | |
1072 | b'progress', b'changedelay', default=1, |
|
1072 | b'progress', b'changedelay', default=1, | |
1073 | ) |
|
1073 | ) | |
1074 | coreconfigitem( |
|
1074 | coreconfigitem( | |
1075 | b'progress', b'clear-complete', default=True, |
|
1075 | b'progress', b'clear-complete', default=True, | |
1076 | ) |
|
1076 | ) | |
1077 | coreconfigitem( |
|
1077 | coreconfigitem( | |
1078 | b'progress', b'debug', default=False, |
|
1078 | b'progress', b'debug', default=False, | |
1079 | ) |
|
1079 | ) | |
1080 | coreconfigitem( |
|
1080 | coreconfigitem( | |
1081 | b'progress', b'delay', default=3, |
|
1081 | b'progress', b'delay', default=3, | |
1082 | ) |
|
1082 | ) | |
1083 | coreconfigitem( |
|
1083 | coreconfigitem( | |
1084 | b'progress', b'disable', default=False, |
|
1084 | b'progress', b'disable', default=False, | |
1085 | ) |
|
1085 | ) | |
1086 | coreconfigitem( |
|
1086 | coreconfigitem( | |
1087 | b'progress', b'estimateinterval', default=60.0, |
|
1087 | b'progress', b'estimateinterval', default=60.0, | |
1088 | ) |
|
1088 | ) | |
1089 | coreconfigitem( |
|
1089 | coreconfigitem( | |
1090 | b'progress', |
|
1090 | b'progress', | |
1091 | b'format', |
|
1091 | b'format', | |
1092 | default=lambda: [b'topic', b'bar', b'number', b'estimate'], |
|
1092 | default=lambda: [b'topic', b'bar', b'number', b'estimate'], | |
1093 | ) |
|
1093 | ) | |
1094 | coreconfigitem( |
|
1094 | coreconfigitem( | |
1095 | b'progress', b'refresh', default=0.1, |
|
1095 | b'progress', b'refresh', default=0.1, | |
1096 | ) |
|
1096 | ) | |
1097 | coreconfigitem( |
|
1097 | coreconfigitem( | |
1098 | b'progress', b'width', default=dynamicdefault, |
|
1098 | b'progress', b'width', default=dynamicdefault, | |
1099 | ) |
|
1099 | ) | |
1100 | coreconfigitem( |
|
1100 | coreconfigitem( | |
1101 | b'pull', b'confirm', default=False, |
|
1101 | b'pull', b'confirm', default=False, | |
1102 | ) |
|
1102 | ) | |
1103 | coreconfigitem( |
|
1103 | coreconfigitem( | |
1104 | b'push', b'pushvars.server', default=False, |
|
1104 | b'push', b'pushvars.server', default=False, | |
1105 | ) |
|
1105 | ) | |
1106 | coreconfigitem( |
|
1106 | coreconfigitem( | |
1107 | b'rewrite', |
|
1107 | b'rewrite', | |
1108 | b'backup-bundle', |
|
1108 | b'backup-bundle', | |
1109 | default=True, |
|
1109 | default=True, | |
1110 | alias=[(b'ui', b'history-editing-backup')], |
|
1110 | alias=[(b'ui', b'history-editing-backup')], | |
1111 | ) |
|
1111 | ) | |
1112 | coreconfigitem( |
|
1112 | coreconfigitem( | |
1113 | b'rewrite', b'update-timestamp', default=False, |
|
1113 | b'rewrite', b'update-timestamp', default=False, | |
1114 | ) |
|
1114 | ) | |
1115 | coreconfigitem( |
|
1115 | coreconfigitem( | |
1116 | b'rewrite', b'empty-successor', default=b'skip', experimental=True, |
|
1116 | b'rewrite', b'empty-successor', default=b'skip', experimental=True, | |
1117 | ) |
|
1117 | ) | |
1118 | coreconfigitem( |
|
1118 | coreconfigitem( | |
1119 | b'storage', b'new-repo-backend', default=b'revlogv1', experimental=True, |
|
1119 | b'storage', b'new-repo-backend', default=b'revlogv1', experimental=True, | |
1120 | ) |
|
1120 | ) | |
1121 | coreconfigitem( |
|
1121 | coreconfigitem( | |
1122 | b'storage', |
|
1122 | b'storage', | |
1123 | b'revlog.optimize-delta-parent-choice', |
|
1123 | b'revlog.optimize-delta-parent-choice', | |
1124 | default=True, |
|
1124 | default=True, | |
1125 | alias=[(b'format', b'aggressivemergedeltas')], |
|
1125 | alias=[(b'format', b'aggressivemergedeltas')], | |
1126 | ) |
|
1126 | ) | |
1127 | # experimental as long as rust is experimental (or a C version is implemented) |
|
1127 | # experimental as long as rust is experimental (or a C version is implemented) | |
1128 | coreconfigitem( |
|
1128 | coreconfigitem( | |
1129 | b'storage', b'revlog.nodemap.mmap', default=True, experimental=True |
|
1129 | b'storage', b'revlog.nodemap.mmap', default=True, experimental=True | |
1130 | ) |
|
1130 | ) | |
1131 | # experimental as long as format.use-persistent-nodemap is. |
|
1131 | # experimental as long as format.use-persistent-nodemap is. | |
1132 | coreconfigitem( |
|
1132 | coreconfigitem( | |
1133 | b'storage', b'revlog.nodemap.mode', default=b'compat', experimental=True |
|
1133 | b'storage', b'revlog.nodemap.mode', default=b'compat', experimental=True | |
1134 | ) |
|
1134 | ) | |
1135 | coreconfigitem( |
|
1135 | coreconfigitem( | |
1136 | b'storage', b'revlog.reuse-external-delta', default=True, |
|
1136 | b'storage', b'revlog.reuse-external-delta', default=True, | |
1137 | ) |
|
1137 | ) | |
1138 | coreconfigitem( |
|
1138 | coreconfigitem( | |
1139 | b'storage', b'revlog.reuse-external-delta-parent', default=None, |
|
1139 | b'storage', b'revlog.reuse-external-delta-parent', default=None, | |
1140 | ) |
|
1140 | ) | |
1141 | coreconfigitem( |
|
1141 | coreconfigitem( | |
1142 | b'storage', b'revlog.zlib.level', default=None, |
|
1142 | b'storage', b'revlog.zlib.level', default=None, | |
1143 | ) |
|
1143 | ) | |
1144 | coreconfigitem( |
|
1144 | coreconfigitem( | |
1145 | b'storage', b'revlog.zstd.level', default=None, |
|
1145 | b'storage', b'revlog.zstd.level', default=None, | |
1146 | ) |
|
1146 | ) | |
1147 | coreconfigitem( |
|
1147 | coreconfigitem( | |
1148 | b'server', b'bookmarks-pushkey-compat', default=True, |
|
1148 | b'server', b'bookmarks-pushkey-compat', default=True, | |
1149 | ) |
|
1149 | ) | |
1150 | coreconfigitem( |
|
1150 | coreconfigitem( | |
1151 | b'server', b'bundle1', default=True, |
|
1151 | b'server', b'bundle1', default=True, | |
1152 | ) |
|
1152 | ) | |
1153 | coreconfigitem( |
|
1153 | coreconfigitem( | |
1154 | b'server', b'bundle1gd', default=None, |
|
1154 | b'server', b'bundle1gd', default=None, | |
1155 | ) |
|
1155 | ) | |
1156 | coreconfigitem( |
|
1156 | coreconfigitem( | |
1157 | b'server', b'bundle1.pull', default=None, |
|
1157 | b'server', b'bundle1.pull', default=None, | |
1158 | ) |
|
1158 | ) | |
1159 | coreconfigitem( |
|
1159 | coreconfigitem( | |
1160 | b'server', b'bundle1gd.pull', default=None, |
|
1160 | b'server', b'bundle1gd.pull', default=None, | |
1161 | ) |
|
1161 | ) | |
1162 | coreconfigitem( |
|
1162 | coreconfigitem( | |
1163 | b'server', b'bundle1.push', default=None, |
|
1163 | b'server', b'bundle1.push', default=None, | |
1164 | ) |
|
1164 | ) | |
1165 | coreconfigitem( |
|
1165 | coreconfigitem( | |
1166 | b'server', b'bundle1gd.push', default=None, |
|
1166 | b'server', b'bundle1gd.push', default=None, | |
1167 | ) |
|
1167 | ) | |
1168 | coreconfigitem( |
|
1168 | coreconfigitem( | |
1169 | b'server', |
|
1169 | b'server', | |
1170 | b'bundle2.stream', |
|
1170 | b'bundle2.stream', | |
1171 | default=True, |
|
1171 | default=True, | |
1172 | alias=[(b'experimental', b'bundle2.stream')], |
|
1172 | alias=[(b'experimental', b'bundle2.stream')], | |
1173 | ) |
|
1173 | ) | |
1174 | coreconfigitem( |
|
1174 | coreconfigitem( | |
1175 | b'server', b'compressionengines', default=list, |
|
1175 | b'server', b'compressionengines', default=list, | |
1176 | ) |
|
1176 | ) | |
1177 | coreconfigitem( |
|
1177 | coreconfigitem( | |
1178 | b'server', b'concurrent-push-mode', default=b'check-related', |
|
1178 | b'server', b'concurrent-push-mode', default=b'check-related', | |
1179 | ) |
|
1179 | ) | |
1180 | coreconfigitem( |
|
1180 | coreconfigitem( | |
1181 | b'server', b'disablefullbundle', default=False, |
|
1181 | b'server', b'disablefullbundle', default=False, | |
1182 | ) |
|
1182 | ) | |
1183 | coreconfigitem( |
|
1183 | coreconfigitem( | |
1184 | b'server', b'maxhttpheaderlen', default=1024, |
|
1184 | b'server', b'maxhttpheaderlen', default=1024, | |
1185 | ) |
|
1185 | ) | |
1186 | coreconfigitem( |
|
1186 | coreconfigitem( | |
1187 | b'server', b'pullbundle', default=False, |
|
1187 | b'server', b'pullbundle', default=False, | |
1188 | ) |
|
1188 | ) | |
1189 | coreconfigitem( |
|
1189 | coreconfigitem( | |
1190 | b'server', b'preferuncompressed', default=False, |
|
1190 | b'server', b'preferuncompressed', default=False, | |
1191 | ) |
|
1191 | ) | |
1192 | coreconfigitem( |
|
1192 | coreconfigitem( | |
1193 | b'server', b'streamunbundle', default=False, |
|
1193 | b'server', b'streamunbundle', default=False, | |
1194 | ) |
|
1194 | ) | |
1195 | coreconfigitem( |
|
1195 | coreconfigitem( | |
1196 | b'server', b'uncompressed', default=True, |
|
1196 | b'server', b'uncompressed', default=True, | |
1197 | ) |
|
1197 | ) | |
1198 | coreconfigitem( |
|
1198 | coreconfigitem( | |
1199 | b'server', b'uncompressedallowsecret', default=False, |
|
1199 | b'server', b'uncompressedallowsecret', default=False, | |
1200 | ) |
|
1200 | ) | |
1201 | coreconfigitem( |
|
1201 | coreconfigitem( | |
1202 | b'server', b'view', default=b'served', |
|
1202 | b'server', b'view', default=b'served', | |
1203 | ) |
|
1203 | ) | |
1204 | coreconfigitem( |
|
1204 | coreconfigitem( | |
1205 | b'server', b'validate', default=False, |
|
1205 | b'server', b'validate', default=False, | |
1206 | ) |
|
1206 | ) | |
1207 | coreconfigitem( |
|
1207 | coreconfigitem( | |
1208 | b'server', b'zliblevel', default=-1, |
|
1208 | b'server', b'zliblevel', default=-1, | |
1209 | ) |
|
1209 | ) | |
1210 | coreconfigitem( |
|
1210 | coreconfigitem( | |
1211 | b'server', b'zstdlevel', default=3, |
|
1211 | b'server', b'zstdlevel', default=3, | |
1212 | ) |
|
1212 | ) | |
1213 | coreconfigitem( |
|
1213 | coreconfigitem( | |
1214 | b'share', b'pool', default=None, |
|
1214 | b'share', b'pool', default=None, | |
1215 | ) |
|
1215 | ) | |
1216 | coreconfigitem( |
|
1216 | coreconfigitem( | |
1217 | b'share', b'poolnaming', default=b'identity', |
|
1217 | b'share', b'poolnaming', default=b'identity', | |
1218 | ) |
|
1218 | ) | |
1219 | coreconfigitem( |
|
1219 | coreconfigitem( | |
1220 | b'shelve', b'maxbackups', default=10, |
|
1220 | b'shelve', b'maxbackups', default=10, | |
1221 | ) |
|
1221 | ) | |
1222 | coreconfigitem( |
|
1222 | coreconfigitem( | |
1223 | b'smtp', b'host', default=None, |
|
1223 | b'smtp', b'host', default=None, | |
1224 | ) |
|
1224 | ) | |
1225 | coreconfigitem( |
|
1225 | coreconfigitem( | |
1226 | b'smtp', b'local_hostname', default=None, |
|
1226 | b'smtp', b'local_hostname', default=None, | |
1227 | ) |
|
1227 | ) | |
1228 | coreconfigitem( |
|
1228 | coreconfigitem( | |
1229 | b'smtp', b'password', default=None, |
|
1229 | b'smtp', b'password', default=None, | |
1230 | ) |
|
1230 | ) | |
1231 | coreconfigitem( |
|
1231 | coreconfigitem( | |
1232 | b'smtp', b'port', default=dynamicdefault, |
|
1232 | b'smtp', b'port', default=dynamicdefault, | |
1233 | ) |
|
1233 | ) | |
1234 | coreconfigitem( |
|
1234 | coreconfigitem( | |
1235 | b'smtp', b'tls', default=b'none', |
|
1235 | b'smtp', b'tls', default=b'none', | |
1236 | ) |
|
1236 | ) | |
1237 | coreconfigitem( |
|
1237 | coreconfigitem( | |
1238 | b'smtp', b'username', default=None, |
|
1238 | b'smtp', b'username', default=None, | |
1239 | ) |
|
1239 | ) | |
1240 | coreconfigitem( |
|
1240 | coreconfigitem( | |
1241 | b'sparse', b'missingwarning', default=True, experimental=True, |
|
1241 | b'sparse', b'missingwarning', default=True, experimental=True, | |
1242 | ) |
|
1242 | ) | |
1243 | coreconfigitem( |
|
1243 | coreconfigitem( | |
1244 | b'subrepos', |
|
1244 | b'subrepos', | |
1245 | b'allowed', |
|
1245 | b'allowed', | |
1246 | default=dynamicdefault, # to make backporting simpler |
|
1246 | default=dynamicdefault, # to make backporting simpler | |
1247 | ) |
|
1247 | ) | |
1248 | coreconfigitem( |
|
1248 | coreconfigitem( | |
1249 | b'subrepos', b'hg:allowed', default=dynamicdefault, |
|
1249 | b'subrepos', b'hg:allowed', default=dynamicdefault, | |
1250 | ) |
|
1250 | ) | |
1251 | coreconfigitem( |
|
1251 | coreconfigitem( | |
1252 | b'subrepos', b'git:allowed', default=dynamicdefault, |
|
1252 | b'subrepos', b'git:allowed', default=dynamicdefault, | |
1253 | ) |
|
1253 | ) | |
1254 | coreconfigitem( |
|
1254 | coreconfigitem( | |
1255 | b'subrepos', b'svn:allowed', default=dynamicdefault, |
|
1255 | b'subrepos', b'svn:allowed', default=dynamicdefault, | |
1256 | ) |
|
1256 | ) | |
1257 | coreconfigitem( |
|
1257 | coreconfigitem( | |
1258 | b'templates', b'.*', default=None, generic=True, |
|
1258 | b'templates', b'.*', default=None, generic=True, | |
1259 | ) |
|
1259 | ) | |
1260 | coreconfigitem( |
|
1260 | coreconfigitem( | |
1261 | b'templateconfig', b'.*', default=dynamicdefault, generic=True, |
|
1261 | b'templateconfig', b'.*', default=dynamicdefault, generic=True, | |
1262 | ) |
|
1262 | ) | |
1263 | coreconfigitem( |
|
1263 | coreconfigitem( | |
1264 | b'trusted', b'groups', default=list, |
|
1264 | b'trusted', b'groups', default=list, | |
1265 | ) |
|
1265 | ) | |
1266 | coreconfigitem( |
|
1266 | coreconfigitem( | |
1267 | b'trusted', b'users', default=list, |
|
1267 | b'trusted', b'users', default=list, | |
1268 | ) |
|
1268 | ) | |
1269 | coreconfigitem( |
|
1269 | coreconfigitem( | |
1270 | b'ui', b'_usedassubrepo', default=False, |
|
1270 | b'ui', b'_usedassubrepo', default=False, | |
1271 | ) |
|
1271 | ) | |
1272 | coreconfigitem( |
|
1272 | coreconfigitem( | |
1273 | b'ui', b'allowemptycommit', default=False, |
|
1273 | b'ui', b'allowemptycommit', default=False, | |
1274 | ) |
|
1274 | ) | |
1275 | coreconfigitem( |
|
1275 | coreconfigitem( | |
1276 | b'ui', b'archivemeta', default=True, |
|
1276 | b'ui', b'archivemeta', default=True, | |
1277 | ) |
|
1277 | ) | |
1278 | coreconfigitem( |
|
1278 | coreconfigitem( | |
1279 | b'ui', b'askusername', default=False, |
|
1279 | b'ui', b'askusername', default=False, | |
1280 | ) |
|
1280 | ) | |
1281 | coreconfigitem( |
|
1281 | coreconfigitem( | |
1282 | b'ui', b'available-memory', default=None, |
|
1282 | b'ui', b'available-memory', default=None, | |
1283 | ) |
|
1283 | ) | |
1284 |
|
1284 | |||
1285 | coreconfigitem( |
|
1285 | coreconfigitem( | |
1286 | b'ui', b'clonebundlefallback', default=False, |
|
1286 | b'ui', b'clonebundlefallback', default=False, | |
1287 | ) |
|
1287 | ) | |
1288 | coreconfigitem( |
|
1288 | coreconfigitem( | |
1289 | b'ui', b'clonebundleprefers', default=list, |
|
1289 | b'ui', b'clonebundleprefers', default=list, | |
1290 | ) |
|
1290 | ) | |
1291 | coreconfigitem( |
|
1291 | coreconfigitem( | |
1292 | b'ui', b'clonebundles', default=True, |
|
1292 | b'ui', b'clonebundles', default=True, | |
1293 | ) |
|
1293 | ) | |
1294 | coreconfigitem( |
|
1294 | coreconfigitem( | |
1295 | b'ui', b'color', default=b'auto', |
|
1295 | b'ui', b'color', default=b'auto', | |
1296 | ) |
|
1296 | ) | |
1297 | coreconfigitem( |
|
1297 | coreconfigitem( | |
1298 | b'ui', b'commitsubrepos', default=False, |
|
1298 | b'ui', b'commitsubrepos', default=False, | |
1299 | ) |
|
1299 | ) | |
1300 | coreconfigitem( |
|
1300 | coreconfigitem( | |
1301 | b'ui', b'debug', default=False, |
|
1301 | b'ui', b'debug', default=False, | |
1302 | ) |
|
1302 | ) | |
1303 | coreconfigitem( |
|
1303 | coreconfigitem( | |
1304 | b'ui', b'debugger', default=None, |
|
1304 | b'ui', b'debugger', default=None, | |
1305 | ) |
|
1305 | ) | |
1306 | coreconfigitem( |
|
1306 | coreconfigitem( | |
1307 | b'ui', b'editor', default=dynamicdefault, |
|
1307 | b'ui', b'editor', default=dynamicdefault, | |
1308 | ) |
|
1308 | ) | |
1309 | coreconfigitem( |
|
1309 | coreconfigitem( | |
|
1310 | b'ui', b'detailed-exit-code', default=False, experimental=True, | |||
|
1311 | ) | |||
|
1312 | coreconfigitem( | |||
1310 | b'ui', b'fallbackencoding', default=None, |
|
1313 | b'ui', b'fallbackencoding', default=None, | |
1311 | ) |
|
1314 | ) | |
1312 | coreconfigitem( |
|
1315 | coreconfigitem( | |
1313 | b'ui', b'forcecwd', default=None, |
|
1316 | b'ui', b'forcecwd', default=None, | |
1314 | ) |
|
1317 | ) | |
1315 | coreconfigitem( |
|
1318 | coreconfigitem( | |
1316 | b'ui', b'forcemerge', default=None, |
|
1319 | b'ui', b'forcemerge', default=None, | |
1317 | ) |
|
1320 | ) | |
1318 | coreconfigitem( |
|
1321 | coreconfigitem( | |
1319 | b'ui', b'formatdebug', default=False, |
|
1322 | b'ui', b'formatdebug', default=False, | |
1320 | ) |
|
1323 | ) | |
1321 | coreconfigitem( |
|
1324 | coreconfigitem( | |
1322 | b'ui', b'formatjson', default=False, |
|
1325 | b'ui', b'formatjson', default=False, | |
1323 | ) |
|
1326 | ) | |
1324 | coreconfigitem( |
|
1327 | coreconfigitem( | |
1325 | b'ui', b'formatted', default=None, |
|
1328 | b'ui', b'formatted', default=None, | |
1326 | ) |
|
1329 | ) | |
1327 | coreconfigitem( |
|
1330 | coreconfigitem( | |
1328 | b'ui', b'interactive', default=None, |
|
1331 | b'ui', b'interactive', default=None, | |
1329 | ) |
|
1332 | ) | |
1330 | coreconfigitem( |
|
1333 | coreconfigitem( | |
1331 | b'ui', b'interface', default=None, |
|
1334 | b'ui', b'interface', default=None, | |
1332 | ) |
|
1335 | ) | |
1333 | coreconfigitem( |
|
1336 | coreconfigitem( | |
1334 | b'ui', b'interface.chunkselector', default=None, |
|
1337 | b'ui', b'interface.chunkselector', default=None, | |
1335 | ) |
|
1338 | ) | |
1336 | coreconfigitem( |
|
1339 | coreconfigitem( | |
1337 | b'ui', b'large-file-limit', default=10000000, |
|
1340 | b'ui', b'large-file-limit', default=10000000, | |
1338 | ) |
|
1341 | ) | |
1339 | coreconfigitem( |
|
1342 | coreconfigitem( | |
1340 | b'ui', b'logblockedtimes', default=False, |
|
1343 | b'ui', b'logblockedtimes', default=False, | |
1341 | ) |
|
1344 | ) | |
1342 | coreconfigitem( |
|
1345 | coreconfigitem( | |
1343 | b'ui', b'merge', default=None, |
|
1346 | b'ui', b'merge', default=None, | |
1344 | ) |
|
1347 | ) | |
1345 | coreconfigitem( |
|
1348 | coreconfigitem( | |
1346 | b'ui', b'mergemarkers', default=b'basic', |
|
1349 | b'ui', b'mergemarkers', default=b'basic', | |
1347 | ) |
|
1350 | ) | |
1348 | coreconfigitem( |
|
1351 | coreconfigitem( | |
1349 | b'ui', b'message-output', default=b'stdio', |
|
1352 | b'ui', b'message-output', default=b'stdio', | |
1350 | ) |
|
1353 | ) | |
1351 | coreconfigitem( |
|
1354 | coreconfigitem( | |
1352 | b'ui', b'nontty', default=False, |
|
1355 | b'ui', b'nontty', default=False, | |
1353 | ) |
|
1356 | ) | |
1354 | coreconfigitem( |
|
1357 | coreconfigitem( | |
1355 | b'ui', b'origbackuppath', default=None, |
|
1358 | b'ui', b'origbackuppath', default=None, | |
1356 | ) |
|
1359 | ) | |
1357 | coreconfigitem( |
|
1360 | coreconfigitem( | |
1358 | b'ui', b'paginate', default=True, |
|
1361 | b'ui', b'paginate', default=True, | |
1359 | ) |
|
1362 | ) | |
1360 | coreconfigitem( |
|
1363 | coreconfigitem( | |
1361 | b'ui', b'patch', default=None, |
|
1364 | b'ui', b'patch', default=None, | |
1362 | ) |
|
1365 | ) | |
1363 | coreconfigitem( |
|
1366 | coreconfigitem( | |
1364 | b'ui', b'portablefilenames', default=b'warn', |
|
1367 | b'ui', b'portablefilenames', default=b'warn', | |
1365 | ) |
|
1368 | ) | |
1366 | coreconfigitem( |
|
1369 | coreconfigitem( | |
1367 | b'ui', b'promptecho', default=False, |
|
1370 | b'ui', b'promptecho', default=False, | |
1368 | ) |
|
1371 | ) | |
1369 | coreconfigitem( |
|
1372 | coreconfigitem( | |
1370 | b'ui', b'quiet', default=False, |
|
1373 | b'ui', b'quiet', default=False, | |
1371 | ) |
|
1374 | ) | |
1372 | coreconfigitem( |
|
1375 | coreconfigitem( | |
1373 | b'ui', b'quietbookmarkmove', default=False, |
|
1376 | b'ui', b'quietbookmarkmove', default=False, | |
1374 | ) |
|
1377 | ) | |
1375 | coreconfigitem( |
|
1378 | coreconfigitem( | |
1376 | b'ui', b'relative-paths', default=b'legacy', |
|
1379 | b'ui', b'relative-paths', default=b'legacy', | |
1377 | ) |
|
1380 | ) | |
1378 | coreconfigitem( |
|
1381 | coreconfigitem( | |
1379 | b'ui', b'remotecmd', default=b'hg', |
|
1382 | b'ui', b'remotecmd', default=b'hg', | |
1380 | ) |
|
1383 | ) | |
1381 | coreconfigitem( |
|
1384 | coreconfigitem( | |
1382 | b'ui', b'report_untrusted', default=True, |
|
1385 | b'ui', b'report_untrusted', default=True, | |
1383 | ) |
|
1386 | ) | |
1384 | coreconfigitem( |
|
1387 | coreconfigitem( | |
1385 | b'ui', b'rollback', default=True, |
|
1388 | b'ui', b'rollback', default=True, | |
1386 | ) |
|
1389 | ) | |
1387 | coreconfigitem( |
|
1390 | coreconfigitem( | |
1388 | b'ui', b'signal-safe-lock', default=True, |
|
1391 | b'ui', b'signal-safe-lock', default=True, | |
1389 | ) |
|
1392 | ) | |
1390 | coreconfigitem( |
|
1393 | coreconfigitem( | |
1391 | b'ui', b'slash', default=False, |
|
1394 | b'ui', b'slash', default=False, | |
1392 | ) |
|
1395 | ) | |
1393 | coreconfigitem( |
|
1396 | coreconfigitem( | |
1394 | b'ui', b'ssh', default=b'ssh', |
|
1397 | b'ui', b'ssh', default=b'ssh', | |
1395 | ) |
|
1398 | ) | |
1396 | coreconfigitem( |
|
1399 | coreconfigitem( | |
1397 | b'ui', b'ssherrorhint', default=None, |
|
1400 | b'ui', b'ssherrorhint', default=None, | |
1398 | ) |
|
1401 | ) | |
1399 | coreconfigitem( |
|
1402 | coreconfigitem( | |
1400 | b'ui', b'statuscopies', default=False, |
|
1403 | b'ui', b'statuscopies', default=False, | |
1401 | ) |
|
1404 | ) | |
1402 | coreconfigitem( |
|
1405 | coreconfigitem( | |
1403 | b'ui', b'strict', default=False, |
|
1406 | b'ui', b'strict', default=False, | |
1404 | ) |
|
1407 | ) | |
1405 | coreconfigitem( |
|
1408 | coreconfigitem( | |
1406 | b'ui', b'style', default=b'', |
|
1409 | b'ui', b'style', default=b'', | |
1407 | ) |
|
1410 | ) | |
1408 | coreconfigitem( |
|
1411 | coreconfigitem( | |
1409 | b'ui', b'supportcontact', default=None, |
|
1412 | b'ui', b'supportcontact', default=None, | |
1410 | ) |
|
1413 | ) | |
1411 | coreconfigitem( |
|
1414 | coreconfigitem( | |
1412 | b'ui', b'textwidth', default=78, |
|
1415 | b'ui', b'textwidth', default=78, | |
1413 | ) |
|
1416 | ) | |
1414 | coreconfigitem( |
|
1417 | coreconfigitem( | |
1415 | b'ui', b'timeout', default=b'600', |
|
1418 | b'ui', b'timeout', default=b'600', | |
1416 | ) |
|
1419 | ) | |
1417 | coreconfigitem( |
|
1420 | coreconfigitem( | |
1418 | b'ui', b'timeout.warn', default=0, |
|
1421 | b'ui', b'timeout.warn', default=0, | |
1419 | ) |
|
1422 | ) | |
1420 | coreconfigitem( |
|
1423 | coreconfigitem( | |
1421 | b'ui', b'timestamp-output', default=False, |
|
1424 | b'ui', b'timestamp-output', default=False, | |
1422 | ) |
|
1425 | ) | |
1423 | coreconfigitem( |
|
1426 | coreconfigitem( | |
1424 | b'ui', b'traceback', default=False, |
|
1427 | b'ui', b'traceback', default=False, | |
1425 | ) |
|
1428 | ) | |
1426 | coreconfigitem( |
|
1429 | coreconfigitem( | |
1427 | b'ui', b'tweakdefaults', default=False, |
|
1430 | b'ui', b'tweakdefaults', default=False, | |
1428 | ) |
|
1431 | ) | |
1429 | coreconfigitem(b'ui', b'username', alias=[(b'ui', b'user')]) |
|
1432 | coreconfigitem(b'ui', b'username', alias=[(b'ui', b'user')]) | |
1430 | coreconfigitem( |
|
1433 | coreconfigitem( | |
1431 | b'ui', b'verbose', default=False, |
|
1434 | b'ui', b'verbose', default=False, | |
1432 | ) |
|
1435 | ) | |
1433 | coreconfigitem( |
|
1436 | coreconfigitem( | |
1434 | b'verify', b'skipflags', default=None, |
|
1437 | b'verify', b'skipflags', default=None, | |
1435 | ) |
|
1438 | ) | |
1436 | coreconfigitem( |
|
1439 | coreconfigitem( | |
1437 | b'web', b'allowbz2', default=False, |
|
1440 | b'web', b'allowbz2', default=False, | |
1438 | ) |
|
1441 | ) | |
1439 | coreconfigitem( |
|
1442 | coreconfigitem( | |
1440 | b'web', b'allowgz', default=False, |
|
1443 | b'web', b'allowgz', default=False, | |
1441 | ) |
|
1444 | ) | |
1442 | coreconfigitem( |
|
1445 | coreconfigitem( | |
1443 | b'web', b'allow-pull', alias=[(b'web', b'allowpull')], default=True, |
|
1446 | b'web', b'allow-pull', alias=[(b'web', b'allowpull')], default=True, | |
1444 | ) |
|
1447 | ) | |
1445 | coreconfigitem( |
|
1448 | coreconfigitem( | |
1446 | b'web', b'allow-push', alias=[(b'web', b'allow_push')], default=list, |
|
1449 | b'web', b'allow-push', alias=[(b'web', b'allow_push')], default=list, | |
1447 | ) |
|
1450 | ) | |
1448 | coreconfigitem( |
|
1451 | coreconfigitem( | |
1449 | b'web', b'allowzip', default=False, |
|
1452 | b'web', b'allowzip', default=False, | |
1450 | ) |
|
1453 | ) | |
1451 | coreconfigitem( |
|
1454 | coreconfigitem( | |
1452 | b'web', b'archivesubrepos', default=False, |
|
1455 | b'web', b'archivesubrepos', default=False, | |
1453 | ) |
|
1456 | ) | |
1454 | coreconfigitem( |
|
1457 | coreconfigitem( | |
1455 | b'web', b'cache', default=True, |
|
1458 | b'web', b'cache', default=True, | |
1456 | ) |
|
1459 | ) | |
1457 | coreconfigitem( |
|
1460 | coreconfigitem( | |
1458 | b'web', b'comparisoncontext', default=5, |
|
1461 | b'web', b'comparisoncontext', default=5, | |
1459 | ) |
|
1462 | ) | |
1460 | coreconfigitem( |
|
1463 | coreconfigitem( | |
1461 | b'web', b'contact', default=None, |
|
1464 | b'web', b'contact', default=None, | |
1462 | ) |
|
1465 | ) | |
1463 | coreconfigitem( |
|
1466 | coreconfigitem( | |
1464 | b'web', b'deny_push', default=list, |
|
1467 | b'web', b'deny_push', default=list, | |
1465 | ) |
|
1468 | ) | |
1466 | coreconfigitem( |
|
1469 | coreconfigitem( | |
1467 | b'web', b'guessmime', default=False, |
|
1470 | b'web', b'guessmime', default=False, | |
1468 | ) |
|
1471 | ) | |
1469 | coreconfigitem( |
|
1472 | coreconfigitem( | |
1470 | b'web', b'hidden', default=False, |
|
1473 | b'web', b'hidden', default=False, | |
1471 | ) |
|
1474 | ) | |
1472 | coreconfigitem( |
|
1475 | coreconfigitem( | |
1473 | b'web', b'labels', default=list, |
|
1476 | b'web', b'labels', default=list, | |
1474 | ) |
|
1477 | ) | |
1475 | coreconfigitem( |
|
1478 | coreconfigitem( | |
1476 | b'web', b'logoimg', default=b'hglogo.png', |
|
1479 | b'web', b'logoimg', default=b'hglogo.png', | |
1477 | ) |
|
1480 | ) | |
1478 | coreconfigitem( |
|
1481 | coreconfigitem( | |
1479 | b'web', b'logourl', default=b'https://mercurial-scm.org/', |
|
1482 | b'web', b'logourl', default=b'https://mercurial-scm.org/', | |
1480 | ) |
|
1483 | ) | |
1481 | coreconfigitem( |
|
1484 | coreconfigitem( | |
1482 | b'web', b'accesslog', default=b'-', |
|
1485 | b'web', b'accesslog', default=b'-', | |
1483 | ) |
|
1486 | ) | |
1484 | coreconfigitem( |
|
1487 | coreconfigitem( | |
1485 | b'web', b'address', default=b'', |
|
1488 | b'web', b'address', default=b'', | |
1486 | ) |
|
1489 | ) | |
1487 | coreconfigitem( |
|
1490 | coreconfigitem( | |
1488 | b'web', b'allow-archive', alias=[(b'web', b'allow_archive')], default=list, |
|
1491 | b'web', b'allow-archive', alias=[(b'web', b'allow_archive')], default=list, | |
1489 | ) |
|
1492 | ) | |
1490 | coreconfigitem( |
|
1493 | coreconfigitem( | |
1491 | b'web', b'allow_read', default=list, |
|
1494 | b'web', b'allow_read', default=list, | |
1492 | ) |
|
1495 | ) | |
1493 | coreconfigitem( |
|
1496 | coreconfigitem( | |
1494 | b'web', b'baseurl', default=None, |
|
1497 | b'web', b'baseurl', default=None, | |
1495 | ) |
|
1498 | ) | |
1496 | coreconfigitem( |
|
1499 | coreconfigitem( | |
1497 | b'web', b'cacerts', default=None, |
|
1500 | b'web', b'cacerts', default=None, | |
1498 | ) |
|
1501 | ) | |
1499 | coreconfigitem( |
|
1502 | coreconfigitem( | |
1500 | b'web', b'certificate', default=None, |
|
1503 | b'web', b'certificate', default=None, | |
1501 | ) |
|
1504 | ) | |
1502 | coreconfigitem( |
|
1505 | coreconfigitem( | |
1503 | b'web', b'collapse', default=False, |
|
1506 | b'web', b'collapse', default=False, | |
1504 | ) |
|
1507 | ) | |
1505 | coreconfigitem( |
|
1508 | coreconfigitem( | |
1506 | b'web', b'csp', default=None, |
|
1509 | b'web', b'csp', default=None, | |
1507 | ) |
|
1510 | ) | |
1508 | coreconfigitem( |
|
1511 | coreconfigitem( | |
1509 | b'web', b'deny_read', default=list, |
|
1512 | b'web', b'deny_read', default=list, | |
1510 | ) |
|
1513 | ) | |
1511 | coreconfigitem( |
|
1514 | coreconfigitem( | |
1512 | b'web', b'descend', default=True, |
|
1515 | b'web', b'descend', default=True, | |
1513 | ) |
|
1516 | ) | |
1514 | coreconfigitem( |
|
1517 | coreconfigitem( | |
1515 | b'web', b'description', default=b"", |
|
1518 | b'web', b'description', default=b"", | |
1516 | ) |
|
1519 | ) | |
1517 | coreconfigitem( |
|
1520 | coreconfigitem( | |
1518 | b'web', b'encoding', default=lambda: encoding.encoding, |
|
1521 | b'web', b'encoding', default=lambda: encoding.encoding, | |
1519 | ) |
|
1522 | ) | |
1520 | coreconfigitem( |
|
1523 | coreconfigitem( | |
1521 | b'web', b'errorlog', default=b'-', |
|
1524 | b'web', b'errorlog', default=b'-', | |
1522 | ) |
|
1525 | ) | |
1523 | coreconfigitem( |
|
1526 | coreconfigitem( | |
1524 | b'web', b'ipv6', default=False, |
|
1527 | b'web', b'ipv6', default=False, | |
1525 | ) |
|
1528 | ) | |
1526 | coreconfigitem( |
|
1529 | coreconfigitem( | |
1527 | b'web', b'maxchanges', default=10, |
|
1530 | b'web', b'maxchanges', default=10, | |
1528 | ) |
|
1531 | ) | |
1529 | coreconfigitem( |
|
1532 | coreconfigitem( | |
1530 | b'web', b'maxfiles', default=10, |
|
1533 | b'web', b'maxfiles', default=10, | |
1531 | ) |
|
1534 | ) | |
1532 | coreconfigitem( |
|
1535 | coreconfigitem( | |
1533 | b'web', b'maxshortchanges', default=60, |
|
1536 | b'web', b'maxshortchanges', default=60, | |
1534 | ) |
|
1537 | ) | |
1535 | coreconfigitem( |
|
1538 | coreconfigitem( | |
1536 | b'web', b'motd', default=b'', |
|
1539 | b'web', b'motd', default=b'', | |
1537 | ) |
|
1540 | ) | |
1538 | coreconfigitem( |
|
1541 | coreconfigitem( | |
1539 | b'web', b'name', default=dynamicdefault, |
|
1542 | b'web', b'name', default=dynamicdefault, | |
1540 | ) |
|
1543 | ) | |
1541 | coreconfigitem( |
|
1544 | coreconfigitem( | |
1542 | b'web', b'port', default=8000, |
|
1545 | b'web', b'port', default=8000, | |
1543 | ) |
|
1546 | ) | |
1544 | coreconfigitem( |
|
1547 | coreconfigitem( | |
1545 | b'web', b'prefix', default=b'', |
|
1548 | b'web', b'prefix', default=b'', | |
1546 | ) |
|
1549 | ) | |
1547 | coreconfigitem( |
|
1550 | coreconfigitem( | |
1548 | b'web', b'push_ssl', default=True, |
|
1551 | b'web', b'push_ssl', default=True, | |
1549 | ) |
|
1552 | ) | |
1550 | coreconfigitem( |
|
1553 | coreconfigitem( | |
1551 | b'web', b'refreshinterval', default=20, |
|
1554 | b'web', b'refreshinterval', default=20, | |
1552 | ) |
|
1555 | ) | |
1553 | coreconfigitem( |
|
1556 | coreconfigitem( | |
1554 | b'web', b'server-header', default=None, |
|
1557 | b'web', b'server-header', default=None, | |
1555 | ) |
|
1558 | ) | |
1556 | coreconfigitem( |
|
1559 | coreconfigitem( | |
1557 | b'web', b'static', default=None, |
|
1560 | b'web', b'static', default=None, | |
1558 | ) |
|
1561 | ) | |
1559 | coreconfigitem( |
|
1562 | coreconfigitem( | |
1560 | b'web', b'staticurl', default=None, |
|
1563 | b'web', b'staticurl', default=None, | |
1561 | ) |
|
1564 | ) | |
1562 | coreconfigitem( |
|
1565 | coreconfigitem( | |
1563 | b'web', b'stripes', default=1, |
|
1566 | b'web', b'stripes', default=1, | |
1564 | ) |
|
1567 | ) | |
1565 | coreconfigitem( |
|
1568 | coreconfigitem( | |
1566 | b'web', b'style', default=b'paper', |
|
1569 | b'web', b'style', default=b'paper', | |
1567 | ) |
|
1570 | ) | |
1568 | coreconfigitem( |
|
1571 | coreconfigitem( | |
1569 | b'web', b'templates', default=None, |
|
1572 | b'web', b'templates', default=None, | |
1570 | ) |
|
1573 | ) | |
1571 | coreconfigitem( |
|
1574 | coreconfigitem( | |
1572 | b'web', b'view', default=b'served', experimental=True, |
|
1575 | b'web', b'view', default=b'served', experimental=True, | |
1573 | ) |
|
1576 | ) | |
1574 | coreconfigitem( |
|
1577 | coreconfigitem( | |
1575 | b'worker', b'backgroundclose', default=dynamicdefault, |
|
1578 | b'worker', b'backgroundclose', default=dynamicdefault, | |
1576 | ) |
|
1579 | ) | |
1577 | # Windows defaults to a limit of 512 open files. A buffer of 128 |
|
1580 | # Windows defaults to a limit of 512 open files. A buffer of 128 | |
1578 | # should give us enough headway. |
|
1581 | # should give us enough headway. | |
1579 | coreconfigitem( |
|
1582 | coreconfigitem( | |
1580 | b'worker', b'backgroundclosemaxqueue', default=384, |
|
1583 | b'worker', b'backgroundclosemaxqueue', default=384, | |
1581 | ) |
|
1584 | ) | |
1582 | coreconfigitem( |
|
1585 | coreconfigitem( | |
1583 | b'worker', b'backgroundcloseminfilecount', default=2048, |
|
1586 | b'worker', b'backgroundcloseminfilecount', default=2048, | |
1584 | ) |
|
1587 | ) | |
1585 | coreconfigitem( |
|
1588 | coreconfigitem( | |
1586 | b'worker', b'backgroundclosethreadcount', default=4, |
|
1589 | b'worker', b'backgroundclosethreadcount', default=4, | |
1587 | ) |
|
1590 | ) | |
1588 | coreconfigitem( |
|
1591 | coreconfigitem( | |
1589 | b'worker', b'enabled', default=True, |
|
1592 | b'worker', b'enabled', default=True, | |
1590 | ) |
|
1593 | ) | |
1591 | coreconfigitem( |
|
1594 | coreconfigitem( | |
1592 | b'worker', b'numcpus', default=None, |
|
1595 | b'worker', b'numcpus', default=None, | |
1593 | ) |
|
1596 | ) | |
1594 |
|
1597 | |||
1595 | # Rebase related configuration moved to core because other extension are doing |
|
1598 | # Rebase related configuration moved to core because other extension are doing | |
1596 | # strange things. For example, shelve import the extensions to reuse some bit |
|
1599 | # strange things. For example, shelve import the extensions to reuse some bit | |
1597 | # without formally loading it. |
|
1600 | # without formally loading it. | |
1598 | coreconfigitem( |
|
1601 | coreconfigitem( | |
1599 | b'commands', b'rebase.requiredest', default=False, |
|
1602 | b'commands', b'rebase.requiredest', default=False, | |
1600 | ) |
|
1603 | ) | |
1601 | coreconfigitem( |
|
1604 | coreconfigitem( | |
1602 | b'experimental', b'rebaseskipobsolete', default=True, |
|
1605 | b'experimental', b'rebaseskipobsolete', default=True, | |
1603 | ) |
|
1606 | ) | |
1604 | coreconfigitem( |
|
1607 | coreconfigitem( | |
1605 | b'rebase', b'singletransaction', default=False, |
|
1608 | b'rebase', b'singletransaction', default=False, | |
1606 | ) |
|
1609 | ) | |
1607 | coreconfigitem( |
|
1610 | coreconfigitem( | |
1608 | b'rebase', b'experimental.inmemory', default=False, |
|
1611 | b'rebase', b'experimental.inmemory', default=False, | |
1609 | ) |
|
1612 | ) |
@@ -1,2290 +1,2297 b'' | |||||
1 | # scmutil.py - Mercurial core utility functions |
|
1 | # scmutil.py - Mercurial core utility functions | |
2 | # |
|
2 | # | |
3 | # Copyright Matt Mackall <mpm@selenic.com> |
|
3 | # Copyright Matt Mackall <mpm@selenic.com> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | from __future__ import absolute_import |
|
8 | from __future__ import absolute_import | |
9 |
|
9 | |||
10 | import errno |
|
10 | import errno | |
11 | import glob |
|
11 | import glob | |
12 | import os |
|
12 | import os | |
13 | import posixpath |
|
13 | import posixpath | |
14 | import re |
|
14 | import re | |
15 | import subprocess |
|
15 | import subprocess | |
16 | import weakref |
|
16 | import weakref | |
17 |
|
17 | |||
18 | from .i18n import _ |
|
18 | from .i18n import _ | |
19 | from .node import ( |
|
19 | from .node import ( | |
20 | bin, |
|
20 | bin, | |
21 | hex, |
|
21 | hex, | |
22 | nullid, |
|
22 | nullid, | |
23 | nullrev, |
|
23 | nullrev, | |
24 | short, |
|
24 | short, | |
25 | wdirid, |
|
25 | wdirid, | |
26 | wdirrev, |
|
26 | wdirrev, | |
27 | ) |
|
27 | ) | |
28 | from .pycompat import getattr |
|
28 | from .pycompat import getattr | |
29 | from .thirdparty import attr |
|
29 | from .thirdparty import attr | |
30 | from . import ( |
|
30 | from . import ( | |
31 | copies as copiesmod, |
|
31 | copies as copiesmod, | |
32 | encoding, |
|
32 | encoding, | |
33 | error, |
|
33 | error, | |
34 | match as matchmod, |
|
34 | match as matchmod, | |
35 | obsolete, |
|
35 | obsolete, | |
36 | obsutil, |
|
36 | obsutil, | |
37 | pathutil, |
|
37 | pathutil, | |
38 | phases, |
|
38 | phases, | |
39 | policy, |
|
39 | policy, | |
40 | pycompat, |
|
40 | pycompat, | |
41 | requirements as requirementsmod, |
|
41 | requirements as requirementsmod, | |
42 | revsetlang, |
|
42 | revsetlang, | |
43 | similar, |
|
43 | similar, | |
44 | smartset, |
|
44 | smartset, | |
45 | url, |
|
45 | url, | |
46 | util, |
|
46 | util, | |
47 | vfs, |
|
47 | vfs, | |
48 | ) |
|
48 | ) | |
49 |
|
49 | |||
50 | from .utils import ( |
|
50 | from .utils import ( | |
51 | hashutil, |
|
51 | hashutil, | |
52 | procutil, |
|
52 | procutil, | |
53 | stringutil, |
|
53 | stringutil, | |
54 | ) |
|
54 | ) | |
55 |
|
55 | |||
56 | if pycompat.iswindows: |
|
56 | if pycompat.iswindows: | |
57 | from . import scmwindows as scmplatform |
|
57 | from . import scmwindows as scmplatform | |
58 | else: |
|
58 | else: | |
59 | from . import scmposix as scmplatform |
|
59 | from . import scmposix as scmplatform | |
60 |
|
60 | |||
61 | parsers = policy.importmod('parsers') |
|
61 | parsers = policy.importmod('parsers') | |
62 | rustrevlog = policy.importrust('revlog') |
|
62 | rustrevlog = policy.importrust('revlog') | |
63 |
|
63 | |||
64 | termsize = scmplatform.termsize |
|
64 | termsize = scmplatform.termsize | |
65 |
|
65 | |||
66 |
|
66 | |||
67 | @attr.s(slots=True, repr=False) |
|
67 | @attr.s(slots=True, repr=False) | |
68 | class status(object): |
|
68 | class status(object): | |
69 | '''Struct with a list of files per status. |
|
69 | '''Struct with a list of files per status. | |
70 |
|
70 | |||
71 | The 'deleted', 'unknown' and 'ignored' properties are only |
|
71 | The 'deleted', 'unknown' and 'ignored' properties are only | |
72 | relevant to the working copy. |
|
72 | relevant to the working copy. | |
73 | ''' |
|
73 | ''' | |
74 |
|
74 | |||
75 | modified = attr.ib(default=attr.Factory(list)) |
|
75 | modified = attr.ib(default=attr.Factory(list)) | |
76 | added = attr.ib(default=attr.Factory(list)) |
|
76 | added = attr.ib(default=attr.Factory(list)) | |
77 | removed = attr.ib(default=attr.Factory(list)) |
|
77 | removed = attr.ib(default=attr.Factory(list)) | |
78 | deleted = attr.ib(default=attr.Factory(list)) |
|
78 | deleted = attr.ib(default=attr.Factory(list)) | |
79 | unknown = attr.ib(default=attr.Factory(list)) |
|
79 | unknown = attr.ib(default=attr.Factory(list)) | |
80 | ignored = attr.ib(default=attr.Factory(list)) |
|
80 | ignored = attr.ib(default=attr.Factory(list)) | |
81 | clean = attr.ib(default=attr.Factory(list)) |
|
81 | clean = attr.ib(default=attr.Factory(list)) | |
82 |
|
82 | |||
83 | def __iter__(self): |
|
83 | def __iter__(self): | |
84 | yield self.modified |
|
84 | yield self.modified | |
85 | yield self.added |
|
85 | yield self.added | |
86 | yield self.removed |
|
86 | yield self.removed | |
87 | yield self.deleted |
|
87 | yield self.deleted | |
88 | yield self.unknown |
|
88 | yield self.unknown | |
89 | yield self.ignored |
|
89 | yield self.ignored | |
90 | yield self.clean |
|
90 | yield self.clean | |
91 |
|
91 | |||
92 | def __repr__(self): |
|
92 | def __repr__(self): | |
93 | return ( |
|
93 | return ( | |
94 | r'<status modified=%s, added=%s, removed=%s, deleted=%s, ' |
|
94 | r'<status modified=%s, added=%s, removed=%s, deleted=%s, ' | |
95 | r'unknown=%s, ignored=%s, clean=%s>' |
|
95 | r'unknown=%s, ignored=%s, clean=%s>' | |
96 | ) % tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self) |
|
96 | ) % tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self) | |
97 |
|
97 | |||
98 |
|
98 | |||
99 | def itersubrepos(ctx1, ctx2): |
|
99 | def itersubrepos(ctx1, ctx2): | |
100 | """find subrepos in ctx1 or ctx2""" |
|
100 | """find subrepos in ctx1 or ctx2""" | |
101 | # Create a (subpath, ctx) mapping where we prefer subpaths from |
|
101 | # Create a (subpath, ctx) mapping where we prefer subpaths from | |
102 | # ctx1. The subpaths from ctx2 are important when the .hgsub file |
|
102 | # ctx1. The subpaths from ctx2 are important when the .hgsub file | |
103 | # has been modified (in ctx2) but not yet committed (in ctx1). |
|
103 | # has been modified (in ctx2) but not yet committed (in ctx1). | |
104 | subpaths = dict.fromkeys(ctx2.substate, ctx2) |
|
104 | subpaths = dict.fromkeys(ctx2.substate, ctx2) | |
105 | subpaths.update(dict.fromkeys(ctx1.substate, ctx1)) |
|
105 | subpaths.update(dict.fromkeys(ctx1.substate, ctx1)) | |
106 |
|
106 | |||
107 | missing = set() |
|
107 | missing = set() | |
108 |
|
108 | |||
109 | for subpath in ctx2.substate: |
|
109 | for subpath in ctx2.substate: | |
110 | if subpath not in ctx1.substate: |
|
110 | if subpath not in ctx1.substate: | |
111 | del subpaths[subpath] |
|
111 | del subpaths[subpath] | |
112 | missing.add(subpath) |
|
112 | missing.add(subpath) | |
113 |
|
113 | |||
114 | for subpath, ctx in sorted(pycompat.iteritems(subpaths)): |
|
114 | for subpath, ctx in sorted(pycompat.iteritems(subpaths)): | |
115 | yield subpath, ctx.sub(subpath) |
|
115 | yield subpath, ctx.sub(subpath) | |
116 |
|
116 | |||
117 | # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way, |
|
117 | # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way, | |
118 | # status and diff will have an accurate result when it does |
|
118 | # status and diff will have an accurate result when it does | |
119 | # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared |
|
119 | # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared | |
120 | # against itself. |
|
120 | # against itself. | |
121 | for subpath in missing: |
|
121 | for subpath in missing: | |
122 | yield subpath, ctx2.nullsub(subpath, ctx1) |
|
122 | yield subpath, ctx2.nullsub(subpath, ctx1) | |
123 |
|
123 | |||
124 |
|
124 | |||
125 | def nochangesfound(ui, repo, excluded=None): |
|
125 | def nochangesfound(ui, repo, excluded=None): | |
126 | '''Report no changes for push/pull, excluded is None or a list of |
|
126 | '''Report no changes for push/pull, excluded is None or a list of | |
127 | nodes excluded from the push/pull. |
|
127 | nodes excluded from the push/pull. | |
128 | ''' |
|
128 | ''' | |
129 | secretlist = [] |
|
129 | secretlist = [] | |
130 | if excluded: |
|
130 | if excluded: | |
131 | for n in excluded: |
|
131 | for n in excluded: | |
132 | ctx = repo[n] |
|
132 | ctx = repo[n] | |
133 | if ctx.phase() >= phases.secret and not ctx.extinct(): |
|
133 | if ctx.phase() >= phases.secret and not ctx.extinct(): | |
134 | secretlist.append(n) |
|
134 | secretlist.append(n) | |
135 |
|
135 | |||
136 | if secretlist: |
|
136 | if secretlist: | |
137 | ui.status( |
|
137 | ui.status( | |
138 | _(b"no changes found (ignored %d secret changesets)\n") |
|
138 | _(b"no changes found (ignored %d secret changesets)\n") | |
139 | % len(secretlist) |
|
139 | % len(secretlist) | |
140 | ) |
|
140 | ) | |
141 | else: |
|
141 | else: | |
142 | ui.status(_(b"no changes found\n")) |
|
142 | ui.status(_(b"no changes found\n")) | |
143 |
|
143 | |||
144 |
|
144 | |||
145 | def callcatch(ui, func): |
|
145 | def callcatch(ui, func): | |
146 | """call func() with global exception handling |
|
146 | """call func() with global exception handling | |
147 |
|
147 | |||
148 | return func() if no exception happens. otherwise do some error handling |
|
148 | return func() if no exception happens. otherwise do some error handling | |
149 | and return an exit code accordingly. does not handle all exceptions. |
|
149 | and return an exit code accordingly. does not handle all exceptions. | |
150 | """ |
|
150 | """ | |
|
151 | coarse_exit_code = -1 | |||
|
152 | detailed_exit_code = -1 | |||
151 | try: |
|
153 | try: | |
152 | try: |
|
154 | try: | |
153 | return func() |
|
155 | return func() | |
154 | except: # re-raises |
|
156 | except: # re-raises | |
155 | ui.traceback() |
|
157 | ui.traceback() | |
156 | raise |
|
158 | raise | |
157 | # Global exception handling, alphabetically |
|
159 | # Global exception handling, alphabetically | |
158 | # Mercurial-specific first, followed by built-in and library exceptions |
|
160 | # Mercurial-specific first, followed by built-in and library exceptions | |
159 | except error.LockHeld as inst: |
|
161 | except error.LockHeld as inst: | |
160 | if inst.errno == errno.ETIMEDOUT: |
|
162 | if inst.errno == errno.ETIMEDOUT: | |
161 | reason = _(b'timed out waiting for lock held by %r') % ( |
|
163 | reason = _(b'timed out waiting for lock held by %r') % ( | |
162 | pycompat.bytestr(inst.locker) |
|
164 | pycompat.bytestr(inst.locker) | |
163 | ) |
|
165 | ) | |
164 | else: |
|
166 | else: | |
165 | reason = _(b'lock held by %r') % inst.locker |
|
167 | reason = _(b'lock held by %r') % inst.locker | |
166 | ui.error( |
|
168 | ui.error( | |
167 | _(b"abort: %s: %s\n") |
|
169 | _(b"abort: %s: %s\n") | |
168 | % (inst.desc or stringutil.forcebytestr(inst.filename), reason) |
|
170 | % (inst.desc or stringutil.forcebytestr(inst.filename), reason) | |
169 | ) |
|
171 | ) | |
170 | if not inst.locker: |
|
172 | if not inst.locker: | |
171 | ui.error(_(b"(lock might be very busy)\n")) |
|
173 | ui.error(_(b"(lock might be very busy)\n")) | |
172 | except error.LockUnavailable as inst: |
|
174 | except error.LockUnavailable as inst: | |
173 | ui.error( |
|
175 | ui.error( | |
174 | _(b"abort: could not lock %s: %s\n") |
|
176 | _(b"abort: could not lock %s: %s\n") | |
175 | % ( |
|
177 | % ( | |
176 | inst.desc or stringutil.forcebytestr(inst.filename), |
|
178 | inst.desc or stringutil.forcebytestr(inst.filename), | |
177 | encoding.strtolocal(inst.strerror), |
|
179 | encoding.strtolocal(inst.strerror), | |
178 | ) |
|
180 | ) | |
179 | ) |
|
181 | ) | |
180 | except error.OutOfBandError as inst: |
|
182 | except error.OutOfBandError as inst: | |
181 | if inst.args: |
|
183 | if inst.args: | |
182 | msg = _(b"abort: remote error:\n") |
|
184 | msg = _(b"abort: remote error:\n") | |
183 | else: |
|
185 | else: | |
184 | msg = _(b"abort: remote error\n") |
|
186 | msg = _(b"abort: remote error\n") | |
185 | ui.error(msg) |
|
187 | ui.error(msg) | |
186 | if inst.args: |
|
188 | if inst.args: | |
187 | ui.error(b''.join(inst.args)) |
|
189 | ui.error(b''.join(inst.args)) | |
188 | if inst.hint: |
|
190 | if inst.hint: | |
189 | ui.error(b'(%s)\n' % inst.hint) |
|
191 | ui.error(b'(%s)\n' % inst.hint) | |
190 | except error.RepoError as inst: |
|
192 | except error.RepoError as inst: | |
191 | ui.error(_(b"abort: %s!\n") % inst) |
|
193 | ui.error(_(b"abort: %s!\n") % inst) | |
192 | if inst.hint: |
|
194 | if inst.hint: | |
193 | ui.error(_(b"(%s)\n") % inst.hint) |
|
195 | ui.error(_(b"(%s)\n") % inst.hint) | |
194 | except error.ResponseError as inst: |
|
196 | except error.ResponseError as inst: | |
195 | ui.error(_(b"abort: %s") % inst.args[0]) |
|
197 | ui.error(_(b"abort: %s") % inst.args[0]) | |
196 | msg = inst.args[1] |
|
198 | msg = inst.args[1] | |
197 | if isinstance(msg, type(u'')): |
|
199 | if isinstance(msg, type(u'')): | |
198 | msg = pycompat.sysbytes(msg) |
|
200 | msg = pycompat.sysbytes(msg) | |
199 | if not isinstance(msg, bytes): |
|
201 | if not isinstance(msg, bytes): | |
200 | ui.error(b" %r\n" % (msg,)) |
|
202 | ui.error(b" %r\n" % (msg,)) | |
201 | elif not msg: |
|
203 | elif not msg: | |
202 | ui.error(_(b" empty string\n")) |
|
204 | ui.error(_(b" empty string\n")) | |
203 | else: |
|
205 | else: | |
204 | ui.error(b"\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg))) |
|
206 | ui.error(b"\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg))) | |
205 | except error.CensoredNodeError as inst: |
|
207 | except error.CensoredNodeError as inst: | |
206 | ui.error(_(b"abort: file censored %s!\n") % inst) |
|
208 | ui.error(_(b"abort: file censored %s!\n") % inst) | |
207 | except error.StorageError as inst: |
|
209 | except error.StorageError as inst: | |
208 | ui.error(_(b"abort: %s!\n") % inst) |
|
210 | ui.error(_(b"abort: %s!\n") % inst) | |
209 | if inst.hint: |
|
211 | if inst.hint: | |
210 | ui.error(_(b"(%s)\n") % inst.hint) |
|
212 | ui.error(_(b"(%s)\n") % inst.hint) | |
211 | except error.InterventionRequired as inst: |
|
213 | except error.InterventionRequired as inst: | |
212 | ui.error(b"%s\n" % inst) |
|
214 | ui.error(b"%s\n" % inst) | |
213 | if inst.hint: |
|
215 | if inst.hint: | |
214 | ui.error(_(b"(%s)\n") % inst.hint) |
|
216 | ui.error(_(b"(%s)\n") % inst.hint) | |
215 | return 1 |
|
217 | detailed_exit_code = 240 | |
|
218 | coarse_exit_code = 1 | |||
216 | except error.WdirUnsupported: |
|
219 | except error.WdirUnsupported: | |
217 | ui.error(_(b"abort: working directory revision cannot be specified\n")) |
|
220 | ui.error(_(b"abort: working directory revision cannot be specified\n")) | |
218 | except error.Abort as inst: |
|
221 | except error.Abort as inst: | |
219 | ui.error(_(b"abort: %s\n") % inst.message) |
|
222 | ui.error(_(b"abort: %s\n") % inst.message) | |
220 | if inst.hint: |
|
223 | if inst.hint: | |
221 | ui.error(_(b"(%s)\n") % inst.hint) |
|
224 | ui.error(_(b"(%s)\n") % inst.hint) | |
222 | except error.WorkerError as inst: |
|
225 | except error.WorkerError as inst: | |
223 | # Don't print a message -- the worker already should have |
|
226 | # Don't print a message -- the worker already should have | |
224 | return inst.status_code |
|
227 | return inst.status_code | |
225 | except ImportError as inst: |
|
228 | except ImportError as inst: | |
226 | ui.error(_(b"abort: %s!\n") % stringutil.forcebytestr(inst)) |
|
229 | ui.error(_(b"abort: %s!\n") % stringutil.forcebytestr(inst)) | |
227 | m = stringutil.forcebytestr(inst).split()[-1] |
|
230 | m = stringutil.forcebytestr(inst).split()[-1] | |
228 | if m in b"mpatch bdiff".split(): |
|
231 | if m in b"mpatch bdiff".split(): | |
229 | ui.error(_(b"(did you forget to compile extensions?)\n")) |
|
232 | ui.error(_(b"(did you forget to compile extensions?)\n")) | |
230 | elif m in b"zlib".split(): |
|
233 | elif m in b"zlib".split(): | |
231 | ui.error(_(b"(is your Python install correct?)\n")) |
|
234 | ui.error(_(b"(is your Python install correct?)\n")) | |
232 | except (IOError, OSError) as inst: |
|
235 | except (IOError, OSError) as inst: | |
233 | if util.safehasattr(inst, b"code"): # HTTPError |
|
236 | if util.safehasattr(inst, b"code"): # HTTPError | |
234 | ui.error(_(b"abort: %s\n") % stringutil.forcebytestr(inst)) |
|
237 | ui.error(_(b"abort: %s\n") % stringutil.forcebytestr(inst)) | |
235 | elif util.safehasattr(inst, b"reason"): # URLError or SSLError |
|
238 | elif util.safehasattr(inst, b"reason"): # URLError or SSLError | |
236 | try: # usually it is in the form (errno, strerror) |
|
239 | try: # usually it is in the form (errno, strerror) | |
237 | reason = inst.reason.args[1] |
|
240 | reason = inst.reason.args[1] | |
238 | except (AttributeError, IndexError): |
|
241 | except (AttributeError, IndexError): | |
239 | # it might be anything, for example a string |
|
242 | # it might be anything, for example a string | |
240 | reason = inst.reason |
|
243 | reason = inst.reason | |
241 | if isinstance(reason, pycompat.unicode): |
|
244 | if isinstance(reason, pycompat.unicode): | |
242 | # SSLError of Python 2.7.9 contains a unicode |
|
245 | # SSLError of Python 2.7.9 contains a unicode | |
243 | reason = encoding.unitolocal(reason) |
|
246 | reason = encoding.unitolocal(reason) | |
244 | ui.error(_(b"abort: error: %s\n") % stringutil.forcebytestr(reason)) |
|
247 | ui.error(_(b"abort: error: %s\n") % stringutil.forcebytestr(reason)) | |
245 | elif ( |
|
248 | elif ( | |
246 | util.safehasattr(inst, b"args") |
|
249 | util.safehasattr(inst, b"args") | |
247 | and inst.args |
|
250 | and inst.args | |
248 | and inst.args[0] == errno.EPIPE |
|
251 | and inst.args[0] == errno.EPIPE | |
249 | ): |
|
252 | ): | |
250 | pass |
|
253 | pass | |
251 | elif getattr(inst, "strerror", None): # common IOError or OSError |
|
254 | elif getattr(inst, "strerror", None): # common IOError or OSError | |
252 | if getattr(inst, "filename", None) is not None: |
|
255 | if getattr(inst, "filename", None) is not None: | |
253 | ui.error( |
|
256 | ui.error( | |
254 | _(b"abort: %s: '%s'\n") |
|
257 | _(b"abort: %s: '%s'\n") | |
255 | % ( |
|
258 | % ( | |
256 | encoding.strtolocal(inst.strerror), |
|
259 | encoding.strtolocal(inst.strerror), | |
257 | stringutil.forcebytestr(inst.filename), |
|
260 | stringutil.forcebytestr(inst.filename), | |
258 | ) |
|
261 | ) | |
259 | ) |
|
262 | ) | |
260 | else: |
|
263 | else: | |
261 | ui.error(_(b"abort: %s\n") % encoding.strtolocal(inst.strerror)) |
|
264 | ui.error(_(b"abort: %s\n") % encoding.strtolocal(inst.strerror)) | |
262 | else: # suspicious IOError |
|
265 | else: # suspicious IOError | |
263 | raise |
|
266 | raise | |
264 | except MemoryError: |
|
267 | except MemoryError: | |
265 | ui.error(_(b"abort: out of memory\n")) |
|
268 | ui.error(_(b"abort: out of memory\n")) | |
266 | except SystemExit as inst: |
|
269 | except SystemExit as inst: | |
267 | # Commands shouldn't sys.exit directly, but give a return code. |
|
270 | # Commands shouldn't sys.exit directly, but give a return code. | |
268 | # Just in case catch this and and pass exit code to caller. |
|
271 | # Just in case catch this and and pass exit code to caller. | |
269 | return inst.code |
|
272 | detailed_exit_code = 254 | |
|
273 | coarse_exit_code = inst.code | |||
270 |
|
274 | |||
271 | return -1 |
|
275 | if ui.configbool(b'ui', b'detailed-exit-code'): | |
|
276 | return detailed_exit_code | |||
|
277 | else: | |||
|
278 | return coarse_exit_code | |||
272 |
|
279 | |||
273 |
|
280 | |||
274 | def checknewlabel(repo, lbl, kind): |
|
281 | def checknewlabel(repo, lbl, kind): | |
275 | # Do not use the "kind" parameter in ui output. |
|
282 | # Do not use the "kind" parameter in ui output. | |
276 | # It makes strings difficult to translate. |
|
283 | # It makes strings difficult to translate. | |
277 | if lbl in [b'tip', b'.', b'null']: |
|
284 | if lbl in [b'tip', b'.', b'null']: | |
278 | raise error.Abort(_(b"the name '%s' is reserved") % lbl) |
|
285 | raise error.Abort(_(b"the name '%s' is reserved") % lbl) | |
279 | for c in (b':', b'\0', b'\n', b'\r'): |
|
286 | for c in (b':', b'\0', b'\n', b'\r'): | |
280 | if c in lbl: |
|
287 | if c in lbl: | |
281 | raise error.Abort( |
|
288 | raise error.Abort( | |
282 | _(b"%r cannot be used in a name") % pycompat.bytestr(c) |
|
289 | _(b"%r cannot be used in a name") % pycompat.bytestr(c) | |
283 | ) |
|
290 | ) | |
284 | try: |
|
291 | try: | |
285 | int(lbl) |
|
292 | int(lbl) | |
286 | raise error.Abort(_(b"cannot use an integer as a name")) |
|
293 | raise error.Abort(_(b"cannot use an integer as a name")) | |
287 | except ValueError: |
|
294 | except ValueError: | |
288 | pass |
|
295 | pass | |
289 | if lbl.strip() != lbl: |
|
296 | if lbl.strip() != lbl: | |
290 | raise error.Abort(_(b"leading or trailing whitespace in name %r") % lbl) |
|
297 | raise error.Abort(_(b"leading or trailing whitespace in name %r") % lbl) | |
291 |
|
298 | |||
292 |
|
299 | |||
293 | def checkfilename(f): |
|
300 | def checkfilename(f): | |
294 | '''Check that the filename f is an acceptable filename for a tracked file''' |
|
301 | '''Check that the filename f is an acceptable filename for a tracked file''' | |
295 | if b'\r' in f or b'\n' in f: |
|
302 | if b'\r' in f or b'\n' in f: | |
296 | raise error.Abort( |
|
303 | raise error.Abort( | |
297 | _(b"'\\n' and '\\r' disallowed in filenames: %r") |
|
304 | _(b"'\\n' and '\\r' disallowed in filenames: %r") | |
298 | % pycompat.bytestr(f) |
|
305 | % pycompat.bytestr(f) | |
299 | ) |
|
306 | ) | |
300 |
|
307 | |||
301 |
|
308 | |||
302 | def checkportable(ui, f): |
|
309 | def checkportable(ui, f): | |
303 | '''Check if filename f is portable and warn or abort depending on config''' |
|
310 | '''Check if filename f is portable and warn or abort depending on config''' | |
304 | checkfilename(f) |
|
311 | checkfilename(f) | |
305 | abort, warn = checkportabilityalert(ui) |
|
312 | abort, warn = checkportabilityalert(ui) | |
306 | if abort or warn: |
|
313 | if abort or warn: | |
307 | msg = util.checkwinfilename(f) |
|
314 | msg = util.checkwinfilename(f) | |
308 | if msg: |
|
315 | if msg: | |
309 | msg = b"%s: %s" % (msg, procutil.shellquote(f)) |
|
316 | msg = b"%s: %s" % (msg, procutil.shellquote(f)) | |
310 | if abort: |
|
317 | if abort: | |
311 | raise error.Abort(msg) |
|
318 | raise error.Abort(msg) | |
312 | ui.warn(_(b"warning: %s\n") % msg) |
|
319 | ui.warn(_(b"warning: %s\n") % msg) | |
313 |
|
320 | |||
314 |
|
321 | |||
315 | def checkportabilityalert(ui): |
|
322 | def checkportabilityalert(ui): | |
316 | '''check if the user's config requests nothing, a warning, or abort for |
|
323 | '''check if the user's config requests nothing, a warning, or abort for | |
317 | non-portable filenames''' |
|
324 | non-portable filenames''' | |
318 | val = ui.config(b'ui', b'portablefilenames') |
|
325 | val = ui.config(b'ui', b'portablefilenames') | |
319 | lval = val.lower() |
|
326 | lval = val.lower() | |
320 | bval = stringutil.parsebool(val) |
|
327 | bval = stringutil.parsebool(val) | |
321 | abort = pycompat.iswindows or lval == b'abort' |
|
328 | abort = pycompat.iswindows or lval == b'abort' | |
322 | warn = bval or lval == b'warn' |
|
329 | warn = bval or lval == b'warn' | |
323 | if bval is None and not (warn or abort or lval == b'ignore'): |
|
330 | if bval is None and not (warn or abort or lval == b'ignore'): | |
324 | raise error.ConfigError( |
|
331 | raise error.ConfigError( | |
325 | _(b"ui.portablefilenames value is invalid ('%s')") % val |
|
332 | _(b"ui.portablefilenames value is invalid ('%s')") % val | |
326 | ) |
|
333 | ) | |
327 | return abort, warn |
|
334 | return abort, warn | |
328 |
|
335 | |||
329 |
|
336 | |||
330 | class casecollisionauditor(object): |
|
337 | class casecollisionauditor(object): | |
331 | def __init__(self, ui, abort, dirstate): |
|
338 | def __init__(self, ui, abort, dirstate): | |
332 | self._ui = ui |
|
339 | self._ui = ui | |
333 | self._abort = abort |
|
340 | self._abort = abort | |
334 | allfiles = b'\0'.join(dirstate) |
|
341 | allfiles = b'\0'.join(dirstate) | |
335 | self._loweredfiles = set(encoding.lower(allfiles).split(b'\0')) |
|
342 | self._loweredfiles = set(encoding.lower(allfiles).split(b'\0')) | |
336 | self._dirstate = dirstate |
|
343 | self._dirstate = dirstate | |
337 | # The purpose of _newfiles is so that we don't complain about |
|
344 | # The purpose of _newfiles is so that we don't complain about | |
338 | # case collisions if someone were to call this object with the |
|
345 | # case collisions if someone were to call this object with the | |
339 | # same filename twice. |
|
346 | # same filename twice. | |
340 | self._newfiles = set() |
|
347 | self._newfiles = set() | |
341 |
|
348 | |||
342 | def __call__(self, f): |
|
349 | def __call__(self, f): | |
343 | if f in self._newfiles: |
|
350 | if f in self._newfiles: | |
344 | return |
|
351 | return | |
345 | fl = encoding.lower(f) |
|
352 | fl = encoding.lower(f) | |
346 | if fl in self._loweredfiles and f not in self._dirstate: |
|
353 | if fl in self._loweredfiles and f not in self._dirstate: | |
347 | msg = _(b'possible case-folding collision for %s') % f |
|
354 | msg = _(b'possible case-folding collision for %s') % f | |
348 | if self._abort: |
|
355 | if self._abort: | |
349 | raise error.Abort(msg) |
|
356 | raise error.Abort(msg) | |
350 | self._ui.warn(_(b"warning: %s\n") % msg) |
|
357 | self._ui.warn(_(b"warning: %s\n") % msg) | |
351 | self._loweredfiles.add(fl) |
|
358 | self._loweredfiles.add(fl) | |
352 | self._newfiles.add(f) |
|
359 | self._newfiles.add(f) | |
353 |
|
360 | |||
354 |
|
361 | |||
355 | def filteredhash(repo, maxrev): |
|
362 | def filteredhash(repo, maxrev): | |
356 | """build hash of filtered revisions in the current repoview. |
|
363 | """build hash of filtered revisions in the current repoview. | |
357 |
|
364 | |||
358 | Multiple caches perform up-to-date validation by checking that the |
|
365 | Multiple caches perform up-to-date validation by checking that the | |
359 | tiprev and tipnode stored in the cache file match the current repository. |
|
366 | tiprev and tipnode stored in the cache file match the current repository. | |
360 | However, this is not sufficient for validating repoviews because the set |
|
367 | However, this is not sufficient for validating repoviews because the set | |
361 | of revisions in the view may change without the repository tiprev and |
|
368 | of revisions in the view may change without the repository tiprev and | |
362 | tipnode changing. |
|
369 | tipnode changing. | |
363 |
|
370 | |||
364 | This function hashes all the revs filtered from the view and returns |
|
371 | This function hashes all the revs filtered from the view and returns | |
365 | that SHA-1 digest. |
|
372 | that SHA-1 digest. | |
366 | """ |
|
373 | """ | |
367 | cl = repo.changelog |
|
374 | cl = repo.changelog | |
368 | if not cl.filteredrevs: |
|
375 | if not cl.filteredrevs: | |
369 | return None |
|
376 | return None | |
370 | key = cl._filteredrevs_hashcache.get(maxrev) |
|
377 | key = cl._filteredrevs_hashcache.get(maxrev) | |
371 | if not key: |
|
378 | if not key: | |
372 | revs = sorted(r for r in cl.filteredrevs if r <= maxrev) |
|
379 | revs = sorted(r for r in cl.filteredrevs if r <= maxrev) | |
373 | if revs: |
|
380 | if revs: | |
374 | s = hashutil.sha1() |
|
381 | s = hashutil.sha1() | |
375 | for rev in revs: |
|
382 | for rev in revs: | |
376 | s.update(b'%d;' % rev) |
|
383 | s.update(b'%d;' % rev) | |
377 | key = s.digest() |
|
384 | key = s.digest() | |
378 | cl._filteredrevs_hashcache[maxrev] = key |
|
385 | cl._filteredrevs_hashcache[maxrev] = key | |
379 | return key |
|
386 | return key | |
380 |
|
387 | |||
381 |
|
388 | |||
382 | def walkrepos(path, followsym=False, seen_dirs=None, recurse=False): |
|
389 | def walkrepos(path, followsym=False, seen_dirs=None, recurse=False): | |
383 | '''yield every hg repository under path, always recursively. |
|
390 | '''yield every hg repository under path, always recursively. | |
384 | The recurse flag will only control recursion into repo working dirs''' |
|
391 | The recurse flag will only control recursion into repo working dirs''' | |
385 |
|
392 | |||
386 | def errhandler(err): |
|
393 | def errhandler(err): | |
387 | if err.filename == path: |
|
394 | if err.filename == path: | |
388 | raise err |
|
395 | raise err | |
389 |
|
396 | |||
390 | samestat = getattr(os.path, 'samestat', None) |
|
397 | samestat = getattr(os.path, 'samestat', None) | |
391 | if followsym and samestat is not None: |
|
398 | if followsym and samestat is not None: | |
392 |
|
399 | |||
393 | def adddir(dirlst, dirname): |
|
400 | def adddir(dirlst, dirname): | |
394 | dirstat = os.stat(dirname) |
|
401 | dirstat = os.stat(dirname) | |
395 | match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst) |
|
402 | match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst) | |
396 | if not match: |
|
403 | if not match: | |
397 | dirlst.append(dirstat) |
|
404 | dirlst.append(dirstat) | |
398 | return not match |
|
405 | return not match | |
399 |
|
406 | |||
400 | else: |
|
407 | else: | |
401 | followsym = False |
|
408 | followsym = False | |
402 |
|
409 | |||
403 | if (seen_dirs is None) and followsym: |
|
410 | if (seen_dirs is None) and followsym: | |
404 | seen_dirs = [] |
|
411 | seen_dirs = [] | |
405 | adddir(seen_dirs, path) |
|
412 | adddir(seen_dirs, path) | |
406 | for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler): |
|
413 | for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler): | |
407 | dirs.sort() |
|
414 | dirs.sort() | |
408 | if b'.hg' in dirs: |
|
415 | if b'.hg' in dirs: | |
409 | yield root # found a repository |
|
416 | yield root # found a repository | |
410 | qroot = os.path.join(root, b'.hg', b'patches') |
|
417 | qroot = os.path.join(root, b'.hg', b'patches') | |
411 | if os.path.isdir(os.path.join(qroot, b'.hg')): |
|
418 | if os.path.isdir(os.path.join(qroot, b'.hg')): | |
412 | yield qroot # we have a patch queue repo here |
|
419 | yield qroot # we have a patch queue repo here | |
413 | if recurse: |
|
420 | if recurse: | |
414 | # avoid recursing inside the .hg directory |
|
421 | # avoid recursing inside the .hg directory | |
415 | dirs.remove(b'.hg') |
|
422 | dirs.remove(b'.hg') | |
416 | else: |
|
423 | else: | |
417 | dirs[:] = [] # don't descend further |
|
424 | dirs[:] = [] # don't descend further | |
418 | elif followsym: |
|
425 | elif followsym: | |
419 | newdirs = [] |
|
426 | newdirs = [] | |
420 | for d in dirs: |
|
427 | for d in dirs: | |
421 | fname = os.path.join(root, d) |
|
428 | fname = os.path.join(root, d) | |
422 | if adddir(seen_dirs, fname): |
|
429 | if adddir(seen_dirs, fname): | |
423 | if os.path.islink(fname): |
|
430 | if os.path.islink(fname): | |
424 | for hgname in walkrepos(fname, True, seen_dirs): |
|
431 | for hgname in walkrepos(fname, True, seen_dirs): | |
425 | yield hgname |
|
432 | yield hgname | |
426 | else: |
|
433 | else: | |
427 | newdirs.append(d) |
|
434 | newdirs.append(d) | |
428 | dirs[:] = newdirs |
|
435 | dirs[:] = newdirs | |
429 |
|
436 | |||
430 |
|
437 | |||
431 | def binnode(ctx): |
|
438 | def binnode(ctx): | |
432 | """Return binary node id for a given basectx""" |
|
439 | """Return binary node id for a given basectx""" | |
433 | node = ctx.node() |
|
440 | node = ctx.node() | |
434 | if node is None: |
|
441 | if node is None: | |
435 | return wdirid |
|
442 | return wdirid | |
436 | return node |
|
443 | return node | |
437 |
|
444 | |||
438 |
|
445 | |||
439 | def intrev(ctx): |
|
446 | def intrev(ctx): | |
440 | """Return integer for a given basectx that can be used in comparison or |
|
447 | """Return integer for a given basectx that can be used in comparison or | |
441 | arithmetic operation""" |
|
448 | arithmetic operation""" | |
442 | rev = ctx.rev() |
|
449 | rev = ctx.rev() | |
443 | if rev is None: |
|
450 | if rev is None: | |
444 | return wdirrev |
|
451 | return wdirrev | |
445 | return rev |
|
452 | return rev | |
446 |
|
453 | |||
447 |
|
454 | |||
448 | def formatchangeid(ctx): |
|
455 | def formatchangeid(ctx): | |
449 | """Format changectx as '{rev}:{node|formatnode}', which is the default |
|
456 | """Format changectx as '{rev}:{node|formatnode}', which is the default | |
450 | template provided by logcmdutil.changesettemplater""" |
|
457 | template provided by logcmdutil.changesettemplater""" | |
451 | repo = ctx.repo() |
|
458 | repo = ctx.repo() | |
452 | return formatrevnode(repo.ui, intrev(ctx), binnode(ctx)) |
|
459 | return formatrevnode(repo.ui, intrev(ctx), binnode(ctx)) | |
453 |
|
460 | |||
454 |
|
461 | |||
455 | def formatrevnode(ui, rev, node): |
|
462 | def formatrevnode(ui, rev, node): | |
456 | """Format given revision and node depending on the current verbosity""" |
|
463 | """Format given revision and node depending on the current verbosity""" | |
457 | if ui.debugflag: |
|
464 | if ui.debugflag: | |
458 | hexfunc = hex |
|
465 | hexfunc = hex | |
459 | else: |
|
466 | else: | |
460 | hexfunc = short |
|
467 | hexfunc = short | |
461 | return b'%d:%s' % (rev, hexfunc(node)) |
|
468 | return b'%d:%s' % (rev, hexfunc(node)) | |
462 |
|
469 | |||
463 |
|
470 | |||
464 | def resolvehexnodeidprefix(repo, prefix): |
|
471 | def resolvehexnodeidprefix(repo, prefix): | |
465 | if prefix.startswith(b'x'): |
|
472 | if prefix.startswith(b'x'): | |
466 | prefix = prefix[1:] |
|
473 | prefix = prefix[1:] | |
467 | try: |
|
474 | try: | |
468 | # Uses unfiltered repo because it's faster when prefix is ambiguous/ |
|
475 | # Uses unfiltered repo because it's faster when prefix is ambiguous/ | |
469 | # This matches the shortesthexnodeidprefix() function below. |
|
476 | # This matches the shortesthexnodeidprefix() function below. | |
470 | node = repo.unfiltered().changelog._partialmatch(prefix) |
|
477 | node = repo.unfiltered().changelog._partialmatch(prefix) | |
471 | except error.AmbiguousPrefixLookupError: |
|
478 | except error.AmbiguousPrefixLookupError: | |
472 | revset = repo.ui.config( |
|
479 | revset = repo.ui.config( | |
473 | b'experimental', b'revisions.disambiguatewithin' |
|
480 | b'experimental', b'revisions.disambiguatewithin' | |
474 | ) |
|
481 | ) | |
475 | if revset: |
|
482 | if revset: | |
476 | # Clear config to avoid infinite recursion |
|
483 | # Clear config to avoid infinite recursion | |
477 | configoverrides = { |
|
484 | configoverrides = { | |
478 | (b'experimental', b'revisions.disambiguatewithin'): None |
|
485 | (b'experimental', b'revisions.disambiguatewithin'): None | |
479 | } |
|
486 | } | |
480 | with repo.ui.configoverride(configoverrides): |
|
487 | with repo.ui.configoverride(configoverrides): | |
481 | revs = repo.anyrevs([revset], user=True) |
|
488 | revs = repo.anyrevs([revset], user=True) | |
482 | matches = [] |
|
489 | matches = [] | |
483 | for rev in revs: |
|
490 | for rev in revs: | |
484 | node = repo.changelog.node(rev) |
|
491 | node = repo.changelog.node(rev) | |
485 | if hex(node).startswith(prefix): |
|
492 | if hex(node).startswith(prefix): | |
486 | matches.append(node) |
|
493 | matches.append(node) | |
487 | if len(matches) == 1: |
|
494 | if len(matches) == 1: | |
488 | return matches[0] |
|
495 | return matches[0] | |
489 | raise |
|
496 | raise | |
490 | if node is None: |
|
497 | if node is None: | |
491 | return |
|
498 | return | |
492 | repo.changelog.rev(node) # make sure node isn't filtered |
|
499 | repo.changelog.rev(node) # make sure node isn't filtered | |
493 | return node |
|
500 | return node | |
494 |
|
501 | |||
495 |
|
502 | |||
496 | def mayberevnum(repo, prefix): |
|
503 | def mayberevnum(repo, prefix): | |
497 | """Checks if the given prefix may be mistaken for a revision number""" |
|
504 | """Checks if the given prefix may be mistaken for a revision number""" | |
498 | try: |
|
505 | try: | |
499 | i = int(prefix) |
|
506 | i = int(prefix) | |
500 | # if we are a pure int, then starting with zero will not be |
|
507 | # if we are a pure int, then starting with zero will not be | |
501 | # confused as a rev; or, obviously, if the int is larger |
|
508 | # confused as a rev; or, obviously, if the int is larger | |
502 | # than the value of the tip rev. We still need to disambiguate if |
|
509 | # than the value of the tip rev. We still need to disambiguate if | |
503 | # prefix == '0', since that *is* a valid revnum. |
|
510 | # prefix == '0', since that *is* a valid revnum. | |
504 | if (prefix != b'0' and prefix[0:1] == b'0') or i >= len(repo): |
|
511 | if (prefix != b'0' and prefix[0:1] == b'0') or i >= len(repo): | |
505 | return False |
|
512 | return False | |
506 | return True |
|
513 | return True | |
507 | except ValueError: |
|
514 | except ValueError: | |
508 | return False |
|
515 | return False | |
509 |
|
516 | |||
510 |
|
517 | |||
511 | def shortesthexnodeidprefix(repo, node, minlength=1, cache=None): |
|
518 | def shortesthexnodeidprefix(repo, node, minlength=1, cache=None): | |
512 | """Find the shortest unambiguous prefix that matches hexnode. |
|
519 | """Find the shortest unambiguous prefix that matches hexnode. | |
513 |
|
520 | |||
514 | If "cache" is not None, it must be a dictionary that can be used for |
|
521 | If "cache" is not None, it must be a dictionary that can be used for | |
515 | caching between calls to this method. |
|
522 | caching between calls to this method. | |
516 | """ |
|
523 | """ | |
517 | # _partialmatch() of filtered changelog could take O(len(repo)) time, |
|
524 | # _partialmatch() of filtered changelog could take O(len(repo)) time, | |
518 | # which would be unacceptably slow. so we look for hash collision in |
|
525 | # which would be unacceptably slow. so we look for hash collision in | |
519 | # unfiltered space, which means some hashes may be slightly longer. |
|
526 | # unfiltered space, which means some hashes may be slightly longer. | |
520 |
|
527 | |||
521 | minlength = max(minlength, 1) |
|
528 | minlength = max(minlength, 1) | |
522 |
|
529 | |||
523 | def disambiguate(prefix): |
|
530 | def disambiguate(prefix): | |
524 | """Disambiguate against revnums.""" |
|
531 | """Disambiguate against revnums.""" | |
525 | if repo.ui.configbool(b'experimental', b'revisions.prefixhexnode'): |
|
532 | if repo.ui.configbool(b'experimental', b'revisions.prefixhexnode'): | |
526 | if mayberevnum(repo, prefix): |
|
533 | if mayberevnum(repo, prefix): | |
527 | return b'x' + prefix |
|
534 | return b'x' + prefix | |
528 | else: |
|
535 | else: | |
529 | return prefix |
|
536 | return prefix | |
530 |
|
537 | |||
531 | hexnode = hex(node) |
|
538 | hexnode = hex(node) | |
532 | for length in range(len(prefix), len(hexnode) + 1): |
|
539 | for length in range(len(prefix), len(hexnode) + 1): | |
533 | prefix = hexnode[:length] |
|
540 | prefix = hexnode[:length] | |
534 | if not mayberevnum(repo, prefix): |
|
541 | if not mayberevnum(repo, prefix): | |
535 | return prefix |
|
542 | return prefix | |
536 |
|
543 | |||
537 | cl = repo.unfiltered().changelog |
|
544 | cl = repo.unfiltered().changelog | |
538 | revset = repo.ui.config(b'experimental', b'revisions.disambiguatewithin') |
|
545 | revset = repo.ui.config(b'experimental', b'revisions.disambiguatewithin') | |
539 | if revset: |
|
546 | if revset: | |
540 | revs = None |
|
547 | revs = None | |
541 | if cache is not None: |
|
548 | if cache is not None: | |
542 | revs = cache.get(b'disambiguationrevset') |
|
549 | revs = cache.get(b'disambiguationrevset') | |
543 | if revs is None: |
|
550 | if revs is None: | |
544 | revs = repo.anyrevs([revset], user=True) |
|
551 | revs = repo.anyrevs([revset], user=True) | |
545 | if cache is not None: |
|
552 | if cache is not None: | |
546 | cache[b'disambiguationrevset'] = revs |
|
553 | cache[b'disambiguationrevset'] = revs | |
547 | if cl.rev(node) in revs: |
|
554 | if cl.rev(node) in revs: | |
548 | hexnode = hex(node) |
|
555 | hexnode = hex(node) | |
549 | nodetree = None |
|
556 | nodetree = None | |
550 | if cache is not None: |
|
557 | if cache is not None: | |
551 | nodetree = cache.get(b'disambiguationnodetree') |
|
558 | nodetree = cache.get(b'disambiguationnodetree') | |
552 | if not nodetree: |
|
559 | if not nodetree: | |
553 | if util.safehasattr(parsers, 'nodetree'): |
|
560 | if util.safehasattr(parsers, 'nodetree'): | |
554 | # The CExt is the only implementation to provide a nodetree |
|
561 | # The CExt is the only implementation to provide a nodetree | |
555 | # class so far. |
|
562 | # class so far. | |
556 | index = cl.index |
|
563 | index = cl.index | |
557 | if util.safehasattr(index, 'get_cindex'): |
|
564 | if util.safehasattr(index, 'get_cindex'): | |
558 | # the rust wrapped need to give access to its internal index |
|
565 | # the rust wrapped need to give access to its internal index | |
559 | index = index.get_cindex() |
|
566 | index = index.get_cindex() | |
560 | nodetree = parsers.nodetree(index, len(revs)) |
|
567 | nodetree = parsers.nodetree(index, len(revs)) | |
561 | for r in revs: |
|
568 | for r in revs: | |
562 | nodetree.insert(r) |
|
569 | nodetree.insert(r) | |
563 | if cache is not None: |
|
570 | if cache is not None: | |
564 | cache[b'disambiguationnodetree'] = nodetree |
|
571 | cache[b'disambiguationnodetree'] = nodetree | |
565 | if nodetree is not None: |
|
572 | if nodetree is not None: | |
566 | length = max(nodetree.shortest(node), minlength) |
|
573 | length = max(nodetree.shortest(node), minlength) | |
567 | prefix = hexnode[:length] |
|
574 | prefix = hexnode[:length] | |
568 | return disambiguate(prefix) |
|
575 | return disambiguate(prefix) | |
569 | for length in range(minlength, len(hexnode) + 1): |
|
576 | for length in range(minlength, len(hexnode) + 1): | |
570 | matches = [] |
|
577 | matches = [] | |
571 | prefix = hexnode[:length] |
|
578 | prefix = hexnode[:length] | |
572 | for rev in revs: |
|
579 | for rev in revs: | |
573 | otherhexnode = repo[rev].hex() |
|
580 | otherhexnode = repo[rev].hex() | |
574 | if prefix == otherhexnode[:length]: |
|
581 | if prefix == otherhexnode[:length]: | |
575 | matches.append(otherhexnode) |
|
582 | matches.append(otherhexnode) | |
576 | if len(matches) == 1: |
|
583 | if len(matches) == 1: | |
577 | return disambiguate(prefix) |
|
584 | return disambiguate(prefix) | |
578 |
|
585 | |||
579 | try: |
|
586 | try: | |
580 | return disambiguate(cl.shortest(node, minlength)) |
|
587 | return disambiguate(cl.shortest(node, minlength)) | |
581 | except error.LookupError: |
|
588 | except error.LookupError: | |
582 | raise error.RepoLookupError() |
|
589 | raise error.RepoLookupError() | |
583 |
|
590 | |||
584 |
|
591 | |||
585 | def isrevsymbol(repo, symbol): |
|
592 | def isrevsymbol(repo, symbol): | |
586 | """Checks if a symbol exists in the repo. |
|
593 | """Checks if a symbol exists in the repo. | |
587 |
|
594 | |||
588 | See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the |
|
595 | See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the | |
589 | symbol is an ambiguous nodeid prefix. |
|
596 | symbol is an ambiguous nodeid prefix. | |
590 | """ |
|
597 | """ | |
591 | try: |
|
598 | try: | |
592 | revsymbol(repo, symbol) |
|
599 | revsymbol(repo, symbol) | |
593 | return True |
|
600 | return True | |
594 | except error.RepoLookupError: |
|
601 | except error.RepoLookupError: | |
595 | return False |
|
602 | return False | |
596 |
|
603 | |||
597 |
|
604 | |||
598 | def revsymbol(repo, symbol): |
|
605 | def revsymbol(repo, symbol): | |
599 | """Returns a context given a single revision symbol (as string). |
|
606 | """Returns a context given a single revision symbol (as string). | |
600 |
|
607 | |||
601 | This is similar to revsingle(), but accepts only a single revision symbol, |
|
608 | This is similar to revsingle(), but accepts only a single revision symbol, | |
602 | i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but |
|
609 | i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but | |
603 | not "max(public())". |
|
610 | not "max(public())". | |
604 | """ |
|
611 | """ | |
605 | if not isinstance(symbol, bytes): |
|
612 | if not isinstance(symbol, bytes): | |
606 | msg = ( |
|
613 | msg = ( | |
607 | b"symbol (%s of type %s) was not a string, did you mean " |
|
614 | b"symbol (%s of type %s) was not a string, did you mean " | |
608 | b"repo[symbol]?" % (symbol, type(symbol)) |
|
615 | b"repo[symbol]?" % (symbol, type(symbol)) | |
609 | ) |
|
616 | ) | |
610 | raise error.ProgrammingError(msg) |
|
617 | raise error.ProgrammingError(msg) | |
611 | try: |
|
618 | try: | |
612 | if symbol in (b'.', b'tip', b'null'): |
|
619 | if symbol in (b'.', b'tip', b'null'): | |
613 | return repo[symbol] |
|
620 | return repo[symbol] | |
614 |
|
621 | |||
615 | try: |
|
622 | try: | |
616 | r = int(symbol) |
|
623 | r = int(symbol) | |
617 | if b'%d' % r != symbol: |
|
624 | if b'%d' % r != symbol: | |
618 | raise ValueError |
|
625 | raise ValueError | |
619 | l = len(repo.changelog) |
|
626 | l = len(repo.changelog) | |
620 | if r < 0: |
|
627 | if r < 0: | |
621 | r += l |
|
628 | r += l | |
622 | if r < 0 or r >= l and r != wdirrev: |
|
629 | if r < 0 or r >= l and r != wdirrev: | |
623 | raise ValueError |
|
630 | raise ValueError | |
624 | return repo[r] |
|
631 | return repo[r] | |
625 | except error.FilteredIndexError: |
|
632 | except error.FilteredIndexError: | |
626 | raise |
|
633 | raise | |
627 | except (ValueError, OverflowError, IndexError): |
|
634 | except (ValueError, OverflowError, IndexError): | |
628 | pass |
|
635 | pass | |
629 |
|
636 | |||
630 | if len(symbol) == 40: |
|
637 | if len(symbol) == 40: | |
631 | try: |
|
638 | try: | |
632 | node = bin(symbol) |
|
639 | node = bin(symbol) | |
633 | rev = repo.changelog.rev(node) |
|
640 | rev = repo.changelog.rev(node) | |
634 | return repo[rev] |
|
641 | return repo[rev] | |
635 | except error.FilteredLookupError: |
|
642 | except error.FilteredLookupError: | |
636 | raise |
|
643 | raise | |
637 | except (TypeError, LookupError): |
|
644 | except (TypeError, LookupError): | |
638 | pass |
|
645 | pass | |
639 |
|
646 | |||
640 | # look up bookmarks through the name interface |
|
647 | # look up bookmarks through the name interface | |
641 | try: |
|
648 | try: | |
642 | node = repo.names.singlenode(repo, symbol) |
|
649 | node = repo.names.singlenode(repo, symbol) | |
643 | rev = repo.changelog.rev(node) |
|
650 | rev = repo.changelog.rev(node) | |
644 | return repo[rev] |
|
651 | return repo[rev] | |
645 | except KeyError: |
|
652 | except KeyError: | |
646 | pass |
|
653 | pass | |
647 |
|
654 | |||
648 | node = resolvehexnodeidprefix(repo, symbol) |
|
655 | node = resolvehexnodeidprefix(repo, symbol) | |
649 | if node is not None: |
|
656 | if node is not None: | |
650 | rev = repo.changelog.rev(node) |
|
657 | rev = repo.changelog.rev(node) | |
651 | return repo[rev] |
|
658 | return repo[rev] | |
652 |
|
659 | |||
653 | raise error.RepoLookupError(_(b"unknown revision '%s'") % symbol) |
|
660 | raise error.RepoLookupError(_(b"unknown revision '%s'") % symbol) | |
654 |
|
661 | |||
655 | except error.WdirUnsupported: |
|
662 | except error.WdirUnsupported: | |
656 | return repo[None] |
|
663 | return repo[None] | |
657 | except ( |
|
664 | except ( | |
658 | error.FilteredIndexError, |
|
665 | error.FilteredIndexError, | |
659 | error.FilteredLookupError, |
|
666 | error.FilteredLookupError, | |
660 | error.FilteredRepoLookupError, |
|
667 | error.FilteredRepoLookupError, | |
661 | ): |
|
668 | ): | |
662 | raise _filterederror(repo, symbol) |
|
669 | raise _filterederror(repo, symbol) | |
663 |
|
670 | |||
664 |
|
671 | |||
665 | def _filterederror(repo, changeid): |
|
672 | def _filterederror(repo, changeid): | |
666 | """build an exception to be raised about a filtered changeid |
|
673 | """build an exception to be raised about a filtered changeid | |
667 |
|
674 | |||
668 | This is extracted in a function to help extensions (eg: evolve) to |
|
675 | This is extracted in a function to help extensions (eg: evolve) to | |
669 | experiment with various message variants.""" |
|
676 | experiment with various message variants.""" | |
670 | if repo.filtername.startswith(b'visible'): |
|
677 | if repo.filtername.startswith(b'visible'): | |
671 |
|
678 | |||
672 | # Check if the changeset is obsolete |
|
679 | # Check if the changeset is obsolete | |
673 | unfilteredrepo = repo.unfiltered() |
|
680 | unfilteredrepo = repo.unfiltered() | |
674 | ctx = revsymbol(unfilteredrepo, changeid) |
|
681 | ctx = revsymbol(unfilteredrepo, changeid) | |
675 |
|
682 | |||
676 | # If the changeset is obsolete, enrich the message with the reason |
|
683 | # If the changeset is obsolete, enrich the message with the reason | |
677 | # that made this changeset not visible |
|
684 | # that made this changeset not visible | |
678 | if ctx.obsolete(): |
|
685 | if ctx.obsolete(): | |
679 | msg = obsutil._getfilteredreason(repo, changeid, ctx) |
|
686 | msg = obsutil._getfilteredreason(repo, changeid, ctx) | |
680 | else: |
|
687 | else: | |
681 | msg = _(b"hidden revision '%s'") % changeid |
|
688 | msg = _(b"hidden revision '%s'") % changeid | |
682 |
|
689 | |||
683 | hint = _(b'use --hidden to access hidden revisions') |
|
690 | hint = _(b'use --hidden to access hidden revisions') | |
684 |
|
691 | |||
685 | return error.FilteredRepoLookupError(msg, hint=hint) |
|
692 | return error.FilteredRepoLookupError(msg, hint=hint) | |
686 | msg = _(b"filtered revision '%s' (not in '%s' subset)") |
|
693 | msg = _(b"filtered revision '%s' (not in '%s' subset)") | |
687 | msg %= (changeid, repo.filtername) |
|
694 | msg %= (changeid, repo.filtername) | |
688 | return error.FilteredRepoLookupError(msg) |
|
695 | return error.FilteredRepoLookupError(msg) | |
689 |
|
696 | |||
690 |
|
697 | |||
691 | def revsingle(repo, revspec, default=b'.', localalias=None): |
|
698 | def revsingle(repo, revspec, default=b'.', localalias=None): | |
692 | if not revspec and revspec != 0: |
|
699 | if not revspec and revspec != 0: | |
693 | return repo[default] |
|
700 | return repo[default] | |
694 |
|
701 | |||
695 | l = revrange(repo, [revspec], localalias=localalias) |
|
702 | l = revrange(repo, [revspec], localalias=localalias) | |
696 | if not l: |
|
703 | if not l: | |
697 | raise error.Abort(_(b'empty revision set')) |
|
704 | raise error.Abort(_(b'empty revision set')) | |
698 | return repo[l.last()] |
|
705 | return repo[l.last()] | |
699 |
|
706 | |||
700 |
|
707 | |||
701 | def _pairspec(revspec): |
|
708 | def _pairspec(revspec): | |
702 | tree = revsetlang.parse(revspec) |
|
709 | tree = revsetlang.parse(revspec) | |
703 | return tree and tree[0] in ( |
|
710 | return tree and tree[0] in ( | |
704 | b'range', |
|
711 | b'range', | |
705 | b'rangepre', |
|
712 | b'rangepre', | |
706 | b'rangepost', |
|
713 | b'rangepost', | |
707 | b'rangeall', |
|
714 | b'rangeall', | |
708 | ) |
|
715 | ) | |
709 |
|
716 | |||
710 |
|
717 | |||
711 | def revpair(repo, revs): |
|
718 | def revpair(repo, revs): | |
712 | if not revs: |
|
719 | if not revs: | |
713 | return repo[b'.'], repo[None] |
|
720 | return repo[b'.'], repo[None] | |
714 |
|
721 | |||
715 | l = revrange(repo, revs) |
|
722 | l = revrange(repo, revs) | |
716 |
|
723 | |||
717 | if not l: |
|
724 | if not l: | |
718 | raise error.Abort(_(b'empty revision range')) |
|
725 | raise error.Abort(_(b'empty revision range')) | |
719 |
|
726 | |||
720 | first = l.first() |
|
727 | first = l.first() | |
721 | second = l.last() |
|
728 | second = l.last() | |
722 |
|
729 | |||
723 | if ( |
|
730 | if ( | |
724 | first == second |
|
731 | first == second | |
725 | and len(revs) >= 2 |
|
732 | and len(revs) >= 2 | |
726 | and not all(revrange(repo, [r]) for r in revs) |
|
733 | and not all(revrange(repo, [r]) for r in revs) | |
727 | ): |
|
734 | ): | |
728 | raise error.Abort(_(b'empty revision on one side of range')) |
|
735 | raise error.Abort(_(b'empty revision on one side of range')) | |
729 |
|
736 | |||
730 | # if top-level is range expression, the result must always be a pair |
|
737 | # if top-level is range expression, the result must always be a pair | |
731 | if first == second and len(revs) == 1 and not _pairspec(revs[0]): |
|
738 | if first == second and len(revs) == 1 and not _pairspec(revs[0]): | |
732 | return repo[first], repo[None] |
|
739 | return repo[first], repo[None] | |
733 |
|
740 | |||
734 | return repo[first], repo[second] |
|
741 | return repo[first], repo[second] | |
735 |
|
742 | |||
736 |
|
743 | |||
737 | def revrange(repo, specs, localalias=None): |
|
744 | def revrange(repo, specs, localalias=None): | |
738 | """Execute 1 to many revsets and return the union. |
|
745 | """Execute 1 to many revsets and return the union. | |
739 |
|
746 | |||
740 | This is the preferred mechanism for executing revsets using user-specified |
|
747 | This is the preferred mechanism for executing revsets using user-specified | |
741 | config options, such as revset aliases. |
|
748 | config options, such as revset aliases. | |
742 |
|
749 | |||
743 | The revsets specified by ``specs`` will be executed via a chained ``OR`` |
|
750 | The revsets specified by ``specs`` will be executed via a chained ``OR`` | |
744 | expression. If ``specs`` is empty, an empty result is returned. |
|
751 | expression. If ``specs`` is empty, an empty result is returned. | |
745 |
|
752 | |||
746 | ``specs`` can contain integers, in which case they are assumed to be |
|
753 | ``specs`` can contain integers, in which case they are assumed to be | |
747 | revision numbers. |
|
754 | revision numbers. | |
748 |
|
755 | |||
749 | It is assumed the revsets are already formatted. If you have arguments |
|
756 | It is assumed the revsets are already formatted. If you have arguments | |
750 | that need to be expanded in the revset, call ``revsetlang.formatspec()`` |
|
757 | that need to be expanded in the revset, call ``revsetlang.formatspec()`` | |
751 | and pass the result as an element of ``specs``. |
|
758 | and pass the result as an element of ``specs``. | |
752 |
|
759 | |||
753 | Specifying a single revset is allowed. |
|
760 | Specifying a single revset is allowed. | |
754 |
|
761 | |||
755 | Returns a ``smartset.abstractsmartset`` which is a list-like interface over |
|
762 | Returns a ``smartset.abstractsmartset`` which is a list-like interface over | |
756 | integer revisions. |
|
763 | integer revisions. | |
757 | """ |
|
764 | """ | |
758 | allspecs = [] |
|
765 | allspecs = [] | |
759 | for spec in specs: |
|
766 | for spec in specs: | |
760 | if isinstance(spec, int): |
|
767 | if isinstance(spec, int): | |
761 | spec = revsetlang.formatspec(b'%d', spec) |
|
768 | spec = revsetlang.formatspec(b'%d', spec) | |
762 | allspecs.append(spec) |
|
769 | allspecs.append(spec) | |
763 | return repo.anyrevs(allspecs, user=True, localalias=localalias) |
|
770 | return repo.anyrevs(allspecs, user=True, localalias=localalias) | |
764 |
|
771 | |||
765 |
|
772 | |||
766 | def increasingwindows(windowsize=8, sizelimit=512): |
|
773 | def increasingwindows(windowsize=8, sizelimit=512): | |
767 | while True: |
|
774 | while True: | |
768 | yield windowsize |
|
775 | yield windowsize | |
769 | if windowsize < sizelimit: |
|
776 | if windowsize < sizelimit: | |
770 | windowsize *= 2 |
|
777 | windowsize *= 2 | |
771 |
|
778 | |||
772 |
|
779 | |||
773 | def walkchangerevs(repo, revs, makefilematcher, prepare): |
|
780 | def walkchangerevs(repo, revs, makefilematcher, prepare): | |
774 | '''Iterate over files and the revs in a "windowed" way. |
|
781 | '''Iterate over files and the revs in a "windowed" way. | |
775 |
|
782 | |||
776 | Callers most commonly need to iterate backwards over the history |
|
783 | Callers most commonly need to iterate backwards over the history | |
777 | in which they are interested. Doing so has awful (quadratic-looking) |
|
784 | in which they are interested. Doing so has awful (quadratic-looking) | |
778 | performance, so we use iterators in a "windowed" way. |
|
785 | performance, so we use iterators in a "windowed" way. | |
779 |
|
786 | |||
780 | We walk a window of revisions in the desired order. Within the |
|
787 | We walk a window of revisions in the desired order. Within the | |
781 | window, we first walk forwards to gather data, then in the desired |
|
788 | window, we first walk forwards to gather data, then in the desired | |
782 | order (usually backwards) to display it. |
|
789 | order (usually backwards) to display it. | |
783 |
|
790 | |||
784 | This function returns an iterator yielding contexts. Before |
|
791 | This function returns an iterator yielding contexts. Before | |
785 | yielding each context, the iterator will first call the prepare |
|
792 | yielding each context, the iterator will first call the prepare | |
786 | function on each context in the window in forward order.''' |
|
793 | function on each context in the window in forward order.''' | |
787 |
|
794 | |||
788 | if not revs: |
|
795 | if not revs: | |
789 | return [] |
|
796 | return [] | |
790 | change = repo.__getitem__ |
|
797 | change = repo.__getitem__ | |
791 |
|
798 | |||
792 | def iterate(): |
|
799 | def iterate(): | |
793 | it = iter(revs) |
|
800 | it = iter(revs) | |
794 | stopiteration = False |
|
801 | stopiteration = False | |
795 | for windowsize in increasingwindows(): |
|
802 | for windowsize in increasingwindows(): | |
796 | nrevs = [] |
|
803 | nrevs = [] | |
797 | for i in pycompat.xrange(windowsize): |
|
804 | for i in pycompat.xrange(windowsize): | |
798 | rev = next(it, None) |
|
805 | rev = next(it, None) | |
799 | if rev is None: |
|
806 | if rev is None: | |
800 | stopiteration = True |
|
807 | stopiteration = True | |
801 | break |
|
808 | break | |
802 | nrevs.append(rev) |
|
809 | nrevs.append(rev) | |
803 | for rev in sorted(nrevs): |
|
810 | for rev in sorted(nrevs): | |
804 | ctx = change(rev) |
|
811 | ctx = change(rev) | |
805 | prepare(ctx, makefilematcher(ctx)) |
|
812 | prepare(ctx, makefilematcher(ctx)) | |
806 | for rev in nrevs: |
|
813 | for rev in nrevs: | |
807 | yield change(rev) |
|
814 | yield change(rev) | |
808 |
|
815 | |||
809 | if stopiteration: |
|
816 | if stopiteration: | |
810 | break |
|
817 | break | |
811 |
|
818 | |||
812 | return iterate() |
|
819 | return iterate() | |
813 |
|
820 | |||
814 |
|
821 | |||
815 | def meaningfulparents(repo, ctx): |
|
822 | def meaningfulparents(repo, ctx): | |
816 | """Return list of meaningful (or all if debug) parentrevs for rev. |
|
823 | """Return list of meaningful (or all if debug) parentrevs for rev. | |
817 |
|
824 | |||
818 | For merges (two non-nullrev revisions) both parents are meaningful. |
|
825 | For merges (two non-nullrev revisions) both parents are meaningful. | |
819 | Otherwise the first parent revision is considered meaningful if it |
|
826 | Otherwise the first parent revision is considered meaningful if it | |
820 | is not the preceding revision. |
|
827 | is not the preceding revision. | |
821 | """ |
|
828 | """ | |
822 | parents = ctx.parents() |
|
829 | parents = ctx.parents() | |
823 | if len(parents) > 1: |
|
830 | if len(parents) > 1: | |
824 | return parents |
|
831 | return parents | |
825 | if repo.ui.debugflag: |
|
832 | if repo.ui.debugflag: | |
826 | return [parents[0], repo[nullrev]] |
|
833 | return [parents[0], repo[nullrev]] | |
827 | if parents[0].rev() >= intrev(ctx) - 1: |
|
834 | if parents[0].rev() >= intrev(ctx) - 1: | |
828 | return [] |
|
835 | return [] | |
829 | return parents |
|
836 | return parents | |
830 |
|
837 | |||
831 |
|
838 | |||
832 | def getuipathfn(repo, legacyrelativevalue=False, forcerelativevalue=None): |
|
839 | def getuipathfn(repo, legacyrelativevalue=False, forcerelativevalue=None): | |
833 | """Return a function that produced paths for presenting to the user. |
|
840 | """Return a function that produced paths for presenting to the user. | |
834 |
|
841 | |||
835 | The returned function takes a repo-relative path and produces a path |
|
842 | The returned function takes a repo-relative path and produces a path | |
836 | that can be presented in the UI. |
|
843 | that can be presented in the UI. | |
837 |
|
844 | |||
838 | Depending on the value of ui.relative-paths, either a repo-relative or |
|
845 | Depending on the value of ui.relative-paths, either a repo-relative or | |
839 | cwd-relative path will be produced. |
|
846 | cwd-relative path will be produced. | |
840 |
|
847 | |||
841 | legacyrelativevalue is the value to use if ui.relative-paths=legacy |
|
848 | legacyrelativevalue is the value to use if ui.relative-paths=legacy | |
842 |
|
849 | |||
843 | If forcerelativevalue is not None, then that value will be used regardless |
|
850 | If forcerelativevalue is not None, then that value will be used regardless | |
844 | of what ui.relative-paths is set to. |
|
851 | of what ui.relative-paths is set to. | |
845 | """ |
|
852 | """ | |
846 | if forcerelativevalue is not None: |
|
853 | if forcerelativevalue is not None: | |
847 | relative = forcerelativevalue |
|
854 | relative = forcerelativevalue | |
848 | else: |
|
855 | else: | |
849 | config = repo.ui.config(b'ui', b'relative-paths') |
|
856 | config = repo.ui.config(b'ui', b'relative-paths') | |
850 | if config == b'legacy': |
|
857 | if config == b'legacy': | |
851 | relative = legacyrelativevalue |
|
858 | relative = legacyrelativevalue | |
852 | else: |
|
859 | else: | |
853 | relative = stringutil.parsebool(config) |
|
860 | relative = stringutil.parsebool(config) | |
854 | if relative is None: |
|
861 | if relative is None: | |
855 | raise error.ConfigError( |
|
862 | raise error.ConfigError( | |
856 | _(b"ui.relative-paths is not a boolean ('%s')") % config |
|
863 | _(b"ui.relative-paths is not a boolean ('%s')") % config | |
857 | ) |
|
864 | ) | |
858 |
|
865 | |||
859 | if relative: |
|
866 | if relative: | |
860 | cwd = repo.getcwd() |
|
867 | cwd = repo.getcwd() | |
861 | if cwd != b'': |
|
868 | if cwd != b'': | |
862 | # this branch would work even if cwd == b'' (ie cwd = repo |
|
869 | # this branch would work even if cwd == b'' (ie cwd = repo | |
863 | # root), but its generality makes the returned function slower |
|
870 | # root), but its generality makes the returned function slower | |
864 | pathto = repo.pathto |
|
871 | pathto = repo.pathto | |
865 | return lambda f: pathto(f, cwd) |
|
872 | return lambda f: pathto(f, cwd) | |
866 | if repo.ui.configbool(b'ui', b'slash'): |
|
873 | if repo.ui.configbool(b'ui', b'slash'): | |
867 | return lambda f: f |
|
874 | return lambda f: f | |
868 | else: |
|
875 | else: | |
869 | return util.localpath |
|
876 | return util.localpath | |
870 |
|
877 | |||
871 |
|
878 | |||
872 | def subdiruipathfn(subpath, uipathfn): |
|
879 | def subdiruipathfn(subpath, uipathfn): | |
873 | '''Create a new uipathfn that treats the file as relative to subpath.''' |
|
880 | '''Create a new uipathfn that treats the file as relative to subpath.''' | |
874 | return lambda f: uipathfn(posixpath.join(subpath, f)) |
|
881 | return lambda f: uipathfn(posixpath.join(subpath, f)) | |
875 |
|
882 | |||
876 |
|
883 | |||
877 | def anypats(pats, opts): |
|
884 | def anypats(pats, opts): | |
878 | '''Checks if any patterns, including --include and --exclude were given. |
|
885 | '''Checks if any patterns, including --include and --exclude were given. | |
879 |
|
886 | |||
880 | Some commands (e.g. addremove) use this condition for deciding whether to |
|
887 | Some commands (e.g. addremove) use this condition for deciding whether to | |
881 | print absolute or relative paths. |
|
888 | print absolute or relative paths. | |
882 | ''' |
|
889 | ''' | |
883 | return bool(pats or opts.get(b'include') or opts.get(b'exclude')) |
|
890 | return bool(pats or opts.get(b'include') or opts.get(b'exclude')) | |
884 |
|
891 | |||
885 |
|
892 | |||
886 | def expandpats(pats): |
|
893 | def expandpats(pats): | |
887 | '''Expand bare globs when running on windows. |
|
894 | '''Expand bare globs when running on windows. | |
888 | On posix we assume it already has already been done by sh.''' |
|
895 | On posix we assume it already has already been done by sh.''' | |
889 | if not util.expandglobs: |
|
896 | if not util.expandglobs: | |
890 | return list(pats) |
|
897 | return list(pats) | |
891 | ret = [] |
|
898 | ret = [] | |
892 | for kindpat in pats: |
|
899 | for kindpat in pats: | |
893 | kind, pat = matchmod._patsplit(kindpat, None) |
|
900 | kind, pat = matchmod._patsplit(kindpat, None) | |
894 | if kind is None: |
|
901 | if kind is None: | |
895 | try: |
|
902 | try: | |
896 | globbed = glob.glob(pat) |
|
903 | globbed = glob.glob(pat) | |
897 | except re.error: |
|
904 | except re.error: | |
898 | globbed = [pat] |
|
905 | globbed = [pat] | |
899 | if globbed: |
|
906 | if globbed: | |
900 | ret.extend(globbed) |
|
907 | ret.extend(globbed) | |
901 | continue |
|
908 | continue | |
902 | ret.append(kindpat) |
|
909 | ret.append(kindpat) | |
903 | return ret |
|
910 | return ret | |
904 |
|
911 | |||
905 |
|
912 | |||
906 | def matchandpats( |
|
913 | def matchandpats( | |
907 | ctx, pats=(), opts=None, globbed=False, default=b'relpath', badfn=None |
|
914 | ctx, pats=(), opts=None, globbed=False, default=b'relpath', badfn=None | |
908 | ): |
|
915 | ): | |
909 | '''Return a matcher and the patterns that were used. |
|
916 | '''Return a matcher and the patterns that were used. | |
910 | The matcher will warn about bad matches, unless an alternate badfn callback |
|
917 | The matcher will warn about bad matches, unless an alternate badfn callback | |
911 | is provided.''' |
|
918 | is provided.''' | |
912 | if opts is None: |
|
919 | if opts is None: | |
913 | opts = {} |
|
920 | opts = {} | |
914 | if not globbed and default == b'relpath': |
|
921 | if not globbed and default == b'relpath': | |
915 | pats = expandpats(pats or []) |
|
922 | pats = expandpats(pats or []) | |
916 |
|
923 | |||
917 | uipathfn = getuipathfn(ctx.repo(), legacyrelativevalue=True) |
|
924 | uipathfn = getuipathfn(ctx.repo(), legacyrelativevalue=True) | |
918 |
|
925 | |||
919 | def bad(f, msg): |
|
926 | def bad(f, msg): | |
920 | ctx.repo().ui.warn(b"%s: %s\n" % (uipathfn(f), msg)) |
|
927 | ctx.repo().ui.warn(b"%s: %s\n" % (uipathfn(f), msg)) | |
921 |
|
928 | |||
922 | if badfn is None: |
|
929 | if badfn is None: | |
923 | badfn = bad |
|
930 | badfn = bad | |
924 |
|
931 | |||
925 | m = ctx.match( |
|
932 | m = ctx.match( | |
926 | pats, |
|
933 | pats, | |
927 | opts.get(b'include'), |
|
934 | opts.get(b'include'), | |
928 | opts.get(b'exclude'), |
|
935 | opts.get(b'exclude'), | |
929 | default, |
|
936 | default, | |
930 | listsubrepos=opts.get(b'subrepos'), |
|
937 | listsubrepos=opts.get(b'subrepos'), | |
931 | badfn=badfn, |
|
938 | badfn=badfn, | |
932 | ) |
|
939 | ) | |
933 |
|
940 | |||
934 | if m.always(): |
|
941 | if m.always(): | |
935 | pats = [] |
|
942 | pats = [] | |
936 | return m, pats |
|
943 | return m, pats | |
937 |
|
944 | |||
938 |
|
945 | |||
939 | def match( |
|
946 | def match( | |
940 | ctx, pats=(), opts=None, globbed=False, default=b'relpath', badfn=None |
|
947 | ctx, pats=(), opts=None, globbed=False, default=b'relpath', badfn=None | |
941 | ): |
|
948 | ): | |
942 | '''Return a matcher that will warn about bad matches.''' |
|
949 | '''Return a matcher that will warn about bad matches.''' | |
943 | return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0] |
|
950 | return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0] | |
944 |
|
951 | |||
945 |
|
952 | |||
946 | def matchall(repo): |
|
953 | def matchall(repo): | |
947 | '''Return a matcher that will efficiently match everything.''' |
|
954 | '''Return a matcher that will efficiently match everything.''' | |
948 | return matchmod.always() |
|
955 | return matchmod.always() | |
949 |
|
956 | |||
950 |
|
957 | |||
951 | def matchfiles(repo, files, badfn=None): |
|
958 | def matchfiles(repo, files, badfn=None): | |
952 | '''Return a matcher that will efficiently match exactly these files.''' |
|
959 | '''Return a matcher that will efficiently match exactly these files.''' | |
953 | return matchmod.exact(files, badfn=badfn) |
|
960 | return matchmod.exact(files, badfn=badfn) | |
954 |
|
961 | |||
955 |
|
962 | |||
956 | def parsefollowlinespattern(repo, rev, pat, msg): |
|
963 | def parsefollowlinespattern(repo, rev, pat, msg): | |
957 | """Return a file name from `pat` pattern suitable for usage in followlines |
|
964 | """Return a file name from `pat` pattern suitable for usage in followlines | |
958 | logic. |
|
965 | logic. | |
959 | """ |
|
966 | """ | |
960 | if not matchmod.patkind(pat): |
|
967 | if not matchmod.patkind(pat): | |
961 | return pathutil.canonpath(repo.root, repo.getcwd(), pat) |
|
968 | return pathutil.canonpath(repo.root, repo.getcwd(), pat) | |
962 | else: |
|
969 | else: | |
963 | ctx = repo[rev] |
|
970 | ctx = repo[rev] | |
964 | m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx) |
|
971 | m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx) | |
965 | files = [f for f in ctx if m(f)] |
|
972 | files = [f for f in ctx if m(f)] | |
966 | if len(files) != 1: |
|
973 | if len(files) != 1: | |
967 | raise error.ParseError(msg) |
|
974 | raise error.ParseError(msg) | |
968 | return files[0] |
|
975 | return files[0] | |
969 |
|
976 | |||
970 |
|
977 | |||
971 | def getorigvfs(ui, repo): |
|
978 | def getorigvfs(ui, repo): | |
972 | """return a vfs suitable to save 'orig' file |
|
979 | """return a vfs suitable to save 'orig' file | |
973 |
|
980 | |||
974 | return None if no special directory is configured""" |
|
981 | return None if no special directory is configured""" | |
975 | origbackuppath = ui.config(b'ui', b'origbackuppath') |
|
982 | origbackuppath = ui.config(b'ui', b'origbackuppath') | |
976 | if not origbackuppath: |
|
983 | if not origbackuppath: | |
977 | return None |
|
984 | return None | |
978 | return vfs.vfs(repo.wvfs.join(origbackuppath)) |
|
985 | return vfs.vfs(repo.wvfs.join(origbackuppath)) | |
979 |
|
986 | |||
980 |
|
987 | |||
981 | def backuppath(ui, repo, filepath): |
|
988 | def backuppath(ui, repo, filepath): | |
982 | '''customize where working copy backup files (.orig files) are created |
|
989 | '''customize where working copy backup files (.orig files) are created | |
983 |
|
990 | |||
984 | Fetch user defined path from config file: [ui] origbackuppath = <path> |
|
991 | Fetch user defined path from config file: [ui] origbackuppath = <path> | |
985 | Fall back to default (filepath with .orig suffix) if not specified |
|
992 | Fall back to default (filepath with .orig suffix) if not specified | |
986 |
|
993 | |||
987 | filepath is repo-relative |
|
994 | filepath is repo-relative | |
988 |
|
995 | |||
989 | Returns an absolute path |
|
996 | Returns an absolute path | |
990 | ''' |
|
997 | ''' | |
991 | origvfs = getorigvfs(ui, repo) |
|
998 | origvfs = getorigvfs(ui, repo) | |
992 | if origvfs is None: |
|
999 | if origvfs is None: | |
993 | return repo.wjoin(filepath + b".orig") |
|
1000 | return repo.wjoin(filepath + b".orig") | |
994 |
|
1001 | |||
995 | origbackupdir = origvfs.dirname(filepath) |
|
1002 | origbackupdir = origvfs.dirname(filepath) | |
996 | if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir): |
|
1003 | if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir): | |
997 | ui.note(_(b'creating directory: %s\n') % origvfs.join(origbackupdir)) |
|
1004 | ui.note(_(b'creating directory: %s\n') % origvfs.join(origbackupdir)) | |
998 |
|
1005 | |||
999 | # Remove any files that conflict with the backup file's path |
|
1006 | # Remove any files that conflict with the backup file's path | |
1000 | for f in reversed(list(pathutil.finddirs(filepath))): |
|
1007 | for f in reversed(list(pathutil.finddirs(filepath))): | |
1001 | if origvfs.isfileorlink(f): |
|
1008 | if origvfs.isfileorlink(f): | |
1002 | ui.note(_(b'removing conflicting file: %s\n') % origvfs.join(f)) |
|
1009 | ui.note(_(b'removing conflicting file: %s\n') % origvfs.join(f)) | |
1003 | origvfs.unlink(f) |
|
1010 | origvfs.unlink(f) | |
1004 | break |
|
1011 | break | |
1005 |
|
1012 | |||
1006 | origvfs.makedirs(origbackupdir) |
|
1013 | origvfs.makedirs(origbackupdir) | |
1007 |
|
1014 | |||
1008 | if origvfs.isdir(filepath) and not origvfs.islink(filepath): |
|
1015 | if origvfs.isdir(filepath) and not origvfs.islink(filepath): | |
1009 | ui.note( |
|
1016 | ui.note( | |
1010 | _(b'removing conflicting directory: %s\n') % origvfs.join(filepath) |
|
1017 | _(b'removing conflicting directory: %s\n') % origvfs.join(filepath) | |
1011 | ) |
|
1018 | ) | |
1012 | origvfs.rmtree(filepath, forcibly=True) |
|
1019 | origvfs.rmtree(filepath, forcibly=True) | |
1013 |
|
1020 | |||
1014 | return origvfs.join(filepath) |
|
1021 | return origvfs.join(filepath) | |
1015 |
|
1022 | |||
1016 |
|
1023 | |||
1017 | class _containsnode(object): |
|
1024 | class _containsnode(object): | |
1018 | """proxy __contains__(node) to container.__contains__ which accepts revs""" |
|
1025 | """proxy __contains__(node) to container.__contains__ which accepts revs""" | |
1019 |
|
1026 | |||
1020 | def __init__(self, repo, revcontainer): |
|
1027 | def __init__(self, repo, revcontainer): | |
1021 | self._torev = repo.changelog.rev |
|
1028 | self._torev = repo.changelog.rev | |
1022 | self._revcontains = revcontainer.__contains__ |
|
1029 | self._revcontains = revcontainer.__contains__ | |
1023 |
|
1030 | |||
1024 | def __contains__(self, node): |
|
1031 | def __contains__(self, node): | |
1025 | return self._revcontains(self._torev(node)) |
|
1032 | return self._revcontains(self._torev(node)) | |
1026 |
|
1033 | |||
1027 |
|
1034 | |||
1028 | def cleanupnodes( |
|
1035 | def cleanupnodes( | |
1029 | repo, |
|
1036 | repo, | |
1030 | replacements, |
|
1037 | replacements, | |
1031 | operation, |
|
1038 | operation, | |
1032 | moves=None, |
|
1039 | moves=None, | |
1033 | metadata=None, |
|
1040 | metadata=None, | |
1034 | fixphase=False, |
|
1041 | fixphase=False, | |
1035 | targetphase=None, |
|
1042 | targetphase=None, | |
1036 | backup=True, |
|
1043 | backup=True, | |
1037 | ): |
|
1044 | ): | |
1038 | """do common cleanups when old nodes are replaced by new nodes |
|
1045 | """do common cleanups when old nodes are replaced by new nodes | |
1039 |
|
1046 | |||
1040 | That includes writing obsmarkers or stripping nodes, and moving bookmarks. |
|
1047 | That includes writing obsmarkers or stripping nodes, and moving bookmarks. | |
1041 | (we might also want to move working directory parent in the future) |
|
1048 | (we might also want to move working directory parent in the future) | |
1042 |
|
1049 | |||
1043 | By default, bookmark moves are calculated automatically from 'replacements', |
|
1050 | By default, bookmark moves are calculated automatically from 'replacements', | |
1044 | but 'moves' can be used to override that. Also, 'moves' may include |
|
1051 | but 'moves' can be used to override that. Also, 'moves' may include | |
1045 | additional bookmark moves that should not have associated obsmarkers. |
|
1052 | additional bookmark moves that should not have associated obsmarkers. | |
1046 |
|
1053 | |||
1047 | replacements is {oldnode: [newnode]} or a iterable of nodes if they do not |
|
1054 | replacements is {oldnode: [newnode]} or a iterable of nodes if they do not | |
1048 | have replacements. operation is a string, like "rebase". |
|
1055 | have replacements. operation is a string, like "rebase". | |
1049 |
|
1056 | |||
1050 | metadata is dictionary containing metadata to be stored in obsmarker if |
|
1057 | metadata is dictionary containing metadata to be stored in obsmarker if | |
1051 | obsolescence is enabled. |
|
1058 | obsolescence is enabled. | |
1052 | """ |
|
1059 | """ | |
1053 | assert fixphase or targetphase is None |
|
1060 | assert fixphase or targetphase is None | |
1054 | if not replacements and not moves: |
|
1061 | if not replacements and not moves: | |
1055 | return |
|
1062 | return | |
1056 |
|
1063 | |||
1057 | # translate mapping's other forms |
|
1064 | # translate mapping's other forms | |
1058 | if not util.safehasattr(replacements, b'items'): |
|
1065 | if not util.safehasattr(replacements, b'items'): | |
1059 | replacements = {(n,): () for n in replacements} |
|
1066 | replacements = {(n,): () for n in replacements} | |
1060 | else: |
|
1067 | else: | |
1061 | # upgrading non tuple "source" to tuple ones for BC |
|
1068 | # upgrading non tuple "source" to tuple ones for BC | |
1062 | repls = {} |
|
1069 | repls = {} | |
1063 | for key, value in replacements.items(): |
|
1070 | for key, value in replacements.items(): | |
1064 | if not isinstance(key, tuple): |
|
1071 | if not isinstance(key, tuple): | |
1065 | key = (key,) |
|
1072 | key = (key,) | |
1066 | repls[key] = value |
|
1073 | repls[key] = value | |
1067 | replacements = repls |
|
1074 | replacements = repls | |
1068 |
|
1075 | |||
1069 | # Unfiltered repo is needed since nodes in replacements might be hidden. |
|
1076 | # Unfiltered repo is needed since nodes in replacements might be hidden. | |
1070 | unfi = repo.unfiltered() |
|
1077 | unfi = repo.unfiltered() | |
1071 |
|
1078 | |||
1072 | # Calculate bookmark movements |
|
1079 | # Calculate bookmark movements | |
1073 | if moves is None: |
|
1080 | if moves is None: | |
1074 | moves = {} |
|
1081 | moves = {} | |
1075 | for oldnodes, newnodes in replacements.items(): |
|
1082 | for oldnodes, newnodes in replacements.items(): | |
1076 | for oldnode in oldnodes: |
|
1083 | for oldnode in oldnodes: | |
1077 | if oldnode in moves: |
|
1084 | if oldnode in moves: | |
1078 | continue |
|
1085 | continue | |
1079 | if len(newnodes) > 1: |
|
1086 | if len(newnodes) > 1: | |
1080 | # usually a split, take the one with biggest rev number |
|
1087 | # usually a split, take the one with biggest rev number | |
1081 | newnode = next(unfi.set(b'max(%ln)', newnodes)).node() |
|
1088 | newnode = next(unfi.set(b'max(%ln)', newnodes)).node() | |
1082 | elif len(newnodes) == 0: |
|
1089 | elif len(newnodes) == 0: | |
1083 | # move bookmark backwards |
|
1090 | # move bookmark backwards | |
1084 | allreplaced = [] |
|
1091 | allreplaced = [] | |
1085 | for rep in replacements: |
|
1092 | for rep in replacements: | |
1086 | allreplaced.extend(rep) |
|
1093 | allreplaced.extend(rep) | |
1087 | roots = list( |
|
1094 | roots = list( | |
1088 | unfi.set(b'max((::%n) - %ln)', oldnode, allreplaced) |
|
1095 | unfi.set(b'max((::%n) - %ln)', oldnode, allreplaced) | |
1089 | ) |
|
1096 | ) | |
1090 | if roots: |
|
1097 | if roots: | |
1091 | newnode = roots[0].node() |
|
1098 | newnode = roots[0].node() | |
1092 | else: |
|
1099 | else: | |
1093 | newnode = nullid |
|
1100 | newnode = nullid | |
1094 | else: |
|
1101 | else: | |
1095 | newnode = newnodes[0] |
|
1102 | newnode = newnodes[0] | |
1096 | moves[oldnode] = newnode |
|
1103 | moves[oldnode] = newnode | |
1097 |
|
1104 | |||
1098 | allnewnodes = [n for ns in replacements.values() for n in ns] |
|
1105 | allnewnodes = [n for ns in replacements.values() for n in ns] | |
1099 | toretract = {} |
|
1106 | toretract = {} | |
1100 | toadvance = {} |
|
1107 | toadvance = {} | |
1101 | if fixphase: |
|
1108 | if fixphase: | |
1102 | precursors = {} |
|
1109 | precursors = {} | |
1103 | for oldnodes, newnodes in replacements.items(): |
|
1110 | for oldnodes, newnodes in replacements.items(): | |
1104 | for oldnode in oldnodes: |
|
1111 | for oldnode in oldnodes: | |
1105 | for newnode in newnodes: |
|
1112 | for newnode in newnodes: | |
1106 | precursors.setdefault(newnode, []).append(oldnode) |
|
1113 | precursors.setdefault(newnode, []).append(oldnode) | |
1107 |
|
1114 | |||
1108 | allnewnodes.sort(key=lambda n: unfi[n].rev()) |
|
1115 | allnewnodes.sort(key=lambda n: unfi[n].rev()) | |
1109 | newphases = {} |
|
1116 | newphases = {} | |
1110 |
|
1117 | |||
1111 | def phase(ctx): |
|
1118 | def phase(ctx): | |
1112 | return newphases.get(ctx.node(), ctx.phase()) |
|
1119 | return newphases.get(ctx.node(), ctx.phase()) | |
1113 |
|
1120 | |||
1114 | for newnode in allnewnodes: |
|
1121 | for newnode in allnewnodes: | |
1115 | ctx = unfi[newnode] |
|
1122 | ctx = unfi[newnode] | |
1116 | parentphase = max(phase(p) for p in ctx.parents()) |
|
1123 | parentphase = max(phase(p) for p in ctx.parents()) | |
1117 | if targetphase is None: |
|
1124 | if targetphase is None: | |
1118 | oldphase = max( |
|
1125 | oldphase = max( | |
1119 | unfi[oldnode].phase() for oldnode in precursors[newnode] |
|
1126 | unfi[oldnode].phase() for oldnode in precursors[newnode] | |
1120 | ) |
|
1127 | ) | |
1121 | newphase = max(oldphase, parentphase) |
|
1128 | newphase = max(oldphase, parentphase) | |
1122 | else: |
|
1129 | else: | |
1123 | newphase = max(targetphase, parentphase) |
|
1130 | newphase = max(targetphase, parentphase) | |
1124 | newphases[newnode] = newphase |
|
1131 | newphases[newnode] = newphase | |
1125 | if newphase > ctx.phase(): |
|
1132 | if newphase > ctx.phase(): | |
1126 | toretract.setdefault(newphase, []).append(newnode) |
|
1133 | toretract.setdefault(newphase, []).append(newnode) | |
1127 | elif newphase < ctx.phase(): |
|
1134 | elif newphase < ctx.phase(): | |
1128 | toadvance.setdefault(newphase, []).append(newnode) |
|
1135 | toadvance.setdefault(newphase, []).append(newnode) | |
1129 |
|
1136 | |||
1130 | with repo.transaction(b'cleanup') as tr: |
|
1137 | with repo.transaction(b'cleanup') as tr: | |
1131 | # Move bookmarks |
|
1138 | # Move bookmarks | |
1132 | bmarks = repo._bookmarks |
|
1139 | bmarks = repo._bookmarks | |
1133 | bmarkchanges = [] |
|
1140 | bmarkchanges = [] | |
1134 | for oldnode, newnode in moves.items(): |
|
1141 | for oldnode, newnode in moves.items(): | |
1135 | oldbmarks = repo.nodebookmarks(oldnode) |
|
1142 | oldbmarks = repo.nodebookmarks(oldnode) | |
1136 | if not oldbmarks: |
|
1143 | if not oldbmarks: | |
1137 | continue |
|
1144 | continue | |
1138 | from . import bookmarks # avoid import cycle |
|
1145 | from . import bookmarks # avoid import cycle | |
1139 |
|
1146 | |||
1140 | repo.ui.debug( |
|
1147 | repo.ui.debug( | |
1141 | b'moving bookmarks %r from %s to %s\n' |
|
1148 | b'moving bookmarks %r from %s to %s\n' | |
1142 | % ( |
|
1149 | % ( | |
1143 | pycompat.rapply(pycompat.maybebytestr, oldbmarks), |
|
1150 | pycompat.rapply(pycompat.maybebytestr, oldbmarks), | |
1144 | hex(oldnode), |
|
1151 | hex(oldnode), | |
1145 | hex(newnode), |
|
1152 | hex(newnode), | |
1146 | ) |
|
1153 | ) | |
1147 | ) |
|
1154 | ) | |
1148 | # Delete divergent bookmarks being parents of related newnodes |
|
1155 | # Delete divergent bookmarks being parents of related newnodes | |
1149 | deleterevs = repo.revs( |
|
1156 | deleterevs = repo.revs( | |
1150 | b'parents(roots(%ln & (::%n))) - parents(%n)', |
|
1157 | b'parents(roots(%ln & (::%n))) - parents(%n)', | |
1151 | allnewnodes, |
|
1158 | allnewnodes, | |
1152 | newnode, |
|
1159 | newnode, | |
1153 | oldnode, |
|
1160 | oldnode, | |
1154 | ) |
|
1161 | ) | |
1155 | deletenodes = _containsnode(repo, deleterevs) |
|
1162 | deletenodes = _containsnode(repo, deleterevs) | |
1156 | for name in oldbmarks: |
|
1163 | for name in oldbmarks: | |
1157 | bmarkchanges.append((name, newnode)) |
|
1164 | bmarkchanges.append((name, newnode)) | |
1158 | for b in bookmarks.divergent2delete(repo, deletenodes, name): |
|
1165 | for b in bookmarks.divergent2delete(repo, deletenodes, name): | |
1159 | bmarkchanges.append((b, None)) |
|
1166 | bmarkchanges.append((b, None)) | |
1160 |
|
1167 | |||
1161 | if bmarkchanges: |
|
1168 | if bmarkchanges: | |
1162 | bmarks.applychanges(repo, tr, bmarkchanges) |
|
1169 | bmarks.applychanges(repo, tr, bmarkchanges) | |
1163 |
|
1170 | |||
1164 | for phase, nodes in toretract.items(): |
|
1171 | for phase, nodes in toretract.items(): | |
1165 | phases.retractboundary(repo, tr, phase, nodes) |
|
1172 | phases.retractboundary(repo, tr, phase, nodes) | |
1166 | for phase, nodes in toadvance.items(): |
|
1173 | for phase, nodes in toadvance.items(): | |
1167 | phases.advanceboundary(repo, tr, phase, nodes) |
|
1174 | phases.advanceboundary(repo, tr, phase, nodes) | |
1168 |
|
1175 | |||
1169 | mayusearchived = repo.ui.config(b'experimental', b'cleanup-as-archived') |
|
1176 | mayusearchived = repo.ui.config(b'experimental', b'cleanup-as-archived') | |
1170 | # Obsolete or strip nodes |
|
1177 | # Obsolete or strip nodes | |
1171 | if obsolete.isenabled(repo, obsolete.createmarkersopt): |
|
1178 | if obsolete.isenabled(repo, obsolete.createmarkersopt): | |
1172 | # If a node is already obsoleted, and we want to obsolete it |
|
1179 | # If a node is already obsoleted, and we want to obsolete it | |
1173 | # without a successor, skip that obssolete request since it's |
|
1180 | # without a successor, skip that obssolete request since it's | |
1174 | # unnecessary. That's the "if s or not isobs(n)" check below. |
|
1181 | # unnecessary. That's the "if s or not isobs(n)" check below. | |
1175 | # Also sort the node in topology order, that might be useful for |
|
1182 | # Also sort the node in topology order, that might be useful for | |
1176 | # some obsstore logic. |
|
1183 | # some obsstore logic. | |
1177 | # NOTE: the sorting might belong to createmarkers. |
|
1184 | # NOTE: the sorting might belong to createmarkers. | |
1178 | torev = unfi.changelog.rev |
|
1185 | torev = unfi.changelog.rev | |
1179 | sortfunc = lambda ns: torev(ns[0][0]) |
|
1186 | sortfunc = lambda ns: torev(ns[0][0]) | |
1180 | rels = [] |
|
1187 | rels = [] | |
1181 | for ns, s in sorted(replacements.items(), key=sortfunc): |
|
1188 | for ns, s in sorted(replacements.items(), key=sortfunc): | |
1182 | rel = (tuple(unfi[n] for n in ns), tuple(unfi[m] for m in s)) |
|
1189 | rel = (tuple(unfi[n] for n in ns), tuple(unfi[m] for m in s)) | |
1183 | rels.append(rel) |
|
1190 | rels.append(rel) | |
1184 | if rels: |
|
1191 | if rels: | |
1185 | obsolete.createmarkers( |
|
1192 | obsolete.createmarkers( | |
1186 | repo, rels, operation=operation, metadata=metadata |
|
1193 | repo, rels, operation=operation, metadata=metadata | |
1187 | ) |
|
1194 | ) | |
1188 | elif phases.supportinternal(repo) and mayusearchived: |
|
1195 | elif phases.supportinternal(repo) and mayusearchived: | |
1189 | # this assume we do not have "unstable" nodes above the cleaned ones |
|
1196 | # this assume we do not have "unstable" nodes above the cleaned ones | |
1190 | allreplaced = set() |
|
1197 | allreplaced = set() | |
1191 | for ns in replacements.keys(): |
|
1198 | for ns in replacements.keys(): | |
1192 | allreplaced.update(ns) |
|
1199 | allreplaced.update(ns) | |
1193 | if backup: |
|
1200 | if backup: | |
1194 | from . import repair # avoid import cycle |
|
1201 | from . import repair # avoid import cycle | |
1195 |
|
1202 | |||
1196 | node = min(allreplaced, key=repo.changelog.rev) |
|
1203 | node = min(allreplaced, key=repo.changelog.rev) | |
1197 | repair.backupbundle( |
|
1204 | repair.backupbundle( | |
1198 | repo, allreplaced, allreplaced, node, operation |
|
1205 | repo, allreplaced, allreplaced, node, operation | |
1199 | ) |
|
1206 | ) | |
1200 | phases.retractboundary(repo, tr, phases.archived, allreplaced) |
|
1207 | phases.retractboundary(repo, tr, phases.archived, allreplaced) | |
1201 | else: |
|
1208 | else: | |
1202 | from . import repair # avoid import cycle |
|
1209 | from . import repair # avoid import cycle | |
1203 |
|
1210 | |||
1204 | tostrip = list(n for ns in replacements for n in ns) |
|
1211 | tostrip = list(n for ns in replacements for n in ns) | |
1205 | if tostrip: |
|
1212 | if tostrip: | |
1206 | repair.delayedstrip( |
|
1213 | repair.delayedstrip( | |
1207 | repo.ui, repo, tostrip, operation, backup=backup |
|
1214 | repo.ui, repo, tostrip, operation, backup=backup | |
1208 | ) |
|
1215 | ) | |
1209 |
|
1216 | |||
1210 |
|
1217 | |||
1211 | def addremove(repo, matcher, prefix, uipathfn, opts=None): |
|
1218 | def addremove(repo, matcher, prefix, uipathfn, opts=None): | |
1212 | if opts is None: |
|
1219 | if opts is None: | |
1213 | opts = {} |
|
1220 | opts = {} | |
1214 | m = matcher |
|
1221 | m = matcher | |
1215 | dry_run = opts.get(b'dry_run') |
|
1222 | dry_run = opts.get(b'dry_run') | |
1216 | try: |
|
1223 | try: | |
1217 | similarity = float(opts.get(b'similarity') or 0) |
|
1224 | similarity = float(opts.get(b'similarity') or 0) | |
1218 | except ValueError: |
|
1225 | except ValueError: | |
1219 | raise error.Abort(_(b'similarity must be a number')) |
|
1226 | raise error.Abort(_(b'similarity must be a number')) | |
1220 | if similarity < 0 or similarity > 100: |
|
1227 | if similarity < 0 or similarity > 100: | |
1221 | raise error.Abort(_(b'similarity must be between 0 and 100')) |
|
1228 | raise error.Abort(_(b'similarity must be between 0 and 100')) | |
1222 | similarity /= 100.0 |
|
1229 | similarity /= 100.0 | |
1223 |
|
1230 | |||
1224 | ret = 0 |
|
1231 | ret = 0 | |
1225 |
|
1232 | |||
1226 | wctx = repo[None] |
|
1233 | wctx = repo[None] | |
1227 | for subpath in sorted(wctx.substate): |
|
1234 | for subpath in sorted(wctx.substate): | |
1228 | submatch = matchmod.subdirmatcher(subpath, m) |
|
1235 | submatch = matchmod.subdirmatcher(subpath, m) | |
1229 | if opts.get(b'subrepos') or m.exact(subpath) or any(submatch.files()): |
|
1236 | if opts.get(b'subrepos') or m.exact(subpath) or any(submatch.files()): | |
1230 | sub = wctx.sub(subpath) |
|
1237 | sub = wctx.sub(subpath) | |
1231 | subprefix = repo.wvfs.reljoin(prefix, subpath) |
|
1238 | subprefix = repo.wvfs.reljoin(prefix, subpath) | |
1232 | subuipathfn = subdiruipathfn(subpath, uipathfn) |
|
1239 | subuipathfn = subdiruipathfn(subpath, uipathfn) | |
1233 | try: |
|
1240 | try: | |
1234 | if sub.addremove(submatch, subprefix, subuipathfn, opts): |
|
1241 | if sub.addremove(submatch, subprefix, subuipathfn, opts): | |
1235 | ret = 1 |
|
1242 | ret = 1 | |
1236 | except error.LookupError: |
|
1243 | except error.LookupError: | |
1237 | repo.ui.status( |
|
1244 | repo.ui.status( | |
1238 | _(b"skipping missing subrepository: %s\n") |
|
1245 | _(b"skipping missing subrepository: %s\n") | |
1239 | % uipathfn(subpath) |
|
1246 | % uipathfn(subpath) | |
1240 | ) |
|
1247 | ) | |
1241 |
|
1248 | |||
1242 | rejected = [] |
|
1249 | rejected = [] | |
1243 |
|
1250 | |||
1244 | def badfn(f, msg): |
|
1251 | def badfn(f, msg): | |
1245 | if f in m.files(): |
|
1252 | if f in m.files(): | |
1246 | m.bad(f, msg) |
|
1253 | m.bad(f, msg) | |
1247 | rejected.append(f) |
|
1254 | rejected.append(f) | |
1248 |
|
1255 | |||
1249 | badmatch = matchmod.badmatch(m, badfn) |
|
1256 | badmatch = matchmod.badmatch(m, badfn) | |
1250 | added, unknown, deleted, removed, forgotten = _interestingfiles( |
|
1257 | added, unknown, deleted, removed, forgotten = _interestingfiles( | |
1251 | repo, badmatch |
|
1258 | repo, badmatch | |
1252 | ) |
|
1259 | ) | |
1253 |
|
1260 | |||
1254 | unknownset = set(unknown + forgotten) |
|
1261 | unknownset = set(unknown + forgotten) | |
1255 | toprint = unknownset.copy() |
|
1262 | toprint = unknownset.copy() | |
1256 | toprint.update(deleted) |
|
1263 | toprint.update(deleted) | |
1257 | for abs in sorted(toprint): |
|
1264 | for abs in sorted(toprint): | |
1258 | if repo.ui.verbose or not m.exact(abs): |
|
1265 | if repo.ui.verbose or not m.exact(abs): | |
1259 | if abs in unknownset: |
|
1266 | if abs in unknownset: | |
1260 | status = _(b'adding %s\n') % uipathfn(abs) |
|
1267 | status = _(b'adding %s\n') % uipathfn(abs) | |
1261 | label = b'ui.addremove.added' |
|
1268 | label = b'ui.addremove.added' | |
1262 | else: |
|
1269 | else: | |
1263 | status = _(b'removing %s\n') % uipathfn(abs) |
|
1270 | status = _(b'removing %s\n') % uipathfn(abs) | |
1264 | label = b'ui.addremove.removed' |
|
1271 | label = b'ui.addremove.removed' | |
1265 | repo.ui.status(status, label=label) |
|
1272 | repo.ui.status(status, label=label) | |
1266 |
|
1273 | |||
1267 | renames = _findrenames( |
|
1274 | renames = _findrenames( | |
1268 | repo, m, added + unknown, removed + deleted, similarity, uipathfn |
|
1275 | repo, m, added + unknown, removed + deleted, similarity, uipathfn | |
1269 | ) |
|
1276 | ) | |
1270 |
|
1277 | |||
1271 | if not dry_run: |
|
1278 | if not dry_run: | |
1272 | _markchanges(repo, unknown + forgotten, deleted, renames) |
|
1279 | _markchanges(repo, unknown + forgotten, deleted, renames) | |
1273 |
|
1280 | |||
1274 | for f in rejected: |
|
1281 | for f in rejected: | |
1275 | if f in m.files(): |
|
1282 | if f in m.files(): | |
1276 | return 1 |
|
1283 | return 1 | |
1277 | return ret |
|
1284 | return ret | |
1278 |
|
1285 | |||
1279 |
|
1286 | |||
1280 | def marktouched(repo, files, similarity=0.0): |
|
1287 | def marktouched(repo, files, similarity=0.0): | |
1281 | '''Assert that files have somehow been operated upon. files are relative to |
|
1288 | '''Assert that files have somehow been operated upon. files are relative to | |
1282 | the repo root.''' |
|
1289 | the repo root.''' | |
1283 | m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x)) |
|
1290 | m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x)) | |
1284 | rejected = [] |
|
1291 | rejected = [] | |
1285 |
|
1292 | |||
1286 | added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m) |
|
1293 | added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m) | |
1287 |
|
1294 | |||
1288 | if repo.ui.verbose: |
|
1295 | if repo.ui.verbose: | |
1289 | unknownset = set(unknown + forgotten) |
|
1296 | unknownset = set(unknown + forgotten) | |
1290 | toprint = unknownset.copy() |
|
1297 | toprint = unknownset.copy() | |
1291 | toprint.update(deleted) |
|
1298 | toprint.update(deleted) | |
1292 | for abs in sorted(toprint): |
|
1299 | for abs in sorted(toprint): | |
1293 | if abs in unknownset: |
|
1300 | if abs in unknownset: | |
1294 | status = _(b'adding %s\n') % abs |
|
1301 | status = _(b'adding %s\n') % abs | |
1295 | else: |
|
1302 | else: | |
1296 | status = _(b'removing %s\n') % abs |
|
1303 | status = _(b'removing %s\n') % abs | |
1297 | repo.ui.status(status) |
|
1304 | repo.ui.status(status) | |
1298 |
|
1305 | |||
1299 | # TODO: We should probably have the caller pass in uipathfn and apply it to |
|
1306 | # TODO: We should probably have the caller pass in uipathfn and apply it to | |
1300 | # the messages above too. legacyrelativevalue=True is consistent with how |
|
1307 | # the messages above too. legacyrelativevalue=True is consistent with how | |
1301 | # it used to work. |
|
1308 | # it used to work. | |
1302 | uipathfn = getuipathfn(repo, legacyrelativevalue=True) |
|
1309 | uipathfn = getuipathfn(repo, legacyrelativevalue=True) | |
1303 | renames = _findrenames( |
|
1310 | renames = _findrenames( | |
1304 | repo, m, added + unknown, removed + deleted, similarity, uipathfn |
|
1311 | repo, m, added + unknown, removed + deleted, similarity, uipathfn | |
1305 | ) |
|
1312 | ) | |
1306 |
|
1313 | |||
1307 | _markchanges(repo, unknown + forgotten, deleted, renames) |
|
1314 | _markchanges(repo, unknown + forgotten, deleted, renames) | |
1308 |
|
1315 | |||
1309 | for f in rejected: |
|
1316 | for f in rejected: | |
1310 | if f in m.files(): |
|
1317 | if f in m.files(): | |
1311 | return 1 |
|
1318 | return 1 | |
1312 | return 0 |
|
1319 | return 0 | |
1313 |
|
1320 | |||
1314 |
|
1321 | |||
1315 | def _interestingfiles(repo, matcher): |
|
1322 | def _interestingfiles(repo, matcher): | |
1316 | '''Walk dirstate with matcher, looking for files that addremove would care |
|
1323 | '''Walk dirstate with matcher, looking for files that addremove would care | |
1317 | about. |
|
1324 | about. | |
1318 |
|
1325 | |||
1319 | This is different from dirstate.status because it doesn't care about |
|
1326 | This is different from dirstate.status because it doesn't care about | |
1320 | whether files are modified or clean.''' |
|
1327 | whether files are modified or clean.''' | |
1321 | added, unknown, deleted, removed, forgotten = [], [], [], [], [] |
|
1328 | added, unknown, deleted, removed, forgotten = [], [], [], [], [] | |
1322 | audit_path = pathutil.pathauditor(repo.root, cached=True) |
|
1329 | audit_path = pathutil.pathauditor(repo.root, cached=True) | |
1323 |
|
1330 | |||
1324 | ctx = repo[None] |
|
1331 | ctx = repo[None] | |
1325 | dirstate = repo.dirstate |
|
1332 | dirstate = repo.dirstate | |
1326 | matcher = repo.narrowmatch(matcher, includeexact=True) |
|
1333 | matcher = repo.narrowmatch(matcher, includeexact=True) | |
1327 | walkresults = dirstate.walk( |
|
1334 | walkresults = dirstate.walk( | |
1328 | matcher, |
|
1335 | matcher, | |
1329 | subrepos=sorted(ctx.substate), |
|
1336 | subrepos=sorted(ctx.substate), | |
1330 | unknown=True, |
|
1337 | unknown=True, | |
1331 | ignored=False, |
|
1338 | ignored=False, | |
1332 | full=False, |
|
1339 | full=False, | |
1333 | ) |
|
1340 | ) | |
1334 | for abs, st in pycompat.iteritems(walkresults): |
|
1341 | for abs, st in pycompat.iteritems(walkresults): | |
1335 | dstate = dirstate[abs] |
|
1342 | dstate = dirstate[abs] | |
1336 | if dstate == b'?' and audit_path.check(abs): |
|
1343 | if dstate == b'?' and audit_path.check(abs): | |
1337 | unknown.append(abs) |
|
1344 | unknown.append(abs) | |
1338 | elif dstate != b'r' and not st: |
|
1345 | elif dstate != b'r' and not st: | |
1339 | deleted.append(abs) |
|
1346 | deleted.append(abs) | |
1340 | elif dstate == b'r' and st: |
|
1347 | elif dstate == b'r' and st: | |
1341 | forgotten.append(abs) |
|
1348 | forgotten.append(abs) | |
1342 | # for finding renames |
|
1349 | # for finding renames | |
1343 | elif dstate == b'r' and not st: |
|
1350 | elif dstate == b'r' and not st: | |
1344 | removed.append(abs) |
|
1351 | removed.append(abs) | |
1345 | elif dstate == b'a': |
|
1352 | elif dstate == b'a': | |
1346 | added.append(abs) |
|
1353 | added.append(abs) | |
1347 |
|
1354 | |||
1348 | return added, unknown, deleted, removed, forgotten |
|
1355 | return added, unknown, deleted, removed, forgotten | |
1349 |
|
1356 | |||
1350 |
|
1357 | |||
1351 | def _findrenames(repo, matcher, added, removed, similarity, uipathfn): |
|
1358 | def _findrenames(repo, matcher, added, removed, similarity, uipathfn): | |
1352 | '''Find renames from removed files to added ones.''' |
|
1359 | '''Find renames from removed files to added ones.''' | |
1353 | renames = {} |
|
1360 | renames = {} | |
1354 | if similarity > 0: |
|
1361 | if similarity > 0: | |
1355 | for old, new, score in similar.findrenames( |
|
1362 | for old, new, score in similar.findrenames( | |
1356 | repo, added, removed, similarity |
|
1363 | repo, added, removed, similarity | |
1357 | ): |
|
1364 | ): | |
1358 | if ( |
|
1365 | if ( | |
1359 | repo.ui.verbose |
|
1366 | repo.ui.verbose | |
1360 | or not matcher.exact(old) |
|
1367 | or not matcher.exact(old) | |
1361 | or not matcher.exact(new) |
|
1368 | or not matcher.exact(new) | |
1362 | ): |
|
1369 | ): | |
1363 | repo.ui.status( |
|
1370 | repo.ui.status( | |
1364 | _( |
|
1371 | _( | |
1365 | b'recording removal of %s as rename to %s ' |
|
1372 | b'recording removal of %s as rename to %s ' | |
1366 | b'(%d%% similar)\n' |
|
1373 | b'(%d%% similar)\n' | |
1367 | ) |
|
1374 | ) | |
1368 | % (uipathfn(old), uipathfn(new), score * 100) |
|
1375 | % (uipathfn(old), uipathfn(new), score * 100) | |
1369 | ) |
|
1376 | ) | |
1370 | renames[new] = old |
|
1377 | renames[new] = old | |
1371 | return renames |
|
1378 | return renames | |
1372 |
|
1379 | |||
1373 |
|
1380 | |||
1374 | def _markchanges(repo, unknown, deleted, renames): |
|
1381 | def _markchanges(repo, unknown, deleted, renames): | |
1375 | '''Marks the files in unknown as added, the files in deleted as removed, |
|
1382 | '''Marks the files in unknown as added, the files in deleted as removed, | |
1376 | and the files in renames as copied.''' |
|
1383 | and the files in renames as copied.''' | |
1377 | wctx = repo[None] |
|
1384 | wctx = repo[None] | |
1378 | with repo.wlock(): |
|
1385 | with repo.wlock(): | |
1379 | wctx.forget(deleted) |
|
1386 | wctx.forget(deleted) | |
1380 | wctx.add(unknown) |
|
1387 | wctx.add(unknown) | |
1381 | for new, old in pycompat.iteritems(renames): |
|
1388 | for new, old in pycompat.iteritems(renames): | |
1382 | wctx.copy(old, new) |
|
1389 | wctx.copy(old, new) | |
1383 |
|
1390 | |||
1384 |
|
1391 | |||
1385 | def getrenamedfn(repo, endrev=None): |
|
1392 | def getrenamedfn(repo, endrev=None): | |
1386 | if copiesmod.usechangesetcentricalgo(repo): |
|
1393 | if copiesmod.usechangesetcentricalgo(repo): | |
1387 |
|
1394 | |||
1388 | def getrenamed(fn, rev): |
|
1395 | def getrenamed(fn, rev): | |
1389 | ctx = repo[rev] |
|
1396 | ctx = repo[rev] | |
1390 | p1copies = ctx.p1copies() |
|
1397 | p1copies = ctx.p1copies() | |
1391 | if fn in p1copies: |
|
1398 | if fn in p1copies: | |
1392 | return p1copies[fn] |
|
1399 | return p1copies[fn] | |
1393 | p2copies = ctx.p2copies() |
|
1400 | p2copies = ctx.p2copies() | |
1394 | if fn in p2copies: |
|
1401 | if fn in p2copies: | |
1395 | return p2copies[fn] |
|
1402 | return p2copies[fn] | |
1396 | return None |
|
1403 | return None | |
1397 |
|
1404 | |||
1398 | return getrenamed |
|
1405 | return getrenamed | |
1399 |
|
1406 | |||
1400 | rcache = {} |
|
1407 | rcache = {} | |
1401 | if endrev is None: |
|
1408 | if endrev is None: | |
1402 | endrev = len(repo) |
|
1409 | endrev = len(repo) | |
1403 |
|
1410 | |||
1404 | def getrenamed(fn, rev): |
|
1411 | def getrenamed(fn, rev): | |
1405 | '''looks up all renames for a file (up to endrev) the first |
|
1412 | '''looks up all renames for a file (up to endrev) the first | |
1406 | time the file is given. It indexes on the changerev and only |
|
1413 | time the file is given. It indexes on the changerev and only | |
1407 | parses the manifest if linkrev != changerev. |
|
1414 | parses the manifest if linkrev != changerev. | |
1408 | Returns rename info for fn at changerev rev.''' |
|
1415 | Returns rename info for fn at changerev rev.''' | |
1409 | if fn not in rcache: |
|
1416 | if fn not in rcache: | |
1410 | rcache[fn] = {} |
|
1417 | rcache[fn] = {} | |
1411 | fl = repo.file(fn) |
|
1418 | fl = repo.file(fn) | |
1412 | for i in fl: |
|
1419 | for i in fl: | |
1413 | lr = fl.linkrev(i) |
|
1420 | lr = fl.linkrev(i) | |
1414 | renamed = fl.renamed(fl.node(i)) |
|
1421 | renamed = fl.renamed(fl.node(i)) | |
1415 | rcache[fn][lr] = renamed and renamed[0] |
|
1422 | rcache[fn][lr] = renamed and renamed[0] | |
1416 | if lr >= endrev: |
|
1423 | if lr >= endrev: | |
1417 | break |
|
1424 | break | |
1418 | if rev in rcache[fn]: |
|
1425 | if rev in rcache[fn]: | |
1419 | return rcache[fn][rev] |
|
1426 | return rcache[fn][rev] | |
1420 |
|
1427 | |||
1421 | # If linkrev != rev (i.e. rev not found in rcache) fallback to |
|
1428 | # If linkrev != rev (i.e. rev not found in rcache) fallback to | |
1422 | # filectx logic. |
|
1429 | # filectx logic. | |
1423 | try: |
|
1430 | try: | |
1424 | return repo[rev][fn].copysource() |
|
1431 | return repo[rev][fn].copysource() | |
1425 | except error.LookupError: |
|
1432 | except error.LookupError: | |
1426 | return None |
|
1433 | return None | |
1427 |
|
1434 | |||
1428 | return getrenamed |
|
1435 | return getrenamed | |
1429 |
|
1436 | |||
1430 |
|
1437 | |||
1431 | def getcopiesfn(repo, endrev=None): |
|
1438 | def getcopiesfn(repo, endrev=None): | |
1432 | if copiesmod.usechangesetcentricalgo(repo): |
|
1439 | if copiesmod.usechangesetcentricalgo(repo): | |
1433 |
|
1440 | |||
1434 | def copiesfn(ctx): |
|
1441 | def copiesfn(ctx): | |
1435 | if ctx.p2copies(): |
|
1442 | if ctx.p2copies(): | |
1436 | allcopies = ctx.p1copies().copy() |
|
1443 | allcopies = ctx.p1copies().copy() | |
1437 | # There should be no overlap |
|
1444 | # There should be no overlap | |
1438 | allcopies.update(ctx.p2copies()) |
|
1445 | allcopies.update(ctx.p2copies()) | |
1439 | return sorted(allcopies.items()) |
|
1446 | return sorted(allcopies.items()) | |
1440 | else: |
|
1447 | else: | |
1441 | return sorted(ctx.p1copies().items()) |
|
1448 | return sorted(ctx.p1copies().items()) | |
1442 |
|
1449 | |||
1443 | else: |
|
1450 | else: | |
1444 | getrenamed = getrenamedfn(repo, endrev) |
|
1451 | getrenamed = getrenamedfn(repo, endrev) | |
1445 |
|
1452 | |||
1446 | def copiesfn(ctx): |
|
1453 | def copiesfn(ctx): | |
1447 | copies = [] |
|
1454 | copies = [] | |
1448 | for fn in ctx.files(): |
|
1455 | for fn in ctx.files(): | |
1449 | rename = getrenamed(fn, ctx.rev()) |
|
1456 | rename = getrenamed(fn, ctx.rev()) | |
1450 | if rename: |
|
1457 | if rename: | |
1451 | copies.append((fn, rename)) |
|
1458 | copies.append((fn, rename)) | |
1452 | return copies |
|
1459 | return copies | |
1453 |
|
1460 | |||
1454 | return copiesfn |
|
1461 | return copiesfn | |
1455 |
|
1462 | |||
1456 |
|
1463 | |||
1457 | def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None): |
|
1464 | def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None): | |
1458 | """Update the dirstate to reflect the intent of copying src to dst. For |
|
1465 | """Update the dirstate to reflect the intent of copying src to dst. For | |
1459 | different reasons it might not end with dst being marked as copied from src. |
|
1466 | different reasons it might not end with dst being marked as copied from src. | |
1460 | """ |
|
1467 | """ | |
1461 | origsrc = repo.dirstate.copied(src) or src |
|
1468 | origsrc = repo.dirstate.copied(src) or src | |
1462 | if dst == origsrc: # copying back a copy? |
|
1469 | if dst == origsrc: # copying back a copy? | |
1463 | if repo.dirstate[dst] not in b'mn' and not dryrun: |
|
1470 | if repo.dirstate[dst] not in b'mn' and not dryrun: | |
1464 | repo.dirstate.normallookup(dst) |
|
1471 | repo.dirstate.normallookup(dst) | |
1465 | else: |
|
1472 | else: | |
1466 | if repo.dirstate[origsrc] == b'a' and origsrc == src: |
|
1473 | if repo.dirstate[origsrc] == b'a' and origsrc == src: | |
1467 | if not ui.quiet: |
|
1474 | if not ui.quiet: | |
1468 | ui.warn( |
|
1475 | ui.warn( | |
1469 | _( |
|
1476 | _( | |
1470 | b"%s has not been committed yet, so no copy " |
|
1477 | b"%s has not been committed yet, so no copy " | |
1471 | b"data will be stored for %s.\n" |
|
1478 | b"data will be stored for %s.\n" | |
1472 | ) |
|
1479 | ) | |
1473 | % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)) |
|
1480 | % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)) | |
1474 | ) |
|
1481 | ) | |
1475 | if repo.dirstate[dst] in b'?r' and not dryrun: |
|
1482 | if repo.dirstate[dst] in b'?r' and not dryrun: | |
1476 | wctx.add([dst]) |
|
1483 | wctx.add([dst]) | |
1477 | elif not dryrun: |
|
1484 | elif not dryrun: | |
1478 | wctx.copy(origsrc, dst) |
|
1485 | wctx.copy(origsrc, dst) | |
1479 |
|
1486 | |||
1480 |
|
1487 | |||
1481 | def movedirstate(repo, newctx, match=None): |
|
1488 | def movedirstate(repo, newctx, match=None): | |
1482 | """Move the dirstate to newctx and adjust it as necessary. |
|
1489 | """Move the dirstate to newctx and adjust it as necessary. | |
1483 |
|
1490 | |||
1484 | A matcher can be provided as an optimization. It is probably a bug to pass |
|
1491 | A matcher can be provided as an optimization. It is probably a bug to pass | |
1485 | a matcher that doesn't match all the differences between the parent of the |
|
1492 | a matcher that doesn't match all the differences between the parent of the | |
1486 | working copy and newctx. |
|
1493 | working copy and newctx. | |
1487 | """ |
|
1494 | """ | |
1488 | oldctx = repo[b'.'] |
|
1495 | oldctx = repo[b'.'] | |
1489 | ds = repo.dirstate |
|
1496 | ds = repo.dirstate | |
1490 | copies = dict(ds.copies()) |
|
1497 | copies = dict(ds.copies()) | |
1491 | ds.setparents(newctx.node(), nullid) |
|
1498 | ds.setparents(newctx.node(), nullid) | |
1492 | s = newctx.status(oldctx, match=match) |
|
1499 | s = newctx.status(oldctx, match=match) | |
1493 | for f in s.modified: |
|
1500 | for f in s.modified: | |
1494 | if ds[f] == b'r': |
|
1501 | if ds[f] == b'r': | |
1495 | # modified + removed -> removed |
|
1502 | # modified + removed -> removed | |
1496 | continue |
|
1503 | continue | |
1497 | ds.normallookup(f) |
|
1504 | ds.normallookup(f) | |
1498 |
|
1505 | |||
1499 | for f in s.added: |
|
1506 | for f in s.added: | |
1500 | if ds[f] == b'r': |
|
1507 | if ds[f] == b'r': | |
1501 | # added + removed -> unknown |
|
1508 | # added + removed -> unknown | |
1502 | ds.drop(f) |
|
1509 | ds.drop(f) | |
1503 | elif ds[f] != b'a': |
|
1510 | elif ds[f] != b'a': | |
1504 | ds.add(f) |
|
1511 | ds.add(f) | |
1505 |
|
1512 | |||
1506 | for f in s.removed: |
|
1513 | for f in s.removed: | |
1507 | if ds[f] == b'a': |
|
1514 | if ds[f] == b'a': | |
1508 | # removed + added -> normal |
|
1515 | # removed + added -> normal | |
1509 | ds.normallookup(f) |
|
1516 | ds.normallookup(f) | |
1510 | elif ds[f] != b'r': |
|
1517 | elif ds[f] != b'r': | |
1511 | ds.remove(f) |
|
1518 | ds.remove(f) | |
1512 |
|
1519 | |||
1513 | # Merge old parent and old working dir copies |
|
1520 | # Merge old parent and old working dir copies | |
1514 | oldcopies = copiesmod.pathcopies(newctx, oldctx, match) |
|
1521 | oldcopies = copiesmod.pathcopies(newctx, oldctx, match) | |
1515 | oldcopies.update(copies) |
|
1522 | oldcopies.update(copies) | |
1516 | copies = { |
|
1523 | copies = { | |
1517 | dst: oldcopies.get(src, src) |
|
1524 | dst: oldcopies.get(src, src) | |
1518 | for dst, src in pycompat.iteritems(oldcopies) |
|
1525 | for dst, src in pycompat.iteritems(oldcopies) | |
1519 | } |
|
1526 | } | |
1520 | # Adjust the dirstate copies |
|
1527 | # Adjust the dirstate copies | |
1521 | for dst, src in pycompat.iteritems(copies): |
|
1528 | for dst, src in pycompat.iteritems(copies): | |
1522 | if src not in newctx or dst in newctx or ds[dst] != b'a': |
|
1529 | if src not in newctx or dst in newctx or ds[dst] != b'a': | |
1523 | src = None |
|
1530 | src = None | |
1524 | ds.copy(src, dst) |
|
1531 | ds.copy(src, dst) | |
1525 | repo._quick_access_changeid_invalidate() |
|
1532 | repo._quick_access_changeid_invalidate() | |
1526 |
|
1533 | |||
1527 |
|
1534 | |||
1528 | def filterrequirements(requirements): |
|
1535 | def filterrequirements(requirements): | |
1529 | """ filters the requirements into two sets: |
|
1536 | """ filters the requirements into two sets: | |
1530 |
|
1537 | |||
1531 | wcreq: requirements which should be written in .hg/requires |
|
1538 | wcreq: requirements which should be written in .hg/requires | |
1532 | storereq: which should be written in .hg/store/requires |
|
1539 | storereq: which should be written in .hg/store/requires | |
1533 |
|
1540 | |||
1534 | Returns (wcreq, storereq) |
|
1541 | Returns (wcreq, storereq) | |
1535 | """ |
|
1542 | """ | |
1536 | if requirementsmod.SHARESAFE_REQUIREMENT in requirements: |
|
1543 | if requirementsmod.SHARESAFE_REQUIREMENT in requirements: | |
1537 | wc, store = set(), set() |
|
1544 | wc, store = set(), set() | |
1538 | for r in requirements: |
|
1545 | for r in requirements: | |
1539 | if r in requirementsmod.WORKING_DIR_REQUIREMENTS: |
|
1546 | if r in requirementsmod.WORKING_DIR_REQUIREMENTS: | |
1540 | wc.add(r) |
|
1547 | wc.add(r) | |
1541 | else: |
|
1548 | else: | |
1542 | store.add(r) |
|
1549 | store.add(r) | |
1543 | return wc, store |
|
1550 | return wc, store | |
1544 | return requirements, None |
|
1551 | return requirements, None | |
1545 |
|
1552 | |||
1546 |
|
1553 | |||
1547 | def istreemanifest(repo): |
|
1554 | def istreemanifest(repo): | |
1548 | """ returns whether the repository is using treemanifest or not """ |
|
1555 | """ returns whether the repository is using treemanifest or not """ | |
1549 | return requirementsmod.TREEMANIFEST_REQUIREMENT in repo.requirements |
|
1556 | return requirementsmod.TREEMANIFEST_REQUIREMENT in repo.requirements | |
1550 |
|
1557 | |||
1551 |
|
1558 | |||
1552 | def writereporequirements(repo, requirements=None): |
|
1559 | def writereporequirements(repo, requirements=None): | |
1553 | """ writes requirements for the repo to .hg/requires """ |
|
1560 | """ writes requirements for the repo to .hg/requires """ | |
1554 | if requirements: |
|
1561 | if requirements: | |
1555 | repo.requirements = requirements |
|
1562 | repo.requirements = requirements | |
1556 | wcreq, storereq = filterrequirements(repo.requirements) |
|
1563 | wcreq, storereq = filterrequirements(repo.requirements) | |
1557 | if wcreq is not None: |
|
1564 | if wcreq is not None: | |
1558 | writerequires(repo.vfs, wcreq) |
|
1565 | writerequires(repo.vfs, wcreq) | |
1559 | if storereq is not None: |
|
1566 | if storereq is not None: | |
1560 | writerequires(repo.svfs, storereq) |
|
1567 | writerequires(repo.svfs, storereq) | |
1561 |
|
1568 | |||
1562 |
|
1569 | |||
1563 | def writerequires(opener, requirements): |
|
1570 | def writerequires(opener, requirements): | |
1564 | with opener(b'requires', b'w', atomictemp=True) as fp: |
|
1571 | with opener(b'requires', b'w', atomictemp=True) as fp: | |
1565 | for r in sorted(requirements): |
|
1572 | for r in sorted(requirements): | |
1566 | fp.write(b"%s\n" % r) |
|
1573 | fp.write(b"%s\n" % r) | |
1567 |
|
1574 | |||
1568 |
|
1575 | |||
1569 | class filecachesubentry(object): |
|
1576 | class filecachesubentry(object): | |
1570 | def __init__(self, path, stat): |
|
1577 | def __init__(self, path, stat): | |
1571 | self.path = path |
|
1578 | self.path = path | |
1572 | self.cachestat = None |
|
1579 | self.cachestat = None | |
1573 | self._cacheable = None |
|
1580 | self._cacheable = None | |
1574 |
|
1581 | |||
1575 | if stat: |
|
1582 | if stat: | |
1576 | self.cachestat = filecachesubentry.stat(self.path) |
|
1583 | self.cachestat = filecachesubentry.stat(self.path) | |
1577 |
|
1584 | |||
1578 | if self.cachestat: |
|
1585 | if self.cachestat: | |
1579 | self._cacheable = self.cachestat.cacheable() |
|
1586 | self._cacheable = self.cachestat.cacheable() | |
1580 | else: |
|
1587 | else: | |
1581 | # None means we don't know yet |
|
1588 | # None means we don't know yet | |
1582 | self._cacheable = None |
|
1589 | self._cacheable = None | |
1583 |
|
1590 | |||
1584 | def refresh(self): |
|
1591 | def refresh(self): | |
1585 | if self.cacheable(): |
|
1592 | if self.cacheable(): | |
1586 | self.cachestat = filecachesubentry.stat(self.path) |
|
1593 | self.cachestat = filecachesubentry.stat(self.path) | |
1587 |
|
1594 | |||
1588 | def cacheable(self): |
|
1595 | def cacheable(self): | |
1589 | if self._cacheable is not None: |
|
1596 | if self._cacheable is not None: | |
1590 | return self._cacheable |
|
1597 | return self._cacheable | |
1591 |
|
1598 | |||
1592 | # we don't know yet, assume it is for now |
|
1599 | # we don't know yet, assume it is for now | |
1593 | return True |
|
1600 | return True | |
1594 |
|
1601 | |||
1595 | def changed(self): |
|
1602 | def changed(self): | |
1596 | # no point in going further if we can't cache it |
|
1603 | # no point in going further if we can't cache it | |
1597 | if not self.cacheable(): |
|
1604 | if not self.cacheable(): | |
1598 | return True |
|
1605 | return True | |
1599 |
|
1606 | |||
1600 | newstat = filecachesubentry.stat(self.path) |
|
1607 | newstat = filecachesubentry.stat(self.path) | |
1601 |
|
1608 | |||
1602 | # we may not know if it's cacheable yet, check again now |
|
1609 | # we may not know if it's cacheable yet, check again now | |
1603 | if newstat and self._cacheable is None: |
|
1610 | if newstat and self._cacheable is None: | |
1604 | self._cacheable = newstat.cacheable() |
|
1611 | self._cacheable = newstat.cacheable() | |
1605 |
|
1612 | |||
1606 | # check again |
|
1613 | # check again | |
1607 | if not self._cacheable: |
|
1614 | if not self._cacheable: | |
1608 | return True |
|
1615 | return True | |
1609 |
|
1616 | |||
1610 | if self.cachestat != newstat: |
|
1617 | if self.cachestat != newstat: | |
1611 | self.cachestat = newstat |
|
1618 | self.cachestat = newstat | |
1612 | return True |
|
1619 | return True | |
1613 | else: |
|
1620 | else: | |
1614 | return False |
|
1621 | return False | |
1615 |
|
1622 | |||
1616 | @staticmethod |
|
1623 | @staticmethod | |
1617 | def stat(path): |
|
1624 | def stat(path): | |
1618 | try: |
|
1625 | try: | |
1619 | return util.cachestat(path) |
|
1626 | return util.cachestat(path) | |
1620 | except OSError as e: |
|
1627 | except OSError as e: | |
1621 | if e.errno != errno.ENOENT: |
|
1628 | if e.errno != errno.ENOENT: | |
1622 | raise |
|
1629 | raise | |
1623 |
|
1630 | |||
1624 |
|
1631 | |||
1625 | class filecacheentry(object): |
|
1632 | class filecacheentry(object): | |
1626 | def __init__(self, paths, stat=True): |
|
1633 | def __init__(self, paths, stat=True): | |
1627 | self._entries = [] |
|
1634 | self._entries = [] | |
1628 | for path in paths: |
|
1635 | for path in paths: | |
1629 | self._entries.append(filecachesubentry(path, stat)) |
|
1636 | self._entries.append(filecachesubentry(path, stat)) | |
1630 |
|
1637 | |||
1631 | def changed(self): |
|
1638 | def changed(self): | |
1632 | '''true if any entry has changed''' |
|
1639 | '''true if any entry has changed''' | |
1633 | for entry in self._entries: |
|
1640 | for entry in self._entries: | |
1634 | if entry.changed(): |
|
1641 | if entry.changed(): | |
1635 | return True |
|
1642 | return True | |
1636 | return False |
|
1643 | return False | |
1637 |
|
1644 | |||
1638 | def refresh(self): |
|
1645 | def refresh(self): | |
1639 | for entry in self._entries: |
|
1646 | for entry in self._entries: | |
1640 | entry.refresh() |
|
1647 | entry.refresh() | |
1641 |
|
1648 | |||
1642 |
|
1649 | |||
1643 | class filecache(object): |
|
1650 | class filecache(object): | |
1644 | """A property like decorator that tracks files under .hg/ for updates. |
|
1651 | """A property like decorator that tracks files under .hg/ for updates. | |
1645 |
|
1652 | |||
1646 | On first access, the files defined as arguments are stat()ed and the |
|
1653 | On first access, the files defined as arguments are stat()ed and the | |
1647 | results cached. The decorated function is called. The results are stashed |
|
1654 | results cached. The decorated function is called. The results are stashed | |
1648 | away in a ``_filecache`` dict on the object whose method is decorated. |
|
1655 | away in a ``_filecache`` dict on the object whose method is decorated. | |
1649 |
|
1656 | |||
1650 | On subsequent access, the cached result is used as it is set to the |
|
1657 | On subsequent access, the cached result is used as it is set to the | |
1651 | instance dictionary. |
|
1658 | instance dictionary. | |
1652 |
|
1659 | |||
1653 | On external property set/delete operations, the caller must update the |
|
1660 | On external property set/delete operations, the caller must update the | |
1654 | corresponding _filecache entry appropriately. Use __class__.<attr>.set() |
|
1661 | corresponding _filecache entry appropriately. Use __class__.<attr>.set() | |
1655 | instead of directly setting <attr>. |
|
1662 | instead of directly setting <attr>. | |
1656 |
|
1663 | |||
1657 | When using the property API, the cached data is always used if available. |
|
1664 | When using the property API, the cached data is always used if available. | |
1658 | No stat() is performed to check if the file has changed. |
|
1665 | No stat() is performed to check if the file has changed. | |
1659 |
|
1666 | |||
1660 | Others can muck about with the state of the ``_filecache`` dict. e.g. they |
|
1667 | Others can muck about with the state of the ``_filecache`` dict. e.g. they | |
1661 | can populate an entry before the property's getter is called. In this case, |
|
1668 | can populate an entry before the property's getter is called. In this case, | |
1662 | entries in ``_filecache`` will be used during property operations, |
|
1669 | entries in ``_filecache`` will be used during property operations, | |
1663 | if available. If the underlying file changes, it is up to external callers |
|
1670 | if available. If the underlying file changes, it is up to external callers | |
1664 | to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached |
|
1671 | to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached | |
1665 | method result as well as possibly calling ``del obj._filecache[attr]`` to |
|
1672 | method result as well as possibly calling ``del obj._filecache[attr]`` to | |
1666 | remove the ``filecacheentry``. |
|
1673 | remove the ``filecacheentry``. | |
1667 | """ |
|
1674 | """ | |
1668 |
|
1675 | |||
1669 | def __init__(self, *paths): |
|
1676 | def __init__(self, *paths): | |
1670 | self.paths = paths |
|
1677 | self.paths = paths | |
1671 |
|
1678 | |||
1672 | def join(self, obj, fname): |
|
1679 | def join(self, obj, fname): | |
1673 | """Used to compute the runtime path of a cached file. |
|
1680 | """Used to compute the runtime path of a cached file. | |
1674 |
|
1681 | |||
1675 | Users should subclass filecache and provide their own version of this |
|
1682 | Users should subclass filecache and provide their own version of this | |
1676 | function to call the appropriate join function on 'obj' (an instance |
|
1683 | function to call the appropriate join function on 'obj' (an instance | |
1677 | of the class that its member function was decorated). |
|
1684 | of the class that its member function was decorated). | |
1678 | """ |
|
1685 | """ | |
1679 | raise NotImplementedError |
|
1686 | raise NotImplementedError | |
1680 |
|
1687 | |||
1681 | def __call__(self, func): |
|
1688 | def __call__(self, func): | |
1682 | self.func = func |
|
1689 | self.func = func | |
1683 | self.sname = func.__name__ |
|
1690 | self.sname = func.__name__ | |
1684 | self.name = pycompat.sysbytes(self.sname) |
|
1691 | self.name = pycompat.sysbytes(self.sname) | |
1685 | return self |
|
1692 | return self | |
1686 |
|
1693 | |||
1687 | def __get__(self, obj, type=None): |
|
1694 | def __get__(self, obj, type=None): | |
1688 | # if accessed on the class, return the descriptor itself. |
|
1695 | # if accessed on the class, return the descriptor itself. | |
1689 | if obj is None: |
|
1696 | if obj is None: | |
1690 | return self |
|
1697 | return self | |
1691 |
|
1698 | |||
1692 | assert self.sname not in obj.__dict__ |
|
1699 | assert self.sname not in obj.__dict__ | |
1693 |
|
1700 | |||
1694 | entry = obj._filecache.get(self.name) |
|
1701 | entry = obj._filecache.get(self.name) | |
1695 |
|
1702 | |||
1696 | if entry: |
|
1703 | if entry: | |
1697 | if entry.changed(): |
|
1704 | if entry.changed(): | |
1698 | entry.obj = self.func(obj) |
|
1705 | entry.obj = self.func(obj) | |
1699 | else: |
|
1706 | else: | |
1700 | paths = [self.join(obj, path) for path in self.paths] |
|
1707 | paths = [self.join(obj, path) for path in self.paths] | |
1701 |
|
1708 | |||
1702 | # We stat -before- creating the object so our cache doesn't lie if |
|
1709 | # We stat -before- creating the object so our cache doesn't lie if | |
1703 | # a writer modified between the time we read and stat |
|
1710 | # a writer modified between the time we read and stat | |
1704 | entry = filecacheentry(paths, True) |
|
1711 | entry = filecacheentry(paths, True) | |
1705 | entry.obj = self.func(obj) |
|
1712 | entry.obj = self.func(obj) | |
1706 |
|
1713 | |||
1707 | obj._filecache[self.name] = entry |
|
1714 | obj._filecache[self.name] = entry | |
1708 |
|
1715 | |||
1709 | obj.__dict__[self.sname] = entry.obj |
|
1716 | obj.__dict__[self.sname] = entry.obj | |
1710 | return entry.obj |
|
1717 | return entry.obj | |
1711 |
|
1718 | |||
1712 | # don't implement __set__(), which would make __dict__ lookup as slow as |
|
1719 | # don't implement __set__(), which would make __dict__ lookup as slow as | |
1713 | # function call. |
|
1720 | # function call. | |
1714 |
|
1721 | |||
1715 | def set(self, obj, value): |
|
1722 | def set(self, obj, value): | |
1716 | if self.name not in obj._filecache: |
|
1723 | if self.name not in obj._filecache: | |
1717 | # we add an entry for the missing value because X in __dict__ |
|
1724 | # we add an entry for the missing value because X in __dict__ | |
1718 | # implies X in _filecache |
|
1725 | # implies X in _filecache | |
1719 | paths = [self.join(obj, path) for path in self.paths] |
|
1726 | paths = [self.join(obj, path) for path in self.paths] | |
1720 | ce = filecacheentry(paths, False) |
|
1727 | ce = filecacheentry(paths, False) | |
1721 | obj._filecache[self.name] = ce |
|
1728 | obj._filecache[self.name] = ce | |
1722 | else: |
|
1729 | else: | |
1723 | ce = obj._filecache[self.name] |
|
1730 | ce = obj._filecache[self.name] | |
1724 |
|
1731 | |||
1725 | ce.obj = value # update cached copy |
|
1732 | ce.obj = value # update cached copy | |
1726 | obj.__dict__[self.sname] = value # update copy returned by obj.x |
|
1733 | obj.__dict__[self.sname] = value # update copy returned by obj.x | |
1727 |
|
1734 | |||
1728 |
|
1735 | |||
1729 | def extdatasource(repo, source): |
|
1736 | def extdatasource(repo, source): | |
1730 | """Gather a map of rev -> value dict from the specified source |
|
1737 | """Gather a map of rev -> value dict from the specified source | |
1731 |
|
1738 | |||
1732 | A source spec is treated as a URL, with a special case shell: type |
|
1739 | A source spec is treated as a URL, with a special case shell: type | |
1733 | for parsing the output from a shell command. |
|
1740 | for parsing the output from a shell command. | |
1734 |
|
1741 | |||
1735 | The data is parsed as a series of newline-separated records where |
|
1742 | The data is parsed as a series of newline-separated records where | |
1736 | each record is a revision specifier optionally followed by a space |
|
1743 | each record is a revision specifier optionally followed by a space | |
1737 | and a freeform string value. If the revision is known locally, it |
|
1744 | and a freeform string value. If the revision is known locally, it | |
1738 | is converted to a rev, otherwise the record is skipped. |
|
1745 | is converted to a rev, otherwise the record is skipped. | |
1739 |
|
1746 | |||
1740 | Note that both key and value are treated as UTF-8 and converted to |
|
1747 | Note that both key and value are treated as UTF-8 and converted to | |
1741 | the local encoding. This allows uniformity between local and |
|
1748 | the local encoding. This allows uniformity between local and | |
1742 | remote data sources. |
|
1749 | remote data sources. | |
1743 | """ |
|
1750 | """ | |
1744 |
|
1751 | |||
1745 | spec = repo.ui.config(b"extdata", source) |
|
1752 | spec = repo.ui.config(b"extdata", source) | |
1746 | if not spec: |
|
1753 | if not spec: | |
1747 | raise error.Abort(_(b"unknown extdata source '%s'") % source) |
|
1754 | raise error.Abort(_(b"unknown extdata source '%s'") % source) | |
1748 |
|
1755 | |||
1749 | data = {} |
|
1756 | data = {} | |
1750 | src = proc = None |
|
1757 | src = proc = None | |
1751 | try: |
|
1758 | try: | |
1752 | if spec.startswith(b"shell:"): |
|
1759 | if spec.startswith(b"shell:"): | |
1753 | # external commands should be run relative to the repo root |
|
1760 | # external commands should be run relative to the repo root | |
1754 | cmd = spec[6:] |
|
1761 | cmd = spec[6:] | |
1755 | proc = subprocess.Popen( |
|
1762 | proc = subprocess.Popen( | |
1756 | procutil.tonativestr(cmd), |
|
1763 | procutil.tonativestr(cmd), | |
1757 | shell=True, |
|
1764 | shell=True, | |
1758 | bufsize=-1, |
|
1765 | bufsize=-1, | |
1759 | close_fds=procutil.closefds, |
|
1766 | close_fds=procutil.closefds, | |
1760 | stdout=subprocess.PIPE, |
|
1767 | stdout=subprocess.PIPE, | |
1761 | cwd=procutil.tonativestr(repo.root), |
|
1768 | cwd=procutil.tonativestr(repo.root), | |
1762 | ) |
|
1769 | ) | |
1763 | src = proc.stdout |
|
1770 | src = proc.stdout | |
1764 | else: |
|
1771 | else: | |
1765 | # treat as a URL or file |
|
1772 | # treat as a URL or file | |
1766 | src = url.open(repo.ui, spec) |
|
1773 | src = url.open(repo.ui, spec) | |
1767 | for l in src: |
|
1774 | for l in src: | |
1768 | if b" " in l: |
|
1775 | if b" " in l: | |
1769 | k, v = l.strip().split(b" ", 1) |
|
1776 | k, v = l.strip().split(b" ", 1) | |
1770 | else: |
|
1777 | else: | |
1771 | k, v = l.strip(), b"" |
|
1778 | k, v = l.strip(), b"" | |
1772 |
|
1779 | |||
1773 | k = encoding.tolocal(k) |
|
1780 | k = encoding.tolocal(k) | |
1774 | try: |
|
1781 | try: | |
1775 | data[revsingle(repo, k).rev()] = encoding.tolocal(v) |
|
1782 | data[revsingle(repo, k).rev()] = encoding.tolocal(v) | |
1776 | except (error.LookupError, error.RepoLookupError): |
|
1783 | except (error.LookupError, error.RepoLookupError): | |
1777 | pass # we ignore data for nodes that don't exist locally |
|
1784 | pass # we ignore data for nodes that don't exist locally | |
1778 | finally: |
|
1785 | finally: | |
1779 | if proc: |
|
1786 | if proc: | |
1780 | try: |
|
1787 | try: | |
1781 | proc.communicate() |
|
1788 | proc.communicate() | |
1782 | except ValueError: |
|
1789 | except ValueError: | |
1783 | # This happens if we started iterating src and then |
|
1790 | # This happens if we started iterating src and then | |
1784 | # get a parse error on a line. It should be safe to ignore. |
|
1791 | # get a parse error on a line. It should be safe to ignore. | |
1785 | pass |
|
1792 | pass | |
1786 | if src: |
|
1793 | if src: | |
1787 | src.close() |
|
1794 | src.close() | |
1788 | if proc and proc.returncode != 0: |
|
1795 | if proc and proc.returncode != 0: | |
1789 | raise error.Abort( |
|
1796 | raise error.Abort( | |
1790 | _(b"extdata command '%s' failed: %s") |
|
1797 | _(b"extdata command '%s' failed: %s") | |
1791 | % (cmd, procutil.explainexit(proc.returncode)) |
|
1798 | % (cmd, procutil.explainexit(proc.returncode)) | |
1792 | ) |
|
1799 | ) | |
1793 |
|
1800 | |||
1794 | return data |
|
1801 | return data | |
1795 |
|
1802 | |||
1796 |
|
1803 | |||
1797 | class progress(object): |
|
1804 | class progress(object): | |
1798 | def __init__(self, ui, updatebar, topic, unit=b"", total=None): |
|
1805 | def __init__(self, ui, updatebar, topic, unit=b"", total=None): | |
1799 | self.ui = ui |
|
1806 | self.ui = ui | |
1800 | self.pos = 0 |
|
1807 | self.pos = 0 | |
1801 | self.topic = topic |
|
1808 | self.topic = topic | |
1802 | self.unit = unit |
|
1809 | self.unit = unit | |
1803 | self.total = total |
|
1810 | self.total = total | |
1804 | self.debug = ui.configbool(b'progress', b'debug') |
|
1811 | self.debug = ui.configbool(b'progress', b'debug') | |
1805 | self._updatebar = updatebar |
|
1812 | self._updatebar = updatebar | |
1806 |
|
1813 | |||
1807 | def __enter__(self): |
|
1814 | def __enter__(self): | |
1808 | return self |
|
1815 | return self | |
1809 |
|
1816 | |||
1810 | def __exit__(self, exc_type, exc_value, exc_tb): |
|
1817 | def __exit__(self, exc_type, exc_value, exc_tb): | |
1811 | self.complete() |
|
1818 | self.complete() | |
1812 |
|
1819 | |||
1813 | def update(self, pos, item=b"", total=None): |
|
1820 | def update(self, pos, item=b"", total=None): | |
1814 | assert pos is not None |
|
1821 | assert pos is not None | |
1815 | if total: |
|
1822 | if total: | |
1816 | self.total = total |
|
1823 | self.total = total | |
1817 | self.pos = pos |
|
1824 | self.pos = pos | |
1818 | self._updatebar(self.topic, self.pos, item, self.unit, self.total) |
|
1825 | self._updatebar(self.topic, self.pos, item, self.unit, self.total) | |
1819 | if self.debug: |
|
1826 | if self.debug: | |
1820 | self._printdebug(item) |
|
1827 | self._printdebug(item) | |
1821 |
|
1828 | |||
1822 | def increment(self, step=1, item=b"", total=None): |
|
1829 | def increment(self, step=1, item=b"", total=None): | |
1823 | self.update(self.pos + step, item, total) |
|
1830 | self.update(self.pos + step, item, total) | |
1824 |
|
1831 | |||
1825 | def complete(self): |
|
1832 | def complete(self): | |
1826 | self.pos = None |
|
1833 | self.pos = None | |
1827 | self.unit = b"" |
|
1834 | self.unit = b"" | |
1828 | self.total = None |
|
1835 | self.total = None | |
1829 | self._updatebar(self.topic, self.pos, b"", self.unit, self.total) |
|
1836 | self._updatebar(self.topic, self.pos, b"", self.unit, self.total) | |
1830 |
|
1837 | |||
1831 | def _printdebug(self, item): |
|
1838 | def _printdebug(self, item): | |
1832 | unit = b'' |
|
1839 | unit = b'' | |
1833 | if self.unit: |
|
1840 | if self.unit: | |
1834 | unit = b' ' + self.unit |
|
1841 | unit = b' ' + self.unit | |
1835 | if item: |
|
1842 | if item: | |
1836 | item = b' ' + item |
|
1843 | item = b' ' + item | |
1837 |
|
1844 | |||
1838 | if self.total: |
|
1845 | if self.total: | |
1839 | pct = 100.0 * self.pos / self.total |
|
1846 | pct = 100.0 * self.pos / self.total | |
1840 | self.ui.debug( |
|
1847 | self.ui.debug( | |
1841 | b'%s:%s %d/%d%s (%4.2f%%)\n' |
|
1848 | b'%s:%s %d/%d%s (%4.2f%%)\n' | |
1842 | % (self.topic, item, self.pos, self.total, unit, pct) |
|
1849 | % (self.topic, item, self.pos, self.total, unit, pct) | |
1843 | ) |
|
1850 | ) | |
1844 | else: |
|
1851 | else: | |
1845 | self.ui.debug(b'%s:%s %d%s\n' % (self.topic, item, self.pos, unit)) |
|
1852 | self.ui.debug(b'%s:%s %d%s\n' % (self.topic, item, self.pos, unit)) | |
1846 |
|
1853 | |||
1847 |
|
1854 | |||
1848 | def gdinitconfig(ui): |
|
1855 | def gdinitconfig(ui): | |
1849 | """helper function to know if a repo should be created as general delta |
|
1856 | """helper function to know if a repo should be created as general delta | |
1850 | """ |
|
1857 | """ | |
1851 | # experimental config: format.generaldelta |
|
1858 | # experimental config: format.generaldelta | |
1852 | return ui.configbool(b'format', b'generaldelta') or ui.configbool( |
|
1859 | return ui.configbool(b'format', b'generaldelta') or ui.configbool( | |
1853 | b'format', b'usegeneraldelta' |
|
1860 | b'format', b'usegeneraldelta' | |
1854 | ) |
|
1861 | ) | |
1855 |
|
1862 | |||
1856 |
|
1863 | |||
1857 | def gddeltaconfig(ui): |
|
1864 | def gddeltaconfig(ui): | |
1858 | """helper function to know if incoming delta should be optimised |
|
1865 | """helper function to know if incoming delta should be optimised | |
1859 | """ |
|
1866 | """ | |
1860 | # experimental config: format.generaldelta |
|
1867 | # experimental config: format.generaldelta | |
1861 | return ui.configbool(b'format', b'generaldelta') |
|
1868 | return ui.configbool(b'format', b'generaldelta') | |
1862 |
|
1869 | |||
1863 |
|
1870 | |||
1864 | class simplekeyvaluefile(object): |
|
1871 | class simplekeyvaluefile(object): | |
1865 | """A simple file with key=value lines |
|
1872 | """A simple file with key=value lines | |
1866 |
|
1873 | |||
1867 | Keys must be alphanumerics and start with a letter, values must not |
|
1874 | Keys must be alphanumerics and start with a letter, values must not | |
1868 | contain '\n' characters""" |
|
1875 | contain '\n' characters""" | |
1869 |
|
1876 | |||
1870 | firstlinekey = b'__firstline' |
|
1877 | firstlinekey = b'__firstline' | |
1871 |
|
1878 | |||
1872 | def __init__(self, vfs, path, keys=None): |
|
1879 | def __init__(self, vfs, path, keys=None): | |
1873 | self.vfs = vfs |
|
1880 | self.vfs = vfs | |
1874 | self.path = path |
|
1881 | self.path = path | |
1875 |
|
1882 | |||
1876 | def read(self, firstlinenonkeyval=False): |
|
1883 | def read(self, firstlinenonkeyval=False): | |
1877 | """Read the contents of a simple key-value file |
|
1884 | """Read the contents of a simple key-value file | |
1878 |
|
1885 | |||
1879 | 'firstlinenonkeyval' indicates whether the first line of file should |
|
1886 | 'firstlinenonkeyval' indicates whether the first line of file should | |
1880 | be treated as a key-value pair or reuturned fully under the |
|
1887 | be treated as a key-value pair or reuturned fully under the | |
1881 | __firstline key.""" |
|
1888 | __firstline key.""" | |
1882 | lines = self.vfs.readlines(self.path) |
|
1889 | lines = self.vfs.readlines(self.path) | |
1883 | d = {} |
|
1890 | d = {} | |
1884 | if firstlinenonkeyval: |
|
1891 | if firstlinenonkeyval: | |
1885 | if not lines: |
|
1892 | if not lines: | |
1886 | e = _(b"empty simplekeyvalue file") |
|
1893 | e = _(b"empty simplekeyvalue file") | |
1887 | raise error.CorruptedState(e) |
|
1894 | raise error.CorruptedState(e) | |
1888 | # we don't want to include '\n' in the __firstline |
|
1895 | # we don't want to include '\n' in the __firstline | |
1889 | d[self.firstlinekey] = lines[0][:-1] |
|
1896 | d[self.firstlinekey] = lines[0][:-1] | |
1890 | del lines[0] |
|
1897 | del lines[0] | |
1891 |
|
1898 | |||
1892 | try: |
|
1899 | try: | |
1893 | # the 'if line.strip()' part prevents us from failing on empty |
|
1900 | # the 'if line.strip()' part prevents us from failing on empty | |
1894 | # lines which only contain '\n' therefore are not skipped |
|
1901 | # lines which only contain '\n' therefore are not skipped | |
1895 | # by 'if line' |
|
1902 | # by 'if line' | |
1896 | updatedict = dict( |
|
1903 | updatedict = dict( | |
1897 | line[:-1].split(b'=', 1) for line in lines if line.strip() |
|
1904 | line[:-1].split(b'=', 1) for line in lines if line.strip() | |
1898 | ) |
|
1905 | ) | |
1899 | if self.firstlinekey in updatedict: |
|
1906 | if self.firstlinekey in updatedict: | |
1900 | e = _(b"%r can't be used as a key") |
|
1907 | e = _(b"%r can't be used as a key") | |
1901 | raise error.CorruptedState(e % self.firstlinekey) |
|
1908 | raise error.CorruptedState(e % self.firstlinekey) | |
1902 | d.update(updatedict) |
|
1909 | d.update(updatedict) | |
1903 | except ValueError as e: |
|
1910 | except ValueError as e: | |
1904 | raise error.CorruptedState(stringutil.forcebytestr(e)) |
|
1911 | raise error.CorruptedState(stringutil.forcebytestr(e)) | |
1905 | return d |
|
1912 | return d | |
1906 |
|
1913 | |||
1907 | def write(self, data, firstline=None): |
|
1914 | def write(self, data, firstline=None): | |
1908 | """Write key=>value mapping to a file |
|
1915 | """Write key=>value mapping to a file | |
1909 | data is a dict. Keys must be alphanumerical and start with a letter. |
|
1916 | data is a dict. Keys must be alphanumerical and start with a letter. | |
1910 | Values must not contain newline characters. |
|
1917 | Values must not contain newline characters. | |
1911 |
|
1918 | |||
1912 | If 'firstline' is not None, it is written to file before |
|
1919 | If 'firstline' is not None, it is written to file before | |
1913 | everything else, as it is, not in a key=value form""" |
|
1920 | everything else, as it is, not in a key=value form""" | |
1914 | lines = [] |
|
1921 | lines = [] | |
1915 | if firstline is not None: |
|
1922 | if firstline is not None: | |
1916 | lines.append(b'%s\n' % firstline) |
|
1923 | lines.append(b'%s\n' % firstline) | |
1917 |
|
1924 | |||
1918 | for k, v in data.items(): |
|
1925 | for k, v in data.items(): | |
1919 | if k == self.firstlinekey: |
|
1926 | if k == self.firstlinekey: | |
1920 | e = b"key name '%s' is reserved" % self.firstlinekey |
|
1927 | e = b"key name '%s' is reserved" % self.firstlinekey | |
1921 | raise error.ProgrammingError(e) |
|
1928 | raise error.ProgrammingError(e) | |
1922 | if not k[0:1].isalpha(): |
|
1929 | if not k[0:1].isalpha(): | |
1923 | e = b"keys must start with a letter in a key-value file" |
|
1930 | e = b"keys must start with a letter in a key-value file" | |
1924 | raise error.ProgrammingError(e) |
|
1931 | raise error.ProgrammingError(e) | |
1925 | if not k.isalnum(): |
|
1932 | if not k.isalnum(): | |
1926 | e = b"invalid key name in a simple key-value file" |
|
1933 | e = b"invalid key name in a simple key-value file" | |
1927 | raise error.ProgrammingError(e) |
|
1934 | raise error.ProgrammingError(e) | |
1928 | if b'\n' in v: |
|
1935 | if b'\n' in v: | |
1929 | e = b"invalid value in a simple key-value file" |
|
1936 | e = b"invalid value in a simple key-value file" | |
1930 | raise error.ProgrammingError(e) |
|
1937 | raise error.ProgrammingError(e) | |
1931 | lines.append(b"%s=%s\n" % (k, v)) |
|
1938 | lines.append(b"%s=%s\n" % (k, v)) | |
1932 | with self.vfs(self.path, mode=b'wb', atomictemp=True) as fp: |
|
1939 | with self.vfs(self.path, mode=b'wb', atomictemp=True) as fp: | |
1933 | fp.write(b''.join(lines)) |
|
1940 | fp.write(b''.join(lines)) | |
1934 |
|
1941 | |||
1935 |
|
1942 | |||
1936 | _reportobsoletedsource = [ |
|
1943 | _reportobsoletedsource = [ | |
1937 | b'debugobsolete', |
|
1944 | b'debugobsolete', | |
1938 | b'pull', |
|
1945 | b'pull', | |
1939 | b'push', |
|
1946 | b'push', | |
1940 | b'serve', |
|
1947 | b'serve', | |
1941 | b'unbundle', |
|
1948 | b'unbundle', | |
1942 | ] |
|
1949 | ] | |
1943 |
|
1950 | |||
1944 | _reportnewcssource = [ |
|
1951 | _reportnewcssource = [ | |
1945 | b'pull', |
|
1952 | b'pull', | |
1946 | b'unbundle', |
|
1953 | b'unbundle', | |
1947 | ] |
|
1954 | ] | |
1948 |
|
1955 | |||
1949 |
|
1956 | |||
1950 | def prefetchfiles(repo, revmatches): |
|
1957 | def prefetchfiles(repo, revmatches): | |
1951 | """Invokes the registered file prefetch functions, allowing extensions to |
|
1958 | """Invokes the registered file prefetch functions, allowing extensions to | |
1952 | ensure the corresponding files are available locally, before the command |
|
1959 | ensure the corresponding files are available locally, before the command | |
1953 | uses them. |
|
1960 | uses them. | |
1954 |
|
1961 | |||
1955 | Args: |
|
1962 | Args: | |
1956 | revmatches: a list of (revision, match) tuples to indicate the files to |
|
1963 | revmatches: a list of (revision, match) tuples to indicate the files to | |
1957 | fetch at each revision. If any of the match elements is None, it matches |
|
1964 | fetch at each revision. If any of the match elements is None, it matches | |
1958 | all files. |
|
1965 | all files. | |
1959 | """ |
|
1966 | """ | |
1960 |
|
1967 | |||
1961 | def _matcher(m): |
|
1968 | def _matcher(m): | |
1962 | if m: |
|
1969 | if m: | |
1963 | assert isinstance(m, matchmod.basematcher) |
|
1970 | assert isinstance(m, matchmod.basematcher) | |
1964 | # The command itself will complain about files that don't exist, so |
|
1971 | # The command itself will complain about files that don't exist, so | |
1965 | # don't duplicate the message. |
|
1972 | # don't duplicate the message. | |
1966 | return matchmod.badmatch(m, lambda fn, msg: None) |
|
1973 | return matchmod.badmatch(m, lambda fn, msg: None) | |
1967 | else: |
|
1974 | else: | |
1968 | return matchall(repo) |
|
1975 | return matchall(repo) | |
1969 |
|
1976 | |||
1970 | revbadmatches = [(rev, _matcher(match)) for (rev, match) in revmatches] |
|
1977 | revbadmatches = [(rev, _matcher(match)) for (rev, match) in revmatches] | |
1971 |
|
1978 | |||
1972 | fileprefetchhooks(repo, revbadmatches) |
|
1979 | fileprefetchhooks(repo, revbadmatches) | |
1973 |
|
1980 | |||
1974 |
|
1981 | |||
1975 | # a list of (repo, revs, match) prefetch functions |
|
1982 | # a list of (repo, revs, match) prefetch functions | |
1976 | fileprefetchhooks = util.hooks() |
|
1983 | fileprefetchhooks = util.hooks() | |
1977 |
|
1984 | |||
1978 | # A marker that tells the evolve extension to suppress its own reporting |
|
1985 | # A marker that tells the evolve extension to suppress its own reporting | |
1979 | _reportstroubledchangesets = True |
|
1986 | _reportstroubledchangesets = True | |
1980 |
|
1987 | |||
1981 |
|
1988 | |||
1982 | def registersummarycallback(repo, otr, txnname=b'', as_validator=False): |
|
1989 | def registersummarycallback(repo, otr, txnname=b'', as_validator=False): | |
1983 | """register a callback to issue a summary after the transaction is closed |
|
1990 | """register a callback to issue a summary after the transaction is closed | |
1984 |
|
1991 | |||
1985 | If as_validator is true, then the callbacks are registered as transaction |
|
1992 | If as_validator is true, then the callbacks are registered as transaction | |
1986 | validators instead |
|
1993 | validators instead | |
1987 | """ |
|
1994 | """ | |
1988 |
|
1995 | |||
1989 | def txmatch(sources): |
|
1996 | def txmatch(sources): | |
1990 | return any(txnname.startswith(source) for source in sources) |
|
1997 | return any(txnname.startswith(source) for source in sources) | |
1991 |
|
1998 | |||
1992 | categories = [] |
|
1999 | categories = [] | |
1993 |
|
2000 | |||
1994 | def reportsummary(func): |
|
2001 | def reportsummary(func): | |
1995 | """decorator for report callbacks.""" |
|
2002 | """decorator for report callbacks.""" | |
1996 | # The repoview life cycle is shorter than the one of the actual |
|
2003 | # The repoview life cycle is shorter than the one of the actual | |
1997 | # underlying repository. So the filtered object can die before the |
|
2004 | # underlying repository. So the filtered object can die before the | |
1998 | # weakref is used leading to troubles. We keep a reference to the |
|
2005 | # weakref is used leading to troubles. We keep a reference to the | |
1999 | # unfiltered object and restore the filtering when retrieving the |
|
2006 | # unfiltered object and restore the filtering when retrieving the | |
2000 | # repository through the weakref. |
|
2007 | # repository through the weakref. | |
2001 | filtername = repo.filtername |
|
2008 | filtername = repo.filtername | |
2002 | reporef = weakref.ref(repo.unfiltered()) |
|
2009 | reporef = weakref.ref(repo.unfiltered()) | |
2003 |
|
2010 | |||
2004 | def wrapped(tr): |
|
2011 | def wrapped(tr): | |
2005 | repo = reporef() |
|
2012 | repo = reporef() | |
2006 | if filtername: |
|
2013 | if filtername: | |
2007 | assert repo is not None # help pytype |
|
2014 | assert repo is not None # help pytype | |
2008 | repo = repo.filtered(filtername) |
|
2015 | repo = repo.filtered(filtername) | |
2009 | func(repo, tr) |
|
2016 | func(repo, tr) | |
2010 |
|
2017 | |||
2011 | newcat = b'%02i-txnreport' % len(categories) |
|
2018 | newcat = b'%02i-txnreport' % len(categories) | |
2012 | if as_validator: |
|
2019 | if as_validator: | |
2013 | otr.addvalidator(newcat, wrapped) |
|
2020 | otr.addvalidator(newcat, wrapped) | |
2014 | else: |
|
2021 | else: | |
2015 | otr.addpostclose(newcat, wrapped) |
|
2022 | otr.addpostclose(newcat, wrapped) | |
2016 | categories.append(newcat) |
|
2023 | categories.append(newcat) | |
2017 | return wrapped |
|
2024 | return wrapped | |
2018 |
|
2025 | |||
2019 | @reportsummary |
|
2026 | @reportsummary | |
2020 | def reportchangegroup(repo, tr): |
|
2027 | def reportchangegroup(repo, tr): | |
2021 | cgchangesets = tr.changes.get(b'changegroup-count-changesets', 0) |
|
2028 | cgchangesets = tr.changes.get(b'changegroup-count-changesets', 0) | |
2022 | cgrevisions = tr.changes.get(b'changegroup-count-revisions', 0) |
|
2029 | cgrevisions = tr.changes.get(b'changegroup-count-revisions', 0) | |
2023 | cgfiles = tr.changes.get(b'changegroup-count-files', 0) |
|
2030 | cgfiles = tr.changes.get(b'changegroup-count-files', 0) | |
2024 | cgheads = tr.changes.get(b'changegroup-count-heads', 0) |
|
2031 | cgheads = tr.changes.get(b'changegroup-count-heads', 0) | |
2025 | if cgchangesets or cgrevisions or cgfiles: |
|
2032 | if cgchangesets or cgrevisions or cgfiles: | |
2026 | htext = b"" |
|
2033 | htext = b"" | |
2027 | if cgheads: |
|
2034 | if cgheads: | |
2028 | htext = _(b" (%+d heads)") % cgheads |
|
2035 | htext = _(b" (%+d heads)") % cgheads | |
2029 | msg = _(b"added %d changesets with %d changes to %d files%s\n") |
|
2036 | msg = _(b"added %d changesets with %d changes to %d files%s\n") | |
2030 | if as_validator: |
|
2037 | if as_validator: | |
2031 | msg = _(b"adding %d changesets with %d changes to %d files%s\n") |
|
2038 | msg = _(b"adding %d changesets with %d changes to %d files%s\n") | |
2032 | assert repo is not None # help pytype |
|
2039 | assert repo is not None # help pytype | |
2033 | repo.ui.status(msg % (cgchangesets, cgrevisions, cgfiles, htext)) |
|
2040 | repo.ui.status(msg % (cgchangesets, cgrevisions, cgfiles, htext)) | |
2034 |
|
2041 | |||
2035 | if txmatch(_reportobsoletedsource): |
|
2042 | if txmatch(_reportobsoletedsource): | |
2036 |
|
2043 | |||
2037 | @reportsummary |
|
2044 | @reportsummary | |
2038 | def reportobsoleted(repo, tr): |
|
2045 | def reportobsoleted(repo, tr): | |
2039 | obsoleted = obsutil.getobsoleted(repo, tr) |
|
2046 | obsoleted = obsutil.getobsoleted(repo, tr) | |
2040 | newmarkers = len(tr.changes.get(b'obsmarkers', ())) |
|
2047 | newmarkers = len(tr.changes.get(b'obsmarkers', ())) | |
2041 | if newmarkers: |
|
2048 | if newmarkers: | |
2042 | repo.ui.status(_(b'%i new obsolescence markers\n') % newmarkers) |
|
2049 | repo.ui.status(_(b'%i new obsolescence markers\n') % newmarkers) | |
2043 | if obsoleted: |
|
2050 | if obsoleted: | |
2044 | msg = _(b'obsoleted %i changesets\n') |
|
2051 | msg = _(b'obsoleted %i changesets\n') | |
2045 | if as_validator: |
|
2052 | if as_validator: | |
2046 | msg = _(b'obsoleting %i changesets\n') |
|
2053 | msg = _(b'obsoleting %i changesets\n') | |
2047 | repo.ui.status(msg % len(obsoleted)) |
|
2054 | repo.ui.status(msg % len(obsoleted)) | |
2048 |
|
2055 | |||
2049 | if obsolete.isenabled( |
|
2056 | if obsolete.isenabled( | |
2050 | repo, obsolete.createmarkersopt |
|
2057 | repo, obsolete.createmarkersopt | |
2051 | ) and repo.ui.configbool( |
|
2058 | ) and repo.ui.configbool( | |
2052 | b'experimental', b'evolution.report-instabilities' |
|
2059 | b'experimental', b'evolution.report-instabilities' | |
2053 | ): |
|
2060 | ): | |
2054 | instabilitytypes = [ |
|
2061 | instabilitytypes = [ | |
2055 | (b'orphan', b'orphan'), |
|
2062 | (b'orphan', b'orphan'), | |
2056 | (b'phase-divergent', b'phasedivergent'), |
|
2063 | (b'phase-divergent', b'phasedivergent'), | |
2057 | (b'content-divergent', b'contentdivergent'), |
|
2064 | (b'content-divergent', b'contentdivergent'), | |
2058 | ] |
|
2065 | ] | |
2059 |
|
2066 | |||
2060 | def getinstabilitycounts(repo): |
|
2067 | def getinstabilitycounts(repo): | |
2061 | filtered = repo.changelog.filteredrevs |
|
2068 | filtered = repo.changelog.filteredrevs | |
2062 | counts = {} |
|
2069 | counts = {} | |
2063 | for instability, revset in instabilitytypes: |
|
2070 | for instability, revset in instabilitytypes: | |
2064 | counts[instability] = len( |
|
2071 | counts[instability] = len( | |
2065 | set(obsolete.getrevs(repo, revset)) - filtered |
|
2072 | set(obsolete.getrevs(repo, revset)) - filtered | |
2066 | ) |
|
2073 | ) | |
2067 | return counts |
|
2074 | return counts | |
2068 |
|
2075 | |||
2069 | oldinstabilitycounts = getinstabilitycounts(repo) |
|
2076 | oldinstabilitycounts = getinstabilitycounts(repo) | |
2070 |
|
2077 | |||
2071 | @reportsummary |
|
2078 | @reportsummary | |
2072 | def reportnewinstabilities(repo, tr): |
|
2079 | def reportnewinstabilities(repo, tr): | |
2073 | newinstabilitycounts = getinstabilitycounts(repo) |
|
2080 | newinstabilitycounts = getinstabilitycounts(repo) | |
2074 | for instability, revset in instabilitytypes: |
|
2081 | for instability, revset in instabilitytypes: | |
2075 | delta = ( |
|
2082 | delta = ( | |
2076 | newinstabilitycounts[instability] |
|
2083 | newinstabilitycounts[instability] | |
2077 | - oldinstabilitycounts[instability] |
|
2084 | - oldinstabilitycounts[instability] | |
2078 | ) |
|
2085 | ) | |
2079 | msg = getinstabilitymessage(delta, instability) |
|
2086 | msg = getinstabilitymessage(delta, instability) | |
2080 | if msg: |
|
2087 | if msg: | |
2081 | repo.ui.warn(msg) |
|
2088 | repo.ui.warn(msg) | |
2082 |
|
2089 | |||
2083 | if txmatch(_reportnewcssource): |
|
2090 | if txmatch(_reportnewcssource): | |
2084 |
|
2091 | |||
2085 | @reportsummary |
|
2092 | @reportsummary | |
2086 | def reportnewcs(repo, tr): |
|
2093 | def reportnewcs(repo, tr): | |
2087 | """Report the range of new revisions pulled/unbundled.""" |
|
2094 | """Report the range of new revisions pulled/unbundled.""" | |
2088 | origrepolen = tr.changes.get(b'origrepolen', len(repo)) |
|
2095 | origrepolen = tr.changes.get(b'origrepolen', len(repo)) | |
2089 | unfi = repo.unfiltered() |
|
2096 | unfi = repo.unfiltered() | |
2090 | if origrepolen >= len(unfi): |
|
2097 | if origrepolen >= len(unfi): | |
2091 | return |
|
2098 | return | |
2092 |
|
2099 | |||
2093 | # Compute the bounds of new visible revisions' range. |
|
2100 | # Compute the bounds of new visible revisions' range. | |
2094 | revs = smartset.spanset(repo, start=origrepolen) |
|
2101 | revs = smartset.spanset(repo, start=origrepolen) | |
2095 | if revs: |
|
2102 | if revs: | |
2096 | minrev, maxrev = repo[revs.min()], repo[revs.max()] |
|
2103 | minrev, maxrev = repo[revs.min()], repo[revs.max()] | |
2097 |
|
2104 | |||
2098 | if minrev == maxrev: |
|
2105 | if minrev == maxrev: | |
2099 | revrange = minrev |
|
2106 | revrange = minrev | |
2100 | else: |
|
2107 | else: | |
2101 | revrange = b'%s:%s' % (minrev, maxrev) |
|
2108 | revrange = b'%s:%s' % (minrev, maxrev) | |
2102 | draft = len(repo.revs(b'%ld and draft()', revs)) |
|
2109 | draft = len(repo.revs(b'%ld and draft()', revs)) | |
2103 | secret = len(repo.revs(b'%ld and secret()', revs)) |
|
2110 | secret = len(repo.revs(b'%ld and secret()', revs)) | |
2104 | if not (draft or secret): |
|
2111 | if not (draft or secret): | |
2105 | msg = _(b'new changesets %s\n') % revrange |
|
2112 | msg = _(b'new changesets %s\n') % revrange | |
2106 | elif draft and secret: |
|
2113 | elif draft and secret: | |
2107 | msg = _(b'new changesets %s (%d drafts, %d secrets)\n') |
|
2114 | msg = _(b'new changesets %s (%d drafts, %d secrets)\n') | |
2108 | msg %= (revrange, draft, secret) |
|
2115 | msg %= (revrange, draft, secret) | |
2109 | elif draft: |
|
2116 | elif draft: | |
2110 | msg = _(b'new changesets %s (%d drafts)\n') |
|
2117 | msg = _(b'new changesets %s (%d drafts)\n') | |
2111 | msg %= (revrange, draft) |
|
2118 | msg %= (revrange, draft) | |
2112 | elif secret: |
|
2119 | elif secret: | |
2113 | msg = _(b'new changesets %s (%d secrets)\n') |
|
2120 | msg = _(b'new changesets %s (%d secrets)\n') | |
2114 | msg %= (revrange, secret) |
|
2121 | msg %= (revrange, secret) | |
2115 | else: |
|
2122 | else: | |
2116 | errormsg = b'entered unreachable condition' |
|
2123 | errormsg = b'entered unreachable condition' | |
2117 | raise error.ProgrammingError(errormsg) |
|
2124 | raise error.ProgrammingError(errormsg) | |
2118 | repo.ui.status(msg) |
|
2125 | repo.ui.status(msg) | |
2119 |
|
2126 | |||
2120 | # search new changesets directly pulled as obsolete |
|
2127 | # search new changesets directly pulled as obsolete | |
2121 | duplicates = tr.changes.get(b'revduplicates', ()) |
|
2128 | duplicates = tr.changes.get(b'revduplicates', ()) | |
2122 | obsadded = unfi.revs( |
|
2129 | obsadded = unfi.revs( | |
2123 | b'(%d: + %ld) and obsolete()', origrepolen, duplicates |
|
2130 | b'(%d: + %ld) and obsolete()', origrepolen, duplicates | |
2124 | ) |
|
2131 | ) | |
2125 | cl = repo.changelog |
|
2132 | cl = repo.changelog | |
2126 | extinctadded = [r for r in obsadded if r not in cl] |
|
2133 | extinctadded = [r for r in obsadded if r not in cl] | |
2127 | if extinctadded: |
|
2134 | if extinctadded: | |
2128 | # They are not just obsolete, but obsolete and invisible |
|
2135 | # They are not just obsolete, but obsolete and invisible | |
2129 | # we call them "extinct" internally but the terms have not been |
|
2136 | # we call them "extinct" internally but the terms have not been | |
2130 | # exposed to users. |
|
2137 | # exposed to users. | |
2131 | msg = b'(%d other changesets obsolete on arrival)\n' |
|
2138 | msg = b'(%d other changesets obsolete on arrival)\n' | |
2132 | repo.ui.status(msg % len(extinctadded)) |
|
2139 | repo.ui.status(msg % len(extinctadded)) | |
2133 |
|
2140 | |||
2134 | @reportsummary |
|
2141 | @reportsummary | |
2135 | def reportphasechanges(repo, tr): |
|
2142 | def reportphasechanges(repo, tr): | |
2136 | """Report statistics of phase changes for changesets pre-existing |
|
2143 | """Report statistics of phase changes for changesets pre-existing | |
2137 | pull/unbundle. |
|
2144 | pull/unbundle. | |
2138 | """ |
|
2145 | """ | |
2139 | origrepolen = tr.changes.get(b'origrepolen', len(repo)) |
|
2146 | origrepolen = tr.changes.get(b'origrepolen', len(repo)) | |
2140 | published = [] |
|
2147 | published = [] | |
2141 | for revs, (old, new) in tr.changes.get(b'phases', []): |
|
2148 | for revs, (old, new) in tr.changes.get(b'phases', []): | |
2142 | if new != phases.public: |
|
2149 | if new != phases.public: | |
2143 | continue |
|
2150 | continue | |
2144 | published.extend(rev for rev in revs if rev < origrepolen) |
|
2151 | published.extend(rev for rev in revs if rev < origrepolen) | |
2145 | if not published: |
|
2152 | if not published: | |
2146 | return |
|
2153 | return | |
2147 | msg = _(b'%d local changesets published\n') |
|
2154 | msg = _(b'%d local changesets published\n') | |
2148 | if as_validator: |
|
2155 | if as_validator: | |
2149 | msg = _(b'%d local changesets will be published\n') |
|
2156 | msg = _(b'%d local changesets will be published\n') | |
2150 | repo.ui.status(msg % len(published)) |
|
2157 | repo.ui.status(msg % len(published)) | |
2151 |
|
2158 | |||
2152 |
|
2159 | |||
2153 | def getinstabilitymessage(delta, instability): |
|
2160 | def getinstabilitymessage(delta, instability): | |
2154 | """function to return the message to show warning about new instabilities |
|
2161 | """function to return the message to show warning about new instabilities | |
2155 |
|
2162 | |||
2156 | exists as a separate function so that extension can wrap to show more |
|
2163 | exists as a separate function so that extension can wrap to show more | |
2157 | information like how to fix instabilities""" |
|
2164 | information like how to fix instabilities""" | |
2158 | if delta > 0: |
|
2165 | if delta > 0: | |
2159 | return _(b'%i new %s changesets\n') % (delta, instability) |
|
2166 | return _(b'%i new %s changesets\n') % (delta, instability) | |
2160 |
|
2167 | |||
2161 |
|
2168 | |||
2162 | def nodesummaries(repo, nodes, maxnumnodes=4): |
|
2169 | def nodesummaries(repo, nodes, maxnumnodes=4): | |
2163 | if len(nodes) <= maxnumnodes or repo.ui.verbose: |
|
2170 | if len(nodes) <= maxnumnodes or repo.ui.verbose: | |
2164 | return b' '.join(short(h) for h in nodes) |
|
2171 | return b' '.join(short(h) for h in nodes) | |
2165 | first = b' '.join(short(h) for h in nodes[:maxnumnodes]) |
|
2172 | first = b' '.join(short(h) for h in nodes[:maxnumnodes]) | |
2166 | return _(b"%s and %d others") % (first, len(nodes) - maxnumnodes) |
|
2173 | return _(b"%s and %d others") % (first, len(nodes) - maxnumnodes) | |
2167 |
|
2174 | |||
2168 |
|
2175 | |||
2169 | def enforcesinglehead(repo, tr, desc, accountclosed=False): |
|
2176 | def enforcesinglehead(repo, tr, desc, accountclosed=False): | |
2170 | """check that no named branch has multiple heads""" |
|
2177 | """check that no named branch has multiple heads""" | |
2171 | if desc in (b'strip', b'repair'): |
|
2178 | if desc in (b'strip', b'repair'): | |
2172 | # skip the logic during strip |
|
2179 | # skip the logic during strip | |
2173 | return |
|
2180 | return | |
2174 | visible = repo.filtered(b'visible') |
|
2181 | visible = repo.filtered(b'visible') | |
2175 | # possible improvement: we could restrict the check to affected branch |
|
2182 | # possible improvement: we could restrict the check to affected branch | |
2176 | bm = visible.branchmap() |
|
2183 | bm = visible.branchmap() | |
2177 | for name in bm: |
|
2184 | for name in bm: | |
2178 | heads = bm.branchheads(name, closed=accountclosed) |
|
2185 | heads = bm.branchheads(name, closed=accountclosed) | |
2179 | if len(heads) > 1: |
|
2186 | if len(heads) > 1: | |
2180 | msg = _(b'rejecting multiple heads on branch "%s"') |
|
2187 | msg = _(b'rejecting multiple heads on branch "%s"') | |
2181 | msg %= name |
|
2188 | msg %= name | |
2182 | hint = _(b'%d heads: %s') |
|
2189 | hint = _(b'%d heads: %s') | |
2183 | hint %= (len(heads), nodesummaries(repo, heads)) |
|
2190 | hint %= (len(heads), nodesummaries(repo, heads)) | |
2184 | raise error.Abort(msg, hint=hint) |
|
2191 | raise error.Abort(msg, hint=hint) | |
2185 |
|
2192 | |||
2186 |
|
2193 | |||
2187 | def wrapconvertsink(sink): |
|
2194 | def wrapconvertsink(sink): | |
2188 | """Allow extensions to wrap the sink returned by convcmd.convertsink() |
|
2195 | """Allow extensions to wrap the sink returned by convcmd.convertsink() | |
2189 | before it is used, whether or not the convert extension was formally loaded. |
|
2196 | before it is used, whether or not the convert extension was formally loaded. | |
2190 | """ |
|
2197 | """ | |
2191 | return sink |
|
2198 | return sink | |
2192 |
|
2199 | |||
2193 |
|
2200 | |||
2194 | def unhidehashlikerevs(repo, specs, hiddentype): |
|
2201 | def unhidehashlikerevs(repo, specs, hiddentype): | |
2195 | """parse the user specs and unhide changesets whose hash or revision number |
|
2202 | """parse the user specs and unhide changesets whose hash or revision number | |
2196 | is passed. |
|
2203 | is passed. | |
2197 |
|
2204 | |||
2198 | hiddentype can be: 1) 'warn': warn while unhiding changesets |
|
2205 | hiddentype can be: 1) 'warn': warn while unhiding changesets | |
2199 | 2) 'nowarn': don't warn while unhiding changesets |
|
2206 | 2) 'nowarn': don't warn while unhiding changesets | |
2200 |
|
2207 | |||
2201 | returns a repo object with the required changesets unhidden |
|
2208 | returns a repo object with the required changesets unhidden | |
2202 | """ |
|
2209 | """ | |
2203 | if not repo.filtername or not repo.ui.configbool( |
|
2210 | if not repo.filtername or not repo.ui.configbool( | |
2204 | b'experimental', b'directaccess' |
|
2211 | b'experimental', b'directaccess' | |
2205 | ): |
|
2212 | ): | |
2206 | return repo |
|
2213 | return repo | |
2207 |
|
2214 | |||
2208 | if repo.filtername not in (b'visible', b'visible-hidden'): |
|
2215 | if repo.filtername not in (b'visible', b'visible-hidden'): | |
2209 | return repo |
|
2216 | return repo | |
2210 |
|
2217 | |||
2211 | symbols = set() |
|
2218 | symbols = set() | |
2212 | for spec in specs: |
|
2219 | for spec in specs: | |
2213 | try: |
|
2220 | try: | |
2214 | tree = revsetlang.parse(spec) |
|
2221 | tree = revsetlang.parse(spec) | |
2215 | except error.ParseError: # will be reported by scmutil.revrange() |
|
2222 | except error.ParseError: # will be reported by scmutil.revrange() | |
2216 | continue |
|
2223 | continue | |
2217 |
|
2224 | |||
2218 | symbols.update(revsetlang.gethashlikesymbols(tree)) |
|
2225 | symbols.update(revsetlang.gethashlikesymbols(tree)) | |
2219 |
|
2226 | |||
2220 | if not symbols: |
|
2227 | if not symbols: | |
2221 | return repo |
|
2228 | return repo | |
2222 |
|
2229 | |||
2223 | revs = _getrevsfromsymbols(repo, symbols) |
|
2230 | revs = _getrevsfromsymbols(repo, symbols) | |
2224 |
|
2231 | |||
2225 | if not revs: |
|
2232 | if not revs: | |
2226 | return repo |
|
2233 | return repo | |
2227 |
|
2234 | |||
2228 | if hiddentype == b'warn': |
|
2235 | if hiddentype == b'warn': | |
2229 | unfi = repo.unfiltered() |
|
2236 | unfi = repo.unfiltered() | |
2230 | revstr = b", ".join([pycompat.bytestr(unfi[l]) for l in revs]) |
|
2237 | revstr = b", ".join([pycompat.bytestr(unfi[l]) for l in revs]) | |
2231 | repo.ui.warn( |
|
2238 | repo.ui.warn( | |
2232 | _( |
|
2239 | _( | |
2233 | b"warning: accessing hidden changesets for write " |
|
2240 | b"warning: accessing hidden changesets for write " | |
2234 | b"operation: %s\n" |
|
2241 | b"operation: %s\n" | |
2235 | ) |
|
2242 | ) | |
2236 | % revstr |
|
2243 | % revstr | |
2237 | ) |
|
2244 | ) | |
2238 |
|
2245 | |||
2239 | # we have to use new filtername to separate branch/tags cache until we can |
|
2246 | # we have to use new filtername to separate branch/tags cache until we can | |
2240 | # disbale these cache when revisions are dynamically pinned. |
|
2247 | # disbale these cache when revisions are dynamically pinned. | |
2241 | return repo.filtered(b'visible-hidden', revs) |
|
2248 | return repo.filtered(b'visible-hidden', revs) | |
2242 |
|
2249 | |||
2243 |
|
2250 | |||
2244 | def _getrevsfromsymbols(repo, symbols): |
|
2251 | def _getrevsfromsymbols(repo, symbols): | |
2245 | """parse the list of symbols and returns a set of revision numbers of hidden |
|
2252 | """parse the list of symbols and returns a set of revision numbers of hidden | |
2246 | changesets present in symbols""" |
|
2253 | changesets present in symbols""" | |
2247 | revs = set() |
|
2254 | revs = set() | |
2248 | unfi = repo.unfiltered() |
|
2255 | unfi = repo.unfiltered() | |
2249 | unficl = unfi.changelog |
|
2256 | unficl = unfi.changelog | |
2250 | cl = repo.changelog |
|
2257 | cl = repo.changelog | |
2251 | tiprev = len(unficl) |
|
2258 | tiprev = len(unficl) | |
2252 | allowrevnums = repo.ui.configbool(b'experimental', b'directaccess.revnums') |
|
2259 | allowrevnums = repo.ui.configbool(b'experimental', b'directaccess.revnums') | |
2253 | for s in symbols: |
|
2260 | for s in symbols: | |
2254 | try: |
|
2261 | try: | |
2255 | n = int(s) |
|
2262 | n = int(s) | |
2256 | if n <= tiprev: |
|
2263 | if n <= tiprev: | |
2257 | if not allowrevnums: |
|
2264 | if not allowrevnums: | |
2258 | continue |
|
2265 | continue | |
2259 | else: |
|
2266 | else: | |
2260 | if n not in cl: |
|
2267 | if n not in cl: | |
2261 | revs.add(n) |
|
2268 | revs.add(n) | |
2262 | continue |
|
2269 | continue | |
2263 | except ValueError: |
|
2270 | except ValueError: | |
2264 | pass |
|
2271 | pass | |
2265 |
|
2272 | |||
2266 | try: |
|
2273 | try: | |
2267 | s = resolvehexnodeidprefix(unfi, s) |
|
2274 | s = resolvehexnodeidprefix(unfi, s) | |
2268 | except (error.LookupError, error.WdirUnsupported): |
|
2275 | except (error.LookupError, error.WdirUnsupported): | |
2269 | s = None |
|
2276 | s = None | |
2270 |
|
2277 | |||
2271 | if s is not None: |
|
2278 | if s is not None: | |
2272 | rev = unficl.rev(s) |
|
2279 | rev = unficl.rev(s) | |
2273 | if rev not in cl: |
|
2280 | if rev not in cl: | |
2274 | revs.add(rev) |
|
2281 | revs.add(rev) | |
2275 |
|
2282 | |||
2276 | return revs |
|
2283 | return revs | |
2277 |
|
2284 | |||
2278 |
|
2285 | |||
2279 | def bookmarkrevs(repo, mark): |
|
2286 | def bookmarkrevs(repo, mark): | |
2280 | """ |
|
2287 | """ | |
2281 | Select revisions reachable by a given bookmark |
|
2288 | Select revisions reachable by a given bookmark | |
2282 | """ |
|
2289 | """ | |
2283 | return repo.revs( |
|
2290 | return repo.revs( | |
2284 | b"ancestors(bookmark(%s)) - " |
|
2291 | b"ancestors(bookmark(%s)) - " | |
2285 | b"ancestors(head() and not bookmark(%s)) - " |
|
2292 | b"ancestors(head() and not bookmark(%s)) - " | |
2286 | b"ancestors(bookmark() and not bookmark(%s))", |
|
2293 | b"ancestors(bookmark() and not bookmark(%s))", | |
2287 | mark, |
|
2294 | mark, | |
2288 | mark, |
|
2295 | mark, | |
2289 | mark, |
|
2296 | mark, | |
2290 | ) |
|
2297 | ) |
@@ -1,2372 +1,2374 b'' | |||||
1 | # ui.py - user interface bits for mercurial |
|
1 | # ui.py - user interface bits for mercurial | |
2 | # |
|
2 | # | |
3 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
3 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | from __future__ import absolute_import |
|
8 | from __future__ import absolute_import | |
9 |
|
9 | |||
10 | import collections |
|
10 | import collections | |
11 | import contextlib |
|
11 | import contextlib | |
12 | import datetime |
|
12 | import datetime | |
13 | import errno |
|
13 | import errno | |
14 | import getpass |
|
14 | import getpass | |
15 | import inspect |
|
15 | import inspect | |
16 | import os |
|
16 | import os | |
17 | import re |
|
17 | import re | |
18 | import signal |
|
18 | import signal | |
19 | import socket |
|
19 | import socket | |
20 | import subprocess |
|
20 | import subprocess | |
21 | import sys |
|
21 | import sys | |
22 | import traceback |
|
22 | import traceback | |
23 |
|
23 | |||
24 | from .i18n import _ |
|
24 | from .i18n import _ | |
25 | from .node import hex |
|
25 | from .node import hex | |
26 | from .pycompat import ( |
|
26 | from .pycompat import ( | |
27 | getattr, |
|
27 | getattr, | |
28 | open, |
|
28 | open, | |
29 | setattr, |
|
29 | setattr, | |
30 | ) |
|
30 | ) | |
31 |
|
31 | |||
32 | from . import ( |
|
32 | from . import ( | |
33 | color, |
|
33 | color, | |
34 | config, |
|
34 | config, | |
35 | configitems, |
|
35 | configitems, | |
36 | encoding, |
|
36 | encoding, | |
37 | error, |
|
37 | error, | |
38 | formatter, |
|
38 | formatter, | |
39 | loggingutil, |
|
39 | loggingutil, | |
40 | progress, |
|
40 | progress, | |
41 | pycompat, |
|
41 | pycompat, | |
42 | rcutil, |
|
42 | rcutil, | |
43 | scmutil, |
|
43 | scmutil, | |
44 | util, |
|
44 | util, | |
45 | ) |
|
45 | ) | |
46 | from .utils import ( |
|
46 | from .utils import ( | |
47 | dateutil, |
|
47 | dateutil, | |
48 | procutil, |
|
48 | procutil, | |
49 | resourceutil, |
|
49 | resourceutil, | |
50 | stringutil, |
|
50 | stringutil, | |
51 | ) |
|
51 | ) | |
52 |
|
52 | |||
53 | urlreq = util.urlreq |
|
53 | urlreq = util.urlreq | |
54 |
|
54 | |||
55 | # for use with str.translate(None, _keepalnum), to keep just alphanumerics |
|
55 | # for use with str.translate(None, _keepalnum), to keep just alphanumerics | |
56 | _keepalnum = b''.join( |
|
56 | _keepalnum = b''.join( | |
57 | c for c in map(pycompat.bytechr, range(256)) if not c.isalnum() |
|
57 | c for c in map(pycompat.bytechr, range(256)) if not c.isalnum() | |
58 | ) |
|
58 | ) | |
59 |
|
59 | |||
60 | # The config knobs that will be altered (if unset) by ui.tweakdefaults. |
|
60 | # The config knobs that will be altered (if unset) by ui.tweakdefaults. | |
61 | tweakrc = b""" |
|
61 | tweakrc = b""" | |
62 | [ui] |
|
62 | [ui] | |
|
63 | # Gives detailed exit codes for input/user errors, config errors, etc. | |||
|
64 | detailed-exit-code = True | |||
63 | # The rollback command is dangerous. As a rule, don't use it. |
|
65 | # The rollback command is dangerous. As a rule, don't use it. | |
64 | rollback = False |
|
66 | rollback = False | |
65 | # Make `hg status` report copy information |
|
67 | # Make `hg status` report copy information | |
66 | statuscopies = yes |
|
68 | statuscopies = yes | |
67 | # Prefer curses UIs when available. Revert to plain-text with `text`. |
|
69 | # Prefer curses UIs when available. Revert to plain-text with `text`. | |
68 | interface = curses |
|
70 | interface = curses | |
69 | # Make compatible commands emit cwd-relative paths by default. |
|
71 | # Make compatible commands emit cwd-relative paths by default. | |
70 | relative-paths = yes |
|
72 | relative-paths = yes | |
71 |
|
73 | |||
72 | [commands] |
|
74 | [commands] | |
73 | # Grep working directory by default. |
|
75 | # Grep working directory by default. | |
74 | grep.all-files = True |
|
76 | grep.all-files = True | |
75 | # Refuse to perform an `hg update` that would cause a file content merge |
|
77 | # Refuse to perform an `hg update` that would cause a file content merge | |
76 | update.check = noconflict |
|
78 | update.check = noconflict | |
77 | # Show conflicts information in `hg status` |
|
79 | # Show conflicts information in `hg status` | |
78 | status.verbose = True |
|
80 | status.verbose = True | |
79 | # Make `hg resolve` with no action (like `-m`) fail instead of re-merging. |
|
81 | # Make `hg resolve` with no action (like `-m`) fail instead of re-merging. | |
80 | resolve.explicit-re-merge = True |
|
82 | resolve.explicit-re-merge = True | |
81 |
|
83 | |||
82 | [diff] |
|
84 | [diff] | |
83 | git = 1 |
|
85 | git = 1 | |
84 | showfunc = 1 |
|
86 | showfunc = 1 | |
85 | word-diff = 1 |
|
87 | word-diff = 1 | |
86 | """ |
|
88 | """ | |
87 |
|
89 | |||
88 | samplehgrcs = { |
|
90 | samplehgrcs = { | |
89 | b'user': b"""# example user config (see 'hg help config' for more info) |
|
91 | b'user': b"""# example user config (see 'hg help config' for more info) | |
90 | [ui] |
|
92 | [ui] | |
91 | # name and email, e.g. |
|
93 | # name and email, e.g. | |
92 | # username = Jane Doe <jdoe@example.com> |
|
94 | # username = Jane Doe <jdoe@example.com> | |
93 | username = |
|
95 | username = | |
94 |
|
96 | |||
95 | # We recommend enabling tweakdefaults to get slight improvements to |
|
97 | # We recommend enabling tweakdefaults to get slight improvements to | |
96 | # the UI over time. Make sure to set HGPLAIN in the environment when |
|
98 | # the UI over time. Make sure to set HGPLAIN in the environment when | |
97 | # writing scripts! |
|
99 | # writing scripts! | |
98 | # tweakdefaults = True |
|
100 | # tweakdefaults = True | |
99 |
|
101 | |||
100 | # uncomment to disable color in command output |
|
102 | # uncomment to disable color in command output | |
101 | # (see 'hg help color' for details) |
|
103 | # (see 'hg help color' for details) | |
102 | # color = never |
|
104 | # color = never | |
103 |
|
105 | |||
104 | # uncomment to disable command output pagination |
|
106 | # uncomment to disable command output pagination | |
105 | # (see 'hg help pager' for details) |
|
107 | # (see 'hg help pager' for details) | |
106 | # paginate = never |
|
108 | # paginate = never | |
107 |
|
109 | |||
108 | [extensions] |
|
110 | [extensions] | |
109 | # uncomment the lines below to enable some popular extensions |
|
111 | # uncomment the lines below to enable some popular extensions | |
110 | # (see 'hg help extensions' for more info) |
|
112 | # (see 'hg help extensions' for more info) | |
111 | # |
|
113 | # | |
112 | # histedit = |
|
114 | # histedit = | |
113 | # rebase = |
|
115 | # rebase = | |
114 | # uncommit = |
|
116 | # uncommit = | |
115 | """, |
|
117 | """, | |
116 | b'cloned': b"""# example repository config (see 'hg help config' for more info) |
|
118 | b'cloned': b"""# example repository config (see 'hg help config' for more info) | |
117 | [paths] |
|
119 | [paths] | |
118 | default = %s |
|
120 | default = %s | |
119 |
|
121 | |||
120 | # path aliases to other clones of this repo in URLs or filesystem paths |
|
122 | # path aliases to other clones of this repo in URLs or filesystem paths | |
121 | # (see 'hg help config.paths' for more info) |
|
123 | # (see 'hg help config.paths' for more info) | |
122 | # |
|
124 | # | |
123 | # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork |
|
125 | # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork | |
124 | # my-fork = ssh://jdoe@example.net/hg/jdoes-fork |
|
126 | # my-fork = ssh://jdoe@example.net/hg/jdoes-fork | |
125 | # my-clone = /home/jdoe/jdoes-clone |
|
127 | # my-clone = /home/jdoe/jdoes-clone | |
126 |
|
128 | |||
127 | [ui] |
|
129 | [ui] | |
128 | # name and email (local to this repository, optional), e.g. |
|
130 | # name and email (local to this repository, optional), e.g. | |
129 | # username = Jane Doe <jdoe@example.com> |
|
131 | # username = Jane Doe <jdoe@example.com> | |
130 | """, |
|
132 | """, | |
131 | b'local': b"""# example repository config (see 'hg help config' for more info) |
|
133 | b'local': b"""# example repository config (see 'hg help config' for more info) | |
132 | [paths] |
|
134 | [paths] | |
133 | # path aliases to other clones of this repo in URLs or filesystem paths |
|
135 | # path aliases to other clones of this repo in URLs or filesystem paths | |
134 | # (see 'hg help config.paths' for more info) |
|
136 | # (see 'hg help config.paths' for more info) | |
135 | # |
|
137 | # | |
136 | # default = http://example.com/hg/example-repo |
|
138 | # default = http://example.com/hg/example-repo | |
137 | # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork |
|
139 | # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork | |
138 | # my-fork = ssh://jdoe@example.net/hg/jdoes-fork |
|
140 | # my-fork = ssh://jdoe@example.net/hg/jdoes-fork | |
139 | # my-clone = /home/jdoe/jdoes-clone |
|
141 | # my-clone = /home/jdoe/jdoes-clone | |
140 |
|
142 | |||
141 | [ui] |
|
143 | [ui] | |
142 | # name and email (local to this repository, optional), e.g. |
|
144 | # name and email (local to this repository, optional), e.g. | |
143 | # username = Jane Doe <jdoe@example.com> |
|
145 | # username = Jane Doe <jdoe@example.com> | |
144 | """, |
|
146 | """, | |
145 | b'global': b"""# example system-wide hg config (see 'hg help config' for more info) |
|
147 | b'global': b"""# example system-wide hg config (see 'hg help config' for more info) | |
146 |
|
148 | |||
147 | [ui] |
|
149 | [ui] | |
148 | # uncomment to disable color in command output |
|
150 | # uncomment to disable color in command output | |
149 | # (see 'hg help color' for details) |
|
151 | # (see 'hg help color' for details) | |
150 | # color = never |
|
152 | # color = never | |
151 |
|
153 | |||
152 | # uncomment to disable command output pagination |
|
154 | # uncomment to disable command output pagination | |
153 | # (see 'hg help pager' for details) |
|
155 | # (see 'hg help pager' for details) | |
154 | # paginate = never |
|
156 | # paginate = never | |
155 |
|
157 | |||
156 | [extensions] |
|
158 | [extensions] | |
157 | # uncomment the lines below to enable some popular extensions |
|
159 | # uncomment the lines below to enable some popular extensions | |
158 | # (see 'hg help extensions' for more info) |
|
160 | # (see 'hg help extensions' for more info) | |
159 | # |
|
161 | # | |
160 | # blackbox = |
|
162 | # blackbox = | |
161 | # churn = |
|
163 | # churn = | |
162 | """, |
|
164 | """, | |
163 | } |
|
165 | } | |
164 |
|
166 | |||
165 |
|
167 | |||
166 | def _maybestrurl(maybebytes): |
|
168 | def _maybestrurl(maybebytes): | |
167 | return pycompat.rapply(pycompat.strurl, maybebytes) |
|
169 | return pycompat.rapply(pycompat.strurl, maybebytes) | |
168 |
|
170 | |||
169 |
|
171 | |||
170 | def _maybebytesurl(maybestr): |
|
172 | def _maybebytesurl(maybestr): | |
171 | return pycompat.rapply(pycompat.bytesurl, maybestr) |
|
173 | return pycompat.rapply(pycompat.bytesurl, maybestr) | |
172 |
|
174 | |||
173 |
|
175 | |||
174 | class httppasswordmgrdbproxy(object): |
|
176 | class httppasswordmgrdbproxy(object): | |
175 | """Delays loading urllib2 until it's needed.""" |
|
177 | """Delays loading urllib2 until it's needed.""" | |
176 |
|
178 | |||
177 | def __init__(self): |
|
179 | def __init__(self): | |
178 | self._mgr = None |
|
180 | self._mgr = None | |
179 |
|
181 | |||
180 | def _get_mgr(self): |
|
182 | def _get_mgr(self): | |
181 | if self._mgr is None: |
|
183 | if self._mgr is None: | |
182 | self._mgr = urlreq.httppasswordmgrwithdefaultrealm() |
|
184 | self._mgr = urlreq.httppasswordmgrwithdefaultrealm() | |
183 | return self._mgr |
|
185 | return self._mgr | |
184 |
|
186 | |||
185 | def add_password(self, realm, uris, user, passwd): |
|
187 | def add_password(self, realm, uris, user, passwd): | |
186 | return self._get_mgr().add_password( |
|
188 | return self._get_mgr().add_password( | |
187 | _maybestrurl(realm), |
|
189 | _maybestrurl(realm), | |
188 | _maybestrurl(uris), |
|
190 | _maybestrurl(uris), | |
189 | _maybestrurl(user), |
|
191 | _maybestrurl(user), | |
190 | _maybestrurl(passwd), |
|
192 | _maybestrurl(passwd), | |
191 | ) |
|
193 | ) | |
192 |
|
194 | |||
193 | def find_user_password(self, realm, uri): |
|
195 | def find_user_password(self, realm, uri): | |
194 | mgr = self._get_mgr() |
|
196 | mgr = self._get_mgr() | |
195 | return _maybebytesurl( |
|
197 | return _maybebytesurl( | |
196 | mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri)) |
|
198 | mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri)) | |
197 | ) |
|
199 | ) | |
198 |
|
200 | |||
199 |
|
201 | |||
200 | def _catchterm(*args): |
|
202 | def _catchterm(*args): | |
201 | raise error.SignalInterrupt |
|
203 | raise error.SignalInterrupt | |
202 |
|
204 | |||
203 |
|
205 | |||
204 | # unique object used to detect no default value has been provided when |
|
206 | # unique object used to detect no default value has been provided when | |
205 | # retrieving configuration value. |
|
207 | # retrieving configuration value. | |
206 | _unset = object() |
|
208 | _unset = object() | |
207 |
|
209 | |||
208 | # _reqexithandlers: callbacks run at the end of a request |
|
210 | # _reqexithandlers: callbacks run at the end of a request | |
209 | _reqexithandlers = [] |
|
211 | _reqexithandlers = [] | |
210 |
|
212 | |||
211 |
|
213 | |||
212 | class ui(object): |
|
214 | class ui(object): | |
213 | def __init__(self, src=None): |
|
215 | def __init__(self, src=None): | |
214 | """Create a fresh new ui object if no src given |
|
216 | """Create a fresh new ui object if no src given | |
215 |
|
217 | |||
216 | Use uimod.ui.load() to create a ui which knows global and user configs. |
|
218 | Use uimod.ui.load() to create a ui which knows global and user configs. | |
217 | In most cases, you should use ui.copy() to create a copy of an existing |
|
219 | In most cases, you should use ui.copy() to create a copy of an existing | |
218 | ui object. |
|
220 | ui object. | |
219 | """ |
|
221 | """ | |
220 | # _buffers: used for temporary capture of output |
|
222 | # _buffers: used for temporary capture of output | |
221 | self._buffers = [] |
|
223 | self._buffers = [] | |
222 | # 3-tuple describing how each buffer in the stack behaves. |
|
224 | # 3-tuple describing how each buffer in the stack behaves. | |
223 | # Values are (capture stderr, capture subprocesses, apply labels). |
|
225 | # Values are (capture stderr, capture subprocesses, apply labels). | |
224 | self._bufferstates = [] |
|
226 | self._bufferstates = [] | |
225 | # When a buffer is active, defines whether we are expanding labels. |
|
227 | # When a buffer is active, defines whether we are expanding labels. | |
226 | # This exists to prevent an extra list lookup. |
|
228 | # This exists to prevent an extra list lookup. | |
227 | self._bufferapplylabels = None |
|
229 | self._bufferapplylabels = None | |
228 | self.quiet = self.verbose = self.debugflag = self.tracebackflag = False |
|
230 | self.quiet = self.verbose = self.debugflag = self.tracebackflag = False | |
229 | self._reportuntrusted = True |
|
231 | self._reportuntrusted = True | |
230 | self._knownconfig = configitems.coreitems |
|
232 | self._knownconfig = configitems.coreitems | |
231 | self._ocfg = config.config() # overlay |
|
233 | self._ocfg = config.config() # overlay | |
232 | self._tcfg = config.config() # trusted |
|
234 | self._tcfg = config.config() # trusted | |
233 | self._ucfg = config.config() # untrusted |
|
235 | self._ucfg = config.config() # untrusted | |
234 | self._trustusers = set() |
|
236 | self._trustusers = set() | |
235 | self._trustgroups = set() |
|
237 | self._trustgroups = set() | |
236 | self.callhooks = True |
|
238 | self.callhooks = True | |
237 | # Insecure server connections requested. |
|
239 | # Insecure server connections requested. | |
238 | self.insecureconnections = False |
|
240 | self.insecureconnections = False | |
239 | # Blocked time |
|
241 | # Blocked time | |
240 | self.logblockedtimes = False |
|
242 | self.logblockedtimes = False | |
241 | # color mode: see mercurial/color.py for possible value |
|
243 | # color mode: see mercurial/color.py for possible value | |
242 | self._colormode = None |
|
244 | self._colormode = None | |
243 | self._terminfoparams = {} |
|
245 | self._terminfoparams = {} | |
244 | self._styles = {} |
|
246 | self._styles = {} | |
245 | self._uninterruptible = False |
|
247 | self._uninterruptible = False | |
246 | self.showtimestamp = False |
|
248 | self.showtimestamp = False | |
247 |
|
249 | |||
248 | if src: |
|
250 | if src: | |
249 | self._fout = src._fout |
|
251 | self._fout = src._fout | |
250 | self._ferr = src._ferr |
|
252 | self._ferr = src._ferr | |
251 | self._fin = src._fin |
|
253 | self._fin = src._fin | |
252 | self._fmsg = src._fmsg |
|
254 | self._fmsg = src._fmsg | |
253 | self._fmsgout = src._fmsgout |
|
255 | self._fmsgout = src._fmsgout | |
254 | self._fmsgerr = src._fmsgerr |
|
256 | self._fmsgerr = src._fmsgerr | |
255 | self._finoutredirected = src._finoutredirected |
|
257 | self._finoutredirected = src._finoutredirected | |
256 | self._loggers = src._loggers.copy() |
|
258 | self._loggers = src._loggers.copy() | |
257 | self.pageractive = src.pageractive |
|
259 | self.pageractive = src.pageractive | |
258 | self._disablepager = src._disablepager |
|
260 | self._disablepager = src._disablepager | |
259 | self._tweaked = src._tweaked |
|
261 | self._tweaked = src._tweaked | |
260 |
|
262 | |||
261 | self._tcfg = src._tcfg.copy() |
|
263 | self._tcfg = src._tcfg.copy() | |
262 | self._ucfg = src._ucfg.copy() |
|
264 | self._ucfg = src._ucfg.copy() | |
263 | self._ocfg = src._ocfg.copy() |
|
265 | self._ocfg = src._ocfg.copy() | |
264 | self._trustusers = src._trustusers.copy() |
|
266 | self._trustusers = src._trustusers.copy() | |
265 | self._trustgroups = src._trustgroups.copy() |
|
267 | self._trustgroups = src._trustgroups.copy() | |
266 | self.environ = src.environ |
|
268 | self.environ = src.environ | |
267 | self.callhooks = src.callhooks |
|
269 | self.callhooks = src.callhooks | |
268 | self.insecureconnections = src.insecureconnections |
|
270 | self.insecureconnections = src.insecureconnections | |
269 | self._colormode = src._colormode |
|
271 | self._colormode = src._colormode | |
270 | self._terminfoparams = src._terminfoparams.copy() |
|
272 | self._terminfoparams = src._terminfoparams.copy() | |
271 | self._styles = src._styles.copy() |
|
273 | self._styles = src._styles.copy() | |
272 |
|
274 | |||
273 | self.fixconfig() |
|
275 | self.fixconfig() | |
274 |
|
276 | |||
275 | self.httppasswordmgrdb = src.httppasswordmgrdb |
|
277 | self.httppasswordmgrdb = src.httppasswordmgrdb | |
276 | self._blockedtimes = src._blockedtimes |
|
278 | self._blockedtimes = src._blockedtimes | |
277 | else: |
|
279 | else: | |
278 | self._fout = procutil.stdout |
|
280 | self._fout = procutil.stdout | |
279 | self._ferr = procutil.stderr |
|
281 | self._ferr = procutil.stderr | |
280 | self._fin = procutil.stdin |
|
282 | self._fin = procutil.stdin | |
281 | self._fmsg = None |
|
283 | self._fmsg = None | |
282 | self._fmsgout = self.fout # configurable |
|
284 | self._fmsgout = self.fout # configurable | |
283 | self._fmsgerr = self.ferr # configurable |
|
285 | self._fmsgerr = self.ferr # configurable | |
284 | self._finoutredirected = False |
|
286 | self._finoutredirected = False | |
285 | self._loggers = {} |
|
287 | self._loggers = {} | |
286 | self.pageractive = False |
|
288 | self.pageractive = False | |
287 | self._disablepager = False |
|
289 | self._disablepager = False | |
288 | self._tweaked = False |
|
290 | self._tweaked = False | |
289 |
|
291 | |||
290 | # shared read-only environment |
|
292 | # shared read-only environment | |
291 | self.environ = encoding.environ |
|
293 | self.environ = encoding.environ | |
292 |
|
294 | |||
293 | self.httppasswordmgrdb = httppasswordmgrdbproxy() |
|
295 | self.httppasswordmgrdb = httppasswordmgrdbproxy() | |
294 | self._blockedtimes = collections.defaultdict(int) |
|
296 | self._blockedtimes = collections.defaultdict(int) | |
295 |
|
297 | |||
296 | allowed = self.configlist(b'experimental', b'exportableenviron') |
|
298 | allowed = self.configlist(b'experimental', b'exportableenviron') | |
297 | if b'*' in allowed: |
|
299 | if b'*' in allowed: | |
298 | self._exportableenviron = self.environ |
|
300 | self._exportableenviron = self.environ | |
299 | else: |
|
301 | else: | |
300 | self._exportableenviron = {} |
|
302 | self._exportableenviron = {} | |
301 | for k in allowed: |
|
303 | for k in allowed: | |
302 | if k in self.environ: |
|
304 | if k in self.environ: | |
303 | self._exportableenviron[k] = self.environ[k] |
|
305 | self._exportableenviron[k] = self.environ[k] | |
304 |
|
306 | |||
305 | @classmethod |
|
307 | @classmethod | |
306 | def load(cls): |
|
308 | def load(cls): | |
307 | """Create a ui and load global and user configs""" |
|
309 | """Create a ui and load global and user configs""" | |
308 | u = cls() |
|
310 | u = cls() | |
309 | # we always trust global config files and environment variables |
|
311 | # we always trust global config files and environment variables | |
310 | for t, f in rcutil.rccomponents(): |
|
312 | for t, f in rcutil.rccomponents(): | |
311 | if t == b'path': |
|
313 | if t == b'path': | |
312 | u.readconfig(f, trust=True) |
|
314 | u.readconfig(f, trust=True) | |
313 | elif t == b'resource': |
|
315 | elif t == b'resource': | |
314 | u.read_resource_config(f, trust=True) |
|
316 | u.read_resource_config(f, trust=True) | |
315 | elif t == b'items': |
|
317 | elif t == b'items': | |
316 | sections = set() |
|
318 | sections = set() | |
317 | for section, name, value, source in f: |
|
319 | for section, name, value, source in f: | |
318 | # do not set u._ocfg |
|
320 | # do not set u._ocfg | |
319 | # XXX clean this up once immutable config object is a thing |
|
321 | # XXX clean this up once immutable config object is a thing | |
320 | u._tcfg.set(section, name, value, source) |
|
322 | u._tcfg.set(section, name, value, source) | |
321 | u._ucfg.set(section, name, value, source) |
|
323 | u._ucfg.set(section, name, value, source) | |
322 | sections.add(section) |
|
324 | sections.add(section) | |
323 | for section in sections: |
|
325 | for section in sections: | |
324 | u.fixconfig(section=section) |
|
326 | u.fixconfig(section=section) | |
325 | else: |
|
327 | else: | |
326 | raise error.ProgrammingError(b'unknown rctype: %s' % t) |
|
328 | raise error.ProgrammingError(b'unknown rctype: %s' % t) | |
327 | u._maybetweakdefaults() |
|
329 | u._maybetweakdefaults() | |
328 | return u |
|
330 | return u | |
329 |
|
331 | |||
330 | def _maybetweakdefaults(self): |
|
332 | def _maybetweakdefaults(self): | |
331 | if not self.configbool(b'ui', b'tweakdefaults'): |
|
333 | if not self.configbool(b'ui', b'tweakdefaults'): | |
332 | return |
|
334 | return | |
333 | if self._tweaked or self.plain(b'tweakdefaults'): |
|
335 | if self._tweaked or self.plain(b'tweakdefaults'): | |
334 | return |
|
336 | return | |
335 |
|
337 | |||
336 | # Note: it is SUPER IMPORTANT that you set self._tweaked to |
|
338 | # Note: it is SUPER IMPORTANT that you set self._tweaked to | |
337 | # True *before* any calls to setconfig(), otherwise you'll get |
|
339 | # True *before* any calls to setconfig(), otherwise you'll get | |
338 | # infinite recursion between setconfig and this method. |
|
340 | # infinite recursion between setconfig and this method. | |
339 | # |
|
341 | # | |
340 | # TODO: We should extract an inner method in setconfig() to |
|
342 | # TODO: We should extract an inner method in setconfig() to | |
341 | # avoid this weirdness. |
|
343 | # avoid this weirdness. | |
342 | self._tweaked = True |
|
344 | self._tweaked = True | |
343 | tmpcfg = config.config() |
|
345 | tmpcfg = config.config() | |
344 | tmpcfg.parse(b'<tweakdefaults>', tweakrc) |
|
346 | tmpcfg.parse(b'<tweakdefaults>', tweakrc) | |
345 | for section in tmpcfg: |
|
347 | for section in tmpcfg: | |
346 | for name, value in tmpcfg.items(section): |
|
348 | for name, value in tmpcfg.items(section): | |
347 | if not self.hasconfig(section, name): |
|
349 | if not self.hasconfig(section, name): | |
348 | self.setconfig(section, name, value, b"<tweakdefaults>") |
|
350 | self.setconfig(section, name, value, b"<tweakdefaults>") | |
349 |
|
351 | |||
350 | def copy(self): |
|
352 | def copy(self): | |
351 | return self.__class__(self) |
|
353 | return self.__class__(self) | |
352 |
|
354 | |||
353 | def resetstate(self): |
|
355 | def resetstate(self): | |
354 | """Clear internal state that shouldn't persist across commands""" |
|
356 | """Clear internal state that shouldn't persist across commands""" | |
355 | if self._progbar: |
|
357 | if self._progbar: | |
356 | self._progbar.resetstate() # reset last-print time of progress bar |
|
358 | self._progbar.resetstate() # reset last-print time of progress bar | |
357 | self.httppasswordmgrdb = httppasswordmgrdbproxy() |
|
359 | self.httppasswordmgrdb = httppasswordmgrdbproxy() | |
358 |
|
360 | |||
359 | @contextlib.contextmanager |
|
361 | @contextlib.contextmanager | |
360 | def timeblockedsection(self, key): |
|
362 | def timeblockedsection(self, key): | |
361 | # this is open-coded below - search for timeblockedsection to find them |
|
363 | # this is open-coded below - search for timeblockedsection to find them | |
362 | starttime = util.timer() |
|
364 | starttime = util.timer() | |
363 | try: |
|
365 | try: | |
364 | yield |
|
366 | yield | |
365 | finally: |
|
367 | finally: | |
366 | self._blockedtimes[key + b'_blocked'] += ( |
|
368 | self._blockedtimes[key + b'_blocked'] += ( | |
367 | util.timer() - starttime |
|
369 | util.timer() - starttime | |
368 | ) * 1000 |
|
370 | ) * 1000 | |
369 |
|
371 | |||
370 | @contextlib.contextmanager |
|
372 | @contextlib.contextmanager | |
371 | def uninterruptible(self): |
|
373 | def uninterruptible(self): | |
372 | """Mark an operation as unsafe. |
|
374 | """Mark an operation as unsafe. | |
373 |
|
375 | |||
374 | Most operations on a repository are safe to interrupt, but a |
|
376 | Most operations on a repository are safe to interrupt, but a | |
375 | few are risky (for example repair.strip). This context manager |
|
377 | few are risky (for example repair.strip). This context manager | |
376 | lets you advise Mercurial that something risky is happening so |
|
378 | lets you advise Mercurial that something risky is happening so | |
377 | that control-C etc can be blocked if desired. |
|
379 | that control-C etc can be blocked if desired. | |
378 | """ |
|
380 | """ | |
379 | enabled = self.configbool(b'experimental', b'nointerrupt') |
|
381 | enabled = self.configbool(b'experimental', b'nointerrupt') | |
380 | if enabled and self.configbool( |
|
382 | if enabled and self.configbool( | |
381 | b'experimental', b'nointerrupt-interactiveonly' |
|
383 | b'experimental', b'nointerrupt-interactiveonly' | |
382 | ): |
|
384 | ): | |
383 | enabled = self.interactive() |
|
385 | enabled = self.interactive() | |
384 | if self._uninterruptible or not enabled: |
|
386 | if self._uninterruptible or not enabled: | |
385 | # if nointerrupt support is turned off, the process isn't |
|
387 | # if nointerrupt support is turned off, the process isn't | |
386 | # interactive, or we're already in an uninterruptible |
|
388 | # interactive, or we're already in an uninterruptible | |
387 | # block, do nothing. |
|
389 | # block, do nothing. | |
388 | yield |
|
390 | yield | |
389 | return |
|
391 | return | |
390 |
|
392 | |||
391 | def warn(): |
|
393 | def warn(): | |
392 | self.warn(_(b"shutting down cleanly\n")) |
|
394 | self.warn(_(b"shutting down cleanly\n")) | |
393 | self.warn( |
|
395 | self.warn( | |
394 | _(b"press ^C again to terminate immediately (dangerous)\n") |
|
396 | _(b"press ^C again to terminate immediately (dangerous)\n") | |
395 | ) |
|
397 | ) | |
396 | return True |
|
398 | return True | |
397 |
|
399 | |||
398 | with procutil.uninterruptible(warn): |
|
400 | with procutil.uninterruptible(warn): | |
399 | try: |
|
401 | try: | |
400 | self._uninterruptible = True |
|
402 | self._uninterruptible = True | |
401 | yield |
|
403 | yield | |
402 | finally: |
|
404 | finally: | |
403 | self._uninterruptible = False |
|
405 | self._uninterruptible = False | |
404 |
|
406 | |||
405 | def formatter(self, topic, opts): |
|
407 | def formatter(self, topic, opts): | |
406 | return formatter.formatter(self, self, topic, opts) |
|
408 | return formatter.formatter(self, self, topic, opts) | |
407 |
|
409 | |||
408 | def _trusted(self, fp, f): |
|
410 | def _trusted(self, fp, f): | |
409 | st = util.fstat(fp) |
|
411 | st = util.fstat(fp) | |
410 | if util.isowner(st): |
|
412 | if util.isowner(st): | |
411 | return True |
|
413 | return True | |
412 |
|
414 | |||
413 | tusers, tgroups = self._trustusers, self._trustgroups |
|
415 | tusers, tgroups = self._trustusers, self._trustgroups | |
414 | if b'*' in tusers or b'*' in tgroups: |
|
416 | if b'*' in tusers or b'*' in tgroups: | |
415 | return True |
|
417 | return True | |
416 |
|
418 | |||
417 | user = util.username(st.st_uid) |
|
419 | user = util.username(st.st_uid) | |
418 | group = util.groupname(st.st_gid) |
|
420 | group = util.groupname(st.st_gid) | |
419 | if user in tusers or group in tgroups or user == util.username(): |
|
421 | if user in tusers or group in tgroups or user == util.username(): | |
420 | return True |
|
422 | return True | |
421 |
|
423 | |||
422 | if self._reportuntrusted: |
|
424 | if self._reportuntrusted: | |
423 | self.warn( |
|
425 | self.warn( | |
424 | _( |
|
426 | _( | |
425 | b'not trusting file %s from untrusted ' |
|
427 | b'not trusting file %s from untrusted ' | |
426 | b'user %s, group %s\n' |
|
428 | b'user %s, group %s\n' | |
427 | ) |
|
429 | ) | |
428 | % (f, user, group) |
|
430 | % (f, user, group) | |
429 | ) |
|
431 | ) | |
430 | return False |
|
432 | return False | |
431 |
|
433 | |||
432 | def read_resource_config( |
|
434 | def read_resource_config( | |
433 | self, name, root=None, trust=False, sections=None, remap=None |
|
435 | self, name, root=None, trust=False, sections=None, remap=None | |
434 | ): |
|
436 | ): | |
435 | try: |
|
437 | try: | |
436 | fp = resourceutil.open_resource(name[0], name[1]) |
|
438 | fp = resourceutil.open_resource(name[0], name[1]) | |
437 | except IOError: |
|
439 | except IOError: | |
438 | if not sections: # ignore unless we were looking for something |
|
440 | if not sections: # ignore unless we were looking for something | |
439 | return |
|
441 | return | |
440 | raise |
|
442 | raise | |
441 |
|
443 | |||
442 | self._readconfig( |
|
444 | self._readconfig( | |
443 | b'resource:%s.%s' % name, fp, root, trust, sections, remap |
|
445 | b'resource:%s.%s' % name, fp, root, trust, sections, remap | |
444 | ) |
|
446 | ) | |
445 |
|
447 | |||
446 | def readconfig( |
|
448 | def readconfig( | |
447 | self, filename, root=None, trust=False, sections=None, remap=None |
|
449 | self, filename, root=None, trust=False, sections=None, remap=None | |
448 | ): |
|
450 | ): | |
449 | try: |
|
451 | try: | |
450 | fp = open(filename, 'rb') |
|
452 | fp = open(filename, 'rb') | |
451 | except IOError: |
|
453 | except IOError: | |
452 | if not sections: # ignore unless we were looking for something |
|
454 | if not sections: # ignore unless we were looking for something | |
453 | return |
|
455 | return | |
454 | raise |
|
456 | raise | |
455 |
|
457 | |||
456 | self._readconfig(filename, fp, root, trust, sections, remap) |
|
458 | self._readconfig(filename, fp, root, trust, sections, remap) | |
457 |
|
459 | |||
458 | def _readconfig( |
|
460 | def _readconfig( | |
459 | self, filename, fp, root=None, trust=False, sections=None, remap=None |
|
461 | self, filename, fp, root=None, trust=False, sections=None, remap=None | |
460 | ): |
|
462 | ): | |
461 | with fp: |
|
463 | with fp: | |
462 | cfg = config.config() |
|
464 | cfg = config.config() | |
463 | trusted = sections or trust or self._trusted(fp, filename) |
|
465 | trusted = sections or trust or self._trusted(fp, filename) | |
464 |
|
466 | |||
465 | try: |
|
467 | try: | |
466 | cfg.read(filename, fp, sections=sections, remap=remap) |
|
468 | cfg.read(filename, fp, sections=sections, remap=remap) | |
467 | except error.ParseError as inst: |
|
469 | except error.ParseError as inst: | |
468 | if trusted: |
|
470 | if trusted: | |
469 | raise |
|
471 | raise | |
470 | self.warn(_(b'ignored: %s\n') % stringutil.forcebytestr(inst)) |
|
472 | self.warn(_(b'ignored: %s\n') % stringutil.forcebytestr(inst)) | |
471 |
|
473 | |||
472 | self._applyconfig(cfg, trusted, root) |
|
474 | self._applyconfig(cfg, trusted, root) | |
473 |
|
475 | |||
474 | def applyconfig(self, configitems, source=b"", root=None): |
|
476 | def applyconfig(self, configitems, source=b"", root=None): | |
475 | """Add configitems from a non-file source. Unlike with ``setconfig()``, |
|
477 | """Add configitems from a non-file source. Unlike with ``setconfig()``, | |
476 | they can be overridden by subsequent config file reads. The items are |
|
478 | they can be overridden by subsequent config file reads. The items are | |
477 | in the same format as ``configoverride()``, namely a dict of the |
|
479 | in the same format as ``configoverride()``, namely a dict of the | |
478 | following structures: {(section, name) : value} |
|
480 | following structures: {(section, name) : value} | |
479 |
|
481 | |||
480 | Typically this is used by extensions that inject themselves into the |
|
482 | Typically this is used by extensions that inject themselves into the | |
481 | config file load procedure by monkeypatching ``localrepo.loadhgrc()``. |
|
483 | config file load procedure by monkeypatching ``localrepo.loadhgrc()``. | |
482 | """ |
|
484 | """ | |
483 | cfg = config.config() |
|
485 | cfg = config.config() | |
484 |
|
486 | |||
485 | for (section, name), value in configitems.items(): |
|
487 | for (section, name), value in configitems.items(): | |
486 | cfg.set(section, name, value, source) |
|
488 | cfg.set(section, name, value, source) | |
487 |
|
489 | |||
488 | self._applyconfig(cfg, True, root) |
|
490 | self._applyconfig(cfg, True, root) | |
489 |
|
491 | |||
490 | def _applyconfig(self, cfg, trusted, root): |
|
492 | def _applyconfig(self, cfg, trusted, root): | |
491 | if self.plain(): |
|
493 | if self.plain(): | |
492 | for k in ( |
|
494 | for k in ( | |
493 | b'debug', |
|
495 | b'debug', | |
494 | b'fallbackencoding', |
|
496 | b'fallbackencoding', | |
495 | b'quiet', |
|
497 | b'quiet', | |
496 | b'slash', |
|
498 | b'slash', | |
497 | b'logtemplate', |
|
499 | b'logtemplate', | |
498 | b'message-output', |
|
500 | b'message-output', | |
499 | b'statuscopies', |
|
501 | b'statuscopies', | |
500 | b'style', |
|
502 | b'style', | |
501 | b'traceback', |
|
503 | b'traceback', | |
502 | b'verbose', |
|
504 | b'verbose', | |
503 | ): |
|
505 | ): | |
504 | if k in cfg[b'ui']: |
|
506 | if k in cfg[b'ui']: | |
505 | del cfg[b'ui'][k] |
|
507 | del cfg[b'ui'][k] | |
506 | for k, v in cfg.items(b'defaults'): |
|
508 | for k, v in cfg.items(b'defaults'): | |
507 | del cfg[b'defaults'][k] |
|
509 | del cfg[b'defaults'][k] | |
508 | for k, v in cfg.items(b'commands'): |
|
510 | for k, v in cfg.items(b'commands'): | |
509 | del cfg[b'commands'][k] |
|
511 | del cfg[b'commands'][k] | |
510 | for k, v in cfg.items(b'command-templates'): |
|
512 | for k, v in cfg.items(b'command-templates'): | |
511 | del cfg[b'command-templates'][k] |
|
513 | del cfg[b'command-templates'][k] | |
512 | # Don't remove aliases from the configuration if in the exceptionlist |
|
514 | # Don't remove aliases from the configuration if in the exceptionlist | |
513 | if self.plain(b'alias'): |
|
515 | if self.plain(b'alias'): | |
514 | for k, v in cfg.items(b'alias'): |
|
516 | for k, v in cfg.items(b'alias'): | |
515 | del cfg[b'alias'][k] |
|
517 | del cfg[b'alias'][k] | |
516 | if self.plain(b'revsetalias'): |
|
518 | if self.plain(b'revsetalias'): | |
517 | for k, v in cfg.items(b'revsetalias'): |
|
519 | for k, v in cfg.items(b'revsetalias'): | |
518 | del cfg[b'revsetalias'][k] |
|
520 | del cfg[b'revsetalias'][k] | |
519 | if self.plain(b'templatealias'): |
|
521 | if self.plain(b'templatealias'): | |
520 | for k, v in cfg.items(b'templatealias'): |
|
522 | for k, v in cfg.items(b'templatealias'): | |
521 | del cfg[b'templatealias'][k] |
|
523 | del cfg[b'templatealias'][k] | |
522 |
|
524 | |||
523 | if trusted: |
|
525 | if trusted: | |
524 | self._tcfg.update(cfg) |
|
526 | self._tcfg.update(cfg) | |
525 | self._tcfg.update(self._ocfg) |
|
527 | self._tcfg.update(self._ocfg) | |
526 | self._ucfg.update(cfg) |
|
528 | self._ucfg.update(cfg) | |
527 | self._ucfg.update(self._ocfg) |
|
529 | self._ucfg.update(self._ocfg) | |
528 |
|
530 | |||
529 | if root is None: |
|
531 | if root is None: | |
530 | root = os.path.expanduser(b'~') |
|
532 | root = os.path.expanduser(b'~') | |
531 | self.fixconfig(root=root) |
|
533 | self.fixconfig(root=root) | |
532 |
|
534 | |||
533 | def fixconfig(self, root=None, section=None): |
|
535 | def fixconfig(self, root=None, section=None): | |
534 | if section in (None, b'paths'): |
|
536 | if section in (None, b'paths'): | |
535 | # expand vars and ~ |
|
537 | # expand vars and ~ | |
536 | # translate paths relative to root (or home) into absolute paths |
|
538 | # translate paths relative to root (or home) into absolute paths | |
537 | root = root or encoding.getcwd() |
|
539 | root = root or encoding.getcwd() | |
538 | for c in self._tcfg, self._ucfg, self._ocfg: |
|
540 | for c in self._tcfg, self._ucfg, self._ocfg: | |
539 | for n, p in c.items(b'paths'): |
|
541 | for n, p in c.items(b'paths'): | |
540 | # Ignore sub-options. |
|
542 | # Ignore sub-options. | |
541 | if b':' in n: |
|
543 | if b':' in n: | |
542 | continue |
|
544 | continue | |
543 | if not p: |
|
545 | if not p: | |
544 | continue |
|
546 | continue | |
545 | if b'%%' in p: |
|
547 | if b'%%' in p: | |
546 | s = self.configsource(b'paths', n) or b'none' |
|
548 | s = self.configsource(b'paths', n) or b'none' | |
547 | self.warn( |
|
549 | self.warn( | |
548 | _(b"(deprecated '%%' in path %s=%s from %s)\n") |
|
550 | _(b"(deprecated '%%' in path %s=%s from %s)\n") | |
549 | % (n, p, s) |
|
551 | % (n, p, s) | |
550 | ) |
|
552 | ) | |
551 | p = p.replace(b'%%', b'%') |
|
553 | p = p.replace(b'%%', b'%') | |
552 | p = util.expandpath(p) |
|
554 | p = util.expandpath(p) | |
553 | if not util.hasscheme(p) and not os.path.isabs(p): |
|
555 | if not util.hasscheme(p) and not os.path.isabs(p): | |
554 | p = os.path.normpath(os.path.join(root, p)) |
|
556 | p = os.path.normpath(os.path.join(root, p)) | |
555 | c.set(b"paths", n, p) |
|
557 | c.set(b"paths", n, p) | |
556 |
|
558 | |||
557 | if section in (None, b'ui'): |
|
559 | if section in (None, b'ui'): | |
558 | # update ui options |
|
560 | # update ui options | |
559 | self._fmsgout, self._fmsgerr = _selectmsgdests(self) |
|
561 | self._fmsgout, self._fmsgerr = _selectmsgdests(self) | |
560 | self.debugflag = self.configbool(b'ui', b'debug') |
|
562 | self.debugflag = self.configbool(b'ui', b'debug') | |
561 | self.verbose = self.debugflag or self.configbool(b'ui', b'verbose') |
|
563 | self.verbose = self.debugflag or self.configbool(b'ui', b'verbose') | |
562 | self.quiet = not self.debugflag and self.configbool(b'ui', b'quiet') |
|
564 | self.quiet = not self.debugflag and self.configbool(b'ui', b'quiet') | |
563 | if self.verbose and self.quiet: |
|
565 | if self.verbose and self.quiet: | |
564 | self.quiet = self.verbose = False |
|
566 | self.quiet = self.verbose = False | |
565 | self._reportuntrusted = self.debugflag or self.configbool( |
|
567 | self._reportuntrusted = self.debugflag or self.configbool( | |
566 | b"ui", b"report_untrusted" |
|
568 | b"ui", b"report_untrusted" | |
567 | ) |
|
569 | ) | |
568 | self.showtimestamp = self.configbool(b'ui', b'timestamp-output') |
|
570 | self.showtimestamp = self.configbool(b'ui', b'timestamp-output') | |
569 | self.tracebackflag = self.configbool(b'ui', b'traceback') |
|
571 | self.tracebackflag = self.configbool(b'ui', b'traceback') | |
570 | self.logblockedtimes = self.configbool(b'ui', b'logblockedtimes') |
|
572 | self.logblockedtimes = self.configbool(b'ui', b'logblockedtimes') | |
571 |
|
573 | |||
572 | if section in (None, b'trusted'): |
|
574 | if section in (None, b'trusted'): | |
573 | # update trust information |
|
575 | # update trust information | |
574 | self._trustusers.update(self.configlist(b'trusted', b'users')) |
|
576 | self._trustusers.update(self.configlist(b'trusted', b'users')) | |
575 | self._trustgroups.update(self.configlist(b'trusted', b'groups')) |
|
577 | self._trustgroups.update(self.configlist(b'trusted', b'groups')) | |
576 |
|
578 | |||
577 | if section in (None, b'devel', b'ui') and self.debugflag: |
|
579 | if section in (None, b'devel', b'ui') and self.debugflag: | |
578 | tracked = set() |
|
580 | tracked = set() | |
579 | if self.configbool(b'devel', b'debug.extensions'): |
|
581 | if self.configbool(b'devel', b'debug.extensions'): | |
580 | tracked.add(b'extension') |
|
582 | tracked.add(b'extension') | |
581 | if tracked: |
|
583 | if tracked: | |
582 | logger = loggingutil.fileobjectlogger(self._ferr, tracked) |
|
584 | logger = loggingutil.fileobjectlogger(self._ferr, tracked) | |
583 | self.setlogger(b'debug', logger) |
|
585 | self.setlogger(b'debug', logger) | |
584 |
|
586 | |||
585 | def backupconfig(self, section, item): |
|
587 | def backupconfig(self, section, item): | |
586 | return ( |
|
588 | return ( | |
587 | self._ocfg.backup(section, item), |
|
589 | self._ocfg.backup(section, item), | |
588 | self._tcfg.backup(section, item), |
|
590 | self._tcfg.backup(section, item), | |
589 | self._ucfg.backup(section, item), |
|
591 | self._ucfg.backup(section, item), | |
590 | ) |
|
592 | ) | |
591 |
|
593 | |||
592 | def restoreconfig(self, data): |
|
594 | def restoreconfig(self, data): | |
593 | self._ocfg.restore(data[0]) |
|
595 | self._ocfg.restore(data[0]) | |
594 | self._tcfg.restore(data[1]) |
|
596 | self._tcfg.restore(data[1]) | |
595 | self._ucfg.restore(data[2]) |
|
597 | self._ucfg.restore(data[2]) | |
596 |
|
598 | |||
597 | def setconfig(self, section, name, value, source=b''): |
|
599 | def setconfig(self, section, name, value, source=b''): | |
598 | for cfg in (self._ocfg, self._tcfg, self._ucfg): |
|
600 | for cfg in (self._ocfg, self._tcfg, self._ucfg): | |
599 | cfg.set(section, name, value, source) |
|
601 | cfg.set(section, name, value, source) | |
600 | self.fixconfig(section=section) |
|
602 | self.fixconfig(section=section) | |
601 | self._maybetweakdefaults() |
|
603 | self._maybetweakdefaults() | |
602 |
|
604 | |||
603 | def _data(self, untrusted): |
|
605 | def _data(self, untrusted): | |
604 | return untrusted and self._ucfg or self._tcfg |
|
606 | return untrusted and self._ucfg or self._tcfg | |
605 |
|
607 | |||
606 | def configsource(self, section, name, untrusted=False): |
|
608 | def configsource(self, section, name, untrusted=False): | |
607 | return self._data(untrusted).source(section, name) |
|
609 | return self._data(untrusted).source(section, name) | |
608 |
|
610 | |||
609 | def config(self, section, name, default=_unset, untrusted=False): |
|
611 | def config(self, section, name, default=_unset, untrusted=False): | |
610 | """return the plain string version of a config""" |
|
612 | """return the plain string version of a config""" | |
611 | value = self._config( |
|
613 | value = self._config( | |
612 | section, name, default=default, untrusted=untrusted |
|
614 | section, name, default=default, untrusted=untrusted | |
613 | ) |
|
615 | ) | |
614 | if value is _unset: |
|
616 | if value is _unset: | |
615 | return None |
|
617 | return None | |
616 | return value |
|
618 | return value | |
617 |
|
619 | |||
618 | def _config(self, section, name, default=_unset, untrusted=False): |
|
620 | def _config(self, section, name, default=_unset, untrusted=False): | |
619 | value = itemdefault = default |
|
621 | value = itemdefault = default | |
620 | item = self._knownconfig.get(section, {}).get(name) |
|
622 | item = self._knownconfig.get(section, {}).get(name) | |
621 | alternates = [(section, name)] |
|
623 | alternates = [(section, name)] | |
622 |
|
624 | |||
623 | if item is not None: |
|
625 | if item is not None: | |
624 | alternates.extend(item.alias) |
|
626 | alternates.extend(item.alias) | |
625 | if callable(item.default): |
|
627 | if callable(item.default): | |
626 | itemdefault = item.default() |
|
628 | itemdefault = item.default() | |
627 | else: |
|
629 | else: | |
628 | itemdefault = item.default |
|
630 | itemdefault = item.default | |
629 | else: |
|
631 | else: | |
630 | msg = b"accessing unregistered config item: '%s.%s'" |
|
632 | msg = b"accessing unregistered config item: '%s.%s'" | |
631 | msg %= (section, name) |
|
633 | msg %= (section, name) | |
632 | self.develwarn(msg, 2, b'warn-config-unknown') |
|
634 | self.develwarn(msg, 2, b'warn-config-unknown') | |
633 |
|
635 | |||
634 | if default is _unset: |
|
636 | if default is _unset: | |
635 | if item is None: |
|
637 | if item is None: | |
636 | value = default |
|
638 | value = default | |
637 | elif item.default is configitems.dynamicdefault: |
|
639 | elif item.default is configitems.dynamicdefault: | |
638 | value = None |
|
640 | value = None | |
639 | msg = b"config item requires an explicit default value: '%s.%s'" |
|
641 | msg = b"config item requires an explicit default value: '%s.%s'" | |
640 | msg %= (section, name) |
|
642 | msg %= (section, name) | |
641 | self.develwarn(msg, 2, b'warn-config-default') |
|
643 | self.develwarn(msg, 2, b'warn-config-default') | |
642 | else: |
|
644 | else: | |
643 | value = itemdefault |
|
645 | value = itemdefault | |
644 | elif ( |
|
646 | elif ( | |
645 | item is not None |
|
647 | item is not None | |
646 | and item.default is not configitems.dynamicdefault |
|
648 | and item.default is not configitems.dynamicdefault | |
647 | and default != itemdefault |
|
649 | and default != itemdefault | |
648 | ): |
|
650 | ): | |
649 | msg = ( |
|
651 | msg = ( | |
650 | b"specifying a mismatched default value for a registered " |
|
652 | b"specifying a mismatched default value for a registered " | |
651 | b"config item: '%s.%s' '%s'" |
|
653 | b"config item: '%s.%s' '%s'" | |
652 | ) |
|
654 | ) | |
653 | msg %= (section, name, pycompat.bytestr(default)) |
|
655 | msg %= (section, name, pycompat.bytestr(default)) | |
654 | self.develwarn(msg, 2, b'warn-config-default') |
|
656 | self.develwarn(msg, 2, b'warn-config-default') | |
655 |
|
657 | |||
656 | for s, n in alternates: |
|
658 | for s, n in alternates: | |
657 | candidate = self._data(untrusted).get(s, n, None) |
|
659 | candidate = self._data(untrusted).get(s, n, None) | |
658 | if candidate is not None: |
|
660 | if candidate is not None: | |
659 | value = candidate |
|
661 | value = candidate | |
660 | break |
|
662 | break | |
661 |
|
663 | |||
662 | if self.debugflag and not untrusted and self._reportuntrusted: |
|
664 | if self.debugflag and not untrusted and self._reportuntrusted: | |
663 | for s, n in alternates: |
|
665 | for s, n in alternates: | |
664 | uvalue = self._ucfg.get(s, n) |
|
666 | uvalue = self._ucfg.get(s, n) | |
665 | if uvalue is not None and uvalue != value: |
|
667 | if uvalue is not None and uvalue != value: | |
666 | self.debug( |
|
668 | self.debug( | |
667 | b"ignoring untrusted configuration option " |
|
669 | b"ignoring untrusted configuration option " | |
668 | b"%s.%s = %s\n" % (s, n, uvalue) |
|
670 | b"%s.%s = %s\n" % (s, n, uvalue) | |
669 | ) |
|
671 | ) | |
670 | return value |
|
672 | return value | |
671 |
|
673 | |||
672 | def configsuboptions(self, section, name, default=_unset, untrusted=False): |
|
674 | def configsuboptions(self, section, name, default=_unset, untrusted=False): | |
673 | """Get a config option and all sub-options. |
|
675 | """Get a config option and all sub-options. | |
674 |
|
676 | |||
675 | Some config options have sub-options that are declared with the |
|
677 | Some config options have sub-options that are declared with the | |
676 | format "key:opt = value". This method is used to return the main |
|
678 | format "key:opt = value". This method is used to return the main | |
677 | option and all its declared sub-options. |
|
679 | option and all its declared sub-options. | |
678 |
|
680 | |||
679 | Returns a 2-tuple of ``(option, sub-options)``, where `sub-options`` |
|
681 | Returns a 2-tuple of ``(option, sub-options)``, where `sub-options`` | |
680 | is a dict of defined sub-options where keys and values are strings. |
|
682 | is a dict of defined sub-options where keys and values are strings. | |
681 | """ |
|
683 | """ | |
682 | main = self.config(section, name, default, untrusted=untrusted) |
|
684 | main = self.config(section, name, default, untrusted=untrusted) | |
683 | data = self._data(untrusted) |
|
685 | data = self._data(untrusted) | |
684 | sub = {} |
|
686 | sub = {} | |
685 | prefix = b'%s:' % name |
|
687 | prefix = b'%s:' % name | |
686 | for k, v in data.items(section): |
|
688 | for k, v in data.items(section): | |
687 | if k.startswith(prefix): |
|
689 | if k.startswith(prefix): | |
688 | sub[k[len(prefix) :]] = v |
|
690 | sub[k[len(prefix) :]] = v | |
689 |
|
691 | |||
690 | if self.debugflag and not untrusted and self._reportuntrusted: |
|
692 | if self.debugflag and not untrusted and self._reportuntrusted: | |
691 | for k, v in sub.items(): |
|
693 | for k, v in sub.items(): | |
692 | uvalue = self._ucfg.get(section, b'%s:%s' % (name, k)) |
|
694 | uvalue = self._ucfg.get(section, b'%s:%s' % (name, k)) | |
693 | if uvalue is not None and uvalue != v: |
|
695 | if uvalue is not None and uvalue != v: | |
694 | self.debug( |
|
696 | self.debug( | |
695 | b'ignoring untrusted configuration option ' |
|
697 | b'ignoring untrusted configuration option ' | |
696 | b'%s:%s.%s = %s\n' % (section, name, k, uvalue) |
|
698 | b'%s:%s.%s = %s\n' % (section, name, k, uvalue) | |
697 | ) |
|
699 | ) | |
698 |
|
700 | |||
699 | return main, sub |
|
701 | return main, sub | |
700 |
|
702 | |||
701 | def configpath(self, section, name, default=_unset, untrusted=False): |
|
703 | def configpath(self, section, name, default=_unset, untrusted=False): | |
702 | """get a path config item, expanded relative to repo root or config |
|
704 | """get a path config item, expanded relative to repo root or config | |
703 | file""" |
|
705 | file""" | |
704 | v = self.config(section, name, default, untrusted) |
|
706 | v = self.config(section, name, default, untrusted) | |
705 | if v is None: |
|
707 | if v is None: | |
706 | return None |
|
708 | return None | |
707 | if not os.path.isabs(v) or b"://" not in v: |
|
709 | if not os.path.isabs(v) or b"://" not in v: | |
708 | src = self.configsource(section, name, untrusted) |
|
710 | src = self.configsource(section, name, untrusted) | |
709 | if b':' in src: |
|
711 | if b':' in src: | |
710 | base = os.path.dirname(src.rsplit(b':')[0]) |
|
712 | base = os.path.dirname(src.rsplit(b':')[0]) | |
711 | v = os.path.join(base, os.path.expanduser(v)) |
|
713 | v = os.path.join(base, os.path.expanduser(v)) | |
712 | return v |
|
714 | return v | |
713 |
|
715 | |||
714 | def configbool(self, section, name, default=_unset, untrusted=False): |
|
716 | def configbool(self, section, name, default=_unset, untrusted=False): | |
715 | """parse a configuration element as a boolean |
|
717 | """parse a configuration element as a boolean | |
716 |
|
718 | |||
717 | >>> u = ui(); s = b'foo' |
|
719 | >>> u = ui(); s = b'foo' | |
718 | >>> u.setconfig(s, b'true', b'yes') |
|
720 | >>> u.setconfig(s, b'true', b'yes') | |
719 | >>> u.configbool(s, b'true') |
|
721 | >>> u.configbool(s, b'true') | |
720 | True |
|
722 | True | |
721 | >>> u.setconfig(s, b'false', b'no') |
|
723 | >>> u.setconfig(s, b'false', b'no') | |
722 | >>> u.configbool(s, b'false') |
|
724 | >>> u.configbool(s, b'false') | |
723 | False |
|
725 | False | |
724 | >>> u.configbool(s, b'unknown') |
|
726 | >>> u.configbool(s, b'unknown') | |
725 | False |
|
727 | False | |
726 | >>> u.configbool(s, b'unknown', True) |
|
728 | >>> u.configbool(s, b'unknown', True) | |
727 | True |
|
729 | True | |
728 | >>> u.setconfig(s, b'invalid', b'somevalue') |
|
730 | >>> u.setconfig(s, b'invalid', b'somevalue') | |
729 | >>> u.configbool(s, b'invalid') |
|
731 | >>> u.configbool(s, b'invalid') | |
730 | Traceback (most recent call last): |
|
732 | Traceback (most recent call last): | |
731 | ... |
|
733 | ... | |
732 | ConfigError: foo.invalid is not a boolean ('somevalue') |
|
734 | ConfigError: foo.invalid is not a boolean ('somevalue') | |
733 | """ |
|
735 | """ | |
734 |
|
736 | |||
735 | v = self._config(section, name, default, untrusted=untrusted) |
|
737 | v = self._config(section, name, default, untrusted=untrusted) | |
736 | if v is None: |
|
738 | if v is None: | |
737 | return v |
|
739 | return v | |
738 | if v is _unset: |
|
740 | if v is _unset: | |
739 | if default is _unset: |
|
741 | if default is _unset: | |
740 | return False |
|
742 | return False | |
741 | return default |
|
743 | return default | |
742 | if isinstance(v, bool): |
|
744 | if isinstance(v, bool): | |
743 | return v |
|
745 | return v | |
744 | b = stringutil.parsebool(v) |
|
746 | b = stringutil.parsebool(v) | |
745 | if b is None: |
|
747 | if b is None: | |
746 | raise error.ConfigError( |
|
748 | raise error.ConfigError( | |
747 | _(b"%s.%s is not a boolean ('%s')") % (section, name, v) |
|
749 | _(b"%s.%s is not a boolean ('%s')") % (section, name, v) | |
748 | ) |
|
750 | ) | |
749 | return b |
|
751 | return b | |
750 |
|
752 | |||
751 | def configwith( |
|
753 | def configwith( | |
752 | self, convert, section, name, default=_unset, desc=None, untrusted=False |
|
754 | self, convert, section, name, default=_unset, desc=None, untrusted=False | |
753 | ): |
|
755 | ): | |
754 | """parse a configuration element with a conversion function |
|
756 | """parse a configuration element with a conversion function | |
755 |
|
757 | |||
756 | >>> u = ui(); s = b'foo' |
|
758 | >>> u = ui(); s = b'foo' | |
757 | >>> u.setconfig(s, b'float1', b'42') |
|
759 | >>> u.setconfig(s, b'float1', b'42') | |
758 | >>> u.configwith(float, s, b'float1') |
|
760 | >>> u.configwith(float, s, b'float1') | |
759 | 42.0 |
|
761 | 42.0 | |
760 | >>> u.setconfig(s, b'float2', b'-4.25') |
|
762 | >>> u.setconfig(s, b'float2', b'-4.25') | |
761 | >>> u.configwith(float, s, b'float2') |
|
763 | >>> u.configwith(float, s, b'float2') | |
762 | -4.25 |
|
764 | -4.25 | |
763 | >>> u.configwith(float, s, b'unknown', 7) |
|
765 | >>> u.configwith(float, s, b'unknown', 7) | |
764 | 7.0 |
|
766 | 7.0 | |
765 | >>> u.setconfig(s, b'invalid', b'somevalue') |
|
767 | >>> u.setconfig(s, b'invalid', b'somevalue') | |
766 | >>> u.configwith(float, s, b'invalid') |
|
768 | >>> u.configwith(float, s, b'invalid') | |
767 | Traceback (most recent call last): |
|
769 | Traceback (most recent call last): | |
768 | ... |
|
770 | ... | |
769 | ConfigError: foo.invalid is not a valid float ('somevalue') |
|
771 | ConfigError: foo.invalid is not a valid float ('somevalue') | |
770 | >>> u.configwith(float, s, b'invalid', desc=b'womble') |
|
772 | >>> u.configwith(float, s, b'invalid', desc=b'womble') | |
771 | Traceback (most recent call last): |
|
773 | Traceback (most recent call last): | |
772 | ... |
|
774 | ... | |
773 | ConfigError: foo.invalid is not a valid womble ('somevalue') |
|
775 | ConfigError: foo.invalid is not a valid womble ('somevalue') | |
774 | """ |
|
776 | """ | |
775 |
|
777 | |||
776 | v = self.config(section, name, default, untrusted) |
|
778 | v = self.config(section, name, default, untrusted) | |
777 | if v is None: |
|
779 | if v is None: | |
778 | return v # do not attempt to convert None |
|
780 | return v # do not attempt to convert None | |
779 | try: |
|
781 | try: | |
780 | return convert(v) |
|
782 | return convert(v) | |
781 | except (ValueError, error.ParseError): |
|
783 | except (ValueError, error.ParseError): | |
782 | if desc is None: |
|
784 | if desc is None: | |
783 | desc = pycompat.sysbytes(convert.__name__) |
|
785 | desc = pycompat.sysbytes(convert.__name__) | |
784 | raise error.ConfigError( |
|
786 | raise error.ConfigError( | |
785 | _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v) |
|
787 | _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v) | |
786 | ) |
|
788 | ) | |
787 |
|
789 | |||
788 | def configint(self, section, name, default=_unset, untrusted=False): |
|
790 | def configint(self, section, name, default=_unset, untrusted=False): | |
789 | """parse a configuration element as an integer |
|
791 | """parse a configuration element as an integer | |
790 |
|
792 | |||
791 | >>> u = ui(); s = b'foo' |
|
793 | >>> u = ui(); s = b'foo' | |
792 | >>> u.setconfig(s, b'int1', b'42') |
|
794 | >>> u.setconfig(s, b'int1', b'42') | |
793 | >>> u.configint(s, b'int1') |
|
795 | >>> u.configint(s, b'int1') | |
794 | 42 |
|
796 | 42 | |
795 | >>> u.setconfig(s, b'int2', b'-42') |
|
797 | >>> u.setconfig(s, b'int2', b'-42') | |
796 | >>> u.configint(s, b'int2') |
|
798 | >>> u.configint(s, b'int2') | |
797 | -42 |
|
799 | -42 | |
798 | >>> u.configint(s, b'unknown', 7) |
|
800 | >>> u.configint(s, b'unknown', 7) | |
799 | 7 |
|
801 | 7 | |
800 | >>> u.setconfig(s, b'invalid', b'somevalue') |
|
802 | >>> u.setconfig(s, b'invalid', b'somevalue') | |
801 | >>> u.configint(s, b'invalid') |
|
803 | >>> u.configint(s, b'invalid') | |
802 | Traceback (most recent call last): |
|
804 | Traceback (most recent call last): | |
803 | ... |
|
805 | ... | |
804 | ConfigError: foo.invalid is not a valid integer ('somevalue') |
|
806 | ConfigError: foo.invalid is not a valid integer ('somevalue') | |
805 | """ |
|
807 | """ | |
806 |
|
808 | |||
807 | return self.configwith( |
|
809 | return self.configwith( | |
808 | int, section, name, default, b'integer', untrusted |
|
810 | int, section, name, default, b'integer', untrusted | |
809 | ) |
|
811 | ) | |
810 |
|
812 | |||
811 | def configbytes(self, section, name, default=_unset, untrusted=False): |
|
813 | def configbytes(self, section, name, default=_unset, untrusted=False): | |
812 | """parse a configuration element as a quantity in bytes |
|
814 | """parse a configuration element as a quantity in bytes | |
813 |
|
815 | |||
814 | Units can be specified as b (bytes), k or kb (kilobytes), m or |
|
816 | Units can be specified as b (bytes), k or kb (kilobytes), m or | |
815 | mb (megabytes), g or gb (gigabytes). |
|
817 | mb (megabytes), g or gb (gigabytes). | |
816 |
|
818 | |||
817 | >>> u = ui(); s = b'foo' |
|
819 | >>> u = ui(); s = b'foo' | |
818 | >>> u.setconfig(s, b'val1', b'42') |
|
820 | >>> u.setconfig(s, b'val1', b'42') | |
819 | >>> u.configbytes(s, b'val1') |
|
821 | >>> u.configbytes(s, b'val1') | |
820 | 42 |
|
822 | 42 | |
821 | >>> u.setconfig(s, b'val2', b'42.5 kb') |
|
823 | >>> u.setconfig(s, b'val2', b'42.5 kb') | |
822 | >>> u.configbytes(s, b'val2') |
|
824 | >>> u.configbytes(s, b'val2') | |
823 | 43520 |
|
825 | 43520 | |
824 | >>> u.configbytes(s, b'unknown', b'7 MB') |
|
826 | >>> u.configbytes(s, b'unknown', b'7 MB') | |
825 | 7340032 |
|
827 | 7340032 | |
826 | >>> u.setconfig(s, b'invalid', b'somevalue') |
|
828 | >>> u.setconfig(s, b'invalid', b'somevalue') | |
827 | >>> u.configbytes(s, b'invalid') |
|
829 | >>> u.configbytes(s, b'invalid') | |
828 | Traceback (most recent call last): |
|
830 | Traceback (most recent call last): | |
829 | ... |
|
831 | ... | |
830 | ConfigError: foo.invalid is not a byte quantity ('somevalue') |
|
832 | ConfigError: foo.invalid is not a byte quantity ('somevalue') | |
831 | """ |
|
833 | """ | |
832 |
|
834 | |||
833 | value = self._config(section, name, default, untrusted) |
|
835 | value = self._config(section, name, default, untrusted) | |
834 | if value is _unset: |
|
836 | if value is _unset: | |
835 | if default is _unset: |
|
837 | if default is _unset: | |
836 | default = 0 |
|
838 | default = 0 | |
837 | value = default |
|
839 | value = default | |
838 | if not isinstance(value, bytes): |
|
840 | if not isinstance(value, bytes): | |
839 | return value |
|
841 | return value | |
840 | try: |
|
842 | try: | |
841 | return util.sizetoint(value) |
|
843 | return util.sizetoint(value) | |
842 | except error.ParseError: |
|
844 | except error.ParseError: | |
843 | raise error.ConfigError( |
|
845 | raise error.ConfigError( | |
844 | _(b"%s.%s is not a byte quantity ('%s')") |
|
846 | _(b"%s.%s is not a byte quantity ('%s')") | |
845 | % (section, name, value) |
|
847 | % (section, name, value) | |
846 | ) |
|
848 | ) | |
847 |
|
849 | |||
848 | def configlist(self, section, name, default=_unset, untrusted=False): |
|
850 | def configlist(self, section, name, default=_unset, untrusted=False): | |
849 | """parse a configuration element as a list of comma/space separated |
|
851 | """parse a configuration element as a list of comma/space separated | |
850 | strings |
|
852 | strings | |
851 |
|
853 | |||
852 | >>> u = ui(); s = b'foo' |
|
854 | >>> u = ui(); s = b'foo' | |
853 | >>> u.setconfig(s, b'list1', b'this,is "a small" ,test') |
|
855 | >>> u.setconfig(s, b'list1', b'this,is "a small" ,test') | |
854 | >>> u.configlist(s, b'list1') |
|
856 | >>> u.configlist(s, b'list1') | |
855 | ['this', 'is', 'a small', 'test'] |
|
857 | ['this', 'is', 'a small', 'test'] | |
856 | >>> u.setconfig(s, b'list2', b'this, is "a small" , test ') |
|
858 | >>> u.setconfig(s, b'list2', b'this, is "a small" , test ') | |
857 | >>> u.configlist(s, b'list2') |
|
859 | >>> u.configlist(s, b'list2') | |
858 | ['this', 'is', 'a small', 'test'] |
|
860 | ['this', 'is', 'a small', 'test'] | |
859 | """ |
|
861 | """ | |
860 | # default is not always a list |
|
862 | # default is not always a list | |
861 | v = self.configwith( |
|
863 | v = self.configwith( | |
862 | config.parselist, section, name, default, b'list', untrusted |
|
864 | config.parselist, section, name, default, b'list', untrusted | |
863 | ) |
|
865 | ) | |
864 | if isinstance(v, bytes): |
|
866 | if isinstance(v, bytes): | |
865 | return config.parselist(v) |
|
867 | return config.parselist(v) | |
866 | elif v is None: |
|
868 | elif v is None: | |
867 | return [] |
|
869 | return [] | |
868 | return v |
|
870 | return v | |
869 |
|
871 | |||
870 | def configdate(self, section, name, default=_unset, untrusted=False): |
|
872 | def configdate(self, section, name, default=_unset, untrusted=False): | |
871 | """parse a configuration element as a tuple of ints |
|
873 | """parse a configuration element as a tuple of ints | |
872 |
|
874 | |||
873 | >>> u = ui(); s = b'foo' |
|
875 | >>> u = ui(); s = b'foo' | |
874 | >>> u.setconfig(s, b'date', b'0 0') |
|
876 | >>> u.setconfig(s, b'date', b'0 0') | |
875 | >>> u.configdate(s, b'date') |
|
877 | >>> u.configdate(s, b'date') | |
876 | (0, 0) |
|
878 | (0, 0) | |
877 | """ |
|
879 | """ | |
878 | if self.config(section, name, default, untrusted): |
|
880 | if self.config(section, name, default, untrusted): | |
879 | return self.configwith( |
|
881 | return self.configwith( | |
880 | dateutil.parsedate, section, name, default, b'date', untrusted |
|
882 | dateutil.parsedate, section, name, default, b'date', untrusted | |
881 | ) |
|
883 | ) | |
882 | if default is _unset: |
|
884 | if default is _unset: | |
883 | return None |
|
885 | return None | |
884 | return default |
|
886 | return default | |
885 |
|
887 | |||
886 | def configdefault(self, section, name): |
|
888 | def configdefault(self, section, name): | |
887 | """returns the default value of the config item""" |
|
889 | """returns the default value of the config item""" | |
888 | item = self._knownconfig.get(section, {}).get(name) |
|
890 | item = self._knownconfig.get(section, {}).get(name) | |
889 | itemdefault = None |
|
891 | itemdefault = None | |
890 | if item is not None: |
|
892 | if item is not None: | |
891 | if callable(item.default): |
|
893 | if callable(item.default): | |
892 | itemdefault = item.default() |
|
894 | itemdefault = item.default() | |
893 | else: |
|
895 | else: | |
894 | itemdefault = item.default |
|
896 | itemdefault = item.default | |
895 | return itemdefault |
|
897 | return itemdefault | |
896 |
|
898 | |||
897 | def hasconfig(self, section, name, untrusted=False): |
|
899 | def hasconfig(self, section, name, untrusted=False): | |
898 | return self._data(untrusted).hasitem(section, name) |
|
900 | return self._data(untrusted).hasitem(section, name) | |
899 |
|
901 | |||
900 | def has_section(self, section, untrusted=False): |
|
902 | def has_section(self, section, untrusted=False): | |
901 | '''tell whether section exists in config.''' |
|
903 | '''tell whether section exists in config.''' | |
902 | return section in self._data(untrusted) |
|
904 | return section in self._data(untrusted) | |
903 |
|
905 | |||
904 | def configitems(self, section, untrusted=False, ignoresub=False): |
|
906 | def configitems(self, section, untrusted=False, ignoresub=False): | |
905 | items = self._data(untrusted).items(section) |
|
907 | items = self._data(untrusted).items(section) | |
906 | if ignoresub: |
|
908 | if ignoresub: | |
907 | items = [i for i in items if b':' not in i[0]] |
|
909 | items = [i for i in items if b':' not in i[0]] | |
908 | if self.debugflag and not untrusted and self._reportuntrusted: |
|
910 | if self.debugflag and not untrusted and self._reportuntrusted: | |
909 | for k, v in self._ucfg.items(section): |
|
911 | for k, v in self._ucfg.items(section): | |
910 | if self._tcfg.get(section, k) != v: |
|
912 | if self._tcfg.get(section, k) != v: | |
911 | self.debug( |
|
913 | self.debug( | |
912 | b"ignoring untrusted configuration option " |
|
914 | b"ignoring untrusted configuration option " | |
913 | b"%s.%s = %s\n" % (section, k, v) |
|
915 | b"%s.%s = %s\n" % (section, k, v) | |
914 | ) |
|
916 | ) | |
915 | return items |
|
917 | return items | |
916 |
|
918 | |||
917 | def walkconfig(self, untrusted=False): |
|
919 | def walkconfig(self, untrusted=False): | |
918 | cfg = self._data(untrusted) |
|
920 | cfg = self._data(untrusted) | |
919 | for section in cfg.sections(): |
|
921 | for section in cfg.sections(): | |
920 | for name, value in self.configitems(section, untrusted): |
|
922 | for name, value in self.configitems(section, untrusted): | |
921 | yield section, name, value |
|
923 | yield section, name, value | |
922 |
|
924 | |||
923 | def plain(self, feature=None): |
|
925 | def plain(self, feature=None): | |
924 | '''is plain mode active? |
|
926 | '''is plain mode active? | |
925 |
|
927 | |||
926 | Plain mode means that all configuration variables which affect |
|
928 | Plain mode means that all configuration variables which affect | |
927 | the behavior and output of Mercurial should be |
|
929 | the behavior and output of Mercurial should be | |
928 | ignored. Additionally, the output should be stable, |
|
930 | ignored. Additionally, the output should be stable, | |
929 | reproducible and suitable for use in scripts or applications. |
|
931 | reproducible and suitable for use in scripts or applications. | |
930 |
|
932 | |||
931 | The only way to trigger plain mode is by setting either the |
|
933 | The only way to trigger plain mode is by setting either the | |
932 | `HGPLAIN' or `HGPLAINEXCEPT' environment variables. |
|
934 | `HGPLAIN' or `HGPLAINEXCEPT' environment variables. | |
933 |
|
935 | |||
934 | The return value can either be |
|
936 | The return value can either be | |
935 | - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT |
|
937 | - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT | |
936 | - False if feature is disabled by default and not included in HGPLAIN |
|
938 | - False if feature is disabled by default and not included in HGPLAIN | |
937 | - True otherwise |
|
939 | - True otherwise | |
938 | ''' |
|
940 | ''' | |
939 | if ( |
|
941 | if ( | |
940 | b'HGPLAIN' not in encoding.environ |
|
942 | b'HGPLAIN' not in encoding.environ | |
941 | and b'HGPLAINEXCEPT' not in encoding.environ |
|
943 | and b'HGPLAINEXCEPT' not in encoding.environ | |
942 | ): |
|
944 | ): | |
943 | return False |
|
945 | return False | |
944 | exceptions = ( |
|
946 | exceptions = ( | |
945 | encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',') |
|
947 | encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',') | |
946 | ) |
|
948 | ) | |
947 | # TODO: add support for HGPLAIN=+feature,-feature syntax |
|
949 | # TODO: add support for HGPLAIN=+feature,-feature syntax | |
948 | if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split( |
|
950 | if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split( | |
949 | b',' |
|
951 | b',' | |
950 | ): |
|
952 | ): | |
951 | exceptions.append(b'strictflags') |
|
953 | exceptions.append(b'strictflags') | |
952 | if feature and exceptions: |
|
954 | if feature and exceptions: | |
953 | return feature not in exceptions |
|
955 | return feature not in exceptions | |
954 | return True |
|
956 | return True | |
955 |
|
957 | |||
956 | def username(self, acceptempty=False): |
|
958 | def username(self, acceptempty=False): | |
957 | """Return default username to be used in commits. |
|
959 | """Return default username to be used in commits. | |
958 |
|
960 | |||
959 | Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL |
|
961 | Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL | |
960 | and stop searching if one of these is set. |
|
962 | and stop searching if one of these is set. | |
961 | If not found and acceptempty is True, returns None. |
|
963 | If not found and acceptempty is True, returns None. | |
962 | If not found and ui.askusername is True, ask the user, else use |
|
964 | If not found and ui.askusername is True, ask the user, else use | |
963 | ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname". |
|
965 | ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname". | |
964 | If no username could be found, raise an Abort error. |
|
966 | If no username could be found, raise an Abort error. | |
965 | """ |
|
967 | """ | |
966 | user = encoding.environ.get(b"HGUSER") |
|
968 | user = encoding.environ.get(b"HGUSER") | |
967 | if user is None: |
|
969 | if user is None: | |
968 | user = self.config(b"ui", b"username") |
|
970 | user = self.config(b"ui", b"username") | |
969 | if user is not None: |
|
971 | if user is not None: | |
970 | user = os.path.expandvars(user) |
|
972 | user = os.path.expandvars(user) | |
971 | if user is None: |
|
973 | if user is None: | |
972 | user = encoding.environ.get(b"EMAIL") |
|
974 | user = encoding.environ.get(b"EMAIL") | |
973 | if user is None and acceptempty: |
|
975 | if user is None and acceptempty: | |
974 | return user |
|
976 | return user | |
975 | if user is None and self.configbool(b"ui", b"askusername"): |
|
977 | if user is None and self.configbool(b"ui", b"askusername"): | |
976 | user = self.prompt(_(b"enter a commit username:"), default=None) |
|
978 | user = self.prompt(_(b"enter a commit username:"), default=None) | |
977 | if user is None and not self.interactive(): |
|
979 | if user is None and not self.interactive(): | |
978 | try: |
|
980 | try: | |
979 | user = b'%s@%s' % ( |
|
981 | user = b'%s@%s' % ( | |
980 | procutil.getuser(), |
|
982 | procutil.getuser(), | |
981 | encoding.strtolocal(socket.getfqdn()), |
|
983 | encoding.strtolocal(socket.getfqdn()), | |
982 | ) |
|
984 | ) | |
983 | self.warn(_(b"no username found, using '%s' instead\n") % user) |
|
985 | self.warn(_(b"no username found, using '%s' instead\n") % user) | |
984 | except KeyError: |
|
986 | except KeyError: | |
985 | pass |
|
987 | pass | |
986 | if not user: |
|
988 | if not user: | |
987 | raise error.Abort( |
|
989 | raise error.Abort( | |
988 | _(b'no username supplied'), |
|
990 | _(b'no username supplied'), | |
989 | hint=_(b"use 'hg config --edit' " b'to set your username'), |
|
991 | hint=_(b"use 'hg config --edit' " b'to set your username'), | |
990 | ) |
|
992 | ) | |
991 | if b"\n" in user: |
|
993 | if b"\n" in user: | |
992 | raise error.Abort( |
|
994 | raise error.Abort( | |
993 | _(b"username %r contains a newline\n") % pycompat.bytestr(user) |
|
995 | _(b"username %r contains a newline\n") % pycompat.bytestr(user) | |
994 | ) |
|
996 | ) | |
995 | return user |
|
997 | return user | |
996 |
|
998 | |||
997 | def shortuser(self, user): |
|
999 | def shortuser(self, user): | |
998 | """Return a short representation of a user name or email address.""" |
|
1000 | """Return a short representation of a user name or email address.""" | |
999 | if not self.verbose: |
|
1001 | if not self.verbose: | |
1000 | user = stringutil.shortuser(user) |
|
1002 | user = stringutil.shortuser(user) | |
1001 | return user |
|
1003 | return user | |
1002 |
|
1004 | |||
1003 | def expandpath(self, loc, default=None): |
|
1005 | def expandpath(self, loc, default=None): | |
1004 | """Return repository location relative to cwd or from [paths]""" |
|
1006 | """Return repository location relative to cwd or from [paths]""" | |
1005 | try: |
|
1007 | try: | |
1006 | p = self.paths.getpath(loc) |
|
1008 | p = self.paths.getpath(loc) | |
1007 | if p: |
|
1009 | if p: | |
1008 | return p.rawloc |
|
1010 | return p.rawloc | |
1009 | except error.RepoError: |
|
1011 | except error.RepoError: | |
1010 | pass |
|
1012 | pass | |
1011 |
|
1013 | |||
1012 | if default: |
|
1014 | if default: | |
1013 | try: |
|
1015 | try: | |
1014 | p = self.paths.getpath(default) |
|
1016 | p = self.paths.getpath(default) | |
1015 | if p: |
|
1017 | if p: | |
1016 | return p.rawloc |
|
1018 | return p.rawloc | |
1017 | except error.RepoError: |
|
1019 | except error.RepoError: | |
1018 | pass |
|
1020 | pass | |
1019 |
|
1021 | |||
1020 | return loc |
|
1022 | return loc | |
1021 |
|
1023 | |||
1022 | @util.propertycache |
|
1024 | @util.propertycache | |
1023 | def paths(self): |
|
1025 | def paths(self): | |
1024 | return paths(self) |
|
1026 | return paths(self) | |
1025 |
|
1027 | |||
1026 | @property |
|
1028 | @property | |
1027 | def fout(self): |
|
1029 | def fout(self): | |
1028 | return self._fout |
|
1030 | return self._fout | |
1029 |
|
1031 | |||
1030 | @fout.setter |
|
1032 | @fout.setter | |
1031 | def fout(self, f): |
|
1033 | def fout(self, f): | |
1032 | self._fout = f |
|
1034 | self._fout = f | |
1033 | self._fmsgout, self._fmsgerr = _selectmsgdests(self) |
|
1035 | self._fmsgout, self._fmsgerr = _selectmsgdests(self) | |
1034 |
|
1036 | |||
1035 | @property |
|
1037 | @property | |
1036 | def ferr(self): |
|
1038 | def ferr(self): | |
1037 | return self._ferr |
|
1039 | return self._ferr | |
1038 |
|
1040 | |||
1039 | @ferr.setter |
|
1041 | @ferr.setter | |
1040 | def ferr(self, f): |
|
1042 | def ferr(self, f): | |
1041 | self._ferr = f |
|
1043 | self._ferr = f | |
1042 | self._fmsgout, self._fmsgerr = _selectmsgdests(self) |
|
1044 | self._fmsgout, self._fmsgerr = _selectmsgdests(self) | |
1043 |
|
1045 | |||
1044 | @property |
|
1046 | @property | |
1045 | def fin(self): |
|
1047 | def fin(self): | |
1046 | return self._fin |
|
1048 | return self._fin | |
1047 |
|
1049 | |||
1048 | @fin.setter |
|
1050 | @fin.setter | |
1049 | def fin(self, f): |
|
1051 | def fin(self, f): | |
1050 | self._fin = f |
|
1052 | self._fin = f | |
1051 |
|
1053 | |||
1052 | @property |
|
1054 | @property | |
1053 | def fmsg(self): |
|
1055 | def fmsg(self): | |
1054 | """Stream dedicated for status/error messages; may be None if |
|
1056 | """Stream dedicated for status/error messages; may be None if | |
1055 | fout/ferr are used""" |
|
1057 | fout/ferr are used""" | |
1056 | return self._fmsg |
|
1058 | return self._fmsg | |
1057 |
|
1059 | |||
1058 | @fmsg.setter |
|
1060 | @fmsg.setter | |
1059 | def fmsg(self, f): |
|
1061 | def fmsg(self, f): | |
1060 | self._fmsg = f |
|
1062 | self._fmsg = f | |
1061 | self._fmsgout, self._fmsgerr = _selectmsgdests(self) |
|
1063 | self._fmsgout, self._fmsgerr = _selectmsgdests(self) | |
1062 |
|
1064 | |||
1063 | def pushbuffer(self, error=False, subproc=False, labeled=False): |
|
1065 | def pushbuffer(self, error=False, subproc=False, labeled=False): | |
1064 | """install a buffer to capture standard output of the ui object |
|
1066 | """install a buffer to capture standard output of the ui object | |
1065 |
|
1067 | |||
1066 | If error is True, the error output will be captured too. |
|
1068 | If error is True, the error output will be captured too. | |
1067 |
|
1069 | |||
1068 | If subproc is True, output from subprocesses (typically hooks) will be |
|
1070 | If subproc is True, output from subprocesses (typically hooks) will be | |
1069 | captured too. |
|
1071 | captured too. | |
1070 |
|
1072 | |||
1071 | If labeled is True, any labels associated with buffered |
|
1073 | If labeled is True, any labels associated with buffered | |
1072 | output will be handled. By default, this has no effect |
|
1074 | output will be handled. By default, this has no effect | |
1073 | on the output returned, but extensions and GUI tools may |
|
1075 | on the output returned, but extensions and GUI tools may | |
1074 | handle this argument and returned styled output. If output |
|
1076 | handle this argument and returned styled output. If output | |
1075 | is being buffered so it can be captured and parsed or |
|
1077 | is being buffered so it can be captured and parsed or | |
1076 | processed, labeled should not be set to True. |
|
1078 | processed, labeled should not be set to True. | |
1077 | """ |
|
1079 | """ | |
1078 | self._buffers.append([]) |
|
1080 | self._buffers.append([]) | |
1079 | self._bufferstates.append((error, subproc, labeled)) |
|
1081 | self._bufferstates.append((error, subproc, labeled)) | |
1080 | self._bufferapplylabels = labeled |
|
1082 | self._bufferapplylabels = labeled | |
1081 |
|
1083 | |||
1082 | def popbuffer(self): |
|
1084 | def popbuffer(self): | |
1083 | '''pop the last buffer and return the buffered output''' |
|
1085 | '''pop the last buffer and return the buffered output''' | |
1084 | self._bufferstates.pop() |
|
1086 | self._bufferstates.pop() | |
1085 | if self._bufferstates: |
|
1087 | if self._bufferstates: | |
1086 | self._bufferapplylabels = self._bufferstates[-1][2] |
|
1088 | self._bufferapplylabels = self._bufferstates[-1][2] | |
1087 | else: |
|
1089 | else: | |
1088 | self._bufferapplylabels = None |
|
1090 | self._bufferapplylabels = None | |
1089 |
|
1091 | |||
1090 | return b"".join(self._buffers.pop()) |
|
1092 | return b"".join(self._buffers.pop()) | |
1091 |
|
1093 | |||
1092 | def _isbuffered(self, dest): |
|
1094 | def _isbuffered(self, dest): | |
1093 | if dest is self._fout: |
|
1095 | if dest is self._fout: | |
1094 | return bool(self._buffers) |
|
1096 | return bool(self._buffers) | |
1095 | if dest is self._ferr: |
|
1097 | if dest is self._ferr: | |
1096 | return bool(self._bufferstates and self._bufferstates[-1][0]) |
|
1098 | return bool(self._bufferstates and self._bufferstates[-1][0]) | |
1097 | return False |
|
1099 | return False | |
1098 |
|
1100 | |||
1099 | def canwritewithoutlabels(self): |
|
1101 | def canwritewithoutlabels(self): | |
1100 | '''check if write skips the label''' |
|
1102 | '''check if write skips the label''' | |
1101 | if self._buffers and not self._bufferapplylabels: |
|
1103 | if self._buffers and not self._bufferapplylabels: | |
1102 | return True |
|
1104 | return True | |
1103 | return self._colormode is None |
|
1105 | return self._colormode is None | |
1104 |
|
1106 | |||
1105 | def canbatchlabeledwrites(self): |
|
1107 | def canbatchlabeledwrites(self): | |
1106 | '''check if write calls with labels are batchable''' |
|
1108 | '''check if write calls with labels are batchable''' | |
1107 | # Windows color printing is special, see ``write``. |
|
1109 | # Windows color printing is special, see ``write``. | |
1108 | return self._colormode != b'win32' |
|
1110 | return self._colormode != b'win32' | |
1109 |
|
1111 | |||
1110 | def write(self, *args, **opts): |
|
1112 | def write(self, *args, **opts): | |
1111 | '''write args to output |
|
1113 | '''write args to output | |
1112 |
|
1114 | |||
1113 | By default, this method simply writes to the buffer or stdout. |
|
1115 | By default, this method simply writes to the buffer or stdout. | |
1114 | Color mode can be set on the UI class to have the output decorated |
|
1116 | Color mode can be set on the UI class to have the output decorated | |
1115 | with color modifier before being written to stdout. |
|
1117 | with color modifier before being written to stdout. | |
1116 |
|
1118 | |||
1117 | The color used is controlled by an optional keyword argument, "label". |
|
1119 | The color used is controlled by an optional keyword argument, "label". | |
1118 | This should be a string containing label names separated by space. |
|
1120 | This should be a string containing label names separated by space. | |
1119 | Label names take the form of "topic.type". For example, ui.debug() |
|
1121 | Label names take the form of "topic.type". For example, ui.debug() | |
1120 | issues a label of "ui.debug". |
|
1122 | issues a label of "ui.debug". | |
1121 |
|
1123 | |||
1122 | Progress reports via stderr are normally cleared before writing as |
|
1124 | Progress reports via stderr are normally cleared before writing as | |
1123 | stdout and stderr go to the same terminal. This can be skipped with |
|
1125 | stdout and stderr go to the same terminal. This can be skipped with | |
1124 | the optional keyword argument "keepprogressbar". The progress bar |
|
1126 | the optional keyword argument "keepprogressbar". The progress bar | |
1125 | will continue to occupy a partial line on stderr in that case. |
|
1127 | will continue to occupy a partial line on stderr in that case. | |
1126 | This functionality is intended when Mercurial acts as data source |
|
1128 | This functionality is intended when Mercurial acts as data source | |
1127 | in a pipe. |
|
1129 | in a pipe. | |
1128 |
|
1130 | |||
1129 | When labeling output for a specific command, a label of |
|
1131 | When labeling output for a specific command, a label of | |
1130 | "cmdname.type" is recommended. For example, status issues |
|
1132 | "cmdname.type" is recommended. For example, status issues | |
1131 | a label of "status.modified" for modified files. |
|
1133 | a label of "status.modified" for modified files. | |
1132 | ''' |
|
1134 | ''' | |
1133 | dest = self._fout |
|
1135 | dest = self._fout | |
1134 |
|
1136 | |||
1135 | # inlined _write() for speed |
|
1137 | # inlined _write() for speed | |
1136 | if self._buffers: |
|
1138 | if self._buffers: | |
1137 | label = opts.get('label', b'') |
|
1139 | label = opts.get('label', b'') | |
1138 | if label and self._bufferapplylabels: |
|
1140 | if label and self._bufferapplylabels: | |
1139 | self._buffers[-1].extend(self.label(a, label) for a in args) |
|
1141 | self._buffers[-1].extend(self.label(a, label) for a in args) | |
1140 | else: |
|
1142 | else: | |
1141 | self._buffers[-1].extend(args) |
|
1143 | self._buffers[-1].extend(args) | |
1142 | return |
|
1144 | return | |
1143 |
|
1145 | |||
1144 | # inlined _writenobuf() for speed |
|
1146 | # inlined _writenobuf() for speed | |
1145 | if not opts.get('keepprogressbar', False): |
|
1147 | if not opts.get('keepprogressbar', False): | |
1146 | self._progclear() |
|
1148 | self._progclear() | |
1147 | msg = b''.join(args) |
|
1149 | msg = b''.join(args) | |
1148 |
|
1150 | |||
1149 | # opencode timeblockedsection because this is a critical path |
|
1151 | # opencode timeblockedsection because this is a critical path | |
1150 | starttime = util.timer() |
|
1152 | starttime = util.timer() | |
1151 | try: |
|
1153 | try: | |
1152 | if self._colormode == b'win32': |
|
1154 | if self._colormode == b'win32': | |
1153 | # windows color printing is its own can of crab, defer to |
|
1155 | # windows color printing is its own can of crab, defer to | |
1154 | # the color module and that is it. |
|
1156 | # the color module and that is it. | |
1155 | color.win32print(self, dest.write, msg, **opts) |
|
1157 | color.win32print(self, dest.write, msg, **opts) | |
1156 | else: |
|
1158 | else: | |
1157 | if self._colormode is not None: |
|
1159 | if self._colormode is not None: | |
1158 | label = opts.get('label', b'') |
|
1160 | label = opts.get('label', b'') | |
1159 | msg = self.label(msg, label) |
|
1161 | msg = self.label(msg, label) | |
1160 | dest.write(msg) |
|
1162 | dest.write(msg) | |
1161 | except IOError as err: |
|
1163 | except IOError as err: | |
1162 | raise error.StdioError(err) |
|
1164 | raise error.StdioError(err) | |
1163 | finally: |
|
1165 | finally: | |
1164 | self._blockedtimes[b'stdio_blocked'] += ( |
|
1166 | self._blockedtimes[b'stdio_blocked'] += ( | |
1165 | util.timer() - starttime |
|
1167 | util.timer() - starttime | |
1166 | ) * 1000 |
|
1168 | ) * 1000 | |
1167 |
|
1169 | |||
1168 | def write_err(self, *args, **opts): |
|
1170 | def write_err(self, *args, **opts): | |
1169 | self._write(self._ferr, *args, **opts) |
|
1171 | self._write(self._ferr, *args, **opts) | |
1170 |
|
1172 | |||
1171 | def _write(self, dest, *args, **opts): |
|
1173 | def _write(self, dest, *args, **opts): | |
1172 | # update write() as well if you touch this code |
|
1174 | # update write() as well if you touch this code | |
1173 | if self._isbuffered(dest): |
|
1175 | if self._isbuffered(dest): | |
1174 | label = opts.get('label', b'') |
|
1176 | label = opts.get('label', b'') | |
1175 | if label and self._bufferapplylabels: |
|
1177 | if label and self._bufferapplylabels: | |
1176 | self._buffers[-1].extend(self.label(a, label) for a in args) |
|
1178 | self._buffers[-1].extend(self.label(a, label) for a in args) | |
1177 | else: |
|
1179 | else: | |
1178 | self._buffers[-1].extend(args) |
|
1180 | self._buffers[-1].extend(args) | |
1179 | else: |
|
1181 | else: | |
1180 | self._writenobuf(dest, *args, **opts) |
|
1182 | self._writenobuf(dest, *args, **opts) | |
1181 |
|
1183 | |||
1182 | def _writenobuf(self, dest, *args, **opts): |
|
1184 | def _writenobuf(self, dest, *args, **opts): | |
1183 | # update write() as well if you touch this code |
|
1185 | # update write() as well if you touch this code | |
1184 | if not opts.get('keepprogressbar', False): |
|
1186 | if not opts.get('keepprogressbar', False): | |
1185 | self._progclear() |
|
1187 | self._progclear() | |
1186 | msg = b''.join(args) |
|
1188 | msg = b''.join(args) | |
1187 |
|
1189 | |||
1188 | # opencode timeblockedsection because this is a critical path |
|
1190 | # opencode timeblockedsection because this is a critical path | |
1189 | starttime = util.timer() |
|
1191 | starttime = util.timer() | |
1190 | try: |
|
1192 | try: | |
1191 | if dest is self._ferr and not getattr(self._fout, 'closed', False): |
|
1193 | if dest is self._ferr and not getattr(self._fout, 'closed', False): | |
1192 | self._fout.flush() |
|
1194 | self._fout.flush() | |
1193 | if getattr(dest, 'structured', False): |
|
1195 | if getattr(dest, 'structured', False): | |
1194 | # channel for machine-readable output with metadata, where |
|
1196 | # channel for machine-readable output with metadata, where | |
1195 | # no extra colorization is necessary. |
|
1197 | # no extra colorization is necessary. | |
1196 | dest.write(msg, **opts) |
|
1198 | dest.write(msg, **opts) | |
1197 | elif self._colormode == b'win32': |
|
1199 | elif self._colormode == b'win32': | |
1198 | # windows color printing is its own can of crab, defer to |
|
1200 | # windows color printing is its own can of crab, defer to | |
1199 | # the color module and that is it. |
|
1201 | # the color module and that is it. | |
1200 | color.win32print(self, dest.write, msg, **opts) |
|
1202 | color.win32print(self, dest.write, msg, **opts) | |
1201 | else: |
|
1203 | else: | |
1202 | if self._colormode is not None: |
|
1204 | if self._colormode is not None: | |
1203 | label = opts.get('label', b'') |
|
1205 | label = opts.get('label', b'') | |
1204 | msg = self.label(msg, label) |
|
1206 | msg = self.label(msg, label) | |
1205 | dest.write(msg) |
|
1207 | dest.write(msg) | |
1206 | # stderr may be buffered under win32 when redirected to files, |
|
1208 | # stderr may be buffered under win32 when redirected to files, | |
1207 | # including stdout. |
|
1209 | # including stdout. | |
1208 | if dest is self._ferr and not getattr(dest, 'closed', False): |
|
1210 | if dest is self._ferr and not getattr(dest, 'closed', False): | |
1209 | dest.flush() |
|
1211 | dest.flush() | |
1210 | except IOError as err: |
|
1212 | except IOError as err: | |
1211 | if dest is self._ferr and err.errno in ( |
|
1213 | if dest is self._ferr and err.errno in ( | |
1212 | errno.EPIPE, |
|
1214 | errno.EPIPE, | |
1213 | errno.EIO, |
|
1215 | errno.EIO, | |
1214 | errno.EBADF, |
|
1216 | errno.EBADF, | |
1215 | ): |
|
1217 | ): | |
1216 | # no way to report the error, so ignore it |
|
1218 | # no way to report the error, so ignore it | |
1217 | return |
|
1219 | return | |
1218 | raise error.StdioError(err) |
|
1220 | raise error.StdioError(err) | |
1219 | finally: |
|
1221 | finally: | |
1220 | self._blockedtimes[b'stdio_blocked'] += ( |
|
1222 | self._blockedtimes[b'stdio_blocked'] += ( | |
1221 | util.timer() - starttime |
|
1223 | util.timer() - starttime | |
1222 | ) * 1000 |
|
1224 | ) * 1000 | |
1223 |
|
1225 | |||
1224 | def _writemsg(self, dest, *args, **opts): |
|
1226 | def _writemsg(self, dest, *args, **opts): | |
1225 | timestamp = self.showtimestamp and opts.get('type') in { |
|
1227 | timestamp = self.showtimestamp and opts.get('type') in { | |
1226 | b'debug', |
|
1228 | b'debug', | |
1227 | b'error', |
|
1229 | b'error', | |
1228 | b'note', |
|
1230 | b'note', | |
1229 | b'status', |
|
1231 | b'status', | |
1230 | b'warning', |
|
1232 | b'warning', | |
1231 | } |
|
1233 | } | |
1232 | if timestamp: |
|
1234 | if timestamp: | |
1233 | args = ( |
|
1235 | args = ( | |
1234 | b'[%s] ' |
|
1236 | b'[%s] ' | |
1235 | % pycompat.bytestr(datetime.datetime.now().isoformat()), |
|
1237 | % pycompat.bytestr(datetime.datetime.now().isoformat()), | |
1236 | ) + args |
|
1238 | ) + args | |
1237 | _writemsgwith(self._write, dest, *args, **opts) |
|
1239 | _writemsgwith(self._write, dest, *args, **opts) | |
1238 | if timestamp: |
|
1240 | if timestamp: | |
1239 | dest.flush() |
|
1241 | dest.flush() | |
1240 |
|
1242 | |||
1241 | def _writemsgnobuf(self, dest, *args, **opts): |
|
1243 | def _writemsgnobuf(self, dest, *args, **opts): | |
1242 | _writemsgwith(self._writenobuf, dest, *args, **opts) |
|
1244 | _writemsgwith(self._writenobuf, dest, *args, **opts) | |
1243 |
|
1245 | |||
1244 | def flush(self): |
|
1246 | def flush(self): | |
1245 | # opencode timeblockedsection because this is a critical path |
|
1247 | # opencode timeblockedsection because this is a critical path | |
1246 | starttime = util.timer() |
|
1248 | starttime = util.timer() | |
1247 | try: |
|
1249 | try: | |
1248 | try: |
|
1250 | try: | |
1249 | self._fout.flush() |
|
1251 | self._fout.flush() | |
1250 | except IOError as err: |
|
1252 | except IOError as err: | |
1251 | if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF): |
|
1253 | if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF): | |
1252 | raise error.StdioError(err) |
|
1254 | raise error.StdioError(err) | |
1253 | finally: |
|
1255 | finally: | |
1254 | try: |
|
1256 | try: | |
1255 | self._ferr.flush() |
|
1257 | self._ferr.flush() | |
1256 | except IOError as err: |
|
1258 | except IOError as err: | |
1257 | if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF): |
|
1259 | if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF): | |
1258 | raise error.StdioError(err) |
|
1260 | raise error.StdioError(err) | |
1259 | finally: |
|
1261 | finally: | |
1260 | self._blockedtimes[b'stdio_blocked'] += ( |
|
1262 | self._blockedtimes[b'stdio_blocked'] += ( | |
1261 | util.timer() - starttime |
|
1263 | util.timer() - starttime | |
1262 | ) * 1000 |
|
1264 | ) * 1000 | |
1263 |
|
1265 | |||
1264 | def _isatty(self, fh): |
|
1266 | def _isatty(self, fh): | |
1265 | if self.configbool(b'ui', b'nontty'): |
|
1267 | if self.configbool(b'ui', b'nontty'): | |
1266 | return False |
|
1268 | return False | |
1267 | return procutil.isatty(fh) |
|
1269 | return procutil.isatty(fh) | |
1268 |
|
1270 | |||
1269 | def protectfinout(self): |
|
1271 | def protectfinout(self): | |
1270 | """Duplicate ui streams and redirect original if they are stdio |
|
1272 | """Duplicate ui streams and redirect original if they are stdio | |
1271 |
|
1273 | |||
1272 | Returns (fin, fout) which point to the original ui fds, but may be |
|
1274 | Returns (fin, fout) which point to the original ui fds, but may be | |
1273 | copy of them. The returned streams can be considered "owned" in that |
|
1275 | copy of them. The returned streams can be considered "owned" in that | |
1274 | print(), exec(), etc. never reach to them. |
|
1276 | print(), exec(), etc. never reach to them. | |
1275 | """ |
|
1277 | """ | |
1276 | if self._finoutredirected: |
|
1278 | if self._finoutredirected: | |
1277 | # if already redirected, protectstdio() would just create another |
|
1279 | # if already redirected, protectstdio() would just create another | |
1278 | # nullfd pair, which is equivalent to returning self._fin/_fout. |
|
1280 | # nullfd pair, which is equivalent to returning self._fin/_fout. | |
1279 | return self._fin, self._fout |
|
1281 | return self._fin, self._fout | |
1280 | fin, fout = procutil.protectstdio(self._fin, self._fout) |
|
1282 | fin, fout = procutil.protectstdio(self._fin, self._fout) | |
1281 | self._finoutredirected = (fin, fout) != (self._fin, self._fout) |
|
1283 | self._finoutredirected = (fin, fout) != (self._fin, self._fout) | |
1282 | return fin, fout |
|
1284 | return fin, fout | |
1283 |
|
1285 | |||
1284 | def restorefinout(self, fin, fout): |
|
1286 | def restorefinout(self, fin, fout): | |
1285 | """Restore ui streams from possibly duplicated (fin, fout)""" |
|
1287 | """Restore ui streams from possibly duplicated (fin, fout)""" | |
1286 | if (fin, fout) == (self._fin, self._fout): |
|
1288 | if (fin, fout) == (self._fin, self._fout): | |
1287 | return |
|
1289 | return | |
1288 | procutil.restorestdio(self._fin, self._fout, fin, fout) |
|
1290 | procutil.restorestdio(self._fin, self._fout, fin, fout) | |
1289 | # protectfinout() won't create more than one duplicated streams, |
|
1291 | # protectfinout() won't create more than one duplicated streams, | |
1290 | # so we can just turn the redirection flag off. |
|
1292 | # so we can just turn the redirection flag off. | |
1291 | self._finoutredirected = False |
|
1293 | self._finoutredirected = False | |
1292 |
|
1294 | |||
1293 | @contextlib.contextmanager |
|
1295 | @contextlib.contextmanager | |
1294 | def protectedfinout(self): |
|
1296 | def protectedfinout(self): | |
1295 | """Run code block with protected standard streams""" |
|
1297 | """Run code block with protected standard streams""" | |
1296 | fin, fout = self.protectfinout() |
|
1298 | fin, fout = self.protectfinout() | |
1297 | try: |
|
1299 | try: | |
1298 | yield fin, fout |
|
1300 | yield fin, fout | |
1299 | finally: |
|
1301 | finally: | |
1300 | self.restorefinout(fin, fout) |
|
1302 | self.restorefinout(fin, fout) | |
1301 |
|
1303 | |||
1302 | def disablepager(self): |
|
1304 | def disablepager(self): | |
1303 | self._disablepager = True |
|
1305 | self._disablepager = True | |
1304 |
|
1306 | |||
1305 | def pager(self, command): |
|
1307 | def pager(self, command): | |
1306 | """Start a pager for subsequent command output. |
|
1308 | """Start a pager for subsequent command output. | |
1307 |
|
1309 | |||
1308 | Commands which produce a long stream of output should call |
|
1310 | Commands which produce a long stream of output should call | |
1309 | this function to activate the user's preferred pagination |
|
1311 | this function to activate the user's preferred pagination | |
1310 | mechanism (which may be no pager). Calling this function |
|
1312 | mechanism (which may be no pager). Calling this function | |
1311 | precludes any future use of interactive functionality, such as |
|
1313 | precludes any future use of interactive functionality, such as | |
1312 | prompting the user or activating curses. |
|
1314 | prompting the user or activating curses. | |
1313 |
|
1315 | |||
1314 | Args: |
|
1316 | Args: | |
1315 | command: The full, non-aliased name of the command. That is, "log" |
|
1317 | command: The full, non-aliased name of the command. That is, "log" | |
1316 | not "history, "summary" not "summ", etc. |
|
1318 | not "history, "summary" not "summ", etc. | |
1317 | """ |
|
1319 | """ | |
1318 | if self._disablepager or self.pageractive: |
|
1320 | if self._disablepager or self.pageractive: | |
1319 | # how pager should do is already determined |
|
1321 | # how pager should do is already determined | |
1320 | return |
|
1322 | return | |
1321 |
|
1323 | |||
1322 | if not command.startswith(b'internal-always-') and ( |
|
1324 | if not command.startswith(b'internal-always-') and ( | |
1323 | # explicit --pager=on (= 'internal-always-' prefix) should |
|
1325 | # explicit --pager=on (= 'internal-always-' prefix) should | |
1324 | # take precedence over disabling factors below |
|
1326 | # take precedence over disabling factors below | |
1325 | command in self.configlist(b'pager', b'ignore') |
|
1327 | command in self.configlist(b'pager', b'ignore') | |
1326 | or not self.configbool(b'ui', b'paginate') |
|
1328 | or not self.configbool(b'ui', b'paginate') | |
1327 | or not self.configbool(b'pager', b'attend-' + command, True) |
|
1329 | or not self.configbool(b'pager', b'attend-' + command, True) | |
1328 | or encoding.environ.get(b'TERM') == b'dumb' |
|
1330 | or encoding.environ.get(b'TERM') == b'dumb' | |
1329 | # TODO: if we want to allow HGPLAINEXCEPT=pager, |
|
1331 | # TODO: if we want to allow HGPLAINEXCEPT=pager, | |
1330 | # formatted() will need some adjustment. |
|
1332 | # formatted() will need some adjustment. | |
1331 | or not self.formatted() |
|
1333 | or not self.formatted() | |
1332 | or self.plain() |
|
1334 | or self.plain() | |
1333 | or self._buffers |
|
1335 | or self._buffers | |
1334 | # TODO: expose debugger-enabled on the UI object |
|
1336 | # TODO: expose debugger-enabled on the UI object | |
1335 | or b'--debugger' in pycompat.sysargv |
|
1337 | or b'--debugger' in pycompat.sysargv | |
1336 | ): |
|
1338 | ): | |
1337 | # We only want to paginate if the ui appears to be |
|
1339 | # We only want to paginate if the ui appears to be | |
1338 | # interactive, the user didn't say HGPLAIN or |
|
1340 | # interactive, the user didn't say HGPLAIN or | |
1339 | # HGPLAINEXCEPT=pager, and the user didn't specify --debug. |
|
1341 | # HGPLAINEXCEPT=pager, and the user didn't specify --debug. | |
1340 | return |
|
1342 | return | |
1341 |
|
1343 | |||
1342 | pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager) |
|
1344 | pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager) | |
1343 | if not pagercmd: |
|
1345 | if not pagercmd: | |
1344 | return |
|
1346 | return | |
1345 |
|
1347 | |||
1346 | pagerenv = {} |
|
1348 | pagerenv = {} | |
1347 | for name, value in rcutil.defaultpagerenv().items(): |
|
1349 | for name, value in rcutil.defaultpagerenv().items(): | |
1348 | if name not in encoding.environ: |
|
1350 | if name not in encoding.environ: | |
1349 | pagerenv[name] = value |
|
1351 | pagerenv[name] = value | |
1350 |
|
1352 | |||
1351 | self.debug( |
|
1353 | self.debug( | |
1352 | b'starting pager for command %s\n' % stringutil.pprint(command) |
|
1354 | b'starting pager for command %s\n' % stringutil.pprint(command) | |
1353 | ) |
|
1355 | ) | |
1354 | self.flush() |
|
1356 | self.flush() | |
1355 |
|
1357 | |||
1356 | wasformatted = self.formatted() |
|
1358 | wasformatted = self.formatted() | |
1357 | if util.safehasattr(signal, b"SIGPIPE"): |
|
1359 | if util.safehasattr(signal, b"SIGPIPE"): | |
1358 | signal.signal(signal.SIGPIPE, _catchterm) |
|
1360 | signal.signal(signal.SIGPIPE, _catchterm) | |
1359 | if self._runpager(pagercmd, pagerenv): |
|
1361 | if self._runpager(pagercmd, pagerenv): | |
1360 | self.pageractive = True |
|
1362 | self.pageractive = True | |
1361 | # Preserve the formatted-ness of the UI. This is important |
|
1363 | # Preserve the formatted-ness of the UI. This is important | |
1362 | # because we mess with stdout, which might confuse |
|
1364 | # because we mess with stdout, which might confuse | |
1363 | # auto-detection of things being formatted. |
|
1365 | # auto-detection of things being formatted. | |
1364 | self.setconfig(b'ui', b'formatted', wasformatted, b'pager') |
|
1366 | self.setconfig(b'ui', b'formatted', wasformatted, b'pager') | |
1365 | self.setconfig(b'ui', b'interactive', False, b'pager') |
|
1367 | self.setconfig(b'ui', b'interactive', False, b'pager') | |
1366 |
|
1368 | |||
1367 | # If pagermode differs from color.mode, reconfigure color now that |
|
1369 | # If pagermode differs from color.mode, reconfigure color now that | |
1368 | # pageractive is set. |
|
1370 | # pageractive is set. | |
1369 | cm = self._colormode |
|
1371 | cm = self._colormode | |
1370 | if cm != self.config(b'color', b'pagermode', cm): |
|
1372 | if cm != self.config(b'color', b'pagermode', cm): | |
1371 | color.setup(self) |
|
1373 | color.setup(self) | |
1372 | else: |
|
1374 | else: | |
1373 | # If the pager can't be spawned in dispatch when --pager=on is |
|
1375 | # If the pager can't be spawned in dispatch when --pager=on is | |
1374 | # given, don't try again when the command runs, to avoid a duplicate |
|
1376 | # given, don't try again when the command runs, to avoid a duplicate | |
1375 | # warning about a missing pager command. |
|
1377 | # warning about a missing pager command. | |
1376 | self.disablepager() |
|
1378 | self.disablepager() | |
1377 |
|
1379 | |||
1378 | def _runpager(self, command, env=None): |
|
1380 | def _runpager(self, command, env=None): | |
1379 | """Actually start the pager and set up file descriptors. |
|
1381 | """Actually start the pager and set up file descriptors. | |
1380 |
|
1382 | |||
1381 | This is separate in part so that extensions (like chg) can |
|
1383 | This is separate in part so that extensions (like chg) can | |
1382 | override how a pager is invoked. |
|
1384 | override how a pager is invoked. | |
1383 | """ |
|
1385 | """ | |
1384 | if command == b'cat': |
|
1386 | if command == b'cat': | |
1385 | # Save ourselves some work. |
|
1387 | # Save ourselves some work. | |
1386 | return False |
|
1388 | return False | |
1387 | # If the command doesn't contain any of these characters, we |
|
1389 | # If the command doesn't contain any of these characters, we | |
1388 | # assume it's a binary and exec it directly. This means for |
|
1390 | # assume it's a binary and exec it directly. This means for | |
1389 | # simple pager command configurations, we can degrade |
|
1391 | # simple pager command configurations, we can degrade | |
1390 | # gracefully and tell the user about their broken pager. |
|
1392 | # gracefully and tell the user about their broken pager. | |
1391 | shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%") |
|
1393 | shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%") | |
1392 |
|
1394 | |||
1393 | if pycompat.iswindows and not shell: |
|
1395 | if pycompat.iswindows and not shell: | |
1394 | # Window's built-in `more` cannot be invoked with shell=False, but |
|
1396 | # Window's built-in `more` cannot be invoked with shell=False, but | |
1395 | # its `more.com` can. Hide this implementation detail from the |
|
1397 | # its `more.com` can. Hide this implementation detail from the | |
1396 | # user so we can also get sane bad PAGER behavior. MSYS has |
|
1398 | # user so we can also get sane bad PAGER behavior. MSYS has | |
1397 | # `more.exe`, so do a cmd.exe style resolution of the executable to |
|
1399 | # `more.exe`, so do a cmd.exe style resolution of the executable to | |
1398 | # determine which one to use. |
|
1400 | # determine which one to use. | |
1399 | fullcmd = procutil.findexe(command) |
|
1401 | fullcmd = procutil.findexe(command) | |
1400 | if not fullcmd: |
|
1402 | if not fullcmd: | |
1401 | self.warn( |
|
1403 | self.warn( | |
1402 | _(b"missing pager command '%s', skipping pager\n") % command |
|
1404 | _(b"missing pager command '%s', skipping pager\n") % command | |
1403 | ) |
|
1405 | ) | |
1404 | return False |
|
1406 | return False | |
1405 |
|
1407 | |||
1406 | command = fullcmd |
|
1408 | command = fullcmd | |
1407 |
|
1409 | |||
1408 | try: |
|
1410 | try: | |
1409 | pager = subprocess.Popen( |
|
1411 | pager = subprocess.Popen( | |
1410 | procutil.tonativestr(command), |
|
1412 | procutil.tonativestr(command), | |
1411 | shell=shell, |
|
1413 | shell=shell, | |
1412 | bufsize=-1, |
|
1414 | bufsize=-1, | |
1413 | close_fds=procutil.closefds, |
|
1415 | close_fds=procutil.closefds, | |
1414 | stdin=subprocess.PIPE, |
|
1416 | stdin=subprocess.PIPE, | |
1415 | stdout=procutil.stdout, |
|
1417 | stdout=procutil.stdout, | |
1416 | stderr=procutil.stderr, |
|
1418 | stderr=procutil.stderr, | |
1417 | env=procutil.tonativeenv(procutil.shellenviron(env)), |
|
1419 | env=procutil.tonativeenv(procutil.shellenviron(env)), | |
1418 | ) |
|
1420 | ) | |
1419 | except OSError as e: |
|
1421 | except OSError as e: | |
1420 | if e.errno == errno.ENOENT and not shell: |
|
1422 | if e.errno == errno.ENOENT and not shell: | |
1421 | self.warn( |
|
1423 | self.warn( | |
1422 | _(b"missing pager command '%s', skipping pager\n") % command |
|
1424 | _(b"missing pager command '%s', skipping pager\n") % command | |
1423 | ) |
|
1425 | ) | |
1424 | return False |
|
1426 | return False | |
1425 | raise |
|
1427 | raise | |
1426 |
|
1428 | |||
1427 | # back up original file descriptors |
|
1429 | # back up original file descriptors | |
1428 | stdoutfd = os.dup(procutil.stdout.fileno()) |
|
1430 | stdoutfd = os.dup(procutil.stdout.fileno()) | |
1429 | stderrfd = os.dup(procutil.stderr.fileno()) |
|
1431 | stderrfd = os.dup(procutil.stderr.fileno()) | |
1430 |
|
1432 | |||
1431 | os.dup2(pager.stdin.fileno(), procutil.stdout.fileno()) |
|
1433 | os.dup2(pager.stdin.fileno(), procutil.stdout.fileno()) | |
1432 | if self._isatty(procutil.stderr): |
|
1434 | if self._isatty(procutil.stderr): | |
1433 | os.dup2(pager.stdin.fileno(), procutil.stderr.fileno()) |
|
1435 | os.dup2(pager.stdin.fileno(), procutil.stderr.fileno()) | |
1434 |
|
1436 | |||
1435 | @self.atexit |
|
1437 | @self.atexit | |
1436 | def killpager(): |
|
1438 | def killpager(): | |
1437 | if util.safehasattr(signal, b"SIGINT"): |
|
1439 | if util.safehasattr(signal, b"SIGINT"): | |
1438 | signal.signal(signal.SIGINT, signal.SIG_IGN) |
|
1440 | signal.signal(signal.SIGINT, signal.SIG_IGN) | |
1439 | # restore original fds, closing pager.stdin copies in the process |
|
1441 | # restore original fds, closing pager.stdin copies in the process | |
1440 | os.dup2(stdoutfd, procutil.stdout.fileno()) |
|
1442 | os.dup2(stdoutfd, procutil.stdout.fileno()) | |
1441 | os.dup2(stderrfd, procutil.stderr.fileno()) |
|
1443 | os.dup2(stderrfd, procutil.stderr.fileno()) | |
1442 | pager.stdin.close() |
|
1444 | pager.stdin.close() | |
1443 | pager.wait() |
|
1445 | pager.wait() | |
1444 |
|
1446 | |||
1445 | return True |
|
1447 | return True | |
1446 |
|
1448 | |||
1447 | @property |
|
1449 | @property | |
1448 | def _exithandlers(self): |
|
1450 | def _exithandlers(self): | |
1449 | return _reqexithandlers |
|
1451 | return _reqexithandlers | |
1450 |
|
1452 | |||
1451 | def atexit(self, func, *args, **kwargs): |
|
1453 | def atexit(self, func, *args, **kwargs): | |
1452 | '''register a function to run after dispatching a request |
|
1454 | '''register a function to run after dispatching a request | |
1453 |
|
1455 | |||
1454 | Handlers do not stay registered across request boundaries.''' |
|
1456 | Handlers do not stay registered across request boundaries.''' | |
1455 | self._exithandlers.append((func, args, kwargs)) |
|
1457 | self._exithandlers.append((func, args, kwargs)) | |
1456 | return func |
|
1458 | return func | |
1457 |
|
1459 | |||
1458 | def interface(self, feature): |
|
1460 | def interface(self, feature): | |
1459 | """what interface to use for interactive console features? |
|
1461 | """what interface to use for interactive console features? | |
1460 |
|
1462 | |||
1461 | The interface is controlled by the value of `ui.interface` but also by |
|
1463 | The interface is controlled by the value of `ui.interface` but also by | |
1462 | the value of feature-specific configuration. For example: |
|
1464 | the value of feature-specific configuration. For example: | |
1463 |
|
1465 | |||
1464 | ui.interface.histedit = text |
|
1466 | ui.interface.histedit = text | |
1465 | ui.interface.chunkselector = curses |
|
1467 | ui.interface.chunkselector = curses | |
1466 |
|
1468 | |||
1467 | Here the features are "histedit" and "chunkselector". |
|
1469 | Here the features are "histedit" and "chunkselector". | |
1468 |
|
1470 | |||
1469 | The configuration above means that the default interfaces for commands |
|
1471 | The configuration above means that the default interfaces for commands | |
1470 | is curses, the interface for histedit is text and the interface for |
|
1472 | is curses, the interface for histedit is text and the interface for | |
1471 | selecting chunk is crecord (the best curses interface available). |
|
1473 | selecting chunk is crecord (the best curses interface available). | |
1472 |
|
1474 | |||
1473 | Consider the following example: |
|
1475 | Consider the following example: | |
1474 | ui.interface = curses |
|
1476 | ui.interface = curses | |
1475 | ui.interface.histedit = text |
|
1477 | ui.interface.histedit = text | |
1476 |
|
1478 | |||
1477 | Then histedit will use the text interface and chunkselector will use |
|
1479 | Then histedit will use the text interface and chunkselector will use | |
1478 | the default curses interface (crecord at the moment). |
|
1480 | the default curses interface (crecord at the moment). | |
1479 | """ |
|
1481 | """ | |
1480 | alldefaults = frozenset([b"text", b"curses"]) |
|
1482 | alldefaults = frozenset([b"text", b"curses"]) | |
1481 |
|
1483 | |||
1482 | featureinterfaces = { |
|
1484 | featureinterfaces = { | |
1483 | b"chunkselector": [b"text", b"curses",], |
|
1485 | b"chunkselector": [b"text", b"curses",], | |
1484 | b"histedit": [b"text", b"curses",], |
|
1486 | b"histedit": [b"text", b"curses",], | |
1485 | } |
|
1487 | } | |
1486 |
|
1488 | |||
1487 | # Feature-specific interface |
|
1489 | # Feature-specific interface | |
1488 | if feature not in featureinterfaces.keys(): |
|
1490 | if feature not in featureinterfaces.keys(): | |
1489 | # Programming error, not user error |
|
1491 | # Programming error, not user error | |
1490 | raise ValueError(b"Unknown feature requested %s" % feature) |
|
1492 | raise ValueError(b"Unknown feature requested %s" % feature) | |
1491 |
|
1493 | |||
1492 | availableinterfaces = frozenset(featureinterfaces[feature]) |
|
1494 | availableinterfaces = frozenset(featureinterfaces[feature]) | |
1493 | if alldefaults > availableinterfaces: |
|
1495 | if alldefaults > availableinterfaces: | |
1494 | # Programming error, not user error. We need a use case to |
|
1496 | # Programming error, not user error. We need a use case to | |
1495 | # define the right thing to do here. |
|
1497 | # define the right thing to do here. | |
1496 | raise ValueError( |
|
1498 | raise ValueError( | |
1497 | b"Feature %s does not handle all default interfaces" % feature |
|
1499 | b"Feature %s does not handle all default interfaces" % feature | |
1498 | ) |
|
1500 | ) | |
1499 |
|
1501 | |||
1500 | if self.plain() or encoding.environ.get(b'TERM') == b'dumb': |
|
1502 | if self.plain() or encoding.environ.get(b'TERM') == b'dumb': | |
1501 | return b"text" |
|
1503 | return b"text" | |
1502 |
|
1504 | |||
1503 | # Default interface for all the features |
|
1505 | # Default interface for all the features | |
1504 | defaultinterface = b"text" |
|
1506 | defaultinterface = b"text" | |
1505 | i = self.config(b"ui", b"interface") |
|
1507 | i = self.config(b"ui", b"interface") | |
1506 | if i in alldefaults: |
|
1508 | if i in alldefaults: | |
1507 | defaultinterface = i |
|
1509 | defaultinterface = i | |
1508 |
|
1510 | |||
1509 | choseninterface = defaultinterface |
|
1511 | choseninterface = defaultinterface | |
1510 | f = self.config(b"ui", b"interface.%s" % feature) |
|
1512 | f = self.config(b"ui", b"interface.%s" % feature) | |
1511 | if f in availableinterfaces: |
|
1513 | if f in availableinterfaces: | |
1512 | choseninterface = f |
|
1514 | choseninterface = f | |
1513 |
|
1515 | |||
1514 | if i is not None and defaultinterface != i: |
|
1516 | if i is not None and defaultinterface != i: | |
1515 | if f is not None: |
|
1517 | if f is not None: | |
1516 | self.warn(_(b"invalid value for ui.interface: %s\n") % (i,)) |
|
1518 | self.warn(_(b"invalid value for ui.interface: %s\n") % (i,)) | |
1517 | else: |
|
1519 | else: | |
1518 | self.warn( |
|
1520 | self.warn( | |
1519 | _(b"invalid value for ui.interface: %s (using %s)\n") |
|
1521 | _(b"invalid value for ui.interface: %s (using %s)\n") | |
1520 | % (i, choseninterface) |
|
1522 | % (i, choseninterface) | |
1521 | ) |
|
1523 | ) | |
1522 | if f is not None and choseninterface != f: |
|
1524 | if f is not None and choseninterface != f: | |
1523 | self.warn( |
|
1525 | self.warn( | |
1524 | _(b"invalid value for ui.interface.%s: %s (using %s)\n") |
|
1526 | _(b"invalid value for ui.interface.%s: %s (using %s)\n") | |
1525 | % (feature, f, choseninterface) |
|
1527 | % (feature, f, choseninterface) | |
1526 | ) |
|
1528 | ) | |
1527 |
|
1529 | |||
1528 | return choseninterface |
|
1530 | return choseninterface | |
1529 |
|
1531 | |||
1530 | def interactive(self): |
|
1532 | def interactive(self): | |
1531 | '''is interactive input allowed? |
|
1533 | '''is interactive input allowed? | |
1532 |
|
1534 | |||
1533 | An interactive session is a session where input can be reasonably read |
|
1535 | An interactive session is a session where input can be reasonably read | |
1534 | from `sys.stdin'. If this function returns false, any attempt to read |
|
1536 | from `sys.stdin'. If this function returns false, any attempt to read | |
1535 | from stdin should fail with an error, unless a sensible default has been |
|
1537 | from stdin should fail with an error, unless a sensible default has been | |
1536 | specified. |
|
1538 | specified. | |
1537 |
|
1539 | |||
1538 | Interactiveness is triggered by the value of the `ui.interactive' |
|
1540 | Interactiveness is triggered by the value of the `ui.interactive' | |
1539 | configuration variable or - if it is unset - when `sys.stdin' points |
|
1541 | configuration variable or - if it is unset - when `sys.stdin' points | |
1540 | to a terminal device. |
|
1542 | to a terminal device. | |
1541 |
|
1543 | |||
1542 | This function refers to input only; for output, see `ui.formatted()'. |
|
1544 | This function refers to input only; for output, see `ui.formatted()'. | |
1543 | ''' |
|
1545 | ''' | |
1544 | i = self.configbool(b"ui", b"interactive") |
|
1546 | i = self.configbool(b"ui", b"interactive") | |
1545 | if i is None: |
|
1547 | if i is None: | |
1546 | # some environments replace stdin without implementing isatty |
|
1548 | # some environments replace stdin without implementing isatty | |
1547 | # usually those are non-interactive |
|
1549 | # usually those are non-interactive | |
1548 | return self._isatty(self._fin) |
|
1550 | return self._isatty(self._fin) | |
1549 |
|
1551 | |||
1550 | return i |
|
1552 | return i | |
1551 |
|
1553 | |||
1552 | def termwidth(self): |
|
1554 | def termwidth(self): | |
1553 | '''how wide is the terminal in columns? |
|
1555 | '''how wide is the terminal in columns? | |
1554 | ''' |
|
1556 | ''' | |
1555 | if b'COLUMNS' in encoding.environ: |
|
1557 | if b'COLUMNS' in encoding.environ: | |
1556 | try: |
|
1558 | try: | |
1557 | return int(encoding.environ[b'COLUMNS']) |
|
1559 | return int(encoding.environ[b'COLUMNS']) | |
1558 | except ValueError: |
|
1560 | except ValueError: | |
1559 | pass |
|
1561 | pass | |
1560 | return scmutil.termsize(self)[0] |
|
1562 | return scmutil.termsize(self)[0] | |
1561 |
|
1563 | |||
1562 | def formatted(self): |
|
1564 | def formatted(self): | |
1563 | '''should formatted output be used? |
|
1565 | '''should formatted output be used? | |
1564 |
|
1566 | |||
1565 | It is often desirable to format the output to suite the output medium. |
|
1567 | It is often desirable to format the output to suite the output medium. | |
1566 | Examples of this are truncating long lines or colorizing messages. |
|
1568 | Examples of this are truncating long lines or colorizing messages. | |
1567 | However, this is not often not desirable when piping output into other |
|
1569 | However, this is not often not desirable when piping output into other | |
1568 | utilities, e.g. `grep'. |
|
1570 | utilities, e.g. `grep'. | |
1569 |
|
1571 | |||
1570 | Formatted output is triggered by the value of the `ui.formatted' |
|
1572 | Formatted output is triggered by the value of the `ui.formatted' | |
1571 | configuration variable or - if it is unset - when `sys.stdout' points |
|
1573 | configuration variable or - if it is unset - when `sys.stdout' points | |
1572 | to a terminal device. Please note that `ui.formatted' should be |
|
1574 | to a terminal device. Please note that `ui.formatted' should be | |
1573 | considered an implementation detail; it is not intended for use outside |
|
1575 | considered an implementation detail; it is not intended for use outside | |
1574 | Mercurial or its extensions. |
|
1576 | Mercurial or its extensions. | |
1575 |
|
1577 | |||
1576 | This function refers to output only; for input, see `ui.interactive()'. |
|
1578 | This function refers to output only; for input, see `ui.interactive()'. | |
1577 | This function always returns false when in plain mode, see `ui.plain()'. |
|
1579 | This function always returns false when in plain mode, see `ui.plain()'. | |
1578 | ''' |
|
1580 | ''' | |
1579 | if self.plain(): |
|
1581 | if self.plain(): | |
1580 | return False |
|
1582 | return False | |
1581 |
|
1583 | |||
1582 | i = self.configbool(b"ui", b"formatted") |
|
1584 | i = self.configbool(b"ui", b"formatted") | |
1583 | if i is None: |
|
1585 | if i is None: | |
1584 | # some environments replace stdout without implementing isatty |
|
1586 | # some environments replace stdout without implementing isatty | |
1585 | # usually those are non-interactive |
|
1587 | # usually those are non-interactive | |
1586 | return self._isatty(self._fout) |
|
1588 | return self._isatty(self._fout) | |
1587 |
|
1589 | |||
1588 | return i |
|
1590 | return i | |
1589 |
|
1591 | |||
1590 | def _readline(self, prompt=b' ', promptopts=None): |
|
1592 | def _readline(self, prompt=b' ', promptopts=None): | |
1591 | # Replacing stdin/stdout temporarily is a hard problem on Python 3 |
|
1593 | # Replacing stdin/stdout temporarily is a hard problem on Python 3 | |
1592 | # because they have to be text streams with *no buffering*. Instead, |
|
1594 | # because they have to be text streams with *no buffering*. Instead, | |
1593 | # we use rawinput() only if call_readline() will be invoked by |
|
1595 | # we use rawinput() only if call_readline() will be invoked by | |
1594 | # PyOS_Readline(), so no I/O will be made at Python layer. |
|
1596 | # PyOS_Readline(), so no I/O will be made at Python layer. | |
1595 | usereadline = ( |
|
1597 | usereadline = ( | |
1596 | self._isatty(self._fin) |
|
1598 | self._isatty(self._fin) | |
1597 | and self._isatty(self._fout) |
|
1599 | and self._isatty(self._fout) | |
1598 | and procutil.isstdin(self._fin) |
|
1600 | and procutil.isstdin(self._fin) | |
1599 | and procutil.isstdout(self._fout) |
|
1601 | and procutil.isstdout(self._fout) | |
1600 | ) |
|
1602 | ) | |
1601 | if usereadline: |
|
1603 | if usereadline: | |
1602 | try: |
|
1604 | try: | |
1603 | # magically add command line editing support, where |
|
1605 | # magically add command line editing support, where | |
1604 | # available |
|
1606 | # available | |
1605 | import readline |
|
1607 | import readline | |
1606 |
|
1608 | |||
1607 | # force demandimport to really load the module |
|
1609 | # force demandimport to really load the module | |
1608 | readline.read_history_file |
|
1610 | readline.read_history_file | |
1609 | # windows sometimes raises something other than ImportError |
|
1611 | # windows sometimes raises something other than ImportError | |
1610 | except Exception: |
|
1612 | except Exception: | |
1611 | usereadline = False |
|
1613 | usereadline = False | |
1612 |
|
1614 | |||
1613 | if self._colormode == b'win32' or not usereadline: |
|
1615 | if self._colormode == b'win32' or not usereadline: | |
1614 | if not promptopts: |
|
1616 | if not promptopts: | |
1615 | promptopts = {} |
|
1617 | promptopts = {} | |
1616 | self._writemsgnobuf( |
|
1618 | self._writemsgnobuf( | |
1617 | self._fmsgout, prompt, type=b'prompt', **promptopts |
|
1619 | self._fmsgout, prompt, type=b'prompt', **promptopts | |
1618 | ) |
|
1620 | ) | |
1619 | self.flush() |
|
1621 | self.flush() | |
1620 | prompt = b' ' |
|
1622 | prompt = b' ' | |
1621 | else: |
|
1623 | else: | |
1622 | prompt = self.label(prompt, b'ui.prompt') + b' ' |
|
1624 | prompt = self.label(prompt, b'ui.prompt') + b' ' | |
1623 |
|
1625 | |||
1624 | # prompt ' ' must exist; otherwise readline may delete entire line |
|
1626 | # prompt ' ' must exist; otherwise readline may delete entire line | |
1625 | # - http://bugs.python.org/issue12833 |
|
1627 | # - http://bugs.python.org/issue12833 | |
1626 | with self.timeblockedsection(b'stdio'): |
|
1628 | with self.timeblockedsection(b'stdio'): | |
1627 | if usereadline: |
|
1629 | if usereadline: | |
1628 | self.flush() |
|
1630 | self.flush() | |
1629 | prompt = encoding.strfromlocal(prompt) |
|
1631 | prompt = encoding.strfromlocal(prompt) | |
1630 | line = encoding.strtolocal(pycompat.rawinput(prompt)) |
|
1632 | line = encoding.strtolocal(pycompat.rawinput(prompt)) | |
1631 | # When stdin is in binary mode on Windows, it can cause |
|
1633 | # When stdin is in binary mode on Windows, it can cause | |
1632 | # raw_input() to emit an extra trailing carriage return |
|
1634 | # raw_input() to emit an extra trailing carriage return | |
1633 | if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'): |
|
1635 | if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'): | |
1634 | line = line[:-1] |
|
1636 | line = line[:-1] | |
1635 | else: |
|
1637 | else: | |
1636 | self._fout.write(pycompat.bytestr(prompt)) |
|
1638 | self._fout.write(pycompat.bytestr(prompt)) | |
1637 | self._fout.flush() |
|
1639 | self._fout.flush() | |
1638 | line = self._fin.readline() |
|
1640 | line = self._fin.readline() | |
1639 | if not line: |
|
1641 | if not line: | |
1640 | raise EOFError |
|
1642 | raise EOFError | |
1641 | line = line.rstrip(pycompat.oslinesep) |
|
1643 | line = line.rstrip(pycompat.oslinesep) | |
1642 |
|
1644 | |||
1643 | return line |
|
1645 | return line | |
1644 |
|
1646 | |||
1645 | def prompt(self, msg, default=b"y"): |
|
1647 | def prompt(self, msg, default=b"y"): | |
1646 | """Prompt user with msg, read response. |
|
1648 | """Prompt user with msg, read response. | |
1647 | If ui is not interactive, the default is returned. |
|
1649 | If ui is not interactive, the default is returned. | |
1648 | """ |
|
1650 | """ | |
1649 | return self._prompt(msg, default=default) |
|
1651 | return self._prompt(msg, default=default) | |
1650 |
|
1652 | |||
1651 | def _prompt(self, msg, **opts): |
|
1653 | def _prompt(self, msg, **opts): | |
1652 | default = opts['default'] |
|
1654 | default = opts['default'] | |
1653 | if not self.interactive(): |
|
1655 | if not self.interactive(): | |
1654 | self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts) |
|
1656 | self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts) | |
1655 | self._writemsg( |
|
1657 | self._writemsg( | |
1656 | self._fmsgout, default or b'', b"\n", type=b'promptecho' |
|
1658 | self._fmsgout, default or b'', b"\n", type=b'promptecho' | |
1657 | ) |
|
1659 | ) | |
1658 | return default |
|
1660 | return default | |
1659 | try: |
|
1661 | try: | |
1660 | r = self._readline(prompt=msg, promptopts=opts) |
|
1662 | r = self._readline(prompt=msg, promptopts=opts) | |
1661 | if not r: |
|
1663 | if not r: | |
1662 | r = default |
|
1664 | r = default | |
1663 | if self.configbool(b'ui', b'promptecho'): |
|
1665 | if self.configbool(b'ui', b'promptecho'): | |
1664 | self._writemsg( |
|
1666 | self._writemsg( | |
1665 | self._fmsgout, r or b'', b"\n", type=b'promptecho' |
|
1667 | self._fmsgout, r or b'', b"\n", type=b'promptecho' | |
1666 | ) |
|
1668 | ) | |
1667 | return r |
|
1669 | return r | |
1668 | except EOFError: |
|
1670 | except EOFError: | |
1669 | raise error.ResponseExpected() |
|
1671 | raise error.ResponseExpected() | |
1670 |
|
1672 | |||
1671 | @staticmethod |
|
1673 | @staticmethod | |
1672 | def extractchoices(prompt): |
|
1674 | def extractchoices(prompt): | |
1673 | """Extract prompt message and list of choices from specified prompt. |
|
1675 | """Extract prompt message and list of choices from specified prompt. | |
1674 |
|
1676 | |||
1675 | This returns tuple "(message, choices)", and "choices" is the |
|
1677 | This returns tuple "(message, choices)", and "choices" is the | |
1676 | list of tuple "(response character, text without &)". |
|
1678 | list of tuple "(response character, text without &)". | |
1677 |
|
1679 | |||
1678 | >>> ui.extractchoices(b"awake? $$ &Yes $$ &No") |
|
1680 | >>> ui.extractchoices(b"awake? $$ &Yes $$ &No") | |
1679 | ('awake? ', [('y', 'Yes'), ('n', 'No')]) |
|
1681 | ('awake? ', [('y', 'Yes'), ('n', 'No')]) | |
1680 | >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No") |
|
1682 | >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No") | |
1681 | ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')]) |
|
1683 | ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')]) | |
1682 | >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o") |
|
1684 | >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o") | |
1683 | ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')]) |
|
1685 | ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')]) | |
1684 | """ |
|
1686 | """ | |
1685 |
|
1687 | |||
1686 | # Sadly, the prompt string may have been built with a filename |
|
1688 | # Sadly, the prompt string may have been built with a filename | |
1687 | # containing "$$" so let's try to find the first valid-looking |
|
1689 | # containing "$$" so let's try to find the first valid-looking | |
1688 | # prompt to start parsing. Sadly, we also can't rely on |
|
1690 | # prompt to start parsing. Sadly, we also can't rely on | |
1689 | # choices containing spaces, ASCII, or basically anything |
|
1691 | # choices containing spaces, ASCII, or basically anything | |
1690 | # except an ampersand followed by a character. |
|
1692 | # except an ampersand followed by a character. | |
1691 | m = re.match(br'(?s)(.+?)\$\$([^$]*&[^ $].*)', prompt) |
|
1693 | m = re.match(br'(?s)(.+?)\$\$([^$]*&[^ $].*)', prompt) | |
1692 | msg = m.group(1) |
|
1694 | msg = m.group(1) | |
1693 | choices = [p.strip(b' ') for p in m.group(2).split(b'$$')] |
|
1695 | choices = [p.strip(b' ') for p in m.group(2).split(b'$$')] | |
1694 |
|
1696 | |||
1695 | def choicetuple(s): |
|
1697 | def choicetuple(s): | |
1696 | ampidx = s.index(b'&') |
|
1698 | ampidx = s.index(b'&') | |
1697 | return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1) |
|
1699 | return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1) | |
1698 |
|
1700 | |||
1699 | return (msg, [choicetuple(s) for s in choices]) |
|
1701 | return (msg, [choicetuple(s) for s in choices]) | |
1700 |
|
1702 | |||
1701 | def promptchoice(self, prompt, default=0): |
|
1703 | def promptchoice(self, prompt, default=0): | |
1702 | """Prompt user with a message, read response, and ensure it matches |
|
1704 | """Prompt user with a message, read response, and ensure it matches | |
1703 | one of the provided choices. The prompt is formatted as follows: |
|
1705 | one of the provided choices. The prompt is formatted as follows: | |
1704 |
|
1706 | |||
1705 | "would you like fries with that (Yn)? $$ &Yes $$ &No" |
|
1707 | "would you like fries with that (Yn)? $$ &Yes $$ &No" | |
1706 |
|
1708 | |||
1707 | The index of the choice is returned. Responses are case |
|
1709 | The index of the choice is returned. Responses are case | |
1708 | insensitive. If ui is not interactive, the default is |
|
1710 | insensitive. If ui is not interactive, the default is | |
1709 | returned. |
|
1711 | returned. | |
1710 | """ |
|
1712 | """ | |
1711 |
|
1713 | |||
1712 | msg, choices = self.extractchoices(prompt) |
|
1714 | msg, choices = self.extractchoices(prompt) | |
1713 | resps = [r for r, t in choices] |
|
1715 | resps = [r for r, t in choices] | |
1714 | while True: |
|
1716 | while True: | |
1715 | r = self._prompt(msg, default=resps[default], choices=choices) |
|
1717 | r = self._prompt(msg, default=resps[default], choices=choices) | |
1716 | if r.lower() in resps: |
|
1718 | if r.lower() in resps: | |
1717 | return resps.index(r.lower()) |
|
1719 | return resps.index(r.lower()) | |
1718 | # TODO: shouldn't it be a warning? |
|
1720 | # TODO: shouldn't it be a warning? | |
1719 | self._writemsg(self._fmsgout, _(b"unrecognized response\n")) |
|
1721 | self._writemsg(self._fmsgout, _(b"unrecognized response\n")) | |
1720 |
|
1722 | |||
1721 | def getpass(self, prompt=None, default=None): |
|
1723 | def getpass(self, prompt=None, default=None): | |
1722 | if not self.interactive(): |
|
1724 | if not self.interactive(): | |
1723 | return default |
|
1725 | return default | |
1724 | try: |
|
1726 | try: | |
1725 | self._writemsg( |
|
1727 | self._writemsg( | |
1726 | self._fmsgerr, |
|
1728 | self._fmsgerr, | |
1727 | prompt or _(b'password: '), |
|
1729 | prompt or _(b'password: '), | |
1728 | type=b'prompt', |
|
1730 | type=b'prompt', | |
1729 | password=True, |
|
1731 | password=True, | |
1730 | ) |
|
1732 | ) | |
1731 | # disable getpass() only if explicitly specified. it's still valid |
|
1733 | # disable getpass() only if explicitly specified. it's still valid | |
1732 | # to interact with tty even if fin is not a tty. |
|
1734 | # to interact with tty even if fin is not a tty. | |
1733 | with self.timeblockedsection(b'stdio'): |
|
1735 | with self.timeblockedsection(b'stdio'): | |
1734 | if self.configbool(b'ui', b'nontty'): |
|
1736 | if self.configbool(b'ui', b'nontty'): | |
1735 | l = self._fin.readline() |
|
1737 | l = self._fin.readline() | |
1736 | if not l: |
|
1738 | if not l: | |
1737 | raise EOFError |
|
1739 | raise EOFError | |
1738 | return l.rstrip(b'\n') |
|
1740 | return l.rstrip(b'\n') | |
1739 | else: |
|
1741 | else: | |
1740 | return getpass.getpass('') |
|
1742 | return getpass.getpass('') | |
1741 | except EOFError: |
|
1743 | except EOFError: | |
1742 | raise error.ResponseExpected() |
|
1744 | raise error.ResponseExpected() | |
1743 |
|
1745 | |||
1744 | def status(self, *msg, **opts): |
|
1746 | def status(self, *msg, **opts): | |
1745 | '''write status message to output (if ui.quiet is False) |
|
1747 | '''write status message to output (if ui.quiet is False) | |
1746 |
|
1748 | |||
1747 | This adds an output label of "ui.status". |
|
1749 | This adds an output label of "ui.status". | |
1748 | ''' |
|
1750 | ''' | |
1749 | if not self.quiet: |
|
1751 | if not self.quiet: | |
1750 | self._writemsg(self._fmsgout, type=b'status', *msg, **opts) |
|
1752 | self._writemsg(self._fmsgout, type=b'status', *msg, **opts) | |
1751 |
|
1753 | |||
1752 | def warn(self, *msg, **opts): |
|
1754 | def warn(self, *msg, **opts): | |
1753 | '''write warning message to output (stderr) |
|
1755 | '''write warning message to output (stderr) | |
1754 |
|
1756 | |||
1755 | This adds an output label of "ui.warning". |
|
1757 | This adds an output label of "ui.warning". | |
1756 | ''' |
|
1758 | ''' | |
1757 | self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts) |
|
1759 | self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts) | |
1758 |
|
1760 | |||
1759 | def error(self, *msg, **opts): |
|
1761 | def error(self, *msg, **opts): | |
1760 | '''write error message to output (stderr) |
|
1762 | '''write error message to output (stderr) | |
1761 |
|
1763 | |||
1762 | This adds an output label of "ui.error". |
|
1764 | This adds an output label of "ui.error". | |
1763 | ''' |
|
1765 | ''' | |
1764 | self._writemsg(self._fmsgerr, type=b'error', *msg, **opts) |
|
1766 | self._writemsg(self._fmsgerr, type=b'error', *msg, **opts) | |
1765 |
|
1767 | |||
1766 | def note(self, *msg, **opts): |
|
1768 | def note(self, *msg, **opts): | |
1767 | '''write note to output (if ui.verbose is True) |
|
1769 | '''write note to output (if ui.verbose is True) | |
1768 |
|
1770 | |||
1769 | This adds an output label of "ui.note". |
|
1771 | This adds an output label of "ui.note". | |
1770 | ''' |
|
1772 | ''' | |
1771 | if self.verbose: |
|
1773 | if self.verbose: | |
1772 | self._writemsg(self._fmsgout, type=b'note', *msg, **opts) |
|
1774 | self._writemsg(self._fmsgout, type=b'note', *msg, **opts) | |
1773 |
|
1775 | |||
1774 | def debug(self, *msg, **opts): |
|
1776 | def debug(self, *msg, **opts): | |
1775 | '''write debug message to output (if ui.debugflag is True) |
|
1777 | '''write debug message to output (if ui.debugflag is True) | |
1776 |
|
1778 | |||
1777 | This adds an output label of "ui.debug". |
|
1779 | This adds an output label of "ui.debug". | |
1778 | ''' |
|
1780 | ''' | |
1779 | if self.debugflag: |
|
1781 | if self.debugflag: | |
1780 | self._writemsg(self._fmsgout, type=b'debug', *msg, **opts) |
|
1782 | self._writemsg(self._fmsgout, type=b'debug', *msg, **opts) | |
1781 | self.log(b'debug', b'%s', b''.join(msg)) |
|
1783 | self.log(b'debug', b'%s', b''.join(msg)) | |
1782 |
|
1784 | |||
1783 | # Aliases to defeat check-code. |
|
1785 | # Aliases to defeat check-code. | |
1784 | statusnoi18n = status |
|
1786 | statusnoi18n = status | |
1785 | notenoi18n = note |
|
1787 | notenoi18n = note | |
1786 | warnnoi18n = warn |
|
1788 | warnnoi18n = warn | |
1787 | writenoi18n = write |
|
1789 | writenoi18n = write | |
1788 |
|
1790 | |||
1789 | def edit( |
|
1791 | def edit( | |
1790 | self, |
|
1792 | self, | |
1791 | text, |
|
1793 | text, | |
1792 | user, |
|
1794 | user, | |
1793 | extra=None, |
|
1795 | extra=None, | |
1794 | editform=None, |
|
1796 | editform=None, | |
1795 | pending=None, |
|
1797 | pending=None, | |
1796 | repopath=None, |
|
1798 | repopath=None, | |
1797 | action=None, |
|
1799 | action=None, | |
1798 | ): |
|
1800 | ): | |
1799 | if action is None: |
|
1801 | if action is None: | |
1800 | self.develwarn( |
|
1802 | self.develwarn( | |
1801 | b'action is None but will soon be a required ' |
|
1803 | b'action is None but will soon be a required ' | |
1802 | b'parameter to ui.edit()' |
|
1804 | b'parameter to ui.edit()' | |
1803 | ) |
|
1805 | ) | |
1804 | extra_defaults = { |
|
1806 | extra_defaults = { | |
1805 | b'prefix': b'editor', |
|
1807 | b'prefix': b'editor', | |
1806 | b'suffix': b'.txt', |
|
1808 | b'suffix': b'.txt', | |
1807 | } |
|
1809 | } | |
1808 | if extra is not None: |
|
1810 | if extra is not None: | |
1809 | if extra.get(b'suffix') is not None: |
|
1811 | if extra.get(b'suffix') is not None: | |
1810 | self.develwarn( |
|
1812 | self.develwarn( | |
1811 | b'extra.suffix is not None but will soon be ' |
|
1813 | b'extra.suffix is not None but will soon be ' | |
1812 | b'ignored by ui.edit()' |
|
1814 | b'ignored by ui.edit()' | |
1813 | ) |
|
1815 | ) | |
1814 | extra_defaults.update(extra) |
|
1816 | extra_defaults.update(extra) | |
1815 | extra = extra_defaults |
|
1817 | extra = extra_defaults | |
1816 |
|
1818 | |||
1817 | if action == b'diff': |
|
1819 | if action == b'diff': | |
1818 | suffix = b'.diff' |
|
1820 | suffix = b'.diff' | |
1819 | elif action: |
|
1821 | elif action: | |
1820 | suffix = b'.%s.hg.txt' % action |
|
1822 | suffix = b'.%s.hg.txt' % action | |
1821 | else: |
|
1823 | else: | |
1822 | suffix = extra[b'suffix'] |
|
1824 | suffix = extra[b'suffix'] | |
1823 |
|
1825 | |||
1824 | rdir = None |
|
1826 | rdir = None | |
1825 | if self.configbool(b'experimental', b'editortmpinhg'): |
|
1827 | if self.configbool(b'experimental', b'editortmpinhg'): | |
1826 | rdir = repopath |
|
1828 | rdir = repopath | |
1827 | (fd, name) = pycompat.mkstemp( |
|
1829 | (fd, name) = pycompat.mkstemp( | |
1828 | prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir |
|
1830 | prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir | |
1829 | ) |
|
1831 | ) | |
1830 | try: |
|
1832 | try: | |
1831 | with os.fdopen(fd, 'wb') as f: |
|
1833 | with os.fdopen(fd, 'wb') as f: | |
1832 | f.write(util.tonativeeol(text)) |
|
1834 | f.write(util.tonativeeol(text)) | |
1833 |
|
1835 | |||
1834 | environ = {b'HGUSER': user} |
|
1836 | environ = {b'HGUSER': user} | |
1835 | if b'transplant_source' in extra: |
|
1837 | if b'transplant_source' in extra: | |
1836 | environ.update( |
|
1838 | environ.update( | |
1837 | {b'HGREVISION': hex(extra[b'transplant_source'])} |
|
1839 | {b'HGREVISION': hex(extra[b'transplant_source'])} | |
1838 | ) |
|
1840 | ) | |
1839 | for label in (b'intermediate-source', b'source', b'rebase_source'): |
|
1841 | for label in (b'intermediate-source', b'source', b'rebase_source'): | |
1840 | if label in extra: |
|
1842 | if label in extra: | |
1841 | environ.update({b'HGREVISION': extra[label]}) |
|
1843 | environ.update({b'HGREVISION': extra[label]}) | |
1842 | break |
|
1844 | break | |
1843 | if editform: |
|
1845 | if editform: | |
1844 | environ.update({b'HGEDITFORM': editform}) |
|
1846 | environ.update({b'HGEDITFORM': editform}) | |
1845 | if pending: |
|
1847 | if pending: | |
1846 | environ.update({b'HG_PENDING': pending}) |
|
1848 | environ.update({b'HG_PENDING': pending}) | |
1847 |
|
1849 | |||
1848 | editor = self.geteditor() |
|
1850 | editor = self.geteditor() | |
1849 |
|
1851 | |||
1850 | self.system( |
|
1852 | self.system( | |
1851 | b"%s \"%s\"" % (editor, name), |
|
1853 | b"%s \"%s\"" % (editor, name), | |
1852 | environ=environ, |
|
1854 | environ=environ, | |
1853 | onerr=error.Abort, |
|
1855 | onerr=error.Abort, | |
1854 | errprefix=_(b"edit failed"), |
|
1856 | errprefix=_(b"edit failed"), | |
1855 | blockedtag=b'editor', |
|
1857 | blockedtag=b'editor', | |
1856 | ) |
|
1858 | ) | |
1857 |
|
1859 | |||
1858 | with open(name, 'rb') as f: |
|
1860 | with open(name, 'rb') as f: | |
1859 | t = util.fromnativeeol(f.read()) |
|
1861 | t = util.fromnativeeol(f.read()) | |
1860 | finally: |
|
1862 | finally: | |
1861 | os.unlink(name) |
|
1863 | os.unlink(name) | |
1862 |
|
1864 | |||
1863 | return t |
|
1865 | return t | |
1864 |
|
1866 | |||
1865 | def system( |
|
1867 | def system( | |
1866 | self, |
|
1868 | self, | |
1867 | cmd, |
|
1869 | cmd, | |
1868 | environ=None, |
|
1870 | environ=None, | |
1869 | cwd=None, |
|
1871 | cwd=None, | |
1870 | onerr=None, |
|
1872 | onerr=None, | |
1871 | errprefix=None, |
|
1873 | errprefix=None, | |
1872 | blockedtag=None, |
|
1874 | blockedtag=None, | |
1873 | ): |
|
1875 | ): | |
1874 | '''execute shell command with appropriate output stream. command |
|
1876 | '''execute shell command with appropriate output stream. command | |
1875 | output will be redirected if fout is not stdout. |
|
1877 | output will be redirected if fout is not stdout. | |
1876 |
|
1878 | |||
1877 | if command fails and onerr is None, return status, else raise onerr |
|
1879 | if command fails and onerr is None, return status, else raise onerr | |
1878 | object as exception. |
|
1880 | object as exception. | |
1879 | ''' |
|
1881 | ''' | |
1880 | if blockedtag is None: |
|
1882 | if blockedtag is None: | |
1881 | # Long cmds tend to be because of an absolute path on cmd. Keep |
|
1883 | # Long cmds tend to be because of an absolute path on cmd. Keep | |
1882 | # the tail end instead |
|
1884 | # the tail end instead | |
1883 | cmdsuffix = cmd.translate(None, _keepalnum)[-85:] |
|
1885 | cmdsuffix = cmd.translate(None, _keepalnum)[-85:] | |
1884 | blockedtag = b'unknown_system_' + cmdsuffix |
|
1886 | blockedtag = b'unknown_system_' + cmdsuffix | |
1885 | out = self._fout |
|
1887 | out = self._fout | |
1886 | if any(s[1] for s in self._bufferstates): |
|
1888 | if any(s[1] for s in self._bufferstates): | |
1887 | out = self |
|
1889 | out = self | |
1888 | with self.timeblockedsection(blockedtag): |
|
1890 | with self.timeblockedsection(blockedtag): | |
1889 | rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out) |
|
1891 | rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out) | |
1890 | if rc and onerr: |
|
1892 | if rc and onerr: | |
1891 | errmsg = b'%s %s' % ( |
|
1893 | errmsg = b'%s %s' % ( | |
1892 | procutil.shellsplit(cmd)[0], |
|
1894 | procutil.shellsplit(cmd)[0], | |
1893 | procutil.explainexit(rc), |
|
1895 | procutil.explainexit(rc), | |
1894 | ) |
|
1896 | ) | |
1895 | if errprefix: |
|
1897 | if errprefix: | |
1896 | errmsg = b'%s: %s' % (errprefix, errmsg) |
|
1898 | errmsg = b'%s: %s' % (errprefix, errmsg) | |
1897 | raise onerr(errmsg) |
|
1899 | raise onerr(errmsg) | |
1898 | return rc |
|
1900 | return rc | |
1899 |
|
1901 | |||
1900 | def _runsystem(self, cmd, environ, cwd, out): |
|
1902 | def _runsystem(self, cmd, environ, cwd, out): | |
1901 | """actually execute the given shell command (can be overridden by |
|
1903 | """actually execute the given shell command (can be overridden by | |
1902 | extensions like chg)""" |
|
1904 | extensions like chg)""" | |
1903 | return procutil.system(cmd, environ=environ, cwd=cwd, out=out) |
|
1905 | return procutil.system(cmd, environ=environ, cwd=cwd, out=out) | |
1904 |
|
1906 | |||
1905 | def traceback(self, exc=None, force=False): |
|
1907 | def traceback(self, exc=None, force=False): | |
1906 | '''print exception traceback if traceback printing enabled or forced. |
|
1908 | '''print exception traceback if traceback printing enabled or forced. | |
1907 | only to call in exception handler. returns true if traceback |
|
1909 | only to call in exception handler. returns true if traceback | |
1908 | printed.''' |
|
1910 | printed.''' | |
1909 | if self.tracebackflag or force: |
|
1911 | if self.tracebackflag or force: | |
1910 | if exc is None: |
|
1912 | if exc is None: | |
1911 | exc = sys.exc_info() |
|
1913 | exc = sys.exc_info() | |
1912 | cause = getattr(exc[1], 'cause', None) |
|
1914 | cause = getattr(exc[1], 'cause', None) | |
1913 |
|
1915 | |||
1914 | if cause is not None: |
|
1916 | if cause is not None: | |
1915 | causetb = traceback.format_tb(cause[2]) |
|
1917 | causetb = traceback.format_tb(cause[2]) | |
1916 | exctb = traceback.format_tb(exc[2]) |
|
1918 | exctb = traceback.format_tb(exc[2]) | |
1917 | exconly = traceback.format_exception_only(cause[0], cause[1]) |
|
1919 | exconly = traceback.format_exception_only(cause[0], cause[1]) | |
1918 |
|
1920 | |||
1919 | # exclude frame where 'exc' was chained and rethrown from exctb |
|
1921 | # exclude frame where 'exc' was chained and rethrown from exctb | |
1920 | self.write_err( |
|
1922 | self.write_err( | |
1921 | b'Traceback (most recent call last):\n', |
|
1923 | b'Traceback (most recent call last):\n', | |
1922 | encoding.strtolocal(''.join(exctb[:-1])), |
|
1924 | encoding.strtolocal(''.join(exctb[:-1])), | |
1923 | encoding.strtolocal(''.join(causetb)), |
|
1925 | encoding.strtolocal(''.join(causetb)), | |
1924 | encoding.strtolocal(''.join(exconly)), |
|
1926 | encoding.strtolocal(''.join(exconly)), | |
1925 | ) |
|
1927 | ) | |
1926 | else: |
|
1928 | else: | |
1927 | output = traceback.format_exception(exc[0], exc[1], exc[2]) |
|
1929 | output = traceback.format_exception(exc[0], exc[1], exc[2]) | |
1928 | self.write_err(encoding.strtolocal(''.join(output))) |
|
1930 | self.write_err(encoding.strtolocal(''.join(output))) | |
1929 | return self.tracebackflag or force |
|
1931 | return self.tracebackflag or force | |
1930 |
|
1932 | |||
1931 | def geteditor(self): |
|
1933 | def geteditor(self): | |
1932 | '''return editor to use''' |
|
1934 | '''return editor to use''' | |
1933 | if pycompat.sysplatform == b'plan9': |
|
1935 | if pycompat.sysplatform == b'plan9': | |
1934 | # vi is the MIPS instruction simulator on Plan 9. We |
|
1936 | # vi is the MIPS instruction simulator on Plan 9. We | |
1935 | # instead default to E to plumb commit messages to |
|
1937 | # instead default to E to plumb commit messages to | |
1936 | # avoid confusion. |
|
1938 | # avoid confusion. | |
1937 | editor = b'E' |
|
1939 | editor = b'E' | |
1938 | elif pycompat.isdarwin: |
|
1940 | elif pycompat.isdarwin: | |
1939 | # vi on darwin is POSIX compatible to a fault, and that includes |
|
1941 | # vi on darwin is POSIX compatible to a fault, and that includes | |
1940 | # exiting non-zero if you make any mistake when running an ex |
|
1942 | # exiting non-zero if you make any mistake when running an ex | |
1941 | # command. Proof: `vi -c ':unknown' -c ':qa'; echo $?` produces 1, |
|
1943 | # command. Proof: `vi -c ':unknown' -c ':qa'; echo $?` produces 1, | |
1942 | # while s/vi/vim/ doesn't. |
|
1944 | # while s/vi/vim/ doesn't. | |
1943 | editor = b'vim' |
|
1945 | editor = b'vim' | |
1944 | else: |
|
1946 | else: | |
1945 | editor = b'vi' |
|
1947 | editor = b'vi' | |
1946 | return encoding.environ.get(b"HGEDITOR") or self.config( |
|
1948 | return encoding.environ.get(b"HGEDITOR") or self.config( | |
1947 | b"ui", b"editor", editor |
|
1949 | b"ui", b"editor", editor | |
1948 | ) |
|
1950 | ) | |
1949 |
|
1951 | |||
1950 | @util.propertycache |
|
1952 | @util.propertycache | |
1951 | def _progbar(self): |
|
1953 | def _progbar(self): | |
1952 | """setup the progbar singleton to the ui object""" |
|
1954 | """setup the progbar singleton to the ui object""" | |
1953 | if ( |
|
1955 | if ( | |
1954 | self.quiet |
|
1956 | self.quiet | |
1955 | or self.debugflag |
|
1957 | or self.debugflag | |
1956 | or self.configbool(b'progress', b'disable') |
|
1958 | or self.configbool(b'progress', b'disable') | |
1957 | or not progress.shouldprint(self) |
|
1959 | or not progress.shouldprint(self) | |
1958 | ): |
|
1960 | ): | |
1959 | return None |
|
1961 | return None | |
1960 | return getprogbar(self) |
|
1962 | return getprogbar(self) | |
1961 |
|
1963 | |||
1962 | def _progclear(self): |
|
1964 | def _progclear(self): | |
1963 | """clear progress bar output if any. use it before any output""" |
|
1965 | """clear progress bar output if any. use it before any output""" | |
1964 | if not haveprogbar(): # nothing loaded yet |
|
1966 | if not haveprogbar(): # nothing loaded yet | |
1965 | return |
|
1967 | return | |
1966 | if self._progbar is not None and self._progbar.printed: |
|
1968 | if self._progbar is not None and self._progbar.printed: | |
1967 | self._progbar.clear() |
|
1969 | self._progbar.clear() | |
1968 |
|
1970 | |||
1969 | def makeprogress(self, topic, unit=b"", total=None): |
|
1971 | def makeprogress(self, topic, unit=b"", total=None): | |
1970 | """Create a progress helper for the specified topic""" |
|
1972 | """Create a progress helper for the specified topic""" | |
1971 | if getattr(self._fmsgerr, 'structured', False): |
|
1973 | if getattr(self._fmsgerr, 'structured', False): | |
1972 | # channel for machine-readable output with metadata, just send |
|
1974 | # channel for machine-readable output with metadata, just send | |
1973 | # raw information |
|
1975 | # raw information | |
1974 | # TODO: consider porting some useful information (e.g. estimated |
|
1976 | # TODO: consider porting some useful information (e.g. estimated | |
1975 | # time) from progbar. we might want to support update delay to |
|
1977 | # time) from progbar. we might want to support update delay to | |
1976 | # reduce the cost of transferring progress messages. |
|
1978 | # reduce the cost of transferring progress messages. | |
1977 | def updatebar(topic, pos, item, unit, total): |
|
1979 | def updatebar(topic, pos, item, unit, total): | |
1978 | self._fmsgerr.write( |
|
1980 | self._fmsgerr.write( | |
1979 | None, |
|
1981 | None, | |
1980 | type=b'progress', |
|
1982 | type=b'progress', | |
1981 | topic=topic, |
|
1983 | topic=topic, | |
1982 | pos=pos, |
|
1984 | pos=pos, | |
1983 | item=item, |
|
1985 | item=item, | |
1984 | unit=unit, |
|
1986 | unit=unit, | |
1985 | total=total, |
|
1987 | total=total, | |
1986 | ) |
|
1988 | ) | |
1987 |
|
1989 | |||
1988 | elif self._progbar is not None: |
|
1990 | elif self._progbar is not None: | |
1989 | updatebar = self._progbar.progress |
|
1991 | updatebar = self._progbar.progress | |
1990 | else: |
|
1992 | else: | |
1991 |
|
1993 | |||
1992 | def updatebar(topic, pos, item, unit, total): |
|
1994 | def updatebar(topic, pos, item, unit, total): | |
1993 | pass |
|
1995 | pass | |
1994 |
|
1996 | |||
1995 | return scmutil.progress(self, updatebar, topic, unit, total) |
|
1997 | return scmutil.progress(self, updatebar, topic, unit, total) | |
1996 |
|
1998 | |||
1997 | def getlogger(self, name): |
|
1999 | def getlogger(self, name): | |
1998 | """Returns a logger of the given name; or None if not registered""" |
|
2000 | """Returns a logger of the given name; or None if not registered""" | |
1999 | return self._loggers.get(name) |
|
2001 | return self._loggers.get(name) | |
2000 |
|
2002 | |||
2001 | def setlogger(self, name, logger): |
|
2003 | def setlogger(self, name, logger): | |
2002 | """Install logger which can be identified later by the given name |
|
2004 | """Install logger which can be identified later by the given name | |
2003 |
|
2005 | |||
2004 | More than one loggers can be registered. Use extension or module |
|
2006 | More than one loggers can be registered. Use extension or module | |
2005 | name to uniquely identify the logger instance. |
|
2007 | name to uniquely identify the logger instance. | |
2006 | """ |
|
2008 | """ | |
2007 | self._loggers[name] = logger |
|
2009 | self._loggers[name] = logger | |
2008 |
|
2010 | |||
2009 | def log(self, event, msgfmt, *msgargs, **opts): |
|
2011 | def log(self, event, msgfmt, *msgargs, **opts): | |
2010 | '''hook for logging facility extensions |
|
2012 | '''hook for logging facility extensions | |
2011 |
|
2013 | |||
2012 | event should be a readily-identifiable subsystem, which will |
|
2014 | event should be a readily-identifiable subsystem, which will | |
2013 | allow filtering. |
|
2015 | allow filtering. | |
2014 |
|
2016 | |||
2015 | msgfmt should be a newline-terminated format string to log, and |
|
2017 | msgfmt should be a newline-terminated format string to log, and | |
2016 | *msgargs are %-formatted into it. |
|
2018 | *msgargs are %-formatted into it. | |
2017 |
|
2019 | |||
2018 | **opts currently has no defined meanings. |
|
2020 | **opts currently has no defined meanings. | |
2019 | ''' |
|
2021 | ''' | |
2020 | if not self._loggers: |
|
2022 | if not self._loggers: | |
2021 | return |
|
2023 | return | |
2022 | activeloggers = [ |
|
2024 | activeloggers = [ | |
2023 | l for l in pycompat.itervalues(self._loggers) if l.tracked(event) |
|
2025 | l for l in pycompat.itervalues(self._loggers) if l.tracked(event) | |
2024 | ] |
|
2026 | ] | |
2025 | if not activeloggers: |
|
2027 | if not activeloggers: | |
2026 | return |
|
2028 | return | |
2027 | msg = msgfmt % msgargs |
|
2029 | msg = msgfmt % msgargs | |
2028 | opts = pycompat.byteskwargs(opts) |
|
2030 | opts = pycompat.byteskwargs(opts) | |
2029 | # guard against recursion from e.g. ui.debug() |
|
2031 | # guard against recursion from e.g. ui.debug() | |
2030 | registeredloggers = self._loggers |
|
2032 | registeredloggers = self._loggers | |
2031 | self._loggers = {} |
|
2033 | self._loggers = {} | |
2032 | try: |
|
2034 | try: | |
2033 | for logger in activeloggers: |
|
2035 | for logger in activeloggers: | |
2034 | logger.log(self, event, msg, opts) |
|
2036 | logger.log(self, event, msg, opts) | |
2035 | finally: |
|
2037 | finally: | |
2036 | self._loggers = registeredloggers |
|
2038 | self._loggers = registeredloggers | |
2037 |
|
2039 | |||
2038 | def label(self, msg, label): |
|
2040 | def label(self, msg, label): | |
2039 | '''style msg based on supplied label |
|
2041 | '''style msg based on supplied label | |
2040 |
|
2042 | |||
2041 | If some color mode is enabled, this will add the necessary control |
|
2043 | If some color mode is enabled, this will add the necessary control | |
2042 | characters to apply such color. In addition, 'debug' color mode adds |
|
2044 | characters to apply such color. In addition, 'debug' color mode adds | |
2043 | markup showing which label affects a piece of text. |
|
2045 | markup showing which label affects a piece of text. | |
2044 |
|
2046 | |||
2045 | ui.write(s, 'label') is equivalent to |
|
2047 | ui.write(s, 'label') is equivalent to | |
2046 | ui.write(ui.label(s, 'label')). |
|
2048 | ui.write(ui.label(s, 'label')). | |
2047 | ''' |
|
2049 | ''' | |
2048 | if self._colormode is not None: |
|
2050 | if self._colormode is not None: | |
2049 | return color.colorlabel(self, msg, label) |
|
2051 | return color.colorlabel(self, msg, label) | |
2050 | return msg |
|
2052 | return msg | |
2051 |
|
2053 | |||
2052 | def develwarn(self, msg, stacklevel=1, config=None): |
|
2054 | def develwarn(self, msg, stacklevel=1, config=None): | |
2053 | """issue a developer warning message |
|
2055 | """issue a developer warning message | |
2054 |
|
2056 | |||
2055 | Use 'stacklevel' to report the offender some layers further up in the |
|
2057 | Use 'stacklevel' to report the offender some layers further up in the | |
2056 | stack. |
|
2058 | stack. | |
2057 | """ |
|
2059 | """ | |
2058 | if not self.configbool(b'devel', b'all-warnings'): |
|
2060 | if not self.configbool(b'devel', b'all-warnings'): | |
2059 | if config is None or not self.configbool(b'devel', config): |
|
2061 | if config is None or not self.configbool(b'devel', config): | |
2060 | return |
|
2062 | return | |
2061 | msg = b'devel-warn: ' + msg |
|
2063 | msg = b'devel-warn: ' + msg | |
2062 | stacklevel += 1 # get in develwarn |
|
2064 | stacklevel += 1 # get in develwarn | |
2063 | if self.tracebackflag: |
|
2065 | if self.tracebackflag: | |
2064 | util.debugstacktrace(msg, stacklevel, self._ferr, self._fout) |
|
2066 | util.debugstacktrace(msg, stacklevel, self._ferr, self._fout) | |
2065 | self.log( |
|
2067 | self.log( | |
2066 | b'develwarn', |
|
2068 | b'develwarn', | |
2067 | b'%s at:\n%s' |
|
2069 | b'%s at:\n%s' | |
2068 | % (msg, b''.join(util.getstackframes(stacklevel))), |
|
2070 | % (msg, b''.join(util.getstackframes(stacklevel))), | |
2069 | ) |
|
2071 | ) | |
2070 | else: |
|
2072 | else: | |
2071 | curframe = inspect.currentframe() |
|
2073 | curframe = inspect.currentframe() | |
2072 | calframe = inspect.getouterframes(curframe, 2) |
|
2074 | calframe = inspect.getouterframes(curframe, 2) | |
2073 | fname, lineno, fmsg = calframe[stacklevel][1:4] |
|
2075 | fname, lineno, fmsg = calframe[stacklevel][1:4] | |
2074 | fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg) |
|
2076 | fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg) | |
2075 | self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg)) |
|
2077 | self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg)) | |
2076 | self.log( |
|
2078 | self.log( | |
2077 | b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg |
|
2079 | b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg | |
2078 | ) |
|
2080 | ) | |
2079 |
|
2081 | |||
2080 | # avoid cycles |
|
2082 | # avoid cycles | |
2081 | del curframe |
|
2083 | del curframe | |
2082 | del calframe |
|
2084 | del calframe | |
2083 |
|
2085 | |||
2084 | def deprecwarn(self, msg, version, stacklevel=2): |
|
2086 | def deprecwarn(self, msg, version, stacklevel=2): | |
2085 | """issue a deprecation warning |
|
2087 | """issue a deprecation warning | |
2086 |
|
2088 | |||
2087 | - msg: message explaining what is deprecated and how to upgrade, |
|
2089 | - msg: message explaining what is deprecated and how to upgrade, | |
2088 | - version: last version where the API will be supported, |
|
2090 | - version: last version where the API will be supported, | |
2089 | """ |
|
2091 | """ | |
2090 | if not ( |
|
2092 | if not ( | |
2091 | self.configbool(b'devel', b'all-warnings') |
|
2093 | self.configbool(b'devel', b'all-warnings') | |
2092 | or self.configbool(b'devel', b'deprec-warn') |
|
2094 | or self.configbool(b'devel', b'deprec-warn') | |
2093 | ): |
|
2095 | ): | |
2094 | return |
|
2096 | return | |
2095 | msg += ( |
|
2097 | msg += ( | |
2096 | b"\n(compatibility will be dropped after Mercurial-%s," |
|
2098 | b"\n(compatibility will be dropped after Mercurial-%s," | |
2097 | b" update your code.)" |
|
2099 | b" update your code.)" | |
2098 | ) % version |
|
2100 | ) % version | |
2099 | self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn') |
|
2101 | self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn') | |
2100 |
|
2102 | |||
2101 | def exportableenviron(self): |
|
2103 | def exportableenviron(self): | |
2102 | """The environment variables that are safe to export, e.g. through |
|
2104 | """The environment variables that are safe to export, e.g. through | |
2103 | hgweb. |
|
2105 | hgweb. | |
2104 | """ |
|
2106 | """ | |
2105 | return self._exportableenviron |
|
2107 | return self._exportableenviron | |
2106 |
|
2108 | |||
2107 | @contextlib.contextmanager |
|
2109 | @contextlib.contextmanager | |
2108 | def configoverride(self, overrides, source=b""): |
|
2110 | def configoverride(self, overrides, source=b""): | |
2109 | """Context manager for temporary config overrides |
|
2111 | """Context manager for temporary config overrides | |
2110 | `overrides` must be a dict of the following structure: |
|
2112 | `overrides` must be a dict of the following structure: | |
2111 | {(section, name) : value}""" |
|
2113 | {(section, name) : value}""" | |
2112 | backups = {} |
|
2114 | backups = {} | |
2113 | try: |
|
2115 | try: | |
2114 | for (section, name), value in overrides.items(): |
|
2116 | for (section, name), value in overrides.items(): | |
2115 | backups[(section, name)] = self.backupconfig(section, name) |
|
2117 | backups[(section, name)] = self.backupconfig(section, name) | |
2116 | self.setconfig(section, name, value, source) |
|
2118 | self.setconfig(section, name, value, source) | |
2117 | yield |
|
2119 | yield | |
2118 | finally: |
|
2120 | finally: | |
2119 | for __, backup in backups.items(): |
|
2121 | for __, backup in backups.items(): | |
2120 | self.restoreconfig(backup) |
|
2122 | self.restoreconfig(backup) | |
2121 | # just restoring ui.quiet config to the previous value is not enough |
|
2123 | # just restoring ui.quiet config to the previous value is not enough | |
2122 | # as it does not update ui.quiet class member |
|
2124 | # as it does not update ui.quiet class member | |
2123 | if (b'ui', b'quiet') in overrides: |
|
2125 | if (b'ui', b'quiet') in overrides: | |
2124 | self.fixconfig(section=b'ui') |
|
2126 | self.fixconfig(section=b'ui') | |
2125 |
|
2127 | |||
2126 | def estimatememory(self): |
|
2128 | def estimatememory(self): | |
2127 | """Provide an estimate for the available system memory in Bytes. |
|
2129 | """Provide an estimate for the available system memory in Bytes. | |
2128 |
|
2130 | |||
2129 | This can be overriden via ui.available-memory. It returns None, if |
|
2131 | This can be overriden via ui.available-memory. It returns None, if | |
2130 | no estimate can be computed. |
|
2132 | no estimate can be computed. | |
2131 | """ |
|
2133 | """ | |
2132 | value = self.config(b'ui', b'available-memory') |
|
2134 | value = self.config(b'ui', b'available-memory') | |
2133 | if value is not None: |
|
2135 | if value is not None: | |
2134 | try: |
|
2136 | try: | |
2135 | return util.sizetoint(value) |
|
2137 | return util.sizetoint(value) | |
2136 | except error.ParseError: |
|
2138 | except error.ParseError: | |
2137 | raise error.ConfigError( |
|
2139 | raise error.ConfigError( | |
2138 | _(b"ui.available-memory value is invalid ('%s')") % value |
|
2140 | _(b"ui.available-memory value is invalid ('%s')") % value | |
2139 | ) |
|
2141 | ) | |
2140 | return util._estimatememory() |
|
2142 | return util._estimatememory() | |
2141 |
|
2143 | |||
2142 |
|
2144 | |||
2143 | class paths(dict): |
|
2145 | class paths(dict): | |
2144 | """Represents a collection of paths and their configs. |
|
2146 | """Represents a collection of paths and their configs. | |
2145 |
|
2147 | |||
2146 | Data is initially derived from ui instances and the config files they have |
|
2148 | Data is initially derived from ui instances and the config files they have | |
2147 | loaded. |
|
2149 | loaded. | |
2148 | """ |
|
2150 | """ | |
2149 |
|
2151 | |||
2150 | def __init__(self, ui): |
|
2152 | def __init__(self, ui): | |
2151 | dict.__init__(self) |
|
2153 | dict.__init__(self) | |
2152 |
|
2154 | |||
2153 | for name, loc in ui.configitems(b'paths', ignoresub=True): |
|
2155 | for name, loc in ui.configitems(b'paths', ignoresub=True): | |
2154 | # No location is the same as not existing. |
|
2156 | # No location is the same as not existing. | |
2155 | if not loc: |
|
2157 | if not loc: | |
2156 | continue |
|
2158 | continue | |
2157 | loc, sub = ui.configsuboptions(b'paths', name) |
|
2159 | loc, sub = ui.configsuboptions(b'paths', name) | |
2158 | self[name] = path(ui, name, rawloc=loc, suboptions=sub) |
|
2160 | self[name] = path(ui, name, rawloc=loc, suboptions=sub) | |
2159 |
|
2161 | |||
2160 | def getpath(self, name, default=None): |
|
2162 | def getpath(self, name, default=None): | |
2161 | """Return a ``path`` from a string, falling back to default. |
|
2163 | """Return a ``path`` from a string, falling back to default. | |
2162 |
|
2164 | |||
2163 | ``name`` can be a named path or locations. Locations are filesystem |
|
2165 | ``name`` can be a named path or locations. Locations are filesystem | |
2164 | paths or URIs. |
|
2166 | paths or URIs. | |
2165 |
|
2167 | |||
2166 | Returns None if ``name`` is not a registered path, a URI, or a local |
|
2168 | Returns None if ``name`` is not a registered path, a URI, or a local | |
2167 | path to a repo. |
|
2169 | path to a repo. | |
2168 | """ |
|
2170 | """ | |
2169 | # Only fall back to default if no path was requested. |
|
2171 | # Only fall back to default if no path was requested. | |
2170 | if name is None: |
|
2172 | if name is None: | |
2171 | if not default: |
|
2173 | if not default: | |
2172 | default = () |
|
2174 | default = () | |
2173 | elif not isinstance(default, (tuple, list)): |
|
2175 | elif not isinstance(default, (tuple, list)): | |
2174 | default = (default,) |
|
2176 | default = (default,) | |
2175 | for k in default: |
|
2177 | for k in default: | |
2176 | try: |
|
2178 | try: | |
2177 | return self[k] |
|
2179 | return self[k] | |
2178 | except KeyError: |
|
2180 | except KeyError: | |
2179 | continue |
|
2181 | continue | |
2180 | return None |
|
2182 | return None | |
2181 |
|
2183 | |||
2182 | # Most likely empty string. |
|
2184 | # Most likely empty string. | |
2183 | # This may need to raise in the future. |
|
2185 | # This may need to raise in the future. | |
2184 | if not name: |
|
2186 | if not name: | |
2185 | return None |
|
2187 | return None | |
2186 |
|
2188 | |||
2187 | try: |
|
2189 | try: | |
2188 | return self[name] |
|
2190 | return self[name] | |
2189 | except KeyError: |
|
2191 | except KeyError: | |
2190 | # Try to resolve as a local path or URI. |
|
2192 | # Try to resolve as a local path or URI. | |
2191 | try: |
|
2193 | try: | |
2192 | # We don't pass sub-options in, so no need to pass ui instance. |
|
2194 | # We don't pass sub-options in, so no need to pass ui instance. | |
2193 | return path(None, None, rawloc=name) |
|
2195 | return path(None, None, rawloc=name) | |
2194 | except ValueError: |
|
2196 | except ValueError: | |
2195 | raise error.RepoError(_(b'repository %s does not exist') % name) |
|
2197 | raise error.RepoError(_(b'repository %s does not exist') % name) | |
2196 |
|
2198 | |||
2197 |
|
2199 | |||
2198 | _pathsuboptions = {} |
|
2200 | _pathsuboptions = {} | |
2199 |
|
2201 | |||
2200 |
|
2202 | |||
2201 | def pathsuboption(option, attr): |
|
2203 | def pathsuboption(option, attr): | |
2202 | """Decorator used to declare a path sub-option. |
|
2204 | """Decorator used to declare a path sub-option. | |
2203 |
|
2205 | |||
2204 | Arguments are the sub-option name and the attribute it should set on |
|
2206 | Arguments are the sub-option name and the attribute it should set on | |
2205 | ``path`` instances. |
|
2207 | ``path`` instances. | |
2206 |
|
2208 | |||
2207 | The decorated function will receive as arguments a ``ui`` instance, |
|
2209 | The decorated function will receive as arguments a ``ui`` instance, | |
2208 | ``path`` instance, and the string value of this option from the config. |
|
2210 | ``path`` instance, and the string value of this option from the config. | |
2209 | The function should return the value that will be set on the ``path`` |
|
2211 | The function should return the value that will be set on the ``path`` | |
2210 | instance. |
|
2212 | instance. | |
2211 |
|
2213 | |||
2212 | This decorator can be used to perform additional verification of |
|
2214 | This decorator can be used to perform additional verification of | |
2213 | sub-options and to change the type of sub-options. |
|
2215 | sub-options and to change the type of sub-options. | |
2214 | """ |
|
2216 | """ | |
2215 |
|
2217 | |||
2216 | def register(func): |
|
2218 | def register(func): | |
2217 | _pathsuboptions[option] = (attr, func) |
|
2219 | _pathsuboptions[option] = (attr, func) | |
2218 | return func |
|
2220 | return func | |
2219 |
|
2221 | |||
2220 | return register |
|
2222 | return register | |
2221 |
|
2223 | |||
2222 |
|
2224 | |||
2223 | @pathsuboption(b'pushurl', b'pushloc') |
|
2225 | @pathsuboption(b'pushurl', b'pushloc') | |
2224 | def pushurlpathoption(ui, path, value): |
|
2226 | def pushurlpathoption(ui, path, value): | |
2225 | u = util.url(value) |
|
2227 | u = util.url(value) | |
2226 | # Actually require a URL. |
|
2228 | # Actually require a URL. | |
2227 | if not u.scheme: |
|
2229 | if not u.scheme: | |
2228 | ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name) |
|
2230 | ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name) | |
2229 | return None |
|
2231 | return None | |
2230 |
|
2232 | |||
2231 | # Don't support the #foo syntax in the push URL to declare branch to |
|
2233 | # Don't support the #foo syntax in the push URL to declare branch to | |
2232 | # push. |
|
2234 | # push. | |
2233 | if u.fragment: |
|
2235 | if u.fragment: | |
2234 | ui.warn( |
|
2236 | ui.warn( | |
2235 | _( |
|
2237 | _( | |
2236 | b'("#fragment" in paths.%s:pushurl not supported; ' |
|
2238 | b'("#fragment" in paths.%s:pushurl not supported; ' | |
2237 | b'ignoring)\n' |
|
2239 | b'ignoring)\n' | |
2238 | ) |
|
2240 | ) | |
2239 | % path.name |
|
2241 | % path.name | |
2240 | ) |
|
2242 | ) | |
2241 | u.fragment = None |
|
2243 | u.fragment = None | |
2242 |
|
2244 | |||
2243 | return bytes(u) |
|
2245 | return bytes(u) | |
2244 |
|
2246 | |||
2245 |
|
2247 | |||
2246 | @pathsuboption(b'pushrev', b'pushrev') |
|
2248 | @pathsuboption(b'pushrev', b'pushrev') | |
2247 | def pushrevpathoption(ui, path, value): |
|
2249 | def pushrevpathoption(ui, path, value): | |
2248 | return value |
|
2250 | return value | |
2249 |
|
2251 | |||
2250 |
|
2252 | |||
2251 | class path(object): |
|
2253 | class path(object): | |
2252 | """Represents an individual path and its configuration.""" |
|
2254 | """Represents an individual path and its configuration.""" | |
2253 |
|
2255 | |||
2254 | def __init__(self, ui, name, rawloc=None, suboptions=None): |
|
2256 | def __init__(self, ui, name, rawloc=None, suboptions=None): | |
2255 | """Construct a path from its config options. |
|
2257 | """Construct a path from its config options. | |
2256 |
|
2258 | |||
2257 | ``ui`` is the ``ui`` instance the path is coming from. |
|
2259 | ``ui`` is the ``ui`` instance the path is coming from. | |
2258 | ``name`` is the symbolic name of the path. |
|
2260 | ``name`` is the symbolic name of the path. | |
2259 | ``rawloc`` is the raw location, as defined in the config. |
|
2261 | ``rawloc`` is the raw location, as defined in the config. | |
2260 | ``pushloc`` is the raw locations pushes should be made to. |
|
2262 | ``pushloc`` is the raw locations pushes should be made to. | |
2261 |
|
2263 | |||
2262 | If ``name`` is not defined, we require that the location be a) a local |
|
2264 | If ``name`` is not defined, we require that the location be a) a local | |
2263 | filesystem path with a .hg directory or b) a URL. If not, |
|
2265 | filesystem path with a .hg directory or b) a URL. If not, | |
2264 | ``ValueError`` is raised. |
|
2266 | ``ValueError`` is raised. | |
2265 | """ |
|
2267 | """ | |
2266 | if not rawloc: |
|
2268 | if not rawloc: | |
2267 | raise ValueError(b'rawloc must be defined') |
|
2269 | raise ValueError(b'rawloc must be defined') | |
2268 |
|
2270 | |||
2269 | # Locations may define branches via syntax <base>#<branch>. |
|
2271 | # Locations may define branches via syntax <base>#<branch>. | |
2270 | u = util.url(rawloc) |
|
2272 | u = util.url(rawloc) | |
2271 | branch = None |
|
2273 | branch = None | |
2272 | if u.fragment: |
|
2274 | if u.fragment: | |
2273 | branch = u.fragment |
|
2275 | branch = u.fragment | |
2274 | u.fragment = None |
|
2276 | u.fragment = None | |
2275 |
|
2277 | |||
2276 | self.url = u |
|
2278 | self.url = u | |
2277 | self.branch = branch |
|
2279 | self.branch = branch | |
2278 |
|
2280 | |||
2279 | self.name = name |
|
2281 | self.name = name | |
2280 | self.rawloc = rawloc |
|
2282 | self.rawloc = rawloc | |
2281 | self.loc = b'%s' % u |
|
2283 | self.loc = b'%s' % u | |
2282 |
|
2284 | |||
2283 | # When given a raw location but not a symbolic name, validate the |
|
2285 | # When given a raw location but not a symbolic name, validate the | |
2284 | # location is valid. |
|
2286 | # location is valid. | |
2285 | if not name and not u.scheme and not self._isvalidlocalpath(self.loc): |
|
2287 | if not name and not u.scheme and not self._isvalidlocalpath(self.loc): | |
2286 | raise ValueError( |
|
2288 | raise ValueError( | |
2287 | b'location is not a URL or path to a local ' |
|
2289 | b'location is not a URL or path to a local ' | |
2288 | b'repo: %s' % rawloc |
|
2290 | b'repo: %s' % rawloc | |
2289 | ) |
|
2291 | ) | |
2290 |
|
2292 | |||
2291 | suboptions = suboptions or {} |
|
2293 | suboptions = suboptions or {} | |
2292 |
|
2294 | |||
2293 | # Now process the sub-options. If a sub-option is registered, its |
|
2295 | # Now process the sub-options. If a sub-option is registered, its | |
2294 | # attribute will always be present. The value will be None if there |
|
2296 | # attribute will always be present. The value will be None if there | |
2295 | # was no valid sub-option. |
|
2297 | # was no valid sub-option. | |
2296 | for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions): |
|
2298 | for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions): | |
2297 | if suboption not in suboptions: |
|
2299 | if suboption not in suboptions: | |
2298 | setattr(self, attr, None) |
|
2300 | setattr(self, attr, None) | |
2299 | continue |
|
2301 | continue | |
2300 |
|
2302 | |||
2301 | value = func(ui, self, suboptions[suboption]) |
|
2303 | value = func(ui, self, suboptions[suboption]) | |
2302 | setattr(self, attr, value) |
|
2304 | setattr(self, attr, value) | |
2303 |
|
2305 | |||
2304 | def _isvalidlocalpath(self, path): |
|
2306 | def _isvalidlocalpath(self, path): | |
2305 | """Returns True if the given path is a potentially valid repository. |
|
2307 | """Returns True if the given path is a potentially valid repository. | |
2306 | This is its own function so that extensions can change the definition of |
|
2308 | This is its own function so that extensions can change the definition of | |
2307 | 'valid' in this case (like when pulling from a git repo into a hg |
|
2309 | 'valid' in this case (like when pulling from a git repo into a hg | |
2308 | one).""" |
|
2310 | one).""" | |
2309 | try: |
|
2311 | try: | |
2310 | return os.path.isdir(os.path.join(path, b'.hg')) |
|
2312 | return os.path.isdir(os.path.join(path, b'.hg')) | |
2311 | # Python 2 may return TypeError. Python 3, ValueError. |
|
2313 | # Python 2 may return TypeError. Python 3, ValueError. | |
2312 | except (TypeError, ValueError): |
|
2314 | except (TypeError, ValueError): | |
2313 | return False |
|
2315 | return False | |
2314 |
|
2316 | |||
2315 | @property |
|
2317 | @property | |
2316 | def suboptions(self): |
|
2318 | def suboptions(self): | |
2317 | """Return sub-options and their values for this path. |
|
2319 | """Return sub-options and their values for this path. | |
2318 |
|
2320 | |||
2319 | This is intended to be used for presentation purposes. |
|
2321 | This is intended to be used for presentation purposes. | |
2320 | """ |
|
2322 | """ | |
2321 | d = {} |
|
2323 | d = {} | |
2322 | for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions): |
|
2324 | for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions): | |
2323 | value = getattr(self, attr) |
|
2325 | value = getattr(self, attr) | |
2324 | if value is not None: |
|
2326 | if value is not None: | |
2325 | d[subopt] = value |
|
2327 | d[subopt] = value | |
2326 | return d |
|
2328 | return d | |
2327 |
|
2329 | |||
2328 |
|
2330 | |||
2329 | # we instantiate one globally shared progress bar to avoid |
|
2331 | # we instantiate one globally shared progress bar to avoid | |
2330 | # competing progress bars when multiple UI objects get created |
|
2332 | # competing progress bars when multiple UI objects get created | |
2331 | _progresssingleton = None |
|
2333 | _progresssingleton = None | |
2332 |
|
2334 | |||
2333 |
|
2335 | |||
2334 | def getprogbar(ui): |
|
2336 | def getprogbar(ui): | |
2335 | global _progresssingleton |
|
2337 | global _progresssingleton | |
2336 | if _progresssingleton is None: |
|
2338 | if _progresssingleton is None: | |
2337 | # passing 'ui' object to the singleton is fishy, |
|
2339 | # passing 'ui' object to the singleton is fishy, | |
2338 | # this is how the extension used to work but feel free to rework it. |
|
2340 | # this is how the extension used to work but feel free to rework it. | |
2339 | _progresssingleton = progress.progbar(ui) |
|
2341 | _progresssingleton = progress.progbar(ui) | |
2340 | return _progresssingleton |
|
2342 | return _progresssingleton | |
2341 |
|
2343 | |||
2342 |
|
2344 | |||
2343 | def haveprogbar(): |
|
2345 | def haveprogbar(): | |
2344 | return _progresssingleton is not None |
|
2346 | return _progresssingleton is not None | |
2345 |
|
2347 | |||
2346 |
|
2348 | |||
2347 | def _selectmsgdests(ui): |
|
2349 | def _selectmsgdests(ui): | |
2348 | name = ui.config(b'ui', b'message-output') |
|
2350 | name = ui.config(b'ui', b'message-output') | |
2349 | if name == b'channel': |
|
2351 | if name == b'channel': | |
2350 | if ui.fmsg: |
|
2352 | if ui.fmsg: | |
2351 | return ui.fmsg, ui.fmsg |
|
2353 | return ui.fmsg, ui.fmsg | |
2352 | else: |
|
2354 | else: | |
2353 | # fall back to ferr if channel isn't ready so that status/error |
|
2355 | # fall back to ferr if channel isn't ready so that status/error | |
2354 | # messages can be printed |
|
2356 | # messages can be printed | |
2355 | return ui.ferr, ui.ferr |
|
2357 | return ui.ferr, ui.ferr | |
2356 | if name == b'stdio': |
|
2358 | if name == b'stdio': | |
2357 | return ui.fout, ui.ferr |
|
2359 | return ui.fout, ui.ferr | |
2358 | if name == b'stderr': |
|
2360 | if name == b'stderr': | |
2359 | return ui.ferr, ui.ferr |
|
2361 | return ui.ferr, ui.ferr | |
2360 | raise error.Abort(b'invalid ui.message-output destination: %s' % name) |
|
2362 | raise error.Abort(b'invalid ui.message-output destination: %s' % name) | |
2361 |
|
2363 | |||
2362 |
|
2364 | |||
2363 | def _writemsgwith(write, dest, *args, **opts): |
|
2365 | def _writemsgwith(write, dest, *args, **opts): | |
2364 | """Write ui message with the given ui._write*() function |
|
2366 | """Write ui message with the given ui._write*() function | |
2365 |
|
2367 | |||
2366 | The specified message type is translated to 'ui.<type>' label if the dest |
|
2368 | The specified message type is translated to 'ui.<type>' label if the dest | |
2367 | isn't a structured channel, so that the message will be colorized. |
|
2369 | isn't a structured channel, so that the message will be colorized. | |
2368 | """ |
|
2370 | """ | |
2369 | # TODO: maybe change 'type' to a mandatory option |
|
2371 | # TODO: maybe change 'type' to a mandatory option | |
2370 | if 'type' in opts and not getattr(dest, 'structured', False): |
|
2372 | if 'type' in opts and not getattr(dest, 'structured', False): | |
2371 | opts['label'] = opts.get('label', b'') + b' ui.%s' % opts.pop('type') |
|
2373 | opts['label'] = opts.get('label', b'') + b' ui.%s' % opts.pop('type') | |
2372 | write(dest, *args, **opts) |
|
2374 | write(dest, *args, **opts) |
@@ -1,3780 +1,3781 b'' | |||||
1 | #!/usr/bin/env python |
|
1 | #!/usr/bin/env python | |
2 | # |
|
2 | # | |
3 | # run-tests.py - Run a set of tests on Mercurial |
|
3 | # run-tests.py - Run a set of tests on Mercurial | |
4 | # |
|
4 | # | |
5 | # Copyright 2006 Matt Mackall <mpm@selenic.com> |
|
5 | # Copyright 2006 Matt Mackall <mpm@selenic.com> | |
6 | # |
|
6 | # | |
7 | # This software may be used and distributed according to the terms of the |
|
7 | # This software may be used and distributed according to the terms of the | |
8 | # GNU General Public License version 2 or any later version. |
|
8 | # GNU General Public License version 2 or any later version. | |
9 |
|
9 | |||
10 | # Modifying this script is tricky because it has many modes: |
|
10 | # Modifying this script is tricky because it has many modes: | |
11 | # - serial (default) vs parallel (-jN, N > 1) |
|
11 | # - serial (default) vs parallel (-jN, N > 1) | |
12 | # - no coverage (default) vs coverage (-c, -C, -s) |
|
12 | # - no coverage (default) vs coverage (-c, -C, -s) | |
13 | # - temp install (default) vs specific hg script (--with-hg, --local) |
|
13 | # - temp install (default) vs specific hg script (--with-hg, --local) | |
14 | # - tests are a mix of shell scripts and Python scripts |
|
14 | # - tests are a mix of shell scripts and Python scripts | |
15 | # |
|
15 | # | |
16 | # If you change this script, it is recommended that you ensure you |
|
16 | # If you change this script, it is recommended that you ensure you | |
17 | # haven't broken it by running it in various modes with a representative |
|
17 | # haven't broken it by running it in various modes with a representative | |
18 | # sample of test scripts. For example: |
|
18 | # sample of test scripts. For example: | |
19 | # |
|
19 | # | |
20 | # 1) serial, no coverage, temp install: |
|
20 | # 1) serial, no coverage, temp install: | |
21 | # ./run-tests.py test-s* |
|
21 | # ./run-tests.py test-s* | |
22 | # 2) serial, no coverage, local hg: |
|
22 | # 2) serial, no coverage, local hg: | |
23 | # ./run-tests.py --local test-s* |
|
23 | # ./run-tests.py --local test-s* | |
24 | # 3) serial, coverage, temp install: |
|
24 | # 3) serial, coverage, temp install: | |
25 | # ./run-tests.py -c test-s* |
|
25 | # ./run-tests.py -c test-s* | |
26 | # 4) serial, coverage, local hg: |
|
26 | # 4) serial, coverage, local hg: | |
27 | # ./run-tests.py -c --local test-s* # unsupported |
|
27 | # ./run-tests.py -c --local test-s* # unsupported | |
28 | # 5) parallel, no coverage, temp install: |
|
28 | # 5) parallel, no coverage, temp install: | |
29 | # ./run-tests.py -j2 test-s* |
|
29 | # ./run-tests.py -j2 test-s* | |
30 | # 6) parallel, no coverage, local hg: |
|
30 | # 6) parallel, no coverage, local hg: | |
31 | # ./run-tests.py -j2 --local test-s* |
|
31 | # ./run-tests.py -j2 --local test-s* | |
32 | # 7) parallel, coverage, temp install: |
|
32 | # 7) parallel, coverage, temp install: | |
33 | # ./run-tests.py -j2 -c test-s* # currently broken |
|
33 | # ./run-tests.py -j2 -c test-s* # currently broken | |
34 | # 8) parallel, coverage, local install: |
|
34 | # 8) parallel, coverage, local install: | |
35 | # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken) |
|
35 | # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken) | |
36 | # 9) parallel, custom tmp dir: |
|
36 | # 9) parallel, custom tmp dir: | |
37 | # ./run-tests.py -j2 --tmpdir /tmp/myhgtests |
|
37 | # ./run-tests.py -j2 --tmpdir /tmp/myhgtests | |
38 | # 10) parallel, pure, tests that call run-tests: |
|
38 | # 10) parallel, pure, tests that call run-tests: | |
39 | # ./run-tests.py --pure `grep -l run-tests.py *.t` |
|
39 | # ./run-tests.py --pure `grep -l run-tests.py *.t` | |
40 | # |
|
40 | # | |
41 | # (You could use any subset of the tests: test-s* happens to match |
|
41 | # (You could use any subset of the tests: test-s* happens to match | |
42 | # enough that it's worth doing parallel runs, few enough that it |
|
42 | # enough that it's worth doing parallel runs, few enough that it | |
43 | # completes fairly quickly, includes both shell and Python scripts, and |
|
43 | # completes fairly quickly, includes both shell and Python scripts, and | |
44 | # includes some scripts that run daemon processes.) |
|
44 | # includes some scripts that run daemon processes.) | |
45 |
|
45 | |||
46 | from __future__ import absolute_import, print_function |
|
46 | from __future__ import absolute_import, print_function | |
47 |
|
47 | |||
48 | import argparse |
|
48 | import argparse | |
49 | import collections |
|
49 | import collections | |
50 | import difflib |
|
50 | import difflib | |
51 | import distutils.version as version |
|
51 | import distutils.version as version | |
52 | import errno |
|
52 | import errno | |
53 | import json |
|
53 | import json | |
54 | import multiprocessing |
|
54 | import multiprocessing | |
55 | import os |
|
55 | import os | |
56 | import platform |
|
56 | import platform | |
57 | import random |
|
57 | import random | |
58 | import re |
|
58 | import re | |
59 | import shutil |
|
59 | import shutil | |
60 | import signal |
|
60 | import signal | |
61 | import socket |
|
61 | import socket | |
62 | import subprocess |
|
62 | import subprocess | |
63 | import sys |
|
63 | import sys | |
64 | import sysconfig |
|
64 | import sysconfig | |
65 | import tempfile |
|
65 | import tempfile | |
66 | import threading |
|
66 | import threading | |
67 | import time |
|
67 | import time | |
68 | import unittest |
|
68 | import unittest | |
69 | import uuid |
|
69 | import uuid | |
70 | import xml.dom.minidom as minidom |
|
70 | import xml.dom.minidom as minidom | |
71 |
|
71 | |||
72 | try: |
|
72 | try: | |
73 | import Queue as queue |
|
73 | import Queue as queue | |
74 | except ImportError: |
|
74 | except ImportError: | |
75 | import queue |
|
75 | import queue | |
76 |
|
76 | |||
77 | try: |
|
77 | try: | |
78 | import shlex |
|
78 | import shlex | |
79 |
|
79 | |||
80 | shellquote = shlex.quote |
|
80 | shellquote = shlex.quote | |
81 | except (ImportError, AttributeError): |
|
81 | except (ImportError, AttributeError): | |
82 | import pipes |
|
82 | import pipes | |
83 |
|
83 | |||
84 | shellquote = pipes.quote |
|
84 | shellquote = pipes.quote | |
85 |
|
85 | |||
86 | processlock = threading.Lock() |
|
86 | processlock = threading.Lock() | |
87 |
|
87 | |||
88 | pygmentspresent = False |
|
88 | pygmentspresent = False | |
89 | # ANSI color is unsupported prior to Windows 10 |
|
89 | # ANSI color is unsupported prior to Windows 10 | |
90 | if os.name != 'nt': |
|
90 | if os.name != 'nt': | |
91 | try: # is pygments installed |
|
91 | try: # is pygments installed | |
92 | import pygments |
|
92 | import pygments | |
93 | import pygments.lexers as lexers |
|
93 | import pygments.lexers as lexers | |
94 | import pygments.lexer as lexer |
|
94 | import pygments.lexer as lexer | |
95 | import pygments.formatters as formatters |
|
95 | import pygments.formatters as formatters | |
96 | import pygments.token as token |
|
96 | import pygments.token as token | |
97 | import pygments.style as style |
|
97 | import pygments.style as style | |
98 |
|
98 | |||
99 | pygmentspresent = True |
|
99 | pygmentspresent = True | |
100 | difflexer = lexers.DiffLexer() |
|
100 | difflexer = lexers.DiffLexer() | |
101 | terminal256formatter = formatters.Terminal256Formatter() |
|
101 | terminal256formatter = formatters.Terminal256Formatter() | |
102 | except ImportError: |
|
102 | except ImportError: | |
103 | pass |
|
103 | pass | |
104 |
|
104 | |||
105 | if pygmentspresent: |
|
105 | if pygmentspresent: | |
106 |
|
106 | |||
107 | class TestRunnerStyle(style.Style): |
|
107 | class TestRunnerStyle(style.Style): | |
108 | default_style = "" |
|
108 | default_style = "" | |
109 | skipped = token.string_to_tokentype("Token.Generic.Skipped") |
|
109 | skipped = token.string_to_tokentype("Token.Generic.Skipped") | |
110 | failed = token.string_to_tokentype("Token.Generic.Failed") |
|
110 | failed = token.string_to_tokentype("Token.Generic.Failed") | |
111 | skippedname = token.string_to_tokentype("Token.Generic.SName") |
|
111 | skippedname = token.string_to_tokentype("Token.Generic.SName") | |
112 | failedname = token.string_to_tokentype("Token.Generic.FName") |
|
112 | failedname = token.string_to_tokentype("Token.Generic.FName") | |
113 | styles = { |
|
113 | styles = { | |
114 | skipped: '#e5e5e5', |
|
114 | skipped: '#e5e5e5', | |
115 | skippedname: '#00ffff', |
|
115 | skippedname: '#00ffff', | |
116 | failed: '#7f0000', |
|
116 | failed: '#7f0000', | |
117 | failedname: '#ff0000', |
|
117 | failedname: '#ff0000', | |
118 | } |
|
118 | } | |
119 |
|
119 | |||
120 | class TestRunnerLexer(lexer.RegexLexer): |
|
120 | class TestRunnerLexer(lexer.RegexLexer): | |
121 | testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?' |
|
121 | testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?' | |
122 | tokens = { |
|
122 | tokens = { | |
123 | 'root': [ |
|
123 | 'root': [ | |
124 | (r'^Skipped', token.Generic.Skipped, 'skipped'), |
|
124 | (r'^Skipped', token.Generic.Skipped, 'skipped'), | |
125 | (r'^Failed ', token.Generic.Failed, 'failed'), |
|
125 | (r'^Failed ', token.Generic.Failed, 'failed'), | |
126 | (r'^ERROR: ', token.Generic.Failed, 'failed'), |
|
126 | (r'^ERROR: ', token.Generic.Failed, 'failed'), | |
127 | ], |
|
127 | ], | |
128 | 'skipped': [ |
|
128 | 'skipped': [ | |
129 | (testpattern, token.Generic.SName), |
|
129 | (testpattern, token.Generic.SName), | |
130 | (r':.*', token.Generic.Skipped), |
|
130 | (r':.*', token.Generic.Skipped), | |
131 | ], |
|
131 | ], | |
132 | 'failed': [ |
|
132 | 'failed': [ | |
133 | (testpattern, token.Generic.FName), |
|
133 | (testpattern, token.Generic.FName), | |
134 | (r'(:| ).*', token.Generic.Failed), |
|
134 | (r'(:| ).*', token.Generic.Failed), | |
135 | ], |
|
135 | ], | |
136 | } |
|
136 | } | |
137 |
|
137 | |||
138 | runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle) |
|
138 | runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle) | |
139 | runnerlexer = TestRunnerLexer() |
|
139 | runnerlexer = TestRunnerLexer() | |
140 |
|
140 | |||
141 | origenviron = os.environ.copy() |
|
141 | origenviron = os.environ.copy() | |
142 |
|
142 | |||
143 | if sys.version_info > (3, 5, 0): |
|
143 | if sys.version_info > (3, 5, 0): | |
144 | PYTHON3 = True |
|
144 | PYTHON3 = True | |
145 | xrange = range # we use xrange in one place, and we'd rather not use range |
|
145 | xrange = range # we use xrange in one place, and we'd rather not use range | |
146 |
|
146 | |||
147 | def _sys2bytes(p): |
|
147 | def _sys2bytes(p): | |
148 | if p is None: |
|
148 | if p is None: | |
149 | return p |
|
149 | return p | |
150 | return p.encode('utf-8') |
|
150 | return p.encode('utf-8') | |
151 |
|
151 | |||
152 | def _bytes2sys(p): |
|
152 | def _bytes2sys(p): | |
153 | if p is None: |
|
153 | if p is None: | |
154 | return p |
|
154 | return p | |
155 | return p.decode('utf-8') |
|
155 | return p.decode('utf-8') | |
156 |
|
156 | |||
157 | osenvironb = getattr(os, 'environb', None) |
|
157 | osenvironb = getattr(os, 'environb', None) | |
158 | if osenvironb is None: |
|
158 | if osenvironb is None: | |
159 | # Windows lacks os.environb, for instance. A proxy over the real thing |
|
159 | # Windows lacks os.environb, for instance. A proxy over the real thing | |
160 | # instead of a copy allows the environment to be updated via bytes on |
|
160 | # instead of a copy allows the environment to be updated via bytes on | |
161 | # all platforms. |
|
161 | # all platforms. | |
162 | class environbytes(object): |
|
162 | class environbytes(object): | |
163 | def __init__(self, strenv): |
|
163 | def __init__(self, strenv): | |
164 | self.__len__ = strenv.__len__ |
|
164 | self.__len__ = strenv.__len__ | |
165 | self.clear = strenv.clear |
|
165 | self.clear = strenv.clear | |
166 | self._strenv = strenv |
|
166 | self._strenv = strenv | |
167 |
|
167 | |||
168 | def __getitem__(self, k): |
|
168 | def __getitem__(self, k): | |
169 | v = self._strenv.__getitem__(_bytes2sys(k)) |
|
169 | v = self._strenv.__getitem__(_bytes2sys(k)) | |
170 | return _sys2bytes(v) |
|
170 | return _sys2bytes(v) | |
171 |
|
171 | |||
172 | def __setitem__(self, k, v): |
|
172 | def __setitem__(self, k, v): | |
173 | self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v)) |
|
173 | self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v)) | |
174 |
|
174 | |||
175 | def __delitem__(self, k): |
|
175 | def __delitem__(self, k): | |
176 | self._strenv.__delitem__(_bytes2sys(k)) |
|
176 | self._strenv.__delitem__(_bytes2sys(k)) | |
177 |
|
177 | |||
178 | def __contains__(self, k): |
|
178 | def __contains__(self, k): | |
179 | return self._strenv.__contains__(_bytes2sys(k)) |
|
179 | return self._strenv.__contains__(_bytes2sys(k)) | |
180 |
|
180 | |||
181 | def __iter__(self): |
|
181 | def __iter__(self): | |
182 | return iter([_sys2bytes(k) for k in iter(self._strenv)]) |
|
182 | return iter([_sys2bytes(k) for k in iter(self._strenv)]) | |
183 |
|
183 | |||
184 | def get(self, k, default=None): |
|
184 | def get(self, k, default=None): | |
185 | v = self._strenv.get(_bytes2sys(k), _bytes2sys(default)) |
|
185 | v = self._strenv.get(_bytes2sys(k), _bytes2sys(default)) | |
186 | return _sys2bytes(v) |
|
186 | return _sys2bytes(v) | |
187 |
|
187 | |||
188 | def pop(self, k, default=None): |
|
188 | def pop(self, k, default=None): | |
189 | v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default)) |
|
189 | v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default)) | |
190 | return _sys2bytes(v) |
|
190 | return _sys2bytes(v) | |
191 |
|
191 | |||
192 | osenvironb = environbytes(os.environ) |
|
192 | osenvironb = environbytes(os.environ) | |
193 |
|
193 | |||
194 | getcwdb = getattr(os, 'getcwdb') |
|
194 | getcwdb = getattr(os, 'getcwdb') | |
195 | if not getcwdb or os.name == 'nt': |
|
195 | if not getcwdb or os.name == 'nt': | |
196 | getcwdb = lambda: _sys2bytes(os.getcwd()) |
|
196 | getcwdb = lambda: _sys2bytes(os.getcwd()) | |
197 |
|
197 | |||
198 | elif sys.version_info >= (3, 0, 0): |
|
198 | elif sys.version_info >= (3, 0, 0): | |
199 | print( |
|
199 | print( | |
200 | '%s is only supported on Python 3.5+ and 2.7, not %s' |
|
200 | '%s is only supported on Python 3.5+ and 2.7, not %s' | |
201 | % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])) |
|
201 | % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])) | |
202 | ) |
|
202 | ) | |
203 | sys.exit(70) # EX_SOFTWARE from `man 3 sysexit` |
|
203 | sys.exit(70) # EX_SOFTWARE from `man 3 sysexit` | |
204 | else: |
|
204 | else: | |
205 | PYTHON3 = False |
|
205 | PYTHON3 = False | |
206 |
|
206 | |||
207 | # In python 2.x, path operations are generally done using |
|
207 | # In python 2.x, path operations are generally done using | |
208 | # bytestrings by default, so we don't have to do any extra |
|
208 | # bytestrings by default, so we don't have to do any extra | |
209 | # fiddling there. We define the wrapper functions anyway just to |
|
209 | # fiddling there. We define the wrapper functions anyway just to | |
210 | # help keep code consistent between platforms. |
|
210 | # help keep code consistent between platforms. | |
211 | def _sys2bytes(p): |
|
211 | def _sys2bytes(p): | |
212 | return p |
|
212 | return p | |
213 |
|
213 | |||
214 | _bytes2sys = _sys2bytes |
|
214 | _bytes2sys = _sys2bytes | |
215 | osenvironb = os.environ |
|
215 | osenvironb = os.environ | |
216 | getcwdb = os.getcwd |
|
216 | getcwdb = os.getcwd | |
217 |
|
217 | |||
218 | # For Windows support |
|
218 | # For Windows support | |
219 | wifexited = getattr(os, "WIFEXITED", lambda x: False) |
|
219 | wifexited = getattr(os, "WIFEXITED", lambda x: False) | |
220 |
|
220 | |||
221 | # Whether to use IPv6 |
|
221 | # Whether to use IPv6 | |
222 | def checksocketfamily(name, port=20058): |
|
222 | def checksocketfamily(name, port=20058): | |
223 | """return true if we can listen on localhost using family=name |
|
223 | """return true if we can listen on localhost using family=name | |
224 |
|
224 | |||
225 | name should be either 'AF_INET', or 'AF_INET6'. |
|
225 | name should be either 'AF_INET', or 'AF_INET6'. | |
226 | port being used is okay - EADDRINUSE is considered as successful. |
|
226 | port being used is okay - EADDRINUSE is considered as successful. | |
227 | """ |
|
227 | """ | |
228 | family = getattr(socket, name, None) |
|
228 | family = getattr(socket, name, None) | |
229 | if family is None: |
|
229 | if family is None: | |
230 | return False |
|
230 | return False | |
231 | try: |
|
231 | try: | |
232 | s = socket.socket(family, socket.SOCK_STREAM) |
|
232 | s = socket.socket(family, socket.SOCK_STREAM) | |
233 | s.bind(('localhost', port)) |
|
233 | s.bind(('localhost', port)) | |
234 | s.close() |
|
234 | s.close() | |
235 | return True |
|
235 | return True | |
236 | except socket.error as exc: |
|
236 | except socket.error as exc: | |
237 | if exc.errno == errno.EADDRINUSE: |
|
237 | if exc.errno == errno.EADDRINUSE: | |
238 | return True |
|
238 | return True | |
239 | elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT): |
|
239 | elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT): | |
240 | return False |
|
240 | return False | |
241 | else: |
|
241 | else: | |
242 | raise |
|
242 | raise | |
243 | else: |
|
243 | else: | |
244 | return False |
|
244 | return False | |
245 |
|
245 | |||
246 |
|
246 | |||
247 | # useipv6 will be set by parseargs |
|
247 | # useipv6 will be set by parseargs | |
248 | useipv6 = None |
|
248 | useipv6 = None | |
249 |
|
249 | |||
250 |
|
250 | |||
251 | def checkportisavailable(port): |
|
251 | def checkportisavailable(port): | |
252 | """return true if a port seems free to bind on localhost""" |
|
252 | """return true if a port seems free to bind on localhost""" | |
253 | if useipv6: |
|
253 | if useipv6: | |
254 | family = socket.AF_INET6 |
|
254 | family = socket.AF_INET6 | |
255 | else: |
|
255 | else: | |
256 | family = socket.AF_INET |
|
256 | family = socket.AF_INET | |
257 | try: |
|
257 | try: | |
258 | s = socket.socket(family, socket.SOCK_STREAM) |
|
258 | s = socket.socket(family, socket.SOCK_STREAM) | |
259 | s.bind(('localhost', port)) |
|
259 | s.bind(('localhost', port)) | |
260 | s.close() |
|
260 | s.close() | |
261 | return True |
|
261 | return True | |
262 | except socket.error as exc: |
|
262 | except socket.error as exc: | |
263 | if exc.errno not in ( |
|
263 | if exc.errno not in ( | |
264 | errno.EADDRINUSE, |
|
264 | errno.EADDRINUSE, | |
265 | errno.EADDRNOTAVAIL, |
|
265 | errno.EADDRNOTAVAIL, | |
266 | errno.EPROTONOSUPPORT, |
|
266 | errno.EPROTONOSUPPORT, | |
267 | ): |
|
267 | ): | |
268 | raise |
|
268 | raise | |
269 | return False |
|
269 | return False | |
270 |
|
270 | |||
271 |
|
271 | |||
272 | closefds = os.name == 'posix' |
|
272 | closefds = os.name == 'posix' | |
273 |
|
273 | |||
274 |
|
274 | |||
275 | def Popen4(cmd, wd, timeout, env=None): |
|
275 | def Popen4(cmd, wd, timeout, env=None): | |
276 | processlock.acquire() |
|
276 | processlock.acquire() | |
277 | p = subprocess.Popen( |
|
277 | p = subprocess.Popen( | |
278 | _bytes2sys(cmd), |
|
278 | _bytes2sys(cmd), | |
279 | shell=True, |
|
279 | shell=True, | |
280 | bufsize=-1, |
|
280 | bufsize=-1, | |
281 | cwd=_bytes2sys(wd), |
|
281 | cwd=_bytes2sys(wd), | |
282 | env=env, |
|
282 | env=env, | |
283 | close_fds=closefds, |
|
283 | close_fds=closefds, | |
284 | stdin=subprocess.PIPE, |
|
284 | stdin=subprocess.PIPE, | |
285 | stdout=subprocess.PIPE, |
|
285 | stdout=subprocess.PIPE, | |
286 | stderr=subprocess.STDOUT, |
|
286 | stderr=subprocess.STDOUT, | |
287 | ) |
|
287 | ) | |
288 | processlock.release() |
|
288 | processlock.release() | |
289 |
|
289 | |||
290 | p.fromchild = p.stdout |
|
290 | p.fromchild = p.stdout | |
291 | p.tochild = p.stdin |
|
291 | p.tochild = p.stdin | |
292 | p.childerr = p.stderr |
|
292 | p.childerr = p.stderr | |
293 |
|
293 | |||
294 | p.timeout = False |
|
294 | p.timeout = False | |
295 | if timeout: |
|
295 | if timeout: | |
296 |
|
296 | |||
297 | def t(): |
|
297 | def t(): | |
298 | start = time.time() |
|
298 | start = time.time() | |
299 | while time.time() - start < timeout and p.returncode is None: |
|
299 | while time.time() - start < timeout and p.returncode is None: | |
300 | time.sleep(0.1) |
|
300 | time.sleep(0.1) | |
301 | p.timeout = True |
|
301 | p.timeout = True | |
302 | if p.returncode is None: |
|
302 | if p.returncode is None: | |
303 | terminate(p) |
|
303 | terminate(p) | |
304 |
|
304 | |||
305 | threading.Thread(target=t).start() |
|
305 | threading.Thread(target=t).start() | |
306 |
|
306 | |||
307 | return p |
|
307 | return p | |
308 |
|
308 | |||
309 |
|
309 | |||
310 | if sys.executable: |
|
310 | if sys.executable: | |
311 | sysexecutable = sys.executable |
|
311 | sysexecutable = sys.executable | |
312 | elif os.environ.get('PYTHONEXECUTABLE'): |
|
312 | elif os.environ.get('PYTHONEXECUTABLE'): | |
313 | sysexecutable = os.environ['PYTHONEXECUTABLE'] |
|
313 | sysexecutable = os.environ['PYTHONEXECUTABLE'] | |
314 | elif os.environ.get('PYTHON'): |
|
314 | elif os.environ.get('PYTHON'): | |
315 | sysexecutable = os.environ['PYTHON'] |
|
315 | sysexecutable = os.environ['PYTHON'] | |
316 | else: |
|
316 | else: | |
317 | raise AssertionError('Could not find Python interpreter') |
|
317 | raise AssertionError('Could not find Python interpreter') | |
318 |
|
318 | |||
319 | PYTHON = _sys2bytes(sysexecutable.replace('\\', '/')) |
|
319 | PYTHON = _sys2bytes(sysexecutable.replace('\\', '/')) | |
320 | IMPL_PATH = b'PYTHONPATH' |
|
320 | IMPL_PATH = b'PYTHONPATH' | |
321 | if 'java' in sys.platform: |
|
321 | if 'java' in sys.platform: | |
322 | IMPL_PATH = b'JYTHONPATH' |
|
322 | IMPL_PATH = b'JYTHONPATH' | |
323 |
|
323 | |||
324 | default_defaults = { |
|
324 | default_defaults = { | |
325 | 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()), |
|
325 | 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()), | |
326 | 'timeout': ('HGTEST_TIMEOUT', 180), |
|
326 | 'timeout': ('HGTEST_TIMEOUT', 180), | |
327 | 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500), |
|
327 | 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500), | |
328 | 'port': ('HGTEST_PORT', 20059), |
|
328 | 'port': ('HGTEST_PORT', 20059), | |
329 | 'shell': ('HGTEST_SHELL', 'sh'), |
|
329 | 'shell': ('HGTEST_SHELL', 'sh'), | |
330 | } |
|
330 | } | |
331 |
|
331 | |||
332 | defaults = default_defaults.copy() |
|
332 | defaults = default_defaults.copy() | |
333 |
|
333 | |||
334 |
|
334 | |||
335 | def canonpath(path): |
|
335 | def canonpath(path): | |
336 | return os.path.realpath(os.path.expanduser(path)) |
|
336 | return os.path.realpath(os.path.expanduser(path)) | |
337 |
|
337 | |||
338 |
|
338 | |||
339 | def parselistfiles(files, listtype, warn=True): |
|
339 | def parselistfiles(files, listtype, warn=True): | |
340 | entries = dict() |
|
340 | entries = dict() | |
341 | for filename in files: |
|
341 | for filename in files: | |
342 | try: |
|
342 | try: | |
343 | path = os.path.expanduser(os.path.expandvars(filename)) |
|
343 | path = os.path.expanduser(os.path.expandvars(filename)) | |
344 | f = open(path, "rb") |
|
344 | f = open(path, "rb") | |
345 | except IOError as err: |
|
345 | except IOError as err: | |
346 | if err.errno != errno.ENOENT: |
|
346 | if err.errno != errno.ENOENT: | |
347 | raise |
|
347 | raise | |
348 | if warn: |
|
348 | if warn: | |
349 | print("warning: no such %s file: %s" % (listtype, filename)) |
|
349 | print("warning: no such %s file: %s" % (listtype, filename)) | |
350 | continue |
|
350 | continue | |
351 |
|
351 | |||
352 | for line in f.readlines(): |
|
352 | for line in f.readlines(): | |
353 | line = line.split(b'#', 1)[0].strip() |
|
353 | line = line.split(b'#', 1)[0].strip() | |
354 | if line: |
|
354 | if line: | |
355 | entries[line] = filename |
|
355 | entries[line] = filename | |
356 |
|
356 | |||
357 | f.close() |
|
357 | f.close() | |
358 | return entries |
|
358 | return entries | |
359 |
|
359 | |||
360 |
|
360 | |||
361 | def parsettestcases(path): |
|
361 | def parsettestcases(path): | |
362 | """read a .t test file, return a set of test case names |
|
362 | """read a .t test file, return a set of test case names | |
363 |
|
363 | |||
364 | If path does not exist, return an empty set. |
|
364 | If path does not exist, return an empty set. | |
365 | """ |
|
365 | """ | |
366 | cases = [] |
|
366 | cases = [] | |
367 | try: |
|
367 | try: | |
368 | with open(path, 'rb') as f: |
|
368 | with open(path, 'rb') as f: | |
369 | for l in f: |
|
369 | for l in f: | |
370 | if l.startswith(b'#testcases '): |
|
370 | if l.startswith(b'#testcases '): | |
371 | cases.append(sorted(l[11:].split())) |
|
371 | cases.append(sorted(l[11:].split())) | |
372 | except IOError as ex: |
|
372 | except IOError as ex: | |
373 | if ex.errno != errno.ENOENT: |
|
373 | if ex.errno != errno.ENOENT: | |
374 | raise |
|
374 | raise | |
375 | return cases |
|
375 | return cases | |
376 |
|
376 | |||
377 |
|
377 | |||
378 | def getparser(): |
|
378 | def getparser(): | |
379 | """Obtain the OptionParser used by the CLI.""" |
|
379 | """Obtain the OptionParser used by the CLI.""" | |
380 | parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]') |
|
380 | parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]') | |
381 |
|
381 | |||
382 | selection = parser.add_argument_group('Test Selection') |
|
382 | selection = parser.add_argument_group('Test Selection') | |
383 | selection.add_argument( |
|
383 | selection.add_argument( | |
384 | '--allow-slow-tests', |
|
384 | '--allow-slow-tests', | |
385 | action='store_true', |
|
385 | action='store_true', | |
386 | help='allow extremely slow tests', |
|
386 | help='allow extremely slow tests', | |
387 | ) |
|
387 | ) | |
388 | selection.add_argument( |
|
388 | selection.add_argument( | |
389 | "--blacklist", |
|
389 | "--blacklist", | |
390 | action="append", |
|
390 | action="append", | |
391 | help="skip tests listed in the specified blacklist file", |
|
391 | help="skip tests listed in the specified blacklist file", | |
392 | ) |
|
392 | ) | |
393 | selection.add_argument( |
|
393 | selection.add_argument( | |
394 | "--changed", |
|
394 | "--changed", | |
395 | help="run tests that are changed in parent rev or working directory", |
|
395 | help="run tests that are changed in parent rev or working directory", | |
396 | ) |
|
396 | ) | |
397 | selection.add_argument( |
|
397 | selection.add_argument( | |
398 | "-k", "--keywords", help="run tests matching keywords" |
|
398 | "-k", "--keywords", help="run tests matching keywords" | |
399 | ) |
|
399 | ) | |
400 | selection.add_argument( |
|
400 | selection.add_argument( | |
401 | "-r", "--retest", action="store_true", help="retest failed tests" |
|
401 | "-r", "--retest", action="store_true", help="retest failed tests" | |
402 | ) |
|
402 | ) | |
403 | selection.add_argument( |
|
403 | selection.add_argument( | |
404 | "--test-list", |
|
404 | "--test-list", | |
405 | action="append", |
|
405 | action="append", | |
406 | help="read tests to run from the specified file", |
|
406 | help="read tests to run from the specified file", | |
407 | ) |
|
407 | ) | |
408 | selection.add_argument( |
|
408 | selection.add_argument( | |
409 | "--whitelist", |
|
409 | "--whitelist", | |
410 | action="append", |
|
410 | action="append", | |
411 | help="always run tests listed in the specified whitelist file", |
|
411 | help="always run tests listed in the specified whitelist file", | |
412 | ) |
|
412 | ) | |
413 | selection.add_argument( |
|
413 | selection.add_argument( | |
414 | 'tests', metavar='TESTS', nargs='*', help='Tests to run' |
|
414 | 'tests', metavar='TESTS', nargs='*', help='Tests to run' | |
415 | ) |
|
415 | ) | |
416 |
|
416 | |||
417 | harness = parser.add_argument_group('Test Harness Behavior') |
|
417 | harness = parser.add_argument_group('Test Harness Behavior') | |
418 | harness.add_argument( |
|
418 | harness.add_argument( | |
419 | '--bisect-repo', |
|
419 | '--bisect-repo', | |
420 | metavar='bisect_repo', |
|
420 | metavar='bisect_repo', | |
421 | help=( |
|
421 | help=( | |
422 | "Path of a repo to bisect. Use together with " "--known-good-rev" |
|
422 | "Path of a repo to bisect. Use together with " "--known-good-rev" | |
423 | ), |
|
423 | ), | |
424 | ) |
|
424 | ) | |
425 | harness.add_argument( |
|
425 | harness.add_argument( | |
426 | "-d", |
|
426 | "-d", | |
427 | "--debug", |
|
427 | "--debug", | |
428 | action="store_true", |
|
428 | action="store_true", | |
429 | help="debug mode: write output of test scripts to console" |
|
429 | help="debug mode: write output of test scripts to console" | |
430 | " rather than capturing and diffing it (disables timeout)", |
|
430 | " rather than capturing and diffing it (disables timeout)", | |
431 | ) |
|
431 | ) | |
432 | harness.add_argument( |
|
432 | harness.add_argument( | |
433 | "-f", |
|
433 | "-f", | |
434 | "--first", |
|
434 | "--first", | |
435 | action="store_true", |
|
435 | action="store_true", | |
436 | help="exit on the first test failure", |
|
436 | help="exit on the first test failure", | |
437 | ) |
|
437 | ) | |
438 | harness.add_argument( |
|
438 | harness.add_argument( | |
439 | "-i", |
|
439 | "-i", | |
440 | "--interactive", |
|
440 | "--interactive", | |
441 | action="store_true", |
|
441 | action="store_true", | |
442 | help="prompt to accept changed output", |
|
442 | help="prompt to accept changed output", | |
443 | ) |
|
443 | ) | |
444 | harness.add_argument( |
|
444 | harness.add_argument( | |
445 | "-j", |
|
445 | "-j", | |
446 | "--jobs", |
|
446 | "--jobs", | |
447 | type=int, |
|
447 | type=int, | |
448 | help="number of jobs to run in parallel" |
|
448 | help="number of jobs to run in parallel" | |
449 | " (default: $%s or %d)" % defaults['jobs'], |
|
449 | " (default: $%s or %d)" % defaults['jobs'], | |
450 | ) |
|
450 | ) | |
451 | harness.add_argument( |
|
451 | harness.add_argument( | |
452 | "--keep-tmpdir", |
|
452 | "--keep-tmpdir", | |
453 | action="store_true", |
|
453 | action="store_true", | |
454 | help="keep temporary directory after running tests", |
|
454 | help="keep temporary directory after running tests", | |
455 | ) |
|
455 | ) | |
456 | harness.add_argument( |
|
456 | harness.add_argument( | |
457 | '--known-good-rev', |
|
457 | '--known-good-rev', | |
458 | metavar="known_good_rev", |
|
458 | metavar="known_good_rev", | |
459 | help=( |
|
459 | help=( | |
460 | "Automatically bisect any failures using this " |
|
460 | "Automatically bisect any failures using this " | |
461 | "revision as a known-good revision." |
|
461 | "revision as a known-good revision." | |
462 | ), |
|
462 | ), | |
463 | ) |
|
463 | ) | |
464 | harness.add_argument( |
|
464 | harness.add_argument( | |
465 | "--list-tests", |
|
465 | "--list-tests", | |
466 | action="store_true", |
|
466 | action="store_true", | |
467 | help="list tests instead of running them", |
|
467 | help="list tests instead of running them", | |
468 | ) |
|
468 | ) | |
469 | harness.add_argument( |
|
469 | harness.add_argument( | |
470 | "--loop", action="store_true", help="loop tests repeatedly" |
|
470 | "--loop", action="store_true", help="loop tests repeatedly" | |
471 | ) |
|
471 | ) | |
472 | harness.add_argument( |
|
472 | harness.add_argument( | |
473 | '--random', action="store_true", help='run tests in random order' |
|
473 | '--random', action="store_true", help='run tests in random order' | |
474 | ) |
|
474 | ) | |
475 | harness.add_argument( |
|
475 | harness.add_argument( | |
476 | '--order-by-runtime', |
|
476 | '--order-by-runtime', | |
477 | action="store_true", |
|
477 | action="store_true", | |
478 | help='run slowest tests first, according to .testtimes', |
|
478 | help='run slowest tests first, according to .testtimes', | |
479 | ) |
|
479 | ) | |
480 | harness.add_argument( |
|
480 | harness.add_argument( | |
481 | "-p", |
|
481 | "-p", | |
482 | "--port", |
|
482 | "--port", | |
483 | type=int, |
|
483 | type=int, | |
484 | help="port on which servers should listen" |
|
484 | help="port on which servers should listen" | |
485 | " (default: $%s or %d)" % defaults['port'], |
|
485 | " (default: $%s or %d)" % defaults['port'], | |
486 | ) |
|
486 | ) | |
487 | harness.add_argument( |
|
487 | harness.add_argument( | |
488 | '--profile-runner', |
|
488 | '--profile-runner', | |
489 | action='store_true', |
|
489 | action='store_true', | |
490 | help='run statprof on run-tests', |
|
490 | help='run statprof on run-tests', | |
491 | ) |
|
491 | ) | |
492 | harness.add_argument( |
|
492 | harness.add_argument( | |
493 | "-R", "--restart", action="store_true", help="restart at last error" |
|
493 | "-R", "--restart", action="store_true", help="restart at last error" | |
494 | ) |
|
494 | ) | |
495 | harness.add_argument( |
|
495 | harness.add_argument( | |
496 | "--runs-per-test", |
|
496 | "--runs-per-test", | |
497 | type=int, |
|
497 | type=int, | |
498 | dest="runs_per_test", |
|
498 | dest="runs_per_test", | |
499 | help="run each test N times (default=1)", |
|
499 | help="run each test N times (default=1)", | |
500 | default=1, |
|
500 | default=1, | |
501 | ) |
|
501 | ) | |
502 | harness.add_argument( |
|
502 | harness.add_argument( | |
503 | "--shell", help="shell to use (default: $%s or %s)" % defaults['shell'] |
|
503 | "--shell", help="shell to use (default: $%s or %s)" % defaults['shell'] | |
504 | ) |
|
504 | ) | |
505 | harness.add_argument( |
|
505 | harness.add_argument( | |
506 | '--showchannels', action='store_true', help='show scheduling channels' |
|
506 | '--showchannels', action='store_true', help='show scheduling channels' | |
507 | ) |
|
507 | ) | |
508 | harness.add_argument( |
|
508 | harness.add_argument( | |
509 | "--slowtimeout", |
|
509 | "--slowtimeout", | |
510 | type=int, |
|
510 | type=int, | |
511 | help="kill errant slow tests after SLOWTIMEOUT seconds" |
|
511 | help="kill errant slow tests after SLOWTIMEOUT seconds" | |
512 | " (default: $%s or %d)" % defaults['slowtimeout'], |
|
512 | " (default: $%s or %d)" % defaults['slowtimeout'], | |
513 | ) |
|
513 | ) | |
514 | harness.add_argument( |
|
514 | harness.add_argument( | |
515 | "-t", |
|
515 | "-t", | |
516 | "--timeout", |
|
516 | "--timeout", | |
517 | type=int, |
|
517 | type=int, | |
518 | help="kill errant tests after TIMEOUT seconds" |
|
518 | help="kill errant tests after TIMEOUT seconds" | |
519 | " (default: $%s or %d)" % defaults['timeout'], |
|
519 | " (default: $%s or %d)" % defaults['timeout'], | |
520 | ) |
|
520 | ) | |
521 | harness.add_argument( |
|
521 | harness.add_argument( | |
522 | "--tmpdir", |
|
522 | "--tmpdir", | |
523 | help="run tests in the given temporary directory" |
|
523 | help="run tests in the given temporary directory" | |
524 | " (implies --keep-tmpdir)", |
|
524 | " (implies --keep-tmpdir)", | |
525 | ) |
|
525 | ) | |
526 | harness.add_argument( |
|
526 | harness.add_argument( | |
527 | "-v", "--verbose", action="store_true", help="output verbose messages" |
|
527 | "-v", "--verbose", action="store_true", help="output verbose messages" | |
528 | ) |
|
528 | ) | |
529 |
|
529 | |||
530 | hgconf = parser.add_argument_group('Mercurial Configuration') |
|
530 | hgconf = parser.add_argument_group('Mercurial Configuration') | |
531 | hgconf.add_argument( |
|
531 | hgconf.add_argument( | |
532 | "--chg", |
|
532 | "--chg", | |
533 | action="store_true", |
|
533 | action="store_true", | |
534 | help="install and use chg wrapper in place of hg", |
|
534 | help="install and use chg wrapper in place of hg", | |
535 | ) |
|
535 | ) | |
536 | hgconf.add_argument( |
|
536 | hgconf.add_argument( | |
537 | "--chg-debug", action="store_true", help="show chg debug logs", |
|
537 | "--chg-debug", action="store_true", help="show chg debug logs", | |
538 | ) |
|
538 | ) | |
539 | hgconf.add_argument("--compiler", help="compiler to build with") |
|
539 | hgconf.add_argument("--compiler", help="compiler to build with") | |
540 | hgconf.add_argument( |
|
540 | hgconf.add_argument( | |
541 | '--extra-config-opt', |
|
541 | '--extra-config-opt', | |
542 | action="append", |
|
542 | action="append", | |
543 | default=[], |
|
543 | default=[], | |
544 | help='set the given config opt in the test hgrc', |
|
544 | help='set the given config opt in the test hgrc', | |
545 | ) |
|
545 | ) | |
546 | hgconf.add_argument( |
|
546 | hgconf.add_argument( | |
547 | "-l", |
|
547 | "-l", | |
548 | "--local", |
|
548 | "--local", | |
549 | action="store_true", |
|
549 | action="store_true", | |
550 | help="shortcut for --with-hg=<testdir>/../hg, " |
|
550 | help="shortcut for --with-hg=<testdir>/../hg, " | |
551 | "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set", |
|
551 | "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set", | |
552 | ) |
|
552 | ) | |
553 | hgconf.add_argument( |
|
553 | hgconf.add_argument( | |
554 | "--ipv6", |
|
554 | "--ipv6", | |
555 | action="store_true", |
|
555 | action="store_true", | |
556 | help="prefer IPv6 to IPv4 for network related tests", |
|
556 | help="prefer IPv6 to IPv4 for network related tests", | |
557 | ) |
|
557 | ) | |
558 | hgconf.add_argument( |
|
558 | hgconf.add_argument( | |
559 | "--pure", |
|
559 | "--pure", | |
560 | action="store_true", |
|
560 | action="store_true", | |
561 | help="use pure Python code instead of C extensions", |
|
561 | help="use pure Python code instead of C extensions", | |
562 | ) |
|
562 | ) | |
563 | hgconf.add_argument( |
|
563 | hgconf.add_argument( | |
564 | "--rust", |
|
564 | "--rust", | |
565 | action="store_true", |
|
565 | action="store_true", | |
566 | help="use Rust code alongside C extensions", |
|
566 | help="use Rust code alongside C extensions", | |
567 | ) |
|
567 | ) | |
568 | hgconf.add_argument( |
|
568 | hgconf.add_argument( | |
569 | "--no-rust", |
|
569 | "--no-rust", | |
570 | action="store_true", |
|
570 | action="store_true", | |
571 | help="do not use Rust code even if compiled", |
|
571 | help="do not use Rust code even if compiled", | |
572 | ) |
|
572 | ) | |
573 | hgconf.add_argument( |
|
573 | hgconf.add_argument( | |
574 | "--with-chg", |
|
574 | "--with-chg", | |
575 | metavar="CHG", |
|
575 | metavar="CHG", | |
576 | help="use specified chg wrapper in place of hg", |
|
576 | help="use specified chg wrapper in place of hg", | |
577 | ) |
|
577 | ) | |
578 | hgconf.add_argument( |
|
578 | hgconf.add_argument( | |
579 | "--with-hg", |
|
579 | "--with-hg", | |
580 | metavar="HG", |
|
580 | metavar="HG", | |
581 | help="test using specified hg script rather than a " |
|
581 | help="test using specified hg script rather than a " | |
582 | "temporary installation", |
|
582 | "temporary installation", | |
583 | ) |
|
583 | ) | |
584 |
|
584 | |||
585 | reporting = parser.add_argument_group('Results Reporting') |
|
585 | reporting = parser.add_argument_group('Results Reporting') | |
586 | reporting.add_argument( |
|
586 | reporting.add_argument( | |
587 | "-C", |
|
587 | "-C", | |
588 | "--annotate", |
|
588 | "--annotate", | |
589 | action="store_true", |
|
589 | action="store_true", | |
590 | help="output files annotated with coverage", |
|
590 | help="output files annotated with coverage", | |
591 | ) |
|
591 | ) | |
592 | reporting.add_argument( |
|
592 | reporting.add_argument( | |
593 | "--color", |
|
593 | "--color", | |
594 | choices=["always", "auto", "never"], |
|
594 | choices=["always", "auto", "never"], | |
595 | default=os.environ.get('HGRUNTESTSCOLOR', 'auto'), |
|
595 | default=os.environ.get('HGRUNTESTSCOLOR', 'auto'), | |
596 | help="colorisation: always|auto|never (default: auto)", |
|
596 | help="colorisation: always|auto|never (default: auto)", | |
597 | ) |
|
597 | ) | |
598 | reporting.add_argument( |
|
598 | reporting.add_argument( | |
599 | "-c", |
|
599 | "-c", | |
600 | "--cover", |
|
600 | "--cover", | |
601 | action="store_true", |
|
601 | action="store_true", | |
602 | help="print a test coverage report", |
|
602 | help="print a test coverage report", | |
603 | ) |
|
603 | ) | |
604 | reporting.add_argument( |
|
604 | reporting.add_argument( | |
605 | '--exceptions', |
|
605 | '--exceptions', | |
606 | action='store_true', |
|
606 | action='store_true', | |
607 | help='log all exceptions and generate an exception report', |
|
607 | help='log all exceptions and generate an exception report', | |
608 | ) |
|
608 | ) | |
609 | reporting.add_argument( |
|
609 | reporting.add_argument( | |
610 | "-H", |
|
610 | "-H", | |
611 | "--htmlcov", |
|
611 | "--htmlcov", | |
612 | action="store_true", |
|
612 | action="store_true", | |
613 | help="create an HTML report of the coverage of the files", |
|
613 | help="create an HTML report of the coverage of the files", | |
614 | ) |
|
614 | ) | |
615 | reporting.add_argument( |
|
615 | reporting.add_argument( | |
616 | "--json", |
|
616 | "--json", | |
617 | action="store_true", |
|
617 | action="store_true", | |
618 | help="store test result data in 'report.json' file", |
|
618 | help="store test result data in 'report.json' file", | |
619 | ) |
|
619 | ) | |
620 | reporting.add_argument( |
|
620 | reporting.add_argument( | |
621 | "--outputdir", |
|
621 | "--outputdir", | |
622 | help="directory to write error logs to (default=test directory)", |
|
622 | help="directory to write error logs to (default=test directory)", | |
623 | ) |
|
623 | ) | |
624 | reporting.add_argument( |
|
624 | reporting.add_argument( | |
625 | "-n", "--nodiff", action="store_true", help="skip showing test changes" |
|
625 | "-n", "--nodiff", action="store_true", help="skip showing test changes" | |
626 | ) |
|
626 | ) | |
627 | reporting.add_argument( |
|
627 | reporting.add_argument( | |
628 | "-S", |
|
628 | "-S", | |
629 | "--noskips", |
|
629 | "--noskips", | |
630 | action="store_true", |
|
630 | action="store_true", | |
631 | help="don't report skip tests verbosely", |
|
631 | help="don't report skip tests verbosely", | |
632 | ) |
|
632 | ) | |
633 | reporting.add_argument( |
|
633 | reporting.add_argument( | |
634 | "--time", action="store_true", help="time how long each test takes" |
|
634 | "--time", action="store_true", help="time how long each test takes" | |
635 | ) |
|
635 | ) | |
636 | reporting.add_argument("--view", help="external diff viewer") |
|
636 | reporting.add_argument("--view", help="external diff viewer") | |
637 | reporting.add_argument( |
|
637 | reporting.add_argument( | |
638 | "--xunit", help="record xunit results at specified path" |
|
638 | "--xunit", help="record xunit results at specified path" | |
639 | ) |
|
639 | ) | |
640 |
|
640 | |||
641 | for option, (envvar, default) in defaults.items(): |
|
641 | for option, (envvar, default) in defaults.items(): | |
642 | defaults[option] = type(default)(os.environ.get(envvar, default)) |
|
642 | defaults[option] = type(default)(os.environ.get(envvar, default)) | |
643 | parser.set_defaults(**defaults) |
|
643 | parser.set_defaults(**defaults) | |
644 |
|
644 | |||
645 | return parser |
|
645 | return parser | |
646 |
|
646 | |||
647 |
|
647 | |||
648 | def parseargs(args, parser): |
|
648 | def parseargs(args, parser): | |
649 | """Parse arguments with our OptionParser and validate results.""" |
|
649 | """Parse arguments with our OptionParser and validate results.""" | |
650 | options = parser.parse_args(args) |
|
650 | options = parser.parse_args(args) | |
651 |
|
651 | |||
652 | # jython is always pure |
|
652 | # jython is always pure | |
653 | if 'java' in sys.platform or '__pypy__' in sys.modules: |
|
653 | if 'java' in sys.platform or '__pypy__' in sys.modules: | |
654 | options.pure = True |
|
654 | options.pure = True | |
655 |
|
655 | |||
656 | if platform.python_implementation() != 'CPython' and options.rust: |
|
656 | if platform.python_implementation() != 'CPython' and options.rust: | |
657 | parser.error('Rust extensions are only available with CPython') |
|
657 | parser.error('Rust extensions are only available with CPython') | |
658 |
|
658 | |||
659 | if options.pure and options.rust: |
|
659 | if options.pure and options.rust: | |
660 | parser.error('--rust cannot be used with --pure') |
|
660 | parser.error('--rust cannot be used with --pure') | |
661 |
|
661 | |||
662 | if options.rust and options.no_rust: |
|
662 | if options.rust and options.no_rust: | |
663 | parser.error('--rust cannot be used with --no-rust') |
|
663 | parser.error('--rust cannot be used with --no-rust') | |
664 |
|
664 | |||
665 | if options.local: |
|
665 | if options.local: | |
666 | if options.with_hg or options.with_chg: |
|
666 | if options.with_hg or options.with_chg: | |
667 | parser.error('--local cannot be used with --with-hg or --with-chg') |
|
667 | parser.error('--local cannot be used with --with-hg or --with-chg') | |
668 | testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0]))) |
|
668 | testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0]))) | |
669 | reporootdir = os.path.dirname(testdir) |
|
669 | reporootdir = os.path.dirname(testdir) | |
670 | pathandattrs = [(b'hg', 'with_hg')] |
|
670 | pathandattrs = [(b'hg', 'with_hg')] | |
671 | if options.chg: |
|
671 | if options.chg: | |
672 | pathandattrs.append((b'contrib/chg/chg', 'with_chg')) |
|
672 | pathandattrs.append((b'contrib/chg/chg', 'with_chg')) | |
673 | for relpath, attr in pathandattrs: |
|
673 | for relpath, attr in pathandattrs: | |
674 | binpath = os.path.join(reporootdir, relpath) |
|
674 | binpath = os.path.join(reporootdir, relpath) | |
675 | if os.name != 'nt' and not os.access(binpath, os.X_OK): |
|
675 | if os.name != 'nt' and not os.access(binpath, os.X_OK): | |
676 | parser.error( |
|
676 | parser.error( | |
677 | '--local specified, but %r not found or ' |
|
677 | '--local specified, but %r not found or ' | |
678 | 'not executable' % binpath |
|
678 | 'not executable' % binpath | |
679 | ) |
|
679 | ) | |
680 | setattr(options, attr, _bytes2sys(binpath)) |
|
680 | setattr(options, attr, _bytes2sys(binpath)) | |
681 |
|
681 | |||
682 | if options.with_hg: |
|
682 | if options.with_hg: | |
683 | options.with_hg = canonpath(_sys2bytes(options.with_hg)) |
|
683 | options.with_hg = canonpath(_sys2bytes(options.with_hg)) | |
684 | if not ( |
|
684 | if not ( | |
685 | os.path.isfile(options.with_hg) |
|
685 | os.path.isfile(options.with_hg) | |
686 | and os.access(options.with_hg, os.X_OK) |
|
686 | and os.access(options.with_hg, os.X_OK) | |
687 | ): |
|
687 | ): | |
688 | parser.error('--with-hg must specify an executable hg script') |
|
688 | parser.error('--with-hg must specify an executable hg script') | |
689 | if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']: |
|
689 | if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']: | |
690 | sys.stderr.write('warning: --with-hg should specify an hg script\n') |
|
690 | sys.stderr.write('warning: --with-hg should specify an hg script\n') | |
691 | sys.stderr.flush() |
|
691 | sys.stderr.flush() | |
692 |
|
692 | |||
693 | if (options.chg or options.with_chg) and os.name == 'nt': |
|
693 | if (options.chg or options.with_chg) and os.name == 'nt': | |
694 | parser.error('chg does not work on %s' % os.name) |
|
694 | parser.error('chg does not work on %s' % os.name) | |
695 | if options.with_chg: |
|
695 | if options.with_chg: | |
696 | options.chg = False # no installation to temporary location |
|
696 | options.chg = False # no installation to temporary location | |
697 | options.with_chg = canonpath(_sys2bytes(options.with_chg)) |
|
697 | options.with_chg = canonpath(_sys2bytes(options.with_chg)) | |
698 | if not ( |
|
698 | if not ( | |
699 | os.path.isfile(options.with_chg) |
|
699 | os.path.isfile(options.with_chg) | |
700 | and os.access(options.with_chg, os.X_OK) |
|
700 | and os.access(options.with_chg, os.X_OK) | |
701 | ): |
|
701 | ): | |
702 | parser.error('--with-chg must specify a chg executable') |
|
702 | parser.error('--with-chg must specify a chg executable') | |
703 | if options.chg and options.with_hg: |
|
703 | if options.chg and options.with_hg: | |
704 | # chg shares installation location with hg |
|
704 | # chg shares installation location with hg | |
705 | parser.error( |
|
705 | parser.error( | |
706 | '--chg does not work when --with-hg is specified ' |
|
706 | '--chg does not work when --with-hg is specified ' | |
707 | '(use --with-chg instead)' |
|
707 | '(use --with-chg instead)' | |
708 | ) |
|
708 | ) | |
709 |
|
709 | |||
710 | if options.color == 'always' and not pygmentspresent: |
|
710 | if options.color == 'always' and not pygmentspresent: | |
711 | sys.stderr.write( |
|
711 | sys.stderr.write( | |
712 | 'warning: --color=always ignored because ' |
|
712 | 'warning: --color=always ignored because ' | |
713 | 'pygments is not installed\n' |
|
713 | 'pygments is not installed\n' | |
714 | ) |
|
714 | ) | |
715 |
|
715 | |||
716 | if options.bisect_repo and not options.known_good_rev: |
|
716 | if options.bisect_repo and not options.known_good_rev: | |
717 | parser.error("--bisect-repo cannot be used without --known-good-rev") |
|
717 | parser.error("--bisect-repo cannot be used without --known-good-rev") | |
718 |
|
718 | |||
719 | global useipv6 |
|
719 | global useipv6 | |
720 | if options.ipv6: |
|
720 | if options.ipv6: | |
721 | useipv6 = checksocketfamily('AF_INET6') |
|
721 | useipv6 = checksocketfamily('AF_INET6') | |
722 | else: |
|
722 | else: | |
723 | # only use IPv6 if IPv4 is unavailable and IPv6 is available |
|
723 | # only use IPv6 if IPv4 is unavailable and IPv6 is available | |
724 | useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily( |
|
724 | useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily( | |
725 | 'AF_INET6' |
|
725 | 'AF_INET6' | |
726 | ) |
|
726 | ) | |
727 |
|
727 | |||
728 | options.anycoverage = options.cover or options.annotate or options.htmlcov |
|
728 | options.anycoverage = options.cover or options.annotate or options.htmlcov | |
729 | if options.anycoverage: |
|
729 | if options.anycoverage: | |
730 | try: |
|
730 | try: | |
731 | import coverage |
|
731 | import coverage | |
732 |
|
732 | |||
733 | covver = version.StrictVersion(coverage.__version__).version |
|
733 | covver = version.StrictVersion(coverage.__version__).version | |
734 | if covver < (3, 3): |
|
734 | if covver < (3, 3): | |
735 | parser.error('coverage options require coverage 3.3 or later') |
|
735 | parser.error('coverage options require coverage 3.3 or later') | |
736 | except ImportError: |
|
736 | except ImportError: | |
737 | parser.error('coverage options now require the coverage package') |
|
737 | parser.error('coverage options now require the coverage package') | |
738 |
|
738 | |||
739 | if options.anycoverage and options.local: |
|
739 | if options.anycoverage and options.local: | |
740 | # this needs some path mangling somewhere, I guess |
|
740 | # this needs some path mangling somewhere, I guess | |
741 | parser.error( |
|
741 | parser.error( | |
742 | "sorry, coverage options do not work when --local " "is specified" |
|
742 | "sorry, coverage options do not work when --local " "is specified" | |
743 | ) |
|
743 | ) | |
744 |
|
744 | |||
745 | if options.anycoverage and options.with_hg: |
|
745 | if options.anycoverage and options.with_hg: | |
746 | parser.error( |
|
746 | parser.error( | |
747 | "sorry, coverage options do not work when --with-hg " "is specified" |
|
747 | "sorry, coverage options do not work when --with-hg " "is specified" | |
748 | ) |
|
748 | ) | |
749 |
|
749 | |||
750 | global verbose |
|
750 | global verbose | |
751 | if options.verbose: |
|
751 | if options.verbose: | |
752 | verbose = '' |
|
752 | verbose = '' | |
753 |
|
753 | |||
754 | if options.tmpdir: |
|
754 | if options.tmpdir: | |
755 | options.tmpdir = canonpath(options.tmpdir) |
|
755 | options.tmpdir = canonpath(options.tmpdir) | |
756 |
|
756 | |||
757 | if options.jobs < 1: |
|
757 | if options.jobs < 1: | |
758 | parser.error('--jobs must be positive') |
|
758 | parser.error('--jobs must be positive') | |
759 | if options.interactive and options.debug: |
|
759 | if options.interactive and options.debug: | |
760 | parser.error("-i/--interactive and -d/--debug are incompatible") |
|
760 | parser.error("-i/--interactive and -d/--debug are incompatible") | |
761 | if options.debug: |
|
761 | if options.debug: | |
762 | if options.timeout != defaults['timeout']: |
|
762 | if options.timeout != defaults['timeout']: | |
763 | sys.stderr.write('warning: --timeout option ignored with --debug\n') |
|
763 | sys.stderr.write('warning: --timeout option ignored with --debug\n') | |
764 | if options.slowtimeout != defaults['slowtimeout']: |
|
764 | if options.slowtimeout != defaults['slowtimeout']: | |
765 | sys.stderr.write( |
|
765 | sys.stderr.write( | |
766 | 'warning: --slowtimeout option ignored with --debug\n' |
|
766 | 'warning: --slowtimeout option ignored with --debug\n' | |
767 | ) |
|
767 | ) | |
768 | options.timeout = 0 |
|
768 | options.timeout = 0 | |
769 | options.slowtimeout = 0 |
|
769 | options.slowtimeout = 0 | |
770 |
|
770 | |||
771 | if options.blacklist: |
|
771 | if options.blacklist: | |
772 | options.blacklist = parselistfiles(options.blacklist, 'blacklist') |
|
772 | options.blacklist = parselistfiles(options.blacklist, 'blacklist') | |
773 | if options.whitelist: |
|
773 | if options.whitelist: | |
774 | options.whitelisted = parselistfiles(options.whitelist, 'whitelist') |
|
774 | options.whitelisted = parselistfiles(options.whitelist, 'whitelist') | |
775 | else: |
|
775 | else: | |
776 | options.whitelisted = {} |
|
776 | options.whitelisted = {} | |
777 |
|
777 | |||
778 | if options.showchannels: |
|
778 | if options.showchannels: | |
779 | options.nodiff = True |
|
779 | options.nodiff = True | |
780 |
|
780 | |||
781 | return options |
|
781 | return options | |
782 |
|
782 | |||
783 |
|
783 | |||
784 | def rename(src, dst): |
|
784 | def rename(src, dst): | |
785 | """Like os.rename(), trade atomicity and opened files friendliness |
|
785 | """Like os.rename(), trade atomicity and opened files friendliness | |
786 | for existing destination support. |
|
786 | for existing destination support. | |
787 | """ |
|
787 | """ | |
788 | shutil.copy(src, dst) |
|
788 | shutil.copy(src, dst) | |
789 | os.remove(src) |
|
789 | os.remove(src) | |
790 |
|
790 | |||
791 |
|
791 | |||
792 | def makecleanable(path): |
|
792 | def makecleanable(path): | |
793 | """Try to fix directory permission recursively so that the entire tree |
|
793 | """Try to fix directory permission recursively so that the entire tree | |
794 | can be deleted""" |
|
794 | can be deleted""" | |
795 | for dirpath, dirnames, _filenames in os.walk(path, topdown=True): |
|
795 | for dirpath, dirnames, _filenames in os.walk(path, topdown=True): | |
796 | for d in dirnames: |
|
796 | for d in dirnames: | |
797 | p = os.path.join(dirpath, d) |
|
797 | p = os.path.join(dirpath, d) | |
798 | try: |
|
798 | try: | |
799 | os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx |
|
799 | os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx | |
800 | except OSError: |
|
800 | except OSError: | |
801 | pass |
|
801 | pass | |
802 |
|
802 | |||
803 |
|
803 | |||
804 | _unified_diff = difflib.unified_diff |
|
804 | _unified_diff = difflib.unified_diff | |
805 | if PYTHON3: |
|
805 | if PYTHON3: | |
806 | import functools |
|
806 | import functools | |
807 |
|
807 | |||
808 | _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff) |
|
808 | _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff) | |
809 |
|
809 | |||
810 |
|
810 | |||
811 | def getdiff(expected, output, ref, err): |
|
811 | def getdiff(expected, output, ref, err): | |
812 | servefail = False |
|
812 | servefail = False | |
813 | lines = [] |
|
813 | lines = [] | |
814 | for line in _unified_diff(expected, output, ref, err): |
|
814 | for line in _unified_diff(expected, output, ref, err): | |
815 | if line.startswith(b'+++') or line.startswith(b'---'): |
|
815 | if line.startswith(b'+++') or line.startswith(b'---'): | |
816 | line = line.replace(b'\\', b'/') |
|
816 | line = line.replace(b'\\', b'/') | |
817 | if line.endswith(b' \n'): |
|
817 | if line.endswith(b' \n'): | |
818 | line = line[:-2] + b'\n' |
|
818 | line = line[:-2] + b'\n' | |
819 | lines.append(line) |
|
819 | lines.append(line) | |
820 | if not servefail and line.startswith( |
|
820 | if not servefail and line.startswith( | |
821 | b'+ abort: child process failed to start' |
|
821 | b'+ abort: child process failed to start' | |
822 | ): |
|
822 | ): | |
823 | servefail = True |
|
823 | servefail = True | |
824 |
|
824 | |||
825 | return servefail, lines |
|
825 | return servefail, lines | |
826 |
|
826 | |||
827 |
|
827 | |||
828 | verbose = False |
|
828 | verbose = False | |
829 |
|
829 | |||
830 |
|
830 | |||
831 | def vlog(*msg): |
|
831 | def vlog(*msg): | |
832 | """Log only when in verbose mode.""" |
|
832 | """Log only when in verbose mode.""" | |
833 | if verbose is False: |
|
833 | if verbose is False: | |
834 | return |
|
834 | return | |
835 |
|
835 | |||
836 | return log(*msg) |
|
836 | return log(*msg) | |
837 |
|
837 | |||
838 |
|
838 | |||
839 | # Bytes that break XML even in a CDATA block: control characters 0-31 |
|
839 | # Bytes that break XML even in a CDATA block: control characters 0-31 | |
840 | # sans \t, \n and \r |
|
840 | # sans \t, \n and \r | |
841 | CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]") |
|
841 | CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]") | |
842 |
|
842 | |||
843 | # Match feature conditionalized output lines in the form, capturing the feature |
|
843 | # Match feature conditionalized output lines in the form, capturing the feature | |
844 | # list in group 2, and the preceeding line output in group 1: |
|
844 | # list in group 2, and the preceeding line output in group 1: | |
845 | # |
|
845 | # | |
846 | # output..output (feature !)\n |
|
846 | # output..output (feature !)\n | |
847 | optline = re.compile(br'(.*) \((.+?) !\)\n$') |
|
847 | optline = re.compile(br'(.*) \((.+?) !\)\n$') | |
848 |
|
848 | |||
849 |
|
849 | |||
850 | def cdatasafe(data): |
|
850 | def cdatasafe(data): | |
851 | """Make a string safe to include in a CDATA block. |
|
851 | """Make a string safe to include in a CDATA block. | |
852 |
|
852 | |||
853 | Certain control characters are illegal in a CDATA block, and |
|
853 | Certain control characters are illegal in a CDATA block, and | |
854 | there's no way to include a ]]> in a CDATA either. This function |
|
854 | there's no way to include a ]]> in a CDATA either. This function | |
855 | replaces illegal bytes with ? and adds a space between the ]] so |
|
855 | replaces illegal bytes with ? and adds a space between the ]] so | |
856 | that it won't break the CDATA block. |
|
856 | that it won't break the CDATA block. | |
857 | """ |
|
857 | """ | |
858 | return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>') |
|
858 | return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>') | |
859 |
|
859 | |||
860 |
|
860 | |||
861 | def log(*msg): |
|
861 | def log(*msg): | |
862 | """Log something to stdout. |
|
862 | """Log something to stdout. | |
863 |
|
863 | |||
864 | Arguments are strings to print. |
|
864 | Arguments are strings to print. | |
865 | """ |
|
865 | """ | |
866 | with iolock: |
|
866 | with iolock: | |
867 | if verbose: |
|
867 | if verbose: | |
868 | print(verbose, end=' ') |
|
868 | print(verbose, end=' ') | |
869 | for m in msg: |
|
869 | for m in msg: | |
870 | print(m, end=' ') |
|
870 | print(m, end=' ') | |
871 | print() |
|
871 | print() | |
872 | sys.stdout.flush() |
|
872 | sys.stdout.flush() | |
873 |
|
873 | |||
874 |
|
874 | |||
875 | def highlightdiff(line, color): |
|
875 | def highlightdiff(line, color): | |
876 | if not color: |
|
876 | if not color: | |
877 | return line |
|
877 | return line | |
878 | assert pygmentspresent |
|
878 | assert pygmentspresent | |
879 | return pygments.highlight( |
|
879 | return pygments.highlight( | |
880 | line.decode('latin1'), difflexer, terminal256formatter |
|
880 | line.decode('latin1'), difflexer, terminal256formatter | |
881 | ).encode('latin1') |
|
881 | ).encode('latin1') | |
882 |
|
882 | |||
883 |
|
883 | |||
884 | def highlightmsg(msg, color): |
|
884 | def highlightmsg(msg, color): | |
885 | if not color: |
|
885 | if not color: | |
886 | return msg |
|
886 | return msg | |
887 | assert pygmentspresent |
|
887 | assert pygmentspresent | |
888 | return pygments.highlight(msg, runnerlexer, runnerformatter) |
|
888 | return pygments.highlight(msg, runnerlexer, runnerformatter) | |
889 |
|
889 | |||
890 |
|
890 | |||
891 | def terminate(proc): |
|
891 | def terminate(proc): | |
892 | """Terminate subprocess""" |
|
892 | """Terminate subprocess""" | |
893 | vlog('# Terminating process %d' % proc.pid) |
|
893 | vlog('# Terminating process %d' % proc.pid) | |
894 | try: |
|
894 | try: | |
895 | proc.terminate() |
|
895 | proc.terminate() | |
896 | except OSError: |
|
896 | except OSError: | |
897 | pass |
|
897 | pass | |
898 |
|
898 | |||
899 |
|
899 | |||
900 | def killdaemons(pidfile): |
|
900 | def killdaemons(pidfile): | |
901 | import killdaemons as killmod |
|
901 | import killdaemons as killmod | |
902 |
|
902 | |||
903 | return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog) |
|
903 | return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog) | |
904 |
|
904 | |||
905 |
|
905 | |||
906 | class Test(unittest.TestCase): |
|
906 | class Test(unittest.TestCase): | |
907 | """Encapsulates a single, runnable test. |
|
907 | """Encapsulates a single, runnable test. | |
908 |
|
908 | |||
909 | While this class conforms to the unittest.TestCase API, it differs in that |
|
909 | While this class conforms to the unittest.TestCase API, it differs in that | |
910 | instances need to be instantiated manually. (Typically, unittest.TestCase |
|
910 | instances need to be instantiated manually. (Typically, unittest.TestCase | |
911 | classes are instantiated automatically by scanning modules.) |
|
911 | classes are instantiated automatically by scanning modules.) | |
912 | """ |
|
912 | """ | |
913 |
|
913 | |||
914 | # Status code reserved for skipped tests (used by hghave). |
|
914 | # Status code reserved for skipped tests (used by hghave). | |
915 | SKIPPED_STATUS = 80 |
|
915 | SKIPPED_STATUS = 80 | |
916 |
|
916 | |||
917 | def __init__( |
|
917 | def __init__( | |
918 | self, |
|
918 | self, | |
919 | path, |
|
919 | path, | |
920 | outputdir, |
|
920 | outputdir, | |
921 | tmpdir, |
|
921 | tmpdir, | |
922 | keeptmpdir=False, |
|
922 | keeptmpdir=False, | |
923 | debug=False, |
|
923 | debug=False, | |
924 | first=False, |
|
924 | first=False, | |
925 | timeout=None, |
|
925 | timeout=None, | |
926 | startport=None, |
|
926 | startport=None, | |
927 | extraconfigopts=None, |
|
927 | extraconfigopts=None, | |
928 | shell=None, |
|
928 | shell=None, | |
929 | hgcommand=None, |
|
929 | hgcommand=None, | |
930 | slowtimeout=None, |
|
930 | slowtimeout=None, | |
931 | usechg=False, |
|
931 | usechg=False, | |
932 | chgdebug=False, |
|
932 | chgdebug=False, | |
933 | useipv6=False, |
|
933 | useipv6=False, | |
934 | ): |
|
934 | ): | |
935 | """Create a test from parameters. |
|
935 | """Create a test from parameters. | |
936 |
|
936 | |||
937 | path is the full path to the file defining the test. |
|
937 | path is the full path to the file defining the test. | |
938 |
|
938 | |||
939 | tmpdir is the main temporary directory to use for this test. |
|
939 | tmpdir is the main temporary directory to use for this test. | |
940 |
|
940 | |||
941 | keeptmpdir determines whether to keep the test's temporary directory |
|
941 | keeptmpdir determines whether to keep the test's temporary directory | |
942 | after execution. It defaults to removal (False). |
|
942 | after execution. It defaults to removal (False). | |
943 |
|
943 | |||
944 | debug mode will make the test execute verbosely, with unfiltered |
|
944 | debug mode will make the test execute verbosely, with unfiltered | |
945 | output. |
|
945 | output. | |
946 |
|
946 | |||
947 | timeout controls the maximum run time of the test. It is ignored when |
|
947 | timeout controls the maximum run time of the test. It is ignored when | |
948 | debug is True. See slowtimeout for tests with #require slow. |
|
948 | debug is True. See slowtimeout for tests with #require slow. | |
949 |
|
949 | |||
950 | slowtimeout overrides timeout if the test has #require slow. |
|
950 | slowtimeout overrides timeout if the test has #require slow. | |
951 |
|
951 | |||
952 | startport controls the starting port number to use for this test. Each |
|
952 | startport controls the starting port number to use for this test. Each | |
953 | test will reserve 3 port numbers for execution. It is the caller's |
|
953 | test will reserve 3 port numbers for execution. It is the caller's | |
954 | responsibility to allocate a non-overlapping port range to Test |
|
954 | responsibility to allocate a non-overlapping port range to Test | |
955 | instances. |
|
955 | instances. | |
956 |
|
956 | |||
957 | extraconfigopts is an iterable of extra hgrc config options. Values |
|
957 | extraconfigopts is an iterable of extra hgrc config options. Values | |
958 | must have the form "key=value" (something understood by hgrc). Values |
|
958 | must have the form "key=value" (something understood by hgrc). Values | |
959 | of the form "foo.key=value" will result in "[foo] key=value". |
|
959 | of the form "foo.key=value" will result in "[foo] key=value". | |
960 |
|
960 | |||
961 | shell is the shell to execute tests in. |
|
961 | shell is the shell to execute tests in. | |
962 | """ |
|
962 | """ | |
963 | if timeout is None: |
|
963 | if timeout is None: | |
964 | timeout = defaults['timeout'] |
|
964 | timeout = defaults['timeout'] | |
965 | if startport is None: |
|
965 | if startport is None: | |
966 | startport = defaults['port'] |
|
966 | startport = defaults['port'] | |
967 | if slowtimeout is None: |
|
967 | if slowtimeout is None: | |
968 | slowtimeout = defaults['slowtimeout'] |
|
968 | slowtimeout = defaults['slowtimeout'] | |
969 | self.path = path |
|
969 | self.path = path | |
970 | self.relpath = os.path.relpath(path) |
|
970 | self.relpath = os.path.relpath(path) | |
971 | self.bname = os.path.basename(path) |
|
971 | self.bname = os.path.basename(path) | |
972 | self.name = _bytes2sys(self.bname) |
|
972 | self.name = _bytes2sys(self.bname) | |
973 | self._testdir = os.path.dirname(path) |
|
973 | self._testdir = os.path.dirname(path) | |
974 | self._outputdir = outputdir |
|
974 | self._outputdir = outputdir | |
975 | self._tmpname = os.path.basename(path) |
|
975 | self._tmpname = os.path.basename(path) | |
976 | self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname) |
|
976 | self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname) | |
977 |
|
977 | |||
978 | self._threadtmp = tmpdir |
|
978 | self._threadtmp = tmpdir | |
979 | self._keeptmpdir = keeptmpdir |
|
979 | self._keeptmpdir = keeptmpdir | |
980 | self._debug = debug |
|
980 | self._debug = debug | |
981 | self._first = first |
|
981 | self._first = first | |
982 | self._timeout = timeout |
|
982 | self._timeout = timeout | |
983 | self._slowtimeout = slowtimeout |
|
983 | self._slowtimeout = slowtimeout | |
984 | self._startport = startport |
|
984 | self._startport = startport | |
985 | self._extraconfigopts = extraconfigopts or [] |
|
985 | self._extraconfigopts = extraconfigopts or [] | |
986 | self._shell = _sys2bytes(shell) |
|
986 | self._shell = _sys2bytes(shell) | |
987 | self._hgcommand = hgcommand or b'hg' |
|
987 | self._hgcommand = hgcommand or b'hg' | |
988 | self._usechg = usechg |
|
988 | self._usechg = usechg | |
989 | self._chgdebug = chgdebug |
|
989 | self._chgdebug = chgdebug | |
990 | self._useipv6 = useipv6 |
|
990 | self._useipv6 = useipv6 | |
991 |
|
991 | |||
992 | self._aborted = False |
|
992 | self._aborted = False | |
993 | self._daemonpids = [] |
|
993 | self._daemonpids = [] | |
994 | self._finished = None |
|
994 | self._finished = None | |
995 | self._ret = None |
|
995 | self._ret = None | |
996 | self._out = None |
|
996 | self._out = None | |
997 | self._skipped = None |
|
997 | self._skipped = None | |
998 | self._testtmp = None |
|
998 | self._testtmp = None | |
999 | self._chgsockdir = None |
|
999 | self._chgsockdir = None | |
1000 |
|
1000 | |||
1001 | self._refout = self.readrefout() |
|
1001 | self._refout = self.readrefout() | |
1002 |
|
1002 | |||
1003 | def readrefout(self): |
|
1003 | def readrefout(self): | |
1004 | """read reference output""" |
|
1004 | """read reference output""" | |
1005 | # If we're not in --debug mode and reference output file exists, |
|
1005 | # If we're not in --debug mode and reference output file exists, | |
1006 | # check test output against it. |
|
1006 | # check test output against it. | |
1007 | if self._debug: |
|
1007 | if self._debug: | |
1008 | return None # to match "out is None" |
|
1008 | return None # to match "out is None" | |
1009 | elif os.path.exists(self.refpath): |
|
1009 | elif os.path.exists(self.refpath): | |
1010 | with open(self.refpath, 'rb') as f: |
|
1010 | with open(self.refpath, 'rb') as f: | |
1011 | return f.read().splitlines(True) |
|
1011 | return f.read().splitlines(True) | |
1012 | else: |
|
1012 | else: | |
1013 | return [] |
|
1013 | return [] | |
1014 |
|
1014 | |||
1015 | # needed to get base class __repr__ running |
|
1015 | # needed to get base class __repr__ running | |
1016 | @property |
|
1016 | @property | |
1017 | def _testMethodName(self): |
|
1017 | def _testMethodName(self): | |
1018 | return self.name |
|
1018 | return self.name | |
1019 |
|
1019 | |||
1020 | def __str__(self): |
|
1020 | def __str__(self): | |
1021 | return self.name |
|
1021 | return self.name | |
1022 |
|
1022 | |||
1023 | def shortDescription(self): |
|
1023 | def shortDescription(self): | |
1024 | return self.name |
|
1024 | return self.name | |
1025 |
|
1025 | |||
1026 | def setUp(self): |
|
1026 | def setUp(self): | |
1027 | """Tasks to perform before run().""" |
|
1027 | """Tasks to perform before run().""" | |
1028 | self._finished = False |
|
1028 | self._finished = False | |
1029 | self._ret = None |
|
1029 | self._ret = None | |
1030 | self._out = None |
|
1030 | self._out = None | |
1031 | self._skipped = None |
|
1031 | self._skipped = None | |
1032 |
|
1032 | |||
1033 | try: |
|
1033 | try: | |
1034 | os.mkdir(self._threadtmp) |
|
1034 | os.mkdir(self._threadtmp) | |
1035 | except OSError as e: |
|
1035 | except OSError as e: | |
1036 | if e.errno != errno.EEXIST: |
|
1036 | if e.errno != errno.EEXIST: | |
1037 | raise |
|
1037 | raise | |
1038 |
|
1038 | |||
1039 | name = self._tmpname |
|
1039 | name = self._tmpname | |
1040 | self._testtmp = os.path.join(self._threadtmp, name) |
|
1040 | self._testtmp = os.path.join(self._threadtmp, name) | |
1041 | os.mkdir(self._testtmp) |
|
1041 | os.mkdir(self._testtmp) | |
1042 |
|
1042 | |||
1043 | # Remove any previous output files. |
|
1043 | # Remove any previous output files. | |
1044 | if os.path.exists(self.errpath): |
|
1044 | if os.path.exists(self.errpath): | |
1045 | try: |
|
1045 | try: | |
1046 | os.remove(self.errpath) |
|
1046 | os.remove(self.errpath) | |
1047 | except OSError as e: |
|
1047 | except OSError as e: | |
1048 | # We might have raced another test to clean up a .err |
|
1048 | # We might have raced another test to clean up a .err | |
1049 | # file, so ignore ENOENT when removing a previous .err |
|
1049 | # file, so ignore ENOENT when removing a previous .err | |
1050 | # file. |
|
1050 | # file. | |
1051 | if e.errno != errno.ENOENT: |
|
1051 | if e.errno != errno.ENOENT: | |
1052 | raise |
|
1052 | raise | |
1053 |
|
1053 | |||
1054 | if self._usechg: |
|
1054 | if self._usechg: | |
1055 | self._chgsockdir = os.path.join( |
|
1055 | self._chgsockdir = os.path.join( | |
1056 | self._threadtmp, b'%s.chgsock' % name |
|
1056 | self._threadtmp, b'%s.chgsock' % name | |
1057 | ) |
|
1057 | ) | |
1058 | os.mkdir(self._chgsockdir) |
|
1058 | os.mkdir(self._chgsockdir) | |
1059 |
|
1059 | |||
1060 | def run(self, result): |
|
1060 | def run(self, result): | |
1061 | """Run this test and report results against a TestResult instance.""" |
|
1061 | """Run this test and report results against a TestResult instance.""" | |
1062 | # This function is extremely similar to unittest.TestCase.run(). Once |
|
1062 | # This function is extremely similar to unittest.TestCase.run(). Once | |
1063 | # we require Python 2.7 (or at least its version of unittest), this |
|
1063 | # we require Python 2.7 (or at least its version of unittest), this | |
1064 | # function can largely go away. |
|
1064 | # function can largely go away. | |
1065 | self._result = result |
|
1065 | self._result = result | |
1066 | result.startTest(self) |
|
1066 | result.startTest(self) | |
1067 | try: |
|
1067 | try: | |
1068 | try: |
|
1068 | try: | |
1069 | self.setUp() |
|
1069 | self.setUp() | |
1070 | except (KeyboardInterrupt, SystemExit): |
|
1070 | except (KeyboardInterrupt, SystemExit): | |
1071 | self._aborted = True |
|
1071 | self._aborted = True | |
1072 | raise |
|
1072 | raise | |
1073 | except Exception: |
|
1073 | except Exception: | |
1074 | result.addError(self, sys.exc_info()) |
|
1074 | result.addError(self, sys.exc_info()) | |
1075 | return |
|
1075 | return | |
1076 |
|
1076 | |||
1077 | success = False |
|
1077 | success = False | |
1078 | try: |
|
1078 | try: | |
1079 | self.runTest() |
|
1079 | self.runTest() | |
1080 | except KeyboardInterrupt: |
|
1080 | except KeyboardInterrupt: | |
1081 | self._aborted = True |
|
1081 | self._aborted = True | |
1082 | raise |
|
1082 | raise | |
1083 | except unittest.SkipTest as e: |
|
1083 | except unittest.SkipTest as e: | |
1084 | result.addSkip(self, str(e)) |
|
1084 | result.addSkip(self, str(e)) | |
1085 | # The base class will have already counted this as a |
|
1085 | # The base class will have already counted this as a | |
1086 | # test we "ran", but we want to exclude skipped tests |
|
1086 | # test we "ran", but we want to exclude skipped tests | |
1087 | # from those we count towards those run. |
|
1087 | # from those we count towards those run. | |
1088 | result.testsRun -= 1 |
|
1088 | result.testsRun -= 1 | |
1089 | except self.failureException as e: |
|
1089 | except self.failureException as e: | |
1090 | # This differs from unittest in that we don't capture |
|
1090 | # This differs from unittest in that we don't capture | |
1091 | # the stack trace. This is for historical reasons and |
|
1091 | # the stack trace. This is for historical reasons and | |
1092 | # this decision could be revisited in the future, |
|
1092 | # this decision could be revisited in the future, | |
1093 | # especially for PythonTest instances. |
|
1093 | # especially for PythonTest instances. | |
1094 | if result.addFailure(self, str(e)): |
|
1094 | if result.addFailure(self, str(e)): | |
1095 | success = True |
|
1095 | success = True | |
1096 | except Exception: |
|
1096 | except Exception: | |
1097 | result.addError(self, sys.exc_info()) |
|
1097 | result.addError(self, sys.exc_info()) | |
1098 | else: |
|
1098 | else: | |
1099 | success = True |
|
1099 | success = True | |
1100 |
|
1100 | |||
1101 | try: |
|
1101 | try: | |
1102 | self.tearDown() |
|
1102 | self.tearDown() | |
1103 | except (KeyboardInterrupt, SystemExit): |
|
1103 | except (KeyboardInterrupt, SystemExit): | |
1104 | self._aborted = True |
|
1104 | self._aborted = True | |
1105 | raise |
|
1105 | raise | |
1106 | except Exception: |
|
1106 | except Exception: | |
1107 | result.addError(self, sys.exc_info()) |
|
1107 | result.addError(self, sys.exc_info()) | |
1108 | success = False |
|
1108 | success = False | |
1109 |
|
1109 | |||
1110 | if success: |
|
1110 | if success: | |
1111 | result.addSuccess(self) |
|
1111 | result.addSuccess(self) | |
1112 | finally: |
|
1112 | finally: | |
1113 | result.stopTest(self, interrupted=self._aborted) |
|
1113 | result.stopTest(self, interrupted=self._aborted) | |
1114 |
|
1114 | |||
1115 | def runTest(self): |
|
1115 | def runTest(self): | |
1116 | """Run this test instance. |
|
1116 | """Run this test instance. | |
1117 |
|
1117 | |||
1118 | This will return a tuple describing the result of the test. |
|
1118 | This will return a tuple describing the result of the test. | |
1119 | """ |
|
1119 | """ | |
1120 | env = self._getenv() |
|
1120 | env = self._getenv() | |
1121 | self._genrestoreenv(env) |
|
1121 | self._genrestoreenv(env) | |
1122 | self._daemonpids.append(env['DAEMON_PIDS']) |
|
1122 | self._daemonpids.append(env['DAEMON_PIDS']) | |
1123 | self._createhgrc(env['HGRCPATH']) |
|
1123 | self._createhgrc(env['HGRCPATH']) | |
1124 |
|
1124 | |||
1125 | vlog('# Test', self.name) |
|
1125 | vlog('# Test', self.name) | |
1126 |
|
1126 | |||
1127 | ret, out = self._run(env) |
|
1127 | ret, out = self._run(env) | |
1128 | self._finished = True |
|
1128 | self._finished = True | |
1129 | self._ret = ret |
|
1129 | self._ret = ret | |
1130 | self._out = out |
|
1130 | self._out = out | |
1131 |
|
1131 | |||
1132 | def describe(ret): |
|
1132 | def describe(ret): | |
1133 | if ret < 0: |
|
1133 | if ret < 0: | |
1134 | return 'killed by signal: %d' % -ret |
|
1134 | return 'killed by signal: %d' % -ret | |
1135 | return 'returned error code %d' % ret |
|
1135 | return 'returned error code %d' % ret | |
1136 |
|
1136 | |||
1137 | self._skipped = False |
|
1137 | self._skipped = False | |
1138 |
|
1138 | |||
1139 | if ret == self.SKIPPED_STATUS: |
|
1139 | if ret == self.SKIPPED_STATUS: | |
1140 | if out is None: # Debug mode, nothing to parse. |
|
1140 | if out is None: # Debug mode, nothing to parse. | |
1141 | missing = ['unknown'] |
|
1141 | missing = ['unknown'] | |
1142 | failed = None |
|
1142 | failed = None | |
1143 | else: |
|
1143 | else: | |
1144 | missing, failed = TTest.parsehghaveoutput(out) |
|
1144 | missing, failed = TTest.parsehghaveoutput(out) | |
1145 |
|
1145 | |||
1146 | if not missing: |
|
1146 | if not missing: | |
1147 | missing = ['skipped'] |
|
1147 | missing = ['skipped'] | |
1148 |
|
1148 | |||
1149 | if failed: |
|
1149 | if failed: | |
1150 | self.fail('hg have failed checking for %s' % failed[-1]) |
|
1150 | self.fail('hg have failed checking for %s' % failed[-1]) | |
1151 | else: |
|
1151 | else: | |
1152 | self._skipped = True |
|
1152 | self._skipped = True | |
1153 | raise unittest.SkipTest(missing[-1]) |
|
1153 | raise unittest.SkipTest(missing[-1]) | |
1154 | elif ret == 'timeout': |
|
1154 | elif ret == 'timeout': | |
1155 | self.fail('timed out') |
|
1155 | self.fail('timed out') | |
1156 | elif ret is False: |
|
1156 | elif ret is False: | |
1157 | self.fail('no result code from test') |
|
1157 | self.fail('no result code from test') | |
1158 | elif out != self._refout: |
|
1158 | elif out != self._refout: | |
1159 | # Diff generation may rely on written .err file. |
|
1159 | # Diff generation may rely on written .err file. | |
1160 | if ( |
|
1160 | if ( | |
1161 | (ret != 0 or out != self._refout) |
|
1161 | (ret != 0 or out != self._refout) | |
1162 | and not self._skipped |
|
1162 | and not self._skipped | |
1163 | and not self._debug |
|
1163 | and not self._debug | |
1164 | ): |
|
1164 | ): | |
1165 | with open(self.errpath, 'wb') as f: |
|
1165 | with open(self.errpath, 'wb') as f: | |
1166 | for line in out: |
|
1166 | for line in out: | |
1167 | f.write(line) |
|
1167 | f.write(line) | |
1168 |
|
1168 | |||
1169 | # The result object handles diff calculation for us. |
|
1169 | # The result object handles diff calculation for us. | |
1170 | with firstlock: |
|
1170 | with firstlock: | |
1171 | if self._result.addOutputMismatch(self, ret, out, self._refout): |
|
1171 | if self._result.addOutputMismatch(self, ret, out, self._refout): | |
1172 | # change was accepted, skip failing |
|
1172 | # change was accepted, skip failing | |
1173 | return |
|
1173 | return | |
1174 | if self._first: |
|
1174 | if self._first: | |
1175 | global firsterror |
|
1175 | global firsterror | |
1176 | firsterror = True |
|
1176 | firsterror = True | |
1177 |
|
1177 | |||
1178 | if ret: |
|
1178 | if ret: | |
1179 | msg = 'output changed and ' + describe(ret) |
|
1179 | msg = 'output changed and ' + describe(ret) | |
1180 | else: |
|
1180 | else: | |
1181 | msg = 'output changed' |
|
1181 | msg = 'output changed' | |
1182 |
|
1182 | |||
1183 | self.fail(msg) |
|
1183 | self.fail(msg) | |
1184 | elif ret: |
|
1184 | elif ret: | |
1185 | self.fail(describe(ret)) |
|
1185 | self.fail(describe(ret)) | |
1186 |
|
1186 | |||
1187 | def tearDown(self): |
|
1187 | def tearDown(self): | |
1188 | """Tasks to perform after run().""" |
|
1188 | """Tasks to perform after run().""" | |
1189 | for entry in self._daemonpids: |
|
1189 | for entry in self._daemonpids: | |
1190 | killdaemons(entry) |
|
1190 | killdaemons(entry) | |
1191 | self._daemonpids = [] |
|
1191 | self._daemonpids = [] | |
1192 |
|
1192 | |||
1193 | if self._keeptmpdir: |
|
1193 | if self._keeptmpdir: | |
1194 | log( |
|
1194 | log( | |
1195 | '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' |
|
1195 | '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' | |
1196 | % (_bytes2sys(self._testtmp), _bytes2sys(self._threadtmp),) |
|
1196 | % (_bytes2sys(self._testtmp), _bytes2sys(self._threadtmp),) | |
1197 | ) |
|
1197 | ) | |
1198 | else: |
|
1198 | else: | |
1199 | try: |
|
1199 | try: | |
1200 | shutil.rmtree(self._testtmp) |
|
1200 | shutil.rmtree(self._testtmp) | |
1201 | except OSError: |
|
1201 | except OSError: | |
1202 | # unreadable directory may be left in $TESTTMP; fix permission |
|
1202 | # unreadable directory may be left in $TESTTMP; fix permission | |
1203 | # and try again |
|
1203 | # and try again | |
1204 | makecleanable(self._testtmp) |
|
1204 | makecleanable(self._testtmp) | |
1205 | shutil.rmtree(self._testtmp, True) |
|
1205 | shutil.rmtree(self._testtmp, True) | |
1206 | shutil.rmtree(self._threadtmp, True) |
|
1206 | shutil.rmtree(self._threadtmp, True) | |
1207 |
|
1207 | |||
1208 | if self._usechg: |
|
1208 | if self._usechg: | |
1209 | # chgservers will stop automatically after they find the socket |
|
1209 | # chgservers will stop automatically after they find the socket | |
1210 | # files are deleted |
|
1210 | # files are deleted | |
1211 | shutil.rmtree(self._chgsockdir, True) |
|
1211 | shutil.rmtree(self._chgsockdir, True) | |
1212 |
|
1212 | |||
1213 | if ( |
|
1213 | if ( | |
1214 | (self._ret != 0 or self._out != self._refout) |
|
1214 | (self._ret != 0 or self._out != self._refout) | |
1215 | and not self._skipped |
|
1215 | and not self._skipped | |
1216 | and not self._debug |
|
1216 | and not self._debug | |
1217 | and self._out |
|
1217 | and self._out | |
1218 | ): |
|
1218 | ): | |
1219 | with open(self.errpath, 'wb') as f: |
|
1219 | with open(self.errpath, 'wb') as f: | |
1220 | for line in self._out: |
|
1220 | for line in self._out: | |
1221 | f.write(line) |
|
1221 | f.write(line) | |
1222 |
|
1222 | |||
1223 | vlog("# Ret was:", self._ret, '(%s)' % self.name) |
|
1223 | vlog("# Ret was:", self._ret, '(%s)' % self.name) | |
1224 |
|
1224 | |||
1225 | def _run(self, env): |
|
1225 | def _run(self, env): | |
1226 | # This should be implemented in child classes to run tests. |
|
1226 | # This should be implemented in child classes to run tests. | |
1227 | raise unittest.SkipTest('unknown test type') |
|
1227 | raise unittest.SkipTest('unknown test type') | |
1228 |
|
1228 | |||
1229 | def abort(self): |
|
1229 | def abort(self): | |
1230 | """Terminate execution of this test.""" |
|
1230 | """Terminate execution of this test.""" | |
1231 | self._aborted = True |
|
1231 | self._aborted = True | |
1232 |
|
1232 | |||
1233 | def _portmap(self, i): |
|
1233 | def _portmap(self, i): | |
1234 | offset = b'' if i == 0 else b'%d' % i |
|
1234 | offset = b'' if i == 0 else b'%d' % i | |
1235 | return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset) |
|
1235 | return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset) | |
1236 |
|
1236 | |||
1237 | def _getreplacements(self): |
|
1237 | def _getreplacements(self): | |
1238 | """Obtain a mapping of text replacements to apply to test output. |
|
1238 | """Obtain a mapping of text replacements to apply to test output. | |
1239 |
|
1239 | |||
1240 | Test output needs to be normalized so it can be compared to expected |
|
1240 | Test output needs to be normalized so it can be compared to expected | |
1241 | output. This function defines how some of that normalization will |
|
1241 | output. This function defines how some of that normalization will | |
1242 | occur. |
|
1242 | occur. | |
1243 | """ |
|
1243 | """ | |
1244 | r = [ |
|
1244 | r = [ | |
1245 | # This list should be parallel to defineport in _getenv |
|
1245 | # This list should be parallel to defineport in _getenv | |
1246 | self._portmap(0), |
|
1246 | self._portmap(0), | |
1247 | self._portmap(1), |
|
1247 | self._portmap(1), | |
1248 | self._portmap(2), |
|
1248 | self._portmap(2), | |
1249 | (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'), |
|
1249 | (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'), | |
1250 | (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'), |
|
1250 | (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'), | |
1251 | ] |
|
1251 | ] | |
1252 | r.append((self._escapepath(self._testtmp), b'$TESTTMP')) |
|
1252 | r.append((self._escapepath(self._testtmp), b'$TESTTMP')) | |
1253 |
|
1253 | |||
1254 | replacementfile = os.path.join(self._testdir, b'common-pattern.py') |
|
1254 | replacementfile = os.path.join(self._testdir, b'common-pattern.py') | |
1255 |
|
1255 | |||
1256 | if os.path.exists(replacementfile): |
|
1256 | if os.path.exists(replacementfile): | |
1257 | data = {} |
|
1257 | data = {} | |
1258 | with open(replacementfile, mode='rb') as source: |
|
1258 | with open(replacementfile, mode='rb') as source: | |
1259 | # the intermediate 'compile' step help with debugging |
|
1259 | # the intermediate 'compile' step help with debugging | |
1260 | code = compile(source.read(), replacementfile, 'exec') |
|
1260 | code = compile(source.read(), replacementfile, 'exec') | |
1261 | exec(code, data) |
|
1261 | exec(code, data) | |
1262 | for value in data.get('substitutions', ()): |
|
1262 | for value in data.get('substitutions', ()): | |
1263 | if len(value) != 2: |
|
1263 | if len(value) != 2: | |
1264 | msg = 'malformatted substitution in %s: %r' |
|
1264 | msg = 'malformatted substitution in %s: %r' | |
1265 | msg %= (replacementfile, value) |
|
1265 | msg %= (replacementfile, value) | |
1266 | raise ValueError(msg) |
|
1266 | raise ValueError(msg) | |
1267 | r.append(value) |
|
1267 | r.append(value) | |
1268 | return r |
|
1268 | return r | |
1269 |
|
1269 | |||
1270 | def _escapepath(self, p): |
|
1270 | def _escapepath(self, p): | |
1271 | if os.name == 'nt': |
|
1271 | if os.name == 'nt': | |
1272 | return b''.join( |
|
1272 | return b''.join( | |
1273 | c.isalpha() |
|
1273 | c.isalpha() | |
1274 | and b'[%s%s]' % (c.lower(), c.upper()) |
|
1274 | and b'[%s%s]' % (c.lower(), c.upper()) | |
1275 | or c in b'/\\' |
|
1275 | or c in b'/\\' | |
1276 | and br'[/\\]' |
|
1276 | and br'[/\\]' | |
1277 | or c.isdigit() |
|
1277 | or c.isdigit() | |
1278 | and c |
|
1278 | and c | |
1279 | or b'\\' + c |
|
1279 | or b'\\' + c | |
1280 | for c in [p[i : i + 1] for i in range(len(p))] |
|
1280 | for c in [p[i : i + 1] for i in range(len(p))] | |
1281 | ) |
|
1281 | ) | |
1282 | else: |
|
1282 | else: | |
1283 | return re.escape(p) |
|
1283 | return re.escape(p) | |
1284 |
|
1284 | |||
1285 | def _localip(self): |
|
1285 | def _localip(self): | |
1286 | if self._useipv6: |
|
1286 | if self._useipv6: | |
1287 | return b'::1' |
|
1287 | return b'::1' | |
1288 | else: |
|
1288 | else: | |
1289 | return b'127.0.0.1' |
|
1289 | return b'127.0.0.1' | |
1290 |
|
1290 | |||
1291 | def _genrestoreenv(self, testenv): |
|
1291 | def _genrestoreenv(self, testenv): | |
1292 | """Generate a script that can be used by tests to restore the original |
|
1292 | """Generate a script that can be used by tests to restore the original | |
1293 | environment.""" |
|
1293 | environment.""" | |
1294 | # Put the restoreenv script inside self._threadtmp |
|
1294 | # Put the restoreenv script inside self._threadtmp | |
1295 | scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh') |
|
1295 | scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh') | |
1296 | testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath) |
|
1296 | testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath) | |
1297 |
|
1297 | |||
1298 | # Only restore environment variable names that the shell allows |
|
1298 | # Only restore environment variable names that the shell allows | |
1299 | # us to export. |
|
1299 | # us to export. | |
1300 | name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$') |
|
1300 | name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$') | |
1301 |
|
1301 | |||
1302 | # Do not restore these variables; otherwise tests would fail. |
|
1302 | # Do not restore these variables; otherwise tests would fail. | |
1303 | reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'} |
|
1303 | reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'} | |
1304 |
|
1304 | |||
1305 | with open(scriptpath, 'w') as envf: |
|
1305 | with open(scriptpath, 'w') as envf: | |
1306 | for name, value in origenviron.items(): |
|
1306 | for name, value in origenviron.items(): | |
1307 | if not name_regex.match(name): |
|
1307 | if not name_regex.match(name): | |
1308 | # Skip environment variables with unusual names not |
|
1308 | # Skip environment variables with unusual names not | |
1309 | # allowed by most shells. |
|
1309 | # allowed by most shells. | |
1310 | continue |
|
1310 | continue | |
1311 | if name in reqnames: |
|
1311 | if name in reqnames: | |
1312 | continue |
|
1312 | continue | |
1313 | envf.write('%s=%s\n' % (name, shellquote(value))) |
|
1313 | envf.write('%s=%s\n' % (name, shellquote(value))) | |
1314 |
|
1314 | |||
1315 | for name in testenv: |
|
1315 | for name in testenv: | |
1316 | if name in origenviron or name in reqnames: |
|
1316 | if name in origenviron or name in reqnames: | |
1317 | continue |
|
1317 | continue | |
1318 | envf.write('unset %s\n' % (name,)) |
|
1318 | envf.write('unset %s\n' % (name,)) | |
1319 |
|
1319 | |||
1320 | def _getenv(self): |
|
1320 | def _getenv(self): | |
1321 | """Obtain environment variables to use during test execution.""" |
|
1321 | """Obtain environment variables to use during test execution.""" | |
1322 |
|
1322 | |||
1323 | def defineport(i): |
|
1323 | def defineport(i): | |
1324 | offset = '' if i == 0 else '%s' % i |
|
1324 | offset = '' if i == 0 else '%s' % i | |
1325 | env["HGPORT%s" % offset] = '%s' % (self._startport + i) |
|
1325 | env["HGPORT%s" % offset] = '%s' % (self._startport + i) | |
1326 |
|
1326 | |||
1327 | env = os.environ.copy() |
|
1327 | env = os.environ.copy() | |
1328 | env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or '' |
|
1328 | env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or '' | |
1329 | env['HGEMITWARNINGS'] = '1' |
|
1329 | env['HGEMITWARNINGS'] = '1' | |
1330 | env['TESTTMP'] = _bytes2sys(self._testtmp) |
|
1330 | env['TESTTMP'] = _bytes2sys(self._testtmp) | |
1331 | env['TESTNAME'] = self.name |
|
1331 | env['TESTNAME'] = self.name | |
1332 | env['HOME'] = _bytes2sys(self._testtmp) |
|
1332 | env['HOME'] = _bytes2sys(self._testtmp) | |
1333 | formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1]) |
|
1333 | formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1]) | |
1334 | env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout |
|
1334 | env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout | |
1335 | env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout) |
|
1335 | env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout) | |
1336 | # This number should match portneeded in _getport |
|
1336 | # This number should match portneeded in _getport | |
1337 | for port in xrange(3): |
|
1337 | for port in xrange(3): | |
1338 | # This list should be parallel to _portmap in _getreplacements |
|
1338 | # This list should be parallel to _portmap in _getreplacements | |
1339 | defineport(port) |
|
1339 | defineport(port) | |
1340 | env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc')) |
|
1340 | env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc')) | |
1341 | env["DAEMON_PIDS"] = _bytes2sys( |
|
1341 | env["DAEMON_PIDS"] = _bytes2sys( | |
1342 | os.path.join(self._threadtmp, b'daemon.pids') |
|
1342 | os.path.join(self._threadtmp, b'daemon.pids') | |
1343 | ) |
|
1343 | ) | |
1344 | env["HGEDITOR"] = ( |
|
1344 | env["HGEDITOR"] = ( | |
1345 | '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"' |
|
1345 | '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"' | |
1346 | ) |
|
1346 | ) | |
1347 | env["HGUSER"] = "test" |
|
1347 | env["HGUSER"] = "test" | |
1348 | env["HGENCODING"] = "ascii" |
|
1348 | env["HGENCODING"] = "ascii" | |
1349 | env["HGENCODINGMODE"] = "strict" |
|
1349 | env["HGENCODINGMODE"] = "strict" | |
1350 | env["HGHOSTNAME"] = "test-hostname" |
|
1350 | env["HGHOSTNAME"] = "test-hostname" | |
1351 | env['HGIPV6'] = str(int(self._useipv6)) |
|
1351 | env['HGIPV6'] = str(int(self._useipv6)) | |
1352 | # See contrib/catapipe.py for how to use this functionality. |
|
1352 | # See contrib/catapipe.py for how to use this functionality. | |
1353 | if 'HGTESTCATAPULTSERVERPIPE' not in env: |
|
1353 | if 'HGTESTCATAPULTSERVERPIPE' not in env: | |
1354 | # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the |
|
1354 | # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the | |
1355 | # non-test one in as a default, otherwise set to devnull |
|
1355 | # non-test one in as a default, otherwise set to devnull | |
1356 | env['HGTESTCATAPULTSERVERPIPE'] = env.get( |
|
1356 | env['HGTESTCATAPULTSERVERPIPE'] = env.get( | |
1357 | 'HGCATAPULTSERVERPIPE', os.devnull |
|
1357 | 'HGCATAPULTSERVERPIPE', os.devnull | |
1358 | ) |
|
1358 | ) | |
1359 |
|
1359 | |||
1360 | extraextensions = [] |
|
1360 | extraextensions = [] | |
1361 | for opt in self._extraconfigopts: |
|
1361 | for opt in self._extraconfigopts: | |
1362 | section, key = _sys2bytes(opt).split(b'.', 1) |
|
1362 | section, key = _sys2bytes(opt).split(b'.', 1) | |
1363 | if section != 'extensions': |
|
1363 | if section != 'extensions': | |
1364 | continue |
|
1364 | continue | |
1365 | name = key.split(b'=', 1)[0] |
|
1365 | name = key.split(b'=', 1)[0] | |
1366 | extraextensions.append(name) |
|
1366 | extraextensions.append(name) | |
1367 |
|
1367 | |||
1368 | if extraextensions: |
|
1368 | if extraextensions: | |
1369 | env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions) |
|
1369 | env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions) | |
1370 |
|
1370 | |||
1371 | # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw |
|
1371 | # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw | |
1372 | # IP addresses. |
|
1372 | # IP addresses. | |
1373 | env['LOCALIP'] = _bytes2sys(self._localip()) |
|
1373 | env['LOCALIP'] = _bytes2sys(self._localip()) | |
1374 |
|
1374 | |||
1375 | # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c, |
|
1375 | # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c, | |
1376 | # but this is needed for testing python instances like dummyssh, |
|
1376 | # but this is needed for testing python instances like dummyssh, | |
1377 | # dummysmtpd.py, and dumbhttp.py. |
|
1377 | # dummysmtpd.py, and dumbhttp.py. | |
1378 | if PYTHON3 and os.name == 'nt': |
|
1378 | if PYTHON3 and os.name == 'nt': | |
1379 | env['PYTHONLEGACYWINDOWSSTDIO'] = '1' |
|
1379 | env['PYTHONLEGACYWINDOWSSTDIO'] = '1' | |
1380 |
|
1380 | |||
1381 | # Modified HOME in test environment can confuse Rust tools. So set |
|
1381 | # Modified HOME in test environment can confuse Rust tools. So set | |
1382 | # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is |
|
1382 | # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is | |
1383 | # present and these variables aren't already defined. |
|
1383 | # present and these variables aren't already defined. | |
1384 | cargo_home_path = os.path.expanduser('~/.cargo') |
|
1384 | cargo_home_path = os.path.expanduser('~/.cargo') | |
1385 | rustup_home_path = os.path.expanduser('~/.rustup') |
|
1385 | rustup_home_path = os.path.expanduser('~/.rustup') | |
1386 |
|
1386 | |||
1387 | if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb: |
|
1387 | if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb: | |
1388 | env['CARGO_HOME'] = cargo_home_path |
|
1388 | env['CARGO_HOME'] = cargo_home_path | |
1389 | if ( |
|
1389 | if ( | |
1390 | os.path.exists(rustup_home_path) |
|
1390 | os.path.exists(rustup_home_path) | |
1391 | and b'RUSTUP_HOME' not in osenvironb |
|
1391 | and b'RUSTUP_HOME' not in osenvironb | |
1392 | ): |
|
1392 | ): | |
1393 | env['RUSTUP_HOME'] = rustup_home_path |
|
1393 | env['RUSTUP_HOME'] = rustup_home_path | |
1394 |
|
1394 | |||
1395 | # Reset some environment variables to well-known values so that |
|
1395 | # Reset some environment variables to well-known values so that | |
1396 | # the tests produce repeatable output. |
|
1396 | # the tests produce repeatable output. | |
1397 | env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C' |
|
1397 | env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C' | |
1398 | env['TZ'] = 'GMT' |
|
1398 | env['TZ'] = 'GMT' | |
1399 | env["EMAIL"] = "Foo Bar <foo.bar@example.com>" |
|
1399 | env["EMAIL"] = "Foo Bar <foo.bar@example.com>" | |
1400 | env['COLUMNS'] = '80' |
|
1400 | env['COLUMNS'] = '80' | |
1401 | env['TERM'] = 'xterm' |
|
1401 | env['TERM'] = 'xterm' | |
1402 |
|
1402 | |||
1403 | dropped = [ |
|
1403 | dropped = [ | |
1404 | 'CDPATH', |
|
1404 | 'CDPATH', | |
1405 | 'CHGDEBUG', |
|
1405 | 'CHGDEBUG', | |
1406 | 'EDITOR', |
|
1406 | 'EDITOR', | |
1407 | 'GREP_OPTIONS', |
|
1407 | 'GREP_OPTIONS', | |
1408 | 'HG', |
|
1408 | 'HG', | |
1409 | 'HGMERGE', |
|
1409 | 'HGMERGE', | |
1410 | 'HGPLAIN', |
|
1410 | 'HGPLAIN', | |
1411 | 'HGPLAINEXCEPT', |
|
1411 | 'HGPLAINEXCEPT', | |
1412 | 'HGPROF', |
|
1412 | 'HGPROF', | |
1413 | 'http_proxy', |
|
1413 | 'http_proxy', | |
1414 | 'no_proxy', |
|
1414 | 'no_proxy', | |
1415 | 'NO_PROXY', |
|
1415 | 'NO_PROXY', | |
1416 | 'PAGER', |
|
1416 | 'PAGER', | |
1417 | 'VISUAL', |
|
1417 | 'VISUAL', | |
1418 | ] |
|
1418 | ] | |
1419 |
|
1419 | |||
1420 | for k in dropped: |
|
1420 | for k in dropped: | |
1421 | if k in env: |
|
1421 | if k in env: | |
1422 | del env[k] |
|
1422 | del env[k] | |
1423 |
|
1423 | |||
1424 | # unset env related to hooks |
|
1424 | # unset env related to hooks | |
1425 | for k in list(env): |
|
1425 | for k in list(env): | |
1426 | if k.startswith('HG_'): |
|
1426 | if k.startswith('HG_'): | |
1427 | del env[k] |
|
1427 | del env[k] | |
1428 |
|
1428 | |||
1429 | if self._usechg: |
|
1429 | if self._usechg: | |
1430 | env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server') |
|
1430 | env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server') | |
1431 | if self._chgdebug: |
|
1431 | if self._chgdebug: | |
1432 | env['CHGDEBUG'] = 'true' |
|
1432 | env['CHGDEBUG'] = 'true' | |
1433 |
|
1433 | |||
1434 | return env |
|
1434 | return env | |
1435 |
|
1435 | |||
1436 | def _createhgrc(self, path): |
|
1436 | def _createhgrc(self, path): | |
1437 | """Create an hgrc file for this test.""" |
|
1437 | """Create an hgrc file for this test.""" | |
1438 | with open(path, 'wb') as hgrc: |
|
1438 | with open(path, 'wb') as hgrc: | |
1439 | hgrc.write(b'[ui]\n') |
|
1439 | hgrc.write(b'[ui]\n') | |
1440 | hgrc.write(b'slash = True\n') |
|
1440 | hgrc.write(b'slash = True\n') | |
1441 | hgrc.write(b'interactive = False\n') |
|
1441 | hgrc.write(b'interactive = False\n') | |
|
1442 | hgrc.write(b'detailed-exit-code = True\n') | |||
1442 | hgrc.write(b'merge = internal:merge\n') |
|
1443 | hgrc.write(b'merge = internal:merge\n') | |
1443 | hgrc.write(b'mergemarkers = detailed\n') |
|
1444 | hgrc.write(b'mergemarkers = detailed\n') | |
1444 | hgrc.write(b'promptecho = True\n') |
|
1445 | hgrc.write(b'promptecho = True\n') | |
1445 | hgrc.write(b'[defaults]\n') |
|
1446 | hgrc.write(b'[defaults]\n') | |
1446 | hgrc.write(b'[devel]\n') |
|
1447 | hgrc.write(b'[devel]\n') | |
1447 | hgrc.write(b'all-warnings = true\n') |
|
1448 | hgrc.write(b'all-warnings = true\n') | |
1448 | hgrc.write(b'default-date = 0 0\n') |
|
1449 | hgrc.write(b'default-date = 0 0\n') | |
1449 | hgrc.write(b'[largefiles]\n') |
|
1450 | hgrc.write(b'[largefiles]\n') | |
1450 | hgrc.write( |
|
1451 | hgrc.write( | |
1451 | b'usercache = %s\n' |
|
1452 | b'usercache = %s\n' | |
1452 | % (os.path.join(self._testtmp, b'.cache/largefiles')) |
|
1453 | % (os.path.join(self._testtmp, b'.cache/largefiles')) | |
1453 | ) |
|
1454 | ) | |
1454 | hgrc.write(b'[lfs]\n') |
|
1455 | hgrc.write(b'[lfs]\n') | |
1455 | hgrc.write( |
|
1456 | hgrc.write( | |
1456 | b'usercache = %s\n' |
|
1457 | b'usercache = %s\n' | |
1457 | % (os.path.join(self._testtmp, b'.cache/lfs')) |
|
1458 | % (os.path.join(self._testtmp, b'.cache/lfs')) | |
1458 | ) |
|
1459 | ) | |
1459 | hgrc.write(b'[web]\n') |
|
1460 | hgrc.write(b'[web]\n') | |
1460 | hgrc.write(b'address = localhost\n') |
|
1461 | hgrc.write(b'address = localhost\n') | |
1461 | hgrc.write(b'ipv6 = %r\n' % self._useipv6) |
|
1462 | hgrc.write(b'ipv6 = %r\n' % self._useipv6) | |
1462 | hgrc.write(b'server-header = testing stub value\n') |
|
1463 | hgrc.write(b'server-header = testing stub value\n') | |
1463 |
|
1464 | |||
1464 | for opt in self._extraconfigopts: |
|
1465 | for opt in self._extraconfigopts: | |
1465 | section, key = _sys2bytes(opt).split(b'.', 1) |
|
1466 | section, key = _sys2bytes(opt).split(b'.', 1) | |
1466 | assert b'=' in key, ( |
|
1467 | assert b'=' in key, ( | |
1467 | 'extra config opt %s must ' 'have an = for assignment' % opt |
|
1468 | 'extra config opt %s must ' 'have an = for assignment' % opt | |
1468 | ) |
|
1469 | ) | |
1469 | hgrc.write(b'[%s]\n%s\n' % (section, key)) |
|
1470 | hgrc.write(b'[%s]\n%s\n' % (section, key)) | |
1470 |
|
1471 | |||
1471 | def fail(self, msg): |
|
1472 | def fail(self, msg): | |
1472 | # unittest differentiates between errored and failed. |
|
1473 | # unittest differentiates between errored and failed. | |
1473 | # Failed is denoted by AssertionError (by default at least). |
|
1474 | # Failed is denoted by AssertionError (by default at least). | |
1474 | raise AssertionError(msg) |
|
1475 | raise AssertionError(msg) | |
1475 |
|
1476 | |||
1476 | def _runcommand(self, cmd, env, normalizenewlines=False): |
|
1477 | def _runcommand(self, cmd, env, normalizenewlines=False): | |
1477 | """Run command in a sub-process, capturing the output (stdout and |
|
1478 | """Run command in a sub-process, capturing the output (stdout and | |
1478 | stderr). |
|
1479 | stderr). | |
1479 |
|
1480 | |||
1480 | Return a tuple (exitcode, output). output is None in debug mode. |
|
1481 | Return a tuple (exitcode, output). output is None in debug mode. | |
1481 | """ |
|
1482 | """ | |
1482 | if self._debug: |
|
1483 | if self._debug: | |
1483 | proc = subprocess.Popen( |
|
1484 | proc = subprocess.Popen( | |
1484 | _bytes2sys(cmd), |
|
1485 | _bytes2sys(cmd), | |
1485 | shell=True, |
|
1486 | shell=True, | |
1486 | cwd=_bytes2sys(self._testtmp), |
|
1487 | cwd=_bytes2sys(self._testtmp), | |
1487 | env=env, |
|
1488 | env=env, | |
1488 | ) |
|
1489 | ) | |
1489 | ret = proc.wait() |
|
1490 | ret = proc.wait() | |
1490 | return (ret, None) |
|
1491 | return (ret, None) | |
1491 |
|
1492 | |||
1492 | proc = Popen4(cmd, self._testtmp, self._timeout, env) |
|
1493 | proc = Popen4(cmd, self._testtmp, self._timeout, env) | |
1493 |
|
1494 | |||
1494 | def cleanup(): |
|
1495 | def cleanup(): | |
1495 | terminate(proc) |
|
1496 | terminate(proc) | |
1496 | ret = proc.wait() |
|
1497 | ret = proc.wait() | |
1497 | if ret == 0: |
|
1498 | if ret == 0: | |
1498 | ret = signal.SIGTERM << 8 |
|
1499 | ret = signal.SIGTERM << 8 | |
1499 | killdaemons(env['DAEMON_PIDS']) |
|
1500 | killdaemons(env['DAEMON_PIDS']) | |
1500 | return ret |
|
1501 | return ret | |
1501 |
|
1502 | |||
1502 | proc.tochild.close() |
|
1503 | proc.tochild.close() | |
1503 |
|
1504 | |||
1504 | try: |
|
1505 | try: | |
1505 | output = proc.fromchild.read() |
|
1506 | output = proc.fromchild.read() | |
1506 | except KeyboardInterrupt: |
|
1507 | except KeyboardInterrupt: | |
1507 | vlog('# Handling keyboard interrupt') |
|
1508 | vlog('# Handling keyboard interrupt') | |
1508 | cleanup() |
|
1509 | cleanup() | |
1509 | raise |
|
1510 | raise | |
1510 |
|
1511 | |||
1511 | ret = proc.wait() |
|
1512 | ret = proc.wait() | |
1512 | if wifexited(ret): |
|
1513 | if wifexited(ret): | |
1513 | ret = os.WEXITSTATUS(ret) |
|
1514 | ret = os.WEXITSTATUS(ret) | |
1514 |
|
1515 | |||
1515 | if proc.timeout: |
|
1516 | if proc.timeout: | |
1516 | ret = 'timeout' |
|
1517 | ret = 'timeout' | |
1517 |
|
1518 | |||
1518 | if ret: |
|
1519 | if ret: | |
1519 | killdaemons(env['DAEMON_PIDS']) |
|
1520 | killdaemons(env['DAEMON_PIDS']) | |
1520 |
|
1521 | |||
1521 | for s, r in self._getreplacements(): |
|
1522 | for s, r in self._getreplacements(): | |
1522 | output = re.sub(s, r, output) |
|
1523 | output = re.sub(s, r, output) | |
1523 |
|
1524 | |||
1524 | if normalizenewlines: |
|
1525 | if normalizenewlines: | |
1525 | output = output.replace(b'\r\n', b'\n') |
|
1526 | output = output.replace(b'\r\n', b'\n') | |
1526 |
|
1527 | |||
1527 | return ret, output.splitlines(True) |
|
1528 | return ret, output.splitlines(True) | |
1528 |
|
1529 | |||
1529 |
|
1530 | |||
1530 | class PythonTest(Test): |
|
1531 | class PythonTest(Test): | |
1531 | """A Python-based test.""" |
|
1532 | """A Python-based test.""" | |
1532 |
|
1533 | |||
1533 | @property |
|
1534 | @property | |
1534 | def refpath(self): |
|
1535 | def refpath(self): | |
1535 | return os.path.join(self._testdir, b'%s.out' % self.bname) |
|
1536 | return os.path.join(self._testdir, b'%s.out' % self.bname) | |
1536 |
|
1537 | |||
1537 | def _run(self, env): |
|
1538 | def _run(self, env): | |
1538 | # Quote the python(3) executable for Windows |
|
1539 | # Quote the python(3) executable for Windows | |
1539 | cmd = b'"%s" "%s"' % (PYTHON, self.path) |
|
1540 | cmd = b'"%s" "%s"' % (PYTHON, self.path) | |
1540 | vlog("# Running", cmd.decode("utf-8")) |
|
1541 | vlog("# Running", cmd.decode("utf-8")) | |
1541 | normalizenewlines = os.name == 'nt' |
|
1542 | normalizenewlines = os.name == 'nt' | |
1542 | result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines) |
|
1543 | result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines) | |
1543 | if self._aborted: |
|
1544 | if self._aborted: | |
1544 | raise KeyboardInterrupt() |
|
1545 | raise KeyboardInterrupt() | |
1545 |
|
1546 | |||
1546 | return result |
|
1547 | return result | |
1547 |
|
1548 | |||
1548 |
|
1549 | |||
1549 | # Some glob patterns apply only in some circumstances, so the script |
|
1550 | # Some glob patterns apply only in some circumstances, so the script | |
1550 | # might want to remove (glob) annotations that otherwise should be |
|
1551 | # might want to remove (glob) annotations that otherwise should be | |
1551 | # retained. |
|
1552 | # retained. | |
1552 | checkcodeglobpats = [ |
|
1553 | checkcodeglobpats = [ | |
1553 | # On Windows it looks like \ doesn't require a (glob), but we know |
|
1554 | # On Windows it looks like \ doesn't require a (glob), but we know | |
1554 | # better. |
|
1555 | # better. | |
1555 | re.compile(br'^pushing to \$TESTTMP/.*[^)]$'), |
|
1556 | re.compile(br'^pushing to \$TESTTMP/.*[^)]$'), | |
1556 | re.compile(br'^moving \S+/.*[^)]$'), |
|
1557 | re.compile(br'^moving \S+/.*[^)]$'), | |
1557 | re.compile(br'^pulling from \$TESTTMP/.*[^)]$'), |
|
1558 | re.compile(br'^pulling from \$TESTTMP/.*[^)]$'), | |
1558 | # Not all platforms have 127.0.0.1 as loopback (though most do), |
|
1559 | # Not all platforms have 127.0.0.1 as loopback (though most do), | |
1559 | # so we always glob that too. |
|
1560 | # so we always glob that too. | |
1560 | re.compile(br'.*\$LOCALIP.*$'), |
|
1561 | re.compile(br'.*\$LOCALIP.*$'), | |
1561 | ] |
|
1562 | ] | |
1562 |
|
1563 | |||
1563 | bchr = chr |
|
1564 | bchr = chr | |
1564 | if PYTHON3: |
|
1565 | if PYTHON3: | |
1565 | bchr = lambda x: bytes([x]) |
|
1566 | bchr = lambda x: bytes([x]) | |
1566 |
|
1567 | |||
1567 | WARN_UNDEFINED = 1 |
|
1568 | WARN_UNDEFINED = 1 | |
1568 | WARN_YES = 2 |
|
1569 | WARN_YES = 2 | |
1569 | WARN_NO = 3 |
|
1570 | WARN_NO = 3 | |
1570 |
|
1571 | |||
1571 | MARK_OPTIONAL = b" (?)\n" |
|
1572 | MARK_OPTIONAL = b" (?)\n" | |
1572 |
|
1573 | |||
1573 |
|
1574 | |||
1574 | def isoptional(line): |
|
1575 | def isoptional(line): | |
1575 | return line.endswith(MARK_OPTIONAL) |
|
1576 | return line.endswith(MARK_OPTIONAL) | |
1576 |
|
1577 | |||
1577 |
|
1578 | |||
1578 | class TTest(Test): |
|
1579 | class TTest(Test): | |
1579 | """A "t test" is a test backed by a .t file.""" |
|
1580 | """A "t test" is a test backed by a .t file.""" | |
1580 |
|
1581 | |||
1581 | SKIPPED_PREFIX = b'skipped: ' |
|
1582 | SKIPPED_PREFIX = b'skipped: ' | |
1582 | FAILED_PREFIX = b'hghave check failed: ' |
|
1583 | FAILED_PREFIX = b'hghave check failed: ' | |
1583 | NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search |
|
1584 | NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search | |
1584 |
|
1585 | |||
1585 | ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub |
|
1586 | ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub | |
1586 | ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)} |
|
1587 | ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)} | |
1587 | ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'}) |
|
1588 | ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'}) | |
1588 |
|
1589 | |||
1589 | def __init__(self, path, *args, **kwds): |
|
1590 | def __init__(self, path, *args, **kwds): | |
1590 | # accept an extra "case" parameter |
|
1591 | # accept an extra "case" parameter | |
1591 | case = kwds.pop('case', []) |
|
1592 | case = kwds.pop('case', []) | |
1592 | self._case = case |
|
1593 | self._case = case | |
1593 | self._allcases = {x for y in parsettestcases(path) for x in y} |
|
1594 | self._allcases = {x for y in parsettestcases(path) for x in y} | |
1594 | super(TTest, self).__init__(path, *args, **kwds) |
|
1595 | super(TTest, self).__init__(path, *args, **kwds) | |
1595 | if case: |
|
1596 | if case: | |
1596 | casepath = b'#'.join(case) |
|
1597 | casepath = b'#'.join(case) | |
1597 | self.name = '%s#%s' % (self.name, _bytes2sys(casepath)) |
|
1598 | self.name = '%s#%s' % (self.name, _bytes2sys(casepath)) | |
1598 | self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath) |
|
1599 | self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath) | |
1599 | self._tmpname += b'-%s' % casepath.replace(b'#', b'-') |
|
1600 | self._tmpname += b'-%s' % casepath.replace(b'#', b'-') | |
1600 | self._have = {} |
|
1601 | self._have = {} | |
1601 |
|
1602 | |||
1602 | @property |
|
1603 | @property | |
1603 | def refpath(self): |
|
1604 | def refpath(self): | |
1604 | return os.path.join(self._testdir, self.bname) |
|
1605 | return os.path.join(self._testdir, self.bname) | |
1605 |
|
1606 | |||
1606 | def _run(self, env): |
|
1607 | def _run(self, env): | |
1607 | with open(self.path, 'rb') as f: |
|
1608 | with open(self.path, 'rb') as f: | |
1608 | lines = f.readlines() |
|
1609 | lines = f.readlines() | |
1609 |
|
1610 | |||
1610 | # .t file is both reference output and the test input, keep reference |
|
1611 | # .t file is both reference output and the test input, keep reference | |
1611 | # output updated with the the test input. This avoids some race |
|
1612 | # output updated with the the test input. This avoids some race | |
1612 | # conditions where the reference output does not match the actual test. |
|
1613 | # conditions where the reference output does not match the actual test. | |
1613 | if self._refout is not None: |
|
1614 | if self._refout is not None: | |
1614 | self._refout = lines |
|
1615 | self._refout = lines | |
1615 |
|
1616 | |||
1616 | salt, script, after, expected = self._parsetest(lines) |
|
1617 | salt, script, after, expected = self._parsetest(lines) | |
1617 |
|
1618 | |||
1618 | # Write out the generated script. |
|
1619 | # Write out the generated script. | |
1619 | fname = b'%s.sh' % self._testtmp |
|
1620 | fname = b'%s.sh' % self._testtmp | |
1620 | with open(fname, 'wb') as f: |
|
1621 | with open(fname, 'wb') as f: | |
1621 | for l in script: |
|
1622 | for l in script: | |
1622 | f.write(l) |
|
1623 | f.write(l) | |
1623 |
|
1624 | |||
1624 | cmd = b'%s "%s"' % (self._shell, fname) |
|
1625 | cmd = b'%s "%s"' % (self._shell, fname) | |
1625 | vlog("# Running", cmd.decode("utf-8")) |
|
1626 | vlog("# Running", cmd.decode("utf-8")) | |
1626 |
|
1627 | |||
1627 | exitcode, output = self._runcommand(cmd, env) |
|
1628 | exitcode, output = self._runcommand(cmd, env) | |
1628 |
|
1629 | |||
1629 | if self._aborted: |
|
1630 | if self._aborted: | |
1630 | raise KeyboardInterrupt() |
|
1631 | raise KeyboardInterrupt() | |
1631 |
|
1632 | |||
1632 | # Do not merge output if skipped. Return hghave message instead. |
|
1633 | # Do not merge output if skipped. Return hghave message instead. | |
1633 | # Similarly, with --debug, output is None. |
|
1634 | # Similarly, with --debug, output is None. | |
1634 | if exitcode == self.SKIPPED_STATUS or output is None: |
|
1635 | if exitcode == self.SKIPPED_STATUS or output is None: | |
1635 | return exitcode, output |
|
1636 | return exitcode, output | |
1636 |
|
1637 | |||
1637 | return self._processoutput(exitcode, output, salt, after, expected) |
|
1638 | return self._processoutput(exitcode, output, salt, after, expected) | |
1638 |
|
1639 | |||
1639 | def _hghave(self, reqs): |
|
1640 | def _hghave(self, reqs): | |
1640 | allreqs = b' '.join(reqs) |
|
1641 | allreqs = b' '.join(reqs) | |
1641 |
|
1642 | |||
1642 | self._detectslow(reqs) |
|
1643 | self._detectslow(reqs) | |
1643 |
|
1644 | |||
1644 | if allreqs in self._have: |
|
1645 | if allreqs in self._have: | |
1645 | return self._have.get(allreqs) |
|
1646 | return self._have.get(allreqs) | |
1646 |
|
1647 | |||
1647 | # TODO do something smarter when all other uses of hghave are gone. |
|
1648 | # TODO do something smarter when all other uses of hghave are gone. | |
1648 | runtestdir = osenvironb[b'RUNTESTDIR'] |
|
1649 | runtestdir = osenvironb[b'RUNTESTDIR'] | |
1649 | tdir = runtestdir.replace(b'\\', b'/') |
|
1650 | tdir = runtestdir.replace(b'\\', b'/') | |
1650 | proc = Popen4( |
|
1651 | proc = Popen4( | |
1651 | b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs), |
|
1652 | b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs), | |
1652 | self._testtmp, |
|
1653 | self._testtmp, | |
1653 | 0, |
|
1654 | 0, | |
1654 | self._getenv(), |
|
1655 | self._getenv(), | |
1655 | ) |
|
1656 | ) | |
1656 | stdout, stderr = proc.communicate() |
|
1657 | stdout, stderr = proc.communicate() | |
1657 | ret = proc.wait() |
|
1658 | ret = proc.wait() | |
1658 | if wifexited(ret): |
|
1659 | if wifexited(ret): | |
1659 | ret = os.WEXITSTATUS(ret) |
|
1660 | ret = os.WEXITSTATUS(ret) | |
1660 | if ret == 2: |
|
1661 | if ret == 2: | |
1661 | print(stdout.decode('utf-8')) |
|
1662 | print(stdout.decode('utf-8')) | |
1662 | sys.exit(1) |
|
1663 | sys.exit(1) | |
1663 |
|
1664 | |||
1664 | if ret != 0: |
|
1665 | if ret != 0: | |
1665 | self._have[allreqs] = (False, stdout) |
|
1666 | self._have[allreqs] = (False, stdout) | |
1666 | return False, stdout |
|
1667 | return False, stdout | |
1667 |
|
1668 | |||
1668 | self._have[allreqs] = (True, None) |
|
1669 | self._have[allreqs] = (True, None) | |
1669 | return True, None |
|
1670 | return True, None | |
1670 |
|
1671 | |||
1671 | def _detectslow(self, reqs): |
|
1672 | def _detectslow(self, reqs): | |
1672 | """update the timeout of slow test when appropriate""" |
|
1673 | """update the timeout of slow test when appropriate""" | |
1673 | if b'slow' in reqs: |
|
1674 | if b'slow' in reqs: | |
1674 | self._timeout = self._slowtimeout |
|
1675 | self._timeout = self._slowtimeout | |
1675 |
|
1676 | |||
1676 | def _iftest(self, args): |
|
1677 | def _iftest(self, args): | |
1677 | # implements "#if" |
|
1678 | # implements "#if" | |
1678 | reqs = [] |
|
1679 | reqs = [] | |
1679 | for arg in args: |
|
1680 | for arg in args: | |
1680 | if arg.startswith(b'no-') and arg[3:] in self._allcases: |
|
1681 | if arg.startswith(b'no-') and arg[3:] in self._allcases: | |
1681 | if arg[3:] in self._case: |
|
1682 | if arg[3:] in self._case: | |
1682 | return False |
|
1683 | return False | |
1683 | elif arg in self._allcases: |
|
1684 | elif arg in self._allcases: | |
1684 | if arg not in self._case: |
|
1685 | if arg not in self._case: | |
1685 | return False |
|
1686 | return False | |
1686 | else: |
|
1687 | else: | |
1687 | reqs.append(arg) |
|
1688 | reqs.append(arg) | |
1688 | self._detectslow(reqs) |
|
1689 | self._detectslow(reqs) | |
1689 | return self._hghave(reqs)[0] |
|
1690 | return self._hghave(reqs)[0] | |
1690 |
|
1691 | |||
1691 | def _parsetest(self, lines): |
|
1692 | def _parsetest(self, lines): | |
1692 | # We generate a shell script which outputs unique markers to line |
|
1693 | # We generate a shell script which outputs unique markers to line | |
1693 | # up script results with our source. These markers include input |
|
1694 | # up script results with our source. These markers include input | |
1694 | # line number and the last return code. |
|
1695 | # line number and the last return code. | |
1695 | salt = b"SALT%d" % time.time() |
|
1696 | salt = b"SALT%d" % time.time() | |
1696 |
|
1697 | |||
1697 | def addsalt(line, inpython): |
|
1698 | def addsalt(line, inpython): | |
1698 | if inpython: |
|
1699 | if inpython: | |
1699 | script.append(b'%s %d 0\n' % (salt, line)) |
|
1700 | script.append(b'%s %d 0\n' % (salt, line)) | |
1700 | else: |
|
1701 | else: | |
1701 | script.append(b'echo %s %d $?\n' % (salt, line)) |
|
1702 | script.append(b'echo %s %d $?\n' % (salt, line)) | |
1702 |
|
1703 | |||
1703 | activetrace = [] |
|
1704 | activetrace = [] | |
1704 | session = str(uuid.uuid4()) |
|
1705 | session = str(uuid.uuid4()) | |
1705 | if PYTHON3: |
|
1706 | if PYTHON3: | |
1706 | session = session.encode('ascii') |
|
1707 | session = session.encode('ascii') | |
1707 | hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv( |
|
1708 | hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv( | |
1708 | 'HGCATAPULTSERVERPIPE' |
|
1709 | 'HGCATAPULTSERVERPIPE' | |
1709 | ) |
|
1710 | ) | |
1710 |
|
1711 | |||
1711 | def toggletrace(cmd=None): |
|
1712 | def toggletrace(cmd=None): | |
1712 | if not hgcatapult or hgcatapult == os.devnull: |
|
1713 | if not hgcatapult or hgcatapult == os.devnull: | |
1713 | return |
|
1714 | return | |
1714 |
|
1715 | |||
1715 | if activetrace: |
|
1716 | if activetrace: | |
1716 | script.append( |
|
1717 | script.append( | |
1717 | b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' |
|
1718 | b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' | |
1718 | % (session, activetrace[0]) |
|
1719 | % (session, activetrace[0]) | |
1719 | ) |
|
1720 | ) | |
1720 | if cmd is None: |
|
1721 | if cmd is None: | |
1721 | return |
|
1722 | return | |
1722 |
|
1723 | |||
1723 | if isinstance(cmd, str): |
|
1724 | if isinstance(cmd, str): | |
1724 | quoted = shellquote(cmd.strip()) |
|
1725 | quoted = shellquote(cmd.strip()) | |
1725 | else: |
|
1726 | else: | |
1726 | quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8') |
|
1727 | quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8') | |
1727 | quoted = quoted.replace(b'\\', b'\\\\') |
|
1728 | quoted = quoted.replace(b'\\', b'\\\\') | |
1728 | script.append( |
|
1729 | script.append( | |
1729 | b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' |
|
1730 | b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' | |
1730 | % (session, quoted) |
|
1731 | % (session, quoted) | |
1731 | ) |
|
1732 | ) | |
1732 | activetrace[0:] = [quoted] |
|
1733 | activetrace[0:] = [quoted] | |
1733 |
|
1734 | |||
1734 | script = [] |
|
1735 | script = [] | |
1735 |
|
1736 | |||
1736 | # After we run the shell script, we re-unify the script output |
|
1737 | # After we run the shell script, we re-unify the script output | |
1737 | # with non-active parts of the source, with synchronization by our |
|
1738 | # with non-active parts of the source, with synchronization by our | |
1738 | # SALT line number markers. The after table contains the non-active |
|
1739 | # SALT line number markers. The after table contains the non-active | |
1739 | # components, ordered by line number. |
|
1740 | # components, ordered by line number. | |
1740 | after = {} |
|
1741 | after = {} | |
1741 |
|
1742 | |||
1742 | # Expected shell script output. |
|
1743 | # Expected shell script output. | |
1743 | expected = {} |
|
1744 | expected = {} | |
1744 |
|
1745 | |||
1745 | pos = prepos = -1 |
|
1746 | pos = prepos = -1 | |
1746 |
|
1747 | |||
1747 | # True or False when in a true or false conditional section |
|
1748 | # True or False when in a true or false conditional section | |
1748 | skipping = None |
|
1749 | skipping = None | |
1749 |
|
1750 | |||
1750 | # We keep track of whether or not we're in a Python block so we |
|
1751 | # We keep track of whether or not we're in a Python block so we | |
1751 | # can generate the surrounding doctest magic. |
|
1752 | # can generate the surrounding doctest magic. | |
1752 | inpython = False |
|
1753 | inpython = False | |
1753 |
|
1754 | |||
1754 | if self._debug: |
|
1755 | if self._debug: | |
1755 | script.append(b'set -x\n') |
|
1756 | script.append(b'set -x\n') | |
1756 | if self._hgcommand != b'hg': |
|
1757 | if self._hgcommand != b'hg': | |
1757 | script.append(b'alias hg="%s"\n' % self._hgcommand) |
|
1758 | script.append(b'alias hg="%s"\n' % self._hgcommand) | |
1758 | if os.getenv('MSYSTEM'): |
|
1759 | if os.getenv('MSYSTEM'): | |
1759 | script.append(b'alias pwd="pwd -W"\n') |
|
1760 | script.append(b'alias pwd="pwd -W"\n') | |
1760 |
|
1761 | |||
1761 | if hgcatapult and hgcatapult != os.devnull: |
|
1762 | if hgcatapult and hgcatapult != os.devnull: | |
1762 | if PYTHON3: |
|
1763 | if PYTHON3: | |
1763 | hgcatapult = hgcatapult.encode('utf8') |
|
1764 | hgcatapult = hgcatapult.encode('utf8') | |
1764 | cataname = self.name.encode('utf8') |
|
1765 | cataname = self.name.encode('utf8') | |
1765 | else: |
|
1766 | else: | |
1766 | cataname = self.name |
|
1767 | cataname = self.name | |
1767 |
|
1768 | |||
1768 | # Kludge: use a while loop to keep the pipe from getting |
|
1769 | # Kludge: use a while loop to keep the pipe from getting | |
1769 | # closed by our echo commands. The still-running file gets |
|
1770 | # closed by our echo commands. The still-running file gets | |
1770 | # reaped at the end of the script, which causes the while |
|
1771 | # reaped at the end of the script, which causes the while | |
1771 | # loop to exit and closes the pipe. Sigh. |
|
1772 | # loop to exit and closes the pipe. Sigh. | |
1772 | script.append( |
|
1773 | script.append( | |
1773 | b'rtendtracing() {\n' |
|
1774 | b'rtendtracing() {\n' | |
1774 | b' echo END %(session)s %(name)s >> %(catapult)s\n' |
|
1775 | b' echo END %(session)s %(name)s >> %(catapult)s\n' | |
1775 | b' rm -f "$TESTTMP/.still-running"\n' |
|
1776 | b' rm -f "$TESTTMP/.still-running"\n' | |
1776 | b'}\n' |
|
1777 | b'}\n' | |
1777 | b'trap "rtendtracing" 0\n' |
|
1778 | b'trap "rtendtracing" 0\n' | |
1778 | b'touch "$TESTTMP/.still-running"\n' |
|
1779 | b'touch "$TESTTMP/.still-running"\n' | |
1779 | b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done ' |
|
1780 | b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done ' | |
1780 | b'> %(catapult)s &\n' |
|
1781 | b'> %(catapult)s &\n' | |
1781 | b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n' |
|
1782 | b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n' | |
1782 | b'echo START %(session)s %(name)s >> %(catapult)s\n' |
|
1783 | b'echo START %(session)s %(name)s >> %(catapult)s\n' | |
1783 | % { |
|
1784 | % { | |
1784 | b'name': cataname, |
|
1785 | b'name': cataname, | |
1785 | b'session': session, |
|
1786 | b'session': session, | |
1786 | b'catapult': hgcatapult, |
|
1787 | b'catapult': hgcatapult, | |
1787 | } |
|
1788 | } | |
1788 | ) |
|
1789 | ) | |
1789 |
|
1790 | |||
1790 | if self._case: |
|
1791 | if self._case: | |
1791 | casestr = b'#'.join(self._case) |
|
1792 | casestr = b'#'.join(self._case) | |
1792 | if isinstance(casestr, str): |
|
1793 | if isinstance(casestr, str): | |
1793 | quoted = shellquote(casestr) |
|
1794 | quoted = shellquote(casestr) | |
1794 | else: |
|
1795 | else: | |
1795 | quoted = shellquote(casestr.decode('utf8')).encode('utf8') |
|
1796 | quoted = shellquote(casestr.decode('utf8')).encode('utf8') | |
1796 | script.append(b'TESTCASE=%s\n' % quoted) |
|
1797 | script.append(b'TESTCASE=%s\n' % quoted) | |
1797 | script.append(b'export TESTCASE\n') |
|
1798 | script.append(b'export TESTCASE\n') | |
1798 |
|
1799 | |||
1799 | n = 0 |
|
1800 | n = 0 | |
1800 | for n, l in enumerate(lines): |
|
1801 | for n, l in enumerate(lines): | |
1801 | if not l.endswith(b'\n'): |
|
1802 | if not l.endswith(b'\n'): | |
1802 | l += b'\n' |
|
1803 | l += b'\n' | |
1803 | if l.startswith(b'#require'): |
|
1804 | if l.startswith(b'#require'): | |
1804 | lsplit = l.split() |
|
1805 | lsplit = l.split() | |
1805 | if len(lsplit) < 2 or lsplit[0] != b'#require': |
|
1806 | if len(lsplit) < 2 or lsplit[0] != b'#require': | |
1806 | after.setdefault(pos, []).append( |
|
1807 | after.setdefault(pos, []).append( | |
1807 | b' !!! invalid #require\n' |
|
1808 | b' !!! invalid #require\n' | |
1808 | ) |
|
1809 | ) | |
1809 | if not skipping: |
|
1810 | if not skipping: | |
1810 | haveresult, message = self._hghave(lsplit[1:]) |
|
1811 | haveresult, message = self._hghave(lsplit[1:]) | |
1811 | if not haveresult: |
|
1812 | if not haveresult: | |
1812 | script = [b'echo "%s"\nexit 80\n' % message] |
|
1813 | script = [b'echo "%s"\nexit 80\n' % message] | |
1813 | break |
|
1814 | break | |
1814 | after.setdefault(pos, []).append(l) |
|
1815 | after.setdefault(pos, []).append(l) | |
1815 | elif l.startswith(b'#if'): |
|
1816 | elif l.startswith(b'#if'): | |
1816 | lsplit = l.split() |
|
1817 | lsplit = l.split() | |
1817 | if len(lsplit) < 2 or lsplit[0] != b'#if': |
|
1818 | if len(lsplit) < 2 or lsplit[0] != b'#if': | |
1818 | after.setdefault(pos, []).append(b' !!! invalid #if\n') |
|
1819 | after.setdefault(pos, []).append(b' !!! invalid #if\n') | |
1819 | if skipping is not None: |
|
1820 | if skipping is not None: | |
1820 | after.setdefault(pos, []).append(b' !!! nested #if\n') |
|
1821 | after.setdefault(pos, []).append(b' !!! nested #if\n') | |
1821 | skipping = not self._iftest(lsplit[1:]) |
|
1822 | skipping = not self._iftest(lsplit[1:]) | |
1822 | after.setdefault(pos, []).append(l) |
|
1823 | after.setdefault(pos, []).append(l) | |
1823 | elif l.startswith(b'#else'): |
|
1824 | elif l.startswith(b'#else'): | |
1824 | if skipping is None: |
|
1825 | if skipping is None: | |
1825 | after.setdefault(pos, []).append(b' !!! missing #if\n') |
|
1826 | after.setdefault(pos, []).append(b' !!! missing #if\n') | |
1826 | skipping = not skipping |
|
1827 | skipping = not skipping | |
1827 | after.setdefault(pos, []).append(l) |
|
1828 | after.setdefault(pos, []).append(l) | |
1828 | elif l.startswith(b'#endif'): |
|
1829 | elif l.startswith(b'#endif'): | |
1829 | if skipping is None: |
|
1830 | if skipping is None: | |
1830 | after.setdefault(pos, []).append(b' !!! missing #if\n') |
|
1831 | after.setdefault(pos, []).append(b' !!! missing #if\n') | |
1831 | skipping = None |
|
1832 | skipping = None | |
1832 | after.setdefault(pos, []).append(l) |
|
1833 | after.setdefault(pos, []).append(l) | |
1833 | elif skipping: |
|
1834 | elif skipping: | |
1834 | after.setdefault(pos, []).append(l) |
|
1835 | after.setdefault(pos, []).append(l) | |
1835 | elif l.startswith(b' >>> '): # python inlines |
|
1836 | elif l.startswith(b' >>> '): # python inlines | |
1836 | after.setdefault(pos, []).append(l) |
|
1837 | after.setdefault(pos, []).append(l) | |
1837 | prepos = pos |
|
1838 | prepos = pos | |
1838 | pos = n |
|
1839 | pos = n | |
1839 | if not inpython: |
|
1840 | if not inpython: | |
1840 | # We've just entered a Python block. Add the header. |
|
1841 | # We've just entered a Python block. Add the header. | |
1841 | inpython = True |
|
1842 | inpython = True | |
1842 | addsalt(prepos, False) # Make sure we report the exit code. |
|
1843 | addsalt(prepos, False) # Make sure we report the exit code. | |
1843 | script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON) |
|
1844 | script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON) | |
1844 | addsalt(n, True) |
|
1845 | addsalt(n, True) | |
1845 | script.append(l[2:]) |
|
1846 | script.append(l[2:]) | |
1846 | elif l.startswith(b' ... '): # python inlines |
|
1847 | elif l.startswith(b' ... '): # python inlines | |
1847 | after.setdefault(prepos, []).append(l) |
|
1848 | after.setdefault(prepos, []).append(l) | |
1848 | script.append(l[2:]) |
|
1849 | script.append(l[2:]) | |
1849 | elif l.startswith(b' $ '): # commands |
|
1850 | elif l.startswith(b' $ '): # commands | |
1850 | if inpython: |
|
1851 | if inpython: | |
1851 | script.append(b'EOF\n') |
|
1852 | script.append(b'EOF\n') | |
1852 | inpython = False |
|
1853 | inpython = False | |
1853 | after.setdefault(pos, []).append(l) |
|
1854 | after.setdefault(pos, []).append(l) | |
1854 | prepos = pos |
|
1855 | prepos = pos | |
1855 | pos = n |
|
1856 | pos = n | |
1856 | addsalt(n, False) |
|
1857 | addsalt(n, False) | |
1857 | rawcmd = l[4:] |
|
1858 | rawcmd = l[4:] | |
1858 | cmd = rawcmd.split() |
|
1859 | cmd = rawcmd.split() | |
1859 | toggletrace(rawcmd) |
|
1860 | toggletrace(rawcmd) | |
1860 | if len(cmd) == 2 and cmd[0] == b'cd': |
|
1861 | if len(cmd) == 2 and cmd[0] == b'cd': | |
1861 | rawcmd = b'cd %s || exit 1\n' % cmd[1] |
|
1862 | rawcmd = b'cd %s || exit 1\n' % cmd[1] | |
1862 | script.append(rawcmd) |
|
1863 | script.append(rawcmd) | |
1863 | elif l.startswith(b' > '): # continuations |
|
1864 | elif l.startswith(b' > '): # continuations | |
1864 | after.setdefault(prepos, []).append(l) |
|
1865 | after.setdefault(prepos, []).append(l) | |
1865 | script.append(l[4:]) |
|
1866 | script.append(l[4:]) | |
1866 | elif l.startswith(b' '): # results |
|
1867 | elif l.startswith(b' '): # results | |
1867 | # Queue up a list of expected results. |
|
1868 | # Queue up a list of expected results. | |
1868 | expected.setdefault(pos, []).append(l[2:]) |
|
1869 | expected.setdefault(pos, []).append(l[2:]) | |
1869 | else: |
|
1870 | else: | |
1870 | if inpython: |
|
1871 | if inpython: | |
1871 | script.append(b'EOF\n') |
|
1872 | script.append(b'EOF\n') | |
1872 | inpython = False |
|
1873 | inpython = False | |
1873 | # Non-command/result. Queue up for merged output. |
|
1874 | # Non-command/result. Queue up for merged output. | |
1874 | after.setdefault(pos, []).append(l) |
|
1875 | after.setdefault(pos, []).append(l) | |
1875 |
|
1876 | |||
1876 | if inpython: |
|
1877 | if inpython: | |
1877 | script.append(b'EOF\n') |
|
1878 | script.append(b'EOF\n') | |
1878 | if skipping is not None: |
|
1879 | if skipping is not None: | |
1879 | after.setdefault(pos, []).append(b' !!! missing #endif\n') |
|
1880 | after.setdefault(pos, []).append(b' !!! missing #endif\n') | |
1880 | addsalt(n + 1, False) |
|
1881 | addsalt(n + 1, False) | |
1881 | # Need to end any current per-command trace |
|
1882 | # Need to end any current per-command trace | |
1882 | if activetrace: |
|
1883 | if activetrace: | |
1883 | toggletrace() |
|
1884 | toggletrace() | |
1884 | return salt, script, after, expected |
|
1885 | return salt, script, after, expected | |
1885 |
|
1886 | |||
1886 | def _processoutput(self, exitcode, output, salt, after, expected): |
|
1887 | def _processoutput(self, exitcode, output, salt, after, expected): | |
1887 | # Merge the script output back into a unified test. |
|
1888 | # Merge the script output back into a unified test. | |
1888 | warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not |
|
1889 | warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not | |
1889 | if exitcode != 0: |
|
1890 | if exitcode != 0: | |
1890 | warnonly = WARN_NO |
|
1891 | warnonly = WARN_NO | |
1891 |
|
1892 | |||
1892 | pos = -1 |
|
1893 | pos = -1 | |
1893 | postout = [] |
|
1894 | postout = [] | |
1894 | for out_rawline in output: |
|
1895 | for out_rawline in output: | |
1895 | out_line, cmd_line = out_rawline, None |
|
1896 | out_line, cmd_line = out_rawline, None | |
1896 | if salt in out_rawline: |
|
1897 | if salt in out_rawline: | |
1897 | out_line, cmd_line = out_rawline.split(salt, 1) |
|
1898 | out_line, cmd_line = out_rawline.split(salt, 1) | |
1898 |
|
1899 | |||
1899 | pos, postout, warnonly = self._process_out_line( |
|
1900 | pos, postout, warnonly = self._process_out_line( | |
1900 | out_line, pos, postout, expected, warnonly |
|
1901 | out_line, pos, postout, expected, warnonly | |
1901 | ) |
|
1902 | ) | |
1902 | pos, postout = self._process_cmd_line(cmd_line, pos, postout, after) |
|
1903 | pos, postout = self._process_cmd_line(cmd_line, pos, postout, after) | |
1903 |
|
1904 | |||
1904 | if pos in after: |
|
1905 | if pos in after: | |
1905 | postout += after.pop(pos) |
|
1906 | postout += after.pop(pos) | |
1906 |
|
1907 | |||
1907 | if warnonly == WARN_YES: |
|
1908 | if warnonly == WARN_YES: | |
1908 | exitcode = False # Set exitcode to warned. |
|
1909 | exitcode = False # Set exitcode to warned. | |
1909 |
|
1910 | |||
1910 | return exitcode, postout |
|
1911 | return exitcode, postout | |
1911 |
|
1912 | |||
1912 | def _process_out_line(self, out_line, pos, postout, expected, warnonly): |
|
1913 | def _process_out_line(self, out_line, pos, postout, expected, warnonly): | |
1913 | while out_line: |
|
1914 | while out_line: | |
1914 | if not out_line.endswith(b'\n'): |
|
1915 | if not out_line.endswith(b'\n'): | |
1915 | out_line += b' (no-eol)\n' |
|
1916 | out_line += b' (no-eol)\n' | |
1916 |
|
1917 | |||
1917 | # Find the expected output at the current position. |
|
1918 | # Find the expected output at the current position. | |
1918 | els = [None] |
|
1919 | els = [None] | |
1919 | if expected.get(pos, None): |
|
1920 | if expected.get(pos, None): | |
1920 | els = expected[pos] |
|
1921 | els = expected[pos] | |
1921 |
|
1922 | |||
1922 | optional = [] |
|
1923 | optional = [] | |
1923 | for i, el in enumerate(els): |
|
1924 | for i, el in enumerate(els): | |
1924 | r = False |
|
1925 | r = False | |
1925 | if el: |
|
1926 | if el: | |
1926 | r, exact = self.linematch(el, out_line) |
|
1927 | r, exact = self.linematch(el, out_line) | |
1927 | if isinstance(r, str): |
|
1928 | if isinstance(r, str): | |
1928 | if r == '-glob': |
|
1929 | if r == '-glob': | |
1929 | out_line = ''.join(el.rsplit(' (glob)', 1)) |
|
1930 | out_line = ''.join(el.rsplit(' (glob)', 1)) | |
1930 | r = '' # Warn only this line. |
|
1931 | r = '' # Warn only this line. | |
1931 | elif r == "retry": |
|
1932 | elif r == "retry": | |
1932 | postout.append(b' ' + el) |
|
1933 | postout.append(b' ' + el) | |
1933 | else: |
|
1934 | else: | |
1934 | log('\ninfo, unknown linematch result: %r\n' % r) |
|
1935 | log('\ninfo, unknown linematch result: %r\n' % r) | |
1935 | r = False |
|
1936 | r = False | |
1936 | if r: |
|
1937 | if r: | |
1937 | els.pop(i) |
|
1938 | els.pop(i) | |
1938 | break |
|
1939 | break | |
1939 | if el: |
|
1940 | if el: | |
1940 | if isoptional(el): |
|
1941 | if isoptional(el): | |
1941 | optional.append(i) |
|
1942 | optional.append(i) | |
1942 | else: |
|
1943 | else: | |
1943 | m = optline.match(el) |
|
1944 | m = optline.match(el) | |
1944 | if m: |
|
1945 | if m: | |
1945 | conditions = [c for c in m.group(2).split(b' ')] |
|
1946 | conditions = [c for c in m.group(2).split(b' ')] | |
1946 |
|
1947 | |||
1947 | if not self._iftest(conditions): |
|
1948 | if not self._iftest(conditions): | |
1948 | optional.append(i) |
|
1949 | optional.append(i) | |
1949 | if exact: |
|
1950 | if exact: | |
1950 | # Don't allow line to be matches against a later |
|
1951 | # Don't allow line to be matches against a later | |
1951 | # line in the output |
|
1952 | # line in the output | |
1952 | els.pop(i) |
|
1953 | els.pop(i) | |
1953 | break |
|
1954 | break | |
1954 |
|
1955 | |||
1955 | if r: |
|
1956 | if r: | |
1956 | if r == "retry": |
|
1957 | if r == "retry": | |
1957 | continue |
|
1958 | continue | |
1958 | # clean up any optional leftovers |
|
1959 | # clean up any optional leftovers | |
1959 | for i in optional: |
|
1960 | for i in optional: | |
1960 | postout.append(b' ' + els[i]) |
|
1961 | postout.append(b' ' + els[i]) | |
1961 | for i in reversed(optional): |
|
1962 | for i in reversed(optional): | |
1962 | del els[i] |
|
1963 | del els[i] | |
1963 | postout.append(b' ' + el) |
|
1964 | postout.append(b' ' + el) | |
1964 | else: |
|
1965 | else: | |
1965 | if self.NEEDESCAPE(out_line): |
|
1966 | if self.NEEDESCAPE(out_line): | |
1966 | out_line = TTest._stringescape( |
|
1967 | out_line = TTest._stringescape( | |
1967 | b'%s (esc)\n' % out_line.rstrip(b'\n') |
|
1968 | b'%s (esc)\n' % out_line.rstrip(b'\n') | |
1968 | ) |
|
1969 | ) | |
1969 | postout.append(b' ' + out_line) # Let diff deal with it. |
|
1970 | postout.append(b' ' + out_line) # Let diff deal with it. | |
1970 | if r != '': # If line failed. |
|
1971 | if r != '': # If line failed. | |
1971 | warnonly = WARN_NO |
|
1972 | warnonly = WARN_NO | |
1972 | elif warnonly == WARN_UNDEFINED: |
|
1973 | elif warnonly == WARN_UNDEFINED: | |
1973 | warnonly = WARN_YES |
|
1974 | warnonly = WARN_YES | |
1974 | break |
|
1975 | break | |
1975 | else: |
|
1976 | else: | |
1976 | # clean up any optional leftovers |
|
1977 | # clean up any optional leftovers | |
1977 | while expected.get(pos, None): |
|
1978 | while expected.get(pos, None): | |
1978 | el = expected[pos].pop(0) |
|
1979 | el = expected[pos].pop(0) | |
1979 | if el: |
|
1980 | if el: | |
1980 | if not isoptional(el): |
|
1981 | if not isoptional(el): | |
1981 | m = optline.match(el) |
|
1982 | m = optline.match(el) | |
1982 | if m: |
|
1983 | if m: | |
1983 | conditions = [c for c in m.group(2).split(b' ')] |
|
1984 | conditions = [c for c in m.group(2).split(b' ')] | |
1984 |
|
1985 | |||
1985 | if self._iftest(conditions): |
|
1986 | if self._iftest(conditions): | |
1986 | # Don't append as optional line |
|
1987 | # Don't append as optional line | |
1987 | continue |
|
1988 | continue | |
1988 | else: |
|
1989 | else: | |
1989 | continue |
|
1990 | continue | |
1990 | postout.append(b' ' + el) |
|
1991 | postout.append(b' ' + el) | |
1991 | return pos, postout, warnonly |
|
1992 | return pos, postout, warnonly | |
1992 |
|
1993 | |||
1993 | def _process_cmd_line(self, cmd_line, pos, postout, after): |
|
1994 | def _process_cmd_line(self, cmd_line, pos, postout, after): | |
1994 | """process a "command" part of a line from unified test output""" |
|
1995 | """process a "command" part of a line from unified test output""" | |
1995 | if cmd_line: |
|
1996 | if cmd_line: | |
1996 | # Add on last return code. |
|
1997 | # Add on last return code. | |
1997 | ret = int(cmd_line.split()[1]) |
|
1998 | ret = int(cmd_line.split()[1]) | |
1998 | if ret != 0: |
|
1999 | if ret != 0: | |
1999 | postout.append(b' [%d]\n' % ret) |
|
2000 | postout.append(b' [%d]\n' % ret) | |
2000 | if pos in after: |
|
2001 | if pos in after: | |
2001 | # Merge in non-active test bits. |
|
2002 | # Merge in non-active test bits. | |
2002 | postout += after.pop(pos) |
|
2003 | postout += after.pop(pos) | |
2003 | pos = int(cmd_line.split()[0]) |
|
2004 | pos = int(cmd_line.split()[0]) | |
2004 | return pos, postout |
|
2005 | return pos, postout | |
2005 |
|
2006 | |||
2006 | @staticmethod |
|
2007 | @staticmethod | |
2007 | def rematch(el, l): |
|
2008 | def rematch(el, l): | |
2008 | try: |
|
2009 | try: | |
2009 | # parse any flags at the beginning of the regex. Only 'i' is |
|
2010 | # parse any flags at the beginning of the regex. Only 'i' is | |
2010 | # supported right now, but this should be easy to extend. |
|
2011 | # supported right now, but this should be easy to extend. | |
2011 | flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2] |
|
2012 | flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2] | |
2012 | flags = flags or b'' |
|
2013 | flags = flags or b'' | |
2013 | el = flags + b'(?:' + el + b')' |
|
2014 | el = flags + b'(?:' + el + b')' | |
2014 | # use \Z to ensure that the regex matches to the end of the string |
|
2015 | # use \Z to ensure that the regex matches to the end of the string | |
2015 | if os.name == 'nt': |
|
2016 | if os.name == 'nt': | |
2016 | return re.match(el + br'\r?\n\Z', l) |
|
2017 | return re.match(el + br'\r?\n\Z', l) | |
2017 | return re.match(el + br'\n\Z', l) |
|
2018 | return re.match(el + br'\n\Z', l) | |
2018 | except re.error: |
|
2019 | except re.error: | |
2019 | # el is an invalid regex |
|
2020 | # el is an invalid regex | |
2020 | return False |
|
2021 | return False | |
2021 |
|
2022 | |||
2022 | @staticmethod |
|
2023 | @staticmethod | |
2023 | def globmatch(el, l): |
|
2024 | def globmatch(el, l): | |
2024 | # The only supported special characters are * and ? plus / which also |
|
2025 | # The only supported special characters are * and ? plus / which also | |
2025 | # matches \ on windows. Escaping of these characters is supported. |
|
2026 | # matches \ on windows. Escaping of these characters is supported. | |
2026 | if el + b'\n' == l: |
|
2027 | if el + b'\n' == l: | |
2027 | if os.altsep: |
|
2028 | if os.altsep: | |
2028 | # matching on "/" is not needed for this line |
|
2029 | # matching on "/" is not needed for this line | |
2029 | for pat in checkcodeglobpats: |
|
2030 | for pat in checkcodeglobpats: | |
2030 | if pat.match(el): |
|
2031 | if pat.match(el): | |
2031 | return True |
|
2032 | return True | |
2032 | return b'-glob' |
|
2033 | return b'-glob' | |
2033 | return True |
|
2034 | return True | |
2034 | el = el.replace(b'$LOCALIP', b'*') |
|
2035 | el = el.replace(b'$LOCALIP', b'*') | |
2035 | i, n = 0, len(el) |
|
2036 | i, n = 0, len(el) | |
2036 | res = b'' |
|
2037 | res = b'' | |
2037 | while i < n: |
|
2038 | while i < n: | |
2038 | c = el[i : i + 1] |
|
2039 | c = el[i : i + 1] | |
2039 | i += 1 |
|
2040 | i += 1 | |
2040 | if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/': |
|
2041 | if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/': | |
2041 | res += el[i - 1 : i + 1] |
|
2042 | res += el[i - 1 : i + 1] | |
2042 | i += 1 |
|
2043 | i += 1 | |
2043 | elif c == b'*': |
|
2044 | elif c == b'*': | |
2044 | res += b'.*' |
|
2045 | res += b'.*' | |
2045 | elif c == b'?': |
|
2046 | elif c == b'?': | |
2046 | res += b'.' |
|
2047 | res += b'.' | |
2047 | elif c == b'/' and os.altsep: |
|
2048 | elif c == b'/' and os.altsep: | |
2048 | res += b'[/\\\\]' |
|
2049 | res += b'[/\\\\]' | |
2049 | else: |
|
2050 | else: | |
2050 | res += re.escape(c) |
|
2051 | res += re.escape(c) | |
2051 | return TTest.rematch(res, l) |
|
2052 | return TTest.rematch(res, l) | |
2052 |
|
2053 | |||
2053 | def linematch(self, el, l): |
|
2054 | def linematch(self, el, l): | |
2054 | if el == l: # perfect match (fast) |
|
2055 | if el == l: # perfect match (fast) | |
2055 | return True, True |
|
2056 | return True, True | |
2056 | retry = False |
|
2057 | retry = False | |
2057 | if isoptional(el): |
|
2058 | if isoptional(el): | |
2058 | retry = "retry" |
|
2059 | retry = "retry" | |
2059 | el = el[: -len(MARK_OPTIONAL)] + b"\n" |
|
2060 | el = el[: -len(MARK_OPTIONAL)] + b"\n" | |
2060 | else: |
|
2061 | else: | |
2061 | m = optline.match(el) |
|
2062 | m = optline.match(el) | |
2062 | if m: |
|
2063 | if m: | |
2063 | conditions = [c for c in m.group(2).split(b' ')] |
|
2064 | conditions = [c for c in m.group(2).split(b' ')] | |
2064 |
|
2065 | |||
2065 | el = m.group(1) + b"\n" |
|
2066 | el = m.group(1) + b"\n" | |
2066 | if not self._iftest(conditions): |
|
2067 | if not self._iftest(conditions): | |
2067 | # listed feature missing, should not match |
|
2068 | # listed feature missing, should not match | |
2068 | return "retry", False |
|
2069 | return "retry", False | |
2069 |
|
2070 | |||
2070 | if el.endswith(b" (esc)\n"): |
|
2071 | if el.endswith(b" (esc)\n"): | |
2071 | if PYTHON3: |
|
2072 | if PYTHON3: | |
2072 | el = el[:-7].decode('unicode_escape') + '\n' |
|
2073 | el = el[:-7].decode('unicode_escape') + '\n' | |
2073 | el = el.encode('latin-1') |
|
2074 | el = el.encode('latin-1') | |
2074 | else: |
|
2075 | else: | |
2075 | el = el[:-7].decode('string-escape') + '\n' |
|
2076 | el = el[:-7].decode('string-escape') + '\n' | |
2076 | if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l: |
|
2077 | if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l: | |
2077 | return True, True |
|
2078 | return True, True | |
2078 | if el.endswith(b" (re)\n"): |
|
2079 | if el.endswith(b" (re)\n"): | |
2079 | return (TTest.rematch(el[:-6], l) or retry), False |
|
2080 | return (TTest.rematch(el[:-6], l) or retry), False | |
2080 | if el.endswith(b" (glob)\n"): |
|
2081 | if el.endswith(b" (glob)\n"): | |
2081 | # ignore '(glob)' added to l by 'replacements' |
|
2082 | # ignore '(glob)' added to l by 'replacements' | |
2082 | if l.endswith(b" (glob)\n"): |
|
2083 | if l.endswith(b" (glob)\n"): | |
2083 | l = l[:-8] + b"\n" |
|
2084 | l = l[:-8] + b"\n" | |
2084 | return (TTest.globmatch(el[:-8], l) or retry), False |
|
2085 | return (TTest.globmatch(el[:-8], l) or retry), False | |
2085 | if os.altsep: |
|
2086 | if os.altsep: | |
2086 | _l = l.replace(b'\\', b'/') |
|
2087 | _l = l.replace(b'\\', b'/') | |
2087 | if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l: |
|
2088 | if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l: | |
2088 | return True, True |
|
2089 | return True, True | |
2089 | return retry, True |
|
2090 | return retry, True | |
2090 |
|
2091 | |||
2091 | @staticmethod |
|
2092 | @staticmethod | |
2092 | def parsehghaveoutput(lines): |
|
2093 | def parsehghaveoutput(lines): | |
2093 | '''Parse hghave log lines. |
|
2094 | '''Parse hghave log lines. | |
2094 |
|
2095 | |||
2095 | Return tuple of lists (missing, failed): |
|
2096 | Return tuple of lists (missing, failed): | |
2096 | * the missing/unknown features |
|
2097 | * the missing/unknown features | |
2097 | * the features for which existence check failed''' |
|
2098 | * the features for which existence check failed''' | |
2098 | missing = [] |
|
2099 | missing = [] | |
2099 | failed = [] |
|
2100 | failed = [] | |
2100 | for line in lines: |
|
2101 | for line in lines: | |
2101 | if line.startswith(TTest.SKIPPED_PREFIX): |
|
2102 | if line.startswith(TTest.SKIPPED_PREFIX): | |
2102 | line = line.splitlines()[0] |
|
2103 | line = line.splitlines()[0] | |
2103 | missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :])) |
|
2104 | missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :])) | |
2104 | elif line.startswith(TTest.FAILED_PREFIX): |
|
2105 | elif line.startswith(TTest.FAILED_PREFIX): | |
2105 | line = line.splitlines()[0] |
|
2106 | line = line.splitlines()[0] | |
2106 | failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :])) |
|
2107 | failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :])) | |
2107 |
|
2108 | |||
2108 | return missing, failed |
|
2109 | return missing, failed | |
2109 |
|
2110 | |||
2110 | @staticmethod |
|
2111 | @staticmethod | |
2111 | def _escapef(m): |
|
2112 | def _escapef(m): | |
2112 | return TTest.ESCAPEMAP[m.group(0)] |
|
2113 | return TTest.ESCAPEMAP[m.group(0)] | |
2113 |
|
2114 | |||
2114 | @staticmethod |
|
2115 | @staticmethod | |
2115 | def _stringescape(s): |
|
2116 | def _stringescape(s): | |
2116 | return TTest.ESCAPESUB(TTest._escapef, s) |
|
2117 | return TTest.ESCAPESUB(TTest._escapef, s) | |
2117 |
|
2118 | |||
2118 |
|
2119 | |||
2119 | iolock = threading.RLock() |
|
2120 | iolock = threading.RLock() | |
2120 | firstlock = threading.RLock() |
|
2121 | firstlock = threading.RLock() | |
2121 | firsterror = False |
|
2122 | firsterror = False | |
2122 |
|
2123 | |||
2123 |
|
2124 | |||
2124 | class TestResult(unittest._TextTestResult): |
|
2125 | class TestResult(unittest._TextTestResult): | |
2125 | """Holds results when executing via unittest.""" |
|
2126 | """Holds results when executing via unittest.""" | |
2126 |
|
2127 | |||
2127 | # Don't worry too much about accessing the non-public _TextTestResult. |
|
2128 | # Don't worry too much about accessing the non-public _TextTestResult. | |
2128 | # It is relatively common in Python testing tools. |
|
2129 | # It is relatively common in Python testing tools. | |
2129 | def __init__(self, options, *args, **kwargs): |
|
2130 | def __init__(self, options, *args, **kwargs): | |
2130 | super(TestResult, self).__init__(*args, **kwargs) |
|
2131 | super(TestResult, self).__init__(*args, **kwargs) | |
2131 |
|
2132 | |||
2132 | self._options = options |
|
2133 | self._options = options | |
2133 |
|
2134 | |||
2134 | # unittest.TestResult didn't have skipped until 2.7. We need to |
|
2135 | # unittest.TestResult didn't have skipped until 2.7. We need to | |
2135 | # polyfill it. |
|
2136 | # polyfill it. | |
2136 | self.skipped = [] |
|
2137 | self.skipped = [] | |
2137 |
|
2138 | |||
2138 | # We have a custom "ignored" result that isn't present in any Python |
|
2139 | # We have a custom "ignored" result that isn't present in any Python | |
2139 | # unittest implementation. It is very similar to skipped. It may make |
|
2140 | # unittest implementation. It is very similar to skipped. It may make | |
2140 | # sense to map it into skip some day. |
|
2141 | # sense to map it into skip some day. | |
2141 | self.ignored = [] |
|
2142 | self.ignored = [] | |
2142 |
|
2143 | |||
2143 | self.times = [] |
|
2144 | self.times = [] | |
2144 | self._firststarttime = None |
|
2145 | self._firststarttime = None | |
2145 | # Data stored for the benefit of generating xunit reports. |
|
2146 | # Data stored for the benefit of generating xunit reports. | |
2146 | self.successes = [] |
|
2147 | self.successes = [] | |
2147 | self.faildata = {} |
|
2148 | self.faildata = {} | |
2148 |
|
2149 | |||
2149 | if options.color == 'auto': |
|
2150 | if options.color == 'auto': | |
2150 | self.color = pygmentspresent and self.stream.isatty() |
|
2151 | self.color = pygmentspresent and self.stream.isatty() | |
2151 | elif options.color == 'never': |
|
2152 | elif options.color == 'never': | |
2152 | self.color = False |
|
2153 | self.color = False | |
2153 | else: # 'always', for testing purposes |
|
2154 | else: # 'always', for testing purposes | |
2154 | self.color = pygmentspresent |
|
2155 | self.color = pygmentspresent | |
2155 |
|
2156 | |||
2156 | def onStart(self, test): |
|
2157 | def onStart(self, test): | |
2157 | """ Can be overriden by custom TestResult |
|
2158 | """ Can be overriden by custom TestResult | |
2158 | """ |
|
2159 | """ | |
2159 |
|
2160 | |||
2160 | def onEnd(self): |
|
2161 | def onEnd(self): | |
2161 | """ Can be overriden by custom TestResult |
|
2162 | """ Can be overriden by custom TestResult | |
2162 | """ |
|
2163 | """ | |
2163 |
|
2164 | |||
2164 | def addFailure(self, test, reason): |
|
2165 | def addFailure(self, test, reason): | |
2165 | self.failures.append((test, reason)) |
|
2166 | self.failures.append((test, reason)) | |
2166 |
|
2167 | |||
2167 | if self._options.first: |
|
2168 | if self._options.first: | |
2168 | self.stop() |
|
2169 | self.stop() | |
2169 | else: |
|
2170 | else: | |
2170 | with iolock: |
|
2171 | with iolock: | |
2171 | if reason == "timed out": |
|
2172 | if reason == "timed out": | |
2172 | self.stream.write('t') |
|
2173 | self.stream.write('t') | |
2173 | else: |
|
2174 | else: | |
2174 | if not self._options.nodiff: |
|
2175 | if not self._options.nodiff: | |
2175 | self.stream.write('\n') |
|
2176 | self.stream.write('\n') | |
2176 | # Exclude the '\n' from highlighting to lex correctly |
|
2177 | # Exclude the '\n' from highlighting to lex correctly | |
2177 | formatted = 'ERROR: %s output changed\n' % test |
|
2178 | formatted = 'ERROR: %s output changed\n' % test | |
2178 | self.stream.write(highlightmsg(formatted, self.color)) |
|
2179 | self.stream.write(highlightmsg(formatted, self.color)) | |
2179 | self.stream.write('!') |
|
2180 | self.stream.write('!') | |
2180 |
|
2181 | |||
2181 | self.stream.flush() |
|
2182 | self.stream.flush() | |
2182 |
|
2183 | |||
2183 | def addSuccess(self, test): |
|
2184 | def addSuccess(self, test): | |
2184 | with iolock: |
|
2185 | with iolock: | |
2185 | super(TestResult, self).addSuccess(test) |
|
2186 | super(TestResult, self).addSuccess(test) | |
2186 | self.successes.append(test) |
|
2187 | self.successes.append(test) | |
2187 |
|
2188 | |||
2188 | def addError(self, test, err): |
|
2189 | def addError(self, test, err): | |
2189 | super(TestResult, self).addError(test, err) |
|
2190 | super(TestResult, self).addError(test, err) | |
2190 | if self._options.first: |
|
2191 | if self._options.first: | |
2191 | self.stop() |
|
2192 | self.stop() | |
2192 |
|
2193 | |||
2193 | # Polyfill. |
|
2194 | # Polyfill. | |
2194 | def addSkip(self, test, reason): |
|
2195 | def addSkip(self, test, reason): | |
2195 | self.skipped.append((test, reason)) |
|
2196 | self.skipped.append((test, reason)) | |
2196 | with iolock: |
|
2197 | with iolock: | |
2197 | if self.showAll: |
|
2198 | if self.showAll: | |
2198 | self.stream.writeln('skipped %s' % reason) |
|
2199 | self.stream.writeln('skipped %s' % reason) | |
2199 | else: |
|
2200 | else: | |
2200 | self.stream.write('s') |
|
2201 | self.stream.write('s') | |
2201 | self.stream.flush() |
|
2202 | self.stream.flush() | |
2202 |
|
2203 | |||
2203 | def addIgnore(self, test, reason): |
|
2204 | def addIgnore(self, test, reason): | |
2204 | self.ignored.append((test, reason)) |
|
2205 | self.ignored.append((test, reason)) | |
2205 | with iolock: |
|
2206 | with iolock: | |
2206 | if self.showAll: |
|
2207 | if self.showAll: | |
2207 | self.stream.writeln('ignored %s' % reason) |
|
2208 | self.stream.writeln('ignored %s' % reason) | |
2208 | else: |
|
2209 | else: | |
2209 | if reason not in ('not retesting', "doesn't match keyword"): |
|
2210 | if reason not in ('not retesting', "doesn't match keyword"): | |
2210 | self.stream.write('i') |
|
2211 | self.stream.write('i') | |
2211 | else: |
|
2212 | else: | |
2212 | self.testsRun += 1 |
|
2213 | self.testsRun += 1 | |
2213 | self.stream.flush() |
|
2214 | self.stream.flush() | |
2214 |
|
2215 | |||
2215 | def addOutputMismatch(self, test, ret, got, expected): |
|
2216 | def addOutputMismatch(self, test, ret, got, expected): | |
2216 | """Record a mismatch in test output for a particular test.""" |
|
2217 | """Record a mismatch in test output for a particular test.""" | |
2217 | if self.shouldStop or firsterror: |
|
2218 | if self.shouldStop or firsterror: | |
2218 | # don't print, some other test case already failed and |
|
2219 | # don't print, some other test case already failed and | |
2219 | # printed, we're just stale and probably failed due to our |
|
2220 | # printed, we're just stale and probably failed due to our | |
2220 | # temp dir getting cleaned up. |
|
2221 | # temp dir getting cleaned up. | |
2221 | return |
|
2222 | return | |
2222 |
|
2223 | |||
2223 | accepted = False |
|
2224 | accepted = False | |
2224 | lines = [] |
|
2225 | lines = [] | |
2225 |
|
2226 | |||
2226 | with iolock: |
|
2227 | with iolock: | |
2227 | if self._options.nodiff: |
|
2228 | if self._options.nodiff: | |
2228 | pass |
|
2229 | pass | |
2229 | elif self._options.view: |
|
2230 | elif self._options.view: | |
2230 | v = self._options.view |
|
2231 | v = self._options.view | |
2231 | subprocess.call( |
|
2232 | subprocess.call( | |
2232 | r'"%s" "%s" "%s"' |
|
2233 | r'"%s" "%s" "%s"' | |
2233 | % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)), |
|
2234 | % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)), | |
2234 | shell=True, |
|
2235 | shell=True, | |
2235 | ) |
|
2236 | ) | |
2236 | else: |
|
2237 | else: | |
2237 | servefail, lines = getdiff( |
|
2238 | servefail, lines = getdiff( | |
2238 | expected, got, test.refpath, test.errpath |
|
2239 | expected, got, test.refpath, test.errpath | |
2239 | ) |
|
2240 | ) | |
2240 | self.stream.write('\n') |
|
2241 | self.stream.write('\n') | |
2241 | for line in lines: |
|
2242 | for line in lines: | |
2242 | line = highlightdiff(line, self.color) |
|
2243 | line = highlightdiff(line, self.color) | |
2243 | if PYTHON3: |
|
2244 | if PYTHON3: | |
2244 | self.stream.flush() |
|
2245 | self.stream.flush() | |
2245 | self.stream.buffer.write(line) |
|
2246 | self.stream.buffer.write(line) | |
2246 | self.stream.buffer.flush() |
|
2247 | self.stream.buffer.flush() | |
2247 | else: |
|
2248 | else: | |
2248 | self.stream.write(line) |
|
2249 | self.stream.write(line) | |
2249 | self.stream.flush() |
|
2250 | self.stream.flush() | |
2250 |
|
2251 | |||
2251 | if servefail: |
|
2252 | if servefail: | |
2252 | raise test.failureException( |
|
2253 | raise test.failureException( | |
2253 | 'server failed to start (HGPORT=%s)' % test._startport |
|
2254 | 'server failed to start (HGPORT=%s)' % test._startport | |
2254 | ) |
|
2255 | ) | |
2255 |
|
2256 | |||
2256 | # handle interactive prompt without releasing iolock |
|
2257 | # handle interactive prompt without releasing iolock | |
2257 | if self._options.interactive: |
|
2258 | if self._options.interactive: | |
2258 | if test.readrefout() != expected: |
|
2259 | if test.readrefout() != expected: | |
2259 | self.stream.write( |
|
2260 | self.stream.write( | |
2260 | 'Reference output has changed (run again to prompt ' |
|
2261 | 'Reference output has changed (run again to prompt ' | |
2261 | 'changes)' |
|
2262 | 'changes)' | |
2262 | ) |
|
2263 | ) | |
2263 | else: |
|
2264 | else: | |
2264 | self.stream.write('Accept this change? [y/N] ') |
|
2265 | self.stream.write('Accept this change? [y/N] ') | |
2265 | self.stream.flush() |
|
2266 | self.stream.flush() | |
2266 | answer = sys.stdin.readline().strip() |
|
2267 | answer = sys.stdin.readline().strip() | |
2267 | if answer.lower() in ('y', 'yes'): |
|
2268 | if answer.lower() in ('y', 'yes'): | |
2268 | if test.path.endswith(b'.t'): |
|
2269 | if test.path.endswith(b'.t'): | |
2269 | rename(test.errpath, test.path) |
|
2270 | rename(test.errpath, test.path) | |
2270 | else: |
|
2271 | else: | |
2271 | rename(test.errpath, '%s.out' % test.path) |
|
2272 | rename(test.errpath, '%s.out' % test.path) | |
2272 | accepted = True |
|
2273 | accepted = True | |
2273 | if not accepted: |
|
2274 | if not accepted: | |
2274 | self.faildata[test.name] = b''.join(lines) |
|
2275 | self.faildata[test.name] = b''.join(lines) | |
2275 |
|
2276 | |||
2276 | return accepted |
|
2277 | return accepted | |
2277 |
|
2278 | |||
2278 | def startTest(self, test): |
|
2279 | def startTest(self, test): | |
2279 | super(TestResult, self).startTest(test) |
|
2280 | super(TestResult, self).startTest(test) | |
2280 |
|
2281 | |||
2281 | # os.times module computes the user time and system time spent by |
|
2282 | # os.times module computes the user time and system time spent by | |
2282 | # child's processes along with real elapsed time taken by a process. |
|
2283 | # child's processes along with real elapsed time taken by a process. | |
2283 | # This module has one limitation. It can only work for Linux user |
|
2284 | # This module has one limitation. It can only work for Linux user | |
2284 | # and not for Windows. Hence why we fall back to another function |
|
2285 | # and not for Windows. Hence why we fall back to another function | |
2285 | # for wall time calculations. |
|
2286 | # for wall time calculations. | |
2286 | test.started_times = os.times() |
|
2287 | test.started_times = os.times() | |
2287 | # TODO use a monotonic clock once support for Python 2.7 is dropped. |
|
2288 | # TODO use a monotonic clock once support for Python 2.7 is dropped. | |
2288 | test.started_time = time.time() |
|
2289 | test.started_time = time.time() | |
2289 | if self._firststarttime is None: # thread racy but irrelevant |
|
2290 | if self._firststarttime is None: # thread racy but irrelevant | |
2290 | self._firststarttime = test.started_time |
|
2291 | self._firststarttime = test.started_time | |
2291 |
|
2292 | |||
2292 | def stopTest(self, test, interrupted=False): |
|
2293 | def stopTest(self, test, interrupted=False): | |
2293 | super(TestResult, self).stopTest(test) |
|
2294 | super(TestResult, self).stopTest(test) | |
2294 |
|
2295 | |||
2295 | test.stopped_times = os.times() |
|
2296 | test.stopped_times = os.times() | |
2296 | stopped_time = time.time() |
|
2297 | stopped_time = time.time() | |
2297 |
|
2298 | |||
2298 | starttime = test.started_times |
|
2299 | starttime = test.started_times | |
2299 | endtime = test.stopped_times |
|
2300 | endtime = test.stopped_times | |
2300 | origin = self._firststarttime |
|
2301 | origin = self._firststarttime | |
2301 | self.times.append( |
|
2302 | self.times.append( | |
2302 | ( |
|
2303 | ( | |
2303 | test.name, |
|
2304 | test.name, | |
2304 | endtime[2] - starttime[2], # user space CPU time |
|
2305 | endtime[2] - starttime[2], # user space CPU time | |
2305 | endtime[3] - starttime[3], # sys space CPU time |
|
2306 | endtime[3] - starttime[3], # sys space CPU time | |
2306 | stopped_time - test.started_time, # real time |
|
2307 | stopped_time - test.started_time, # real time | |
2307 | test.started_time - origin, # start date in run context |
|
2308 | test.started_time - origin, # start date in run context | |
2308 | stopped_time - origin, # end date in run context |
|
2309 | stopped_time - origin, # end date in run context | |
2309 | ) |
|
2310 | ) | |
2310 | ) |
|
2311 | ) | |
2311 |
|
2312 | |||
2312 | if interrupted: |
|
2313 | if interrupted: | |
2313 | with iolock: |
|
2314 | with iolock: | |
2314 | self.stream.writeln( |
|
2315 | self.stream.writeln( | |
2315 | 'INTERRUPTED: %s (after %d seconds)' |
|
2316 | 'INTERRUPTED: %s (after %d seconds)' | |
2316 | % (test.name, self.times[-1][3]) |
|
2317 | % (test.name, self.times[-1][3]) | |
2317 | ) |
|
2318 | ) | |
2318 |
|
2319 | |||
2319 |
|
2320 | |||
2320 | def getTestResult(): |
|
2321 | def getTestResult(): | |
2321 | """ |
|
2322 | """ | |
2322 | Returns the relevant test result |
|
2323 | Returns the relevant test result | |
2323 | """ |
|
2324 | """ | |
2324 | if "CUSTOM_TEST_RESULT" in os.environ: |
|
2325 | if "CUSTOM_TEST_RESULT" in os.environ: | |
2325 | testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"]) |
|
2326 | testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"]) | |
2326 | return testresultmodule.TestResult |
|
2327 | return testresultmodule.TestResult | |
2327 | else: |
|
2328 | else: | |
2328 | return TestResult |
|
2329 | return TestResult | |
2329 |
|
2330 | |||
2330 |
|
2331 | |||
2331 | class TestSuite(unittest.TestSuite): |
|
2332 | class TestSuite(unittest.TestSuite): | |
2332 | """Custom unittest TestSuite that knows how to execute Mercurial tests.""" |
|
2333 | """Custom unittest TestSuite that knows how to execute Mercurial tests.""" | |
2333 |
|
2334 | |||
2334 | def __init__( |
|
2335 | def __init__( | |
2335 | self, |
|
2336 | self, | |
2336 | testdir, |
|
2337 | testdir, | |
2337 | jobs=1, |
|
2338 | jobs=1, | |
2338 | whitelist=None, |
|
2339 | whitelist=None, | |
2339 | blacklist=None, |
|
2340 | blacklist=None, | |
2340 | keywords=None, |
|
2341 | keywords=None, | |
2341 | loop=False, |
|
2342 | loop=False, | |
2342 | runs_per_test=1, |
|
2343 | runs_per_test=1, | |
2343 | loadtest=None, |
|
2344 | loadtest=None, | |
2344 | showchannels=False, |
|
2345 | showchannels=False, | |
2345 | *args, |
|
2346 | *args, | |
2346 | **kwargs |
|
2347 | **kwargs | |
2347 | ): |
|
2348 | ): | |
2348 | """Create a new instance that can run tests with a configuration. |
|
2349 | """Create a new instance that can run tests with a configuration. | |
2349 |
|
2350 | |||
2350 | testdir specifies the directory where tests are executed from. This |
|
2351 | testdir specifies the directory where tests are executed from. This | |
2351 | is typically the ``tests`` directory from Mercurial's source |
|
2352 | is typically the ``tests`` directory from Mercurial's source | |
2352 | repository. |
|
2353 | repository. | |
2353 |
|
2354 | |||
2354 | jobs specifies the number of jobs to run concurrently. Each test |
|
2355 | jobs specifies the number of jobs to run concurrently. Each test | |
2355 | executes on its own thread. Tests actually spawn new processes, so |
|
2356 | executes on its own thread. Tests actually spawn new processes, so | |
2356 | state mutation should not be an issue. |
|
2357 | state mutation should not be an issue. | |
2357 |
|
2358 | |||
2358 | If there is only one job, it will use the main thread. |
|
2359 | If there is only one job, it will use the main thread. | |
2359 |
|
2360 | |||
2360 | whitelist and blacklist denote tests that have been whitelisted and |
|
2361 | whitelist and blacklist denote tests that have been whitelisted and | |
2361 | blacklisted, respectively. These arguments don't belong in TestSuite. |
|
2362 | blacklisted, respectively. These arguments don't belong in TestSuite. | |
2362 | Instead, whitelist and blacklist should be handled by the thing that |
|
2363 | Instead, whitelist and blacklist should be handled by the thing that | |
2363 | populates the TestSuite with tests. They are present to preserve |
|
2364 | populates the TestSuite with tests. They are present to preserve | |
2364 | backwards compatible behavior which reports skipped tests as part |
|
2365 | backwards compatible behavior which reports skipped tests as part | |
2365 | of the results. |
|
2366 | of the results. | |
2366 |
|
2367 | |||
2367 | keywords denotes key words that will be used to filter which tests |
|
2368 | keywords denotes key words that will be used to filter which tests | |
2368 | to execute. This arguably belongs outside of TestSuite. |
|
2369 | to execute. This arguably belongs outside of TestSuite. | |
2369 |
|
2370 | |||
2370 | loop denotes whether to loop over tests forever. |
|
2371 | loop denotes whether to loop over tests forever. | |
2371 | """ |
|
2372 | """ | |
2372 | super(TestSuite, self).__init__(*args, **kwargs) |
|
2373 | super(TestSuite, self).__init__(*args, **kwargs) | |
2373 |
|
2374 | |||
2374 | self._jobs = jobs |
|
2375 | self._jobs = jobs | |
2375 | self._whitelist = whitelist |
|
2376 | self._whitelist = whitelist | |
2376 | self._blacklist = blacklist |
|
2377 | self._blacklist = blacklist | |
2377 | self._keywords = keywords |
|
2378 | self._keywords = keywords | |
2378 | self._loop = loop |
|
2379 | self._loop = loop | |
2379 | self._runs_per_test = runs_per_test |
|
2380 | self._runs_per_test = runs_per_test | |
2380 | self._loadtest = loadtest |
|
2381 | self._loadtest = loadtest | |
2381 | self._showchannels = showchannels |
|
2382 | self._showchannels = showchannels | |
2382 |
|
2383 | |||
2383 | def run(self, result): |
|
2384 | def run(self, result): | |
2384 | # We have a number of filters that need to be applied. We do this |
|
2385 | # We have a number of filters that need to be applied. We do this | |
2385 | # here instead of inside Test because it makes the running logic for |
|
2386 | # here instead of inside Test because it makes the running logic for | |
2386 | # Test simpler. |
|
2387 | # Test simpler. | |
2387 | tests = [] |
|
2388 | tests = [] | |
2388 | num_tests = [0] |
|
2389 | num_tests = [0] | |
2389 | for test in self._tests: |
|
2390 | for test in self._tests: | |
2390 |
|
2391 | |||
2391 | def get(): |
|
2392 | def get(): | |
2392 | num_tests[0] += 1 |
|
2393 | num_tests[0] += 1 | |
2393 | if getattr(test, 'should_reload', False): |
|
2394 | if getattr(test, 'should_reload', False): | |
2394 | return self._loadtest(test, num_tests[0]) |
|
2395 | return self._loadtest(test, num_tests[0]) | |
2395 | return test |
|
2396 | return test | |
2396 |
|
2397 | |||
2397 | if not os.path.exists(test.path): |
|
2398 | if not os.path.exists(test.path): | |
2398 | result.addSkip(test, "Doesn't exist") |
|
2399 | result.addSkip(test, "Doesn't exist") | |
2399 | continue |
|
2400 | continue | |
2400 |
|
2401 | |||
2401 | is_whitelisted = self._whitelist and ( |
|
2402 | is_whitelisted = self._whitelist and ( | |
2402 | test.relpath in self._whitelist or test.bname in self._whitelist |
|
2403 | test.relpath in self._whitelist or test.bname in self._whitelist | |
2403 | ) |
|
2404 | ) | |
2404 | if not is_whitelisted: |
|
2405 | if not is_whitelisted: | |
2405 | is_blacklisted = self._blacklist and ( |
|
2406 | is_blacklisted = self._blacklist and ( | |
2406 | test.relpath in self._blacklist |
|
2407 | test.relpath in self._blacklist | |
2407 | or test.bname in self._blacklist |
|
2408 | or test.bname in self._blacklist | |
2408 | ) |
|
2409 | ) | |
2409 | if is_blacklisted: |
|
2410 | if is_blacklisted: | |
2410 | result.addSkip(test, 'blacklisted') |
|
2411 | result.addSkip(test, 'blacklisted') | |
2411 | continue |
|
2412 | continue | |
2412 | if self._keywords: |
|
2413 | if self._keywords: | |
2413 | with open(test.path, 'rb') as f: |
|
2414 | with open(test.path, 'rb') as f: | |
2414 | t = f.read().lower() + test.bname.lower() |
|
2415 | t = f.read().lower() + test.bname.lower() | |
2415 | ignored = False |
|
2416 | ignored = False | |
2416 | for k in self._keywords.lower().split(): |
|
2417 | for k in self._keywords.lower().split(): | |
2417 | if k not in t: |
|
2418 | if k not in t: | |
2418 | result.addIgnore(test, "doesn't match keyword") |
|
2419 | result.addIgnore(test, "doesn't match keyword") | |
2419 | ignored = True |
|
2420 | ignored = True | |
2420 | break |
|
2421 | break | |
2421 |
|
2422 | |||
2422 | if ignored: |
|
2423 | if ignored: | |
2423 | continue |
|
2424 | continue | |
2424 | for _ in xrange(self._runs_per_test): |
|
2425 | for _ in xrange(self._runs_per_test): | |
2425 | tests.append(get()) |
|
2426 | tests.append(get()) | |
2426 |
|
2427 | |||
2427 | runtests = list(tests) |
|
2428 | runtests = list(tests) | |
2428 | done = queue.Queue() |
|
2429 | done = queue.Queue() | |
2429 | running = 0 |
|
2430 | running = 0 | |
2430 |
|
2431 | |||
2431 | channels = [""] * self._jobs |
|
2432 | channels = [""] * self._jobs | |
2432 |
|
2433 | |||
2433 | def job(test, result): |
|
2434 | def job(test, result): | |
2434 | for n, v in enumerate(channels): |
|
2435 | for n, v in enumerate(channels): | |
2435 | if not v: |
|
2436 | if not v: | |
2436 | channel = n |
|
2437 | channel = n | |
2437 | break |
|
2438 | break | |
2438 | else: |
|
2439 | else: | |
2439 | raise ValueError('Could not find output channel') |
|
2440 | raise ValueError('Could not find output channel') | |
2440 | channels[channel] = "=" + test.name[5:].split(".")[0] |
|
2441 | channels[channel] = "=" + test.name[5:].split(".")[0] | |
2441 | try: |
|
2442 | try: | |
2442 | test(result) |
|
2443 | test(result) | |
2443 | done.put(None) |
|
2444 | done.put(None) | |
2444 | except KeyboardInterrupt: |
|
2445 | except KeyboardInterrupt: | |
2445 | pass |
|
2446 | pass | |
2446 | except: # re-raises |
|
2447 | except: # re-raises | |
2447 | done.put(('!', test, 'run-test raised an error, see traceback')) |
|
2448 | done.put(('!', test, 'run-test raised an error, see traceback')) | |
2448 | raise |
|
2449 | raise | |
2449 | finally: |
|
2450 | finally: | |
2450 | try: |
|
2451 | try: | |
2451 | channels[channel] = '' |
|
2452 | channels[channel] = '' | |
2452 | except IndexError: |
|
2453 | except IndexError: | |
2453 | pass |
|
2454 | pass | |
2454 |
|
2455 | |||
2455 | def stat(): |
|
2456 | def stat(): | |
2456 | count = 0 |
|
2457 | count = 0 | |
2457 | while channels: |
|
2458 | while channels: | |
2458 | d = '\n%03s ' % count |
|
2459 | d = '\n%03s ' % count | |
2459 | for n, v in enumerate(channels): |
|
2460 | for n, v in enumerate(channels): | |
2460 | if v: |
|
2461 | if v: | |
2461 | d += v[0] |
|
2462 | d += v[0] | |
2462 | channels[n] = v[1:] or '.' |
|
2463 | channels[n] = v[1:] or '.' | |
2463 | else: |
|
2464 | else: | |
2464 | d += ' ' |
|
2465 | d += ' ' | |
2465 | d += ' ' |
|
2466 | d += ' ' | |
2466 | with iolock: |
|
2467 | with iolock: | |
2467 | sys.stdout.write(d + ' ') |
|
2468 | sys.stdout.write(d + ' ') | |
2468 | sys.stdout.flush() |
|
2469 | sys.stdout.flush() | |
2469 | for x in xrange(10): |
|
2470 | for x in xrange(10): | |
2470 | if channels: |
|
2471 | if channels: | |
2471 | time.sleep(0.1) |
|
2472 | time.sleep(0.1) | |
2472 | count += 1 |
|
2473 | count += 1 | |
2473 |
|
2474 | |||
2474 | stoppedearly = False |
|
2475 | stoppedearly = False | |
2475 |
|
2476 | |||
2476 | if self._showchannels: |
|
2477 | if self._showchannels: | |
2477 | statthread = threading.Thread(target=stat, name="stat") |
|
2478 | statthread = threading.Thread(target=stat, name="stat") | |
2478 | statthread.start() |
|
2479 | statthread.start() | |
2479 |
|
2480 | |||
2480 | try: |
|
2481 | try: | |
2481 | while tests or running: |
|
2482 | while tests or running: | |
2482 | if not done.empty() or running == self._jobs or not tests: |
|
2483 | if not done.empty() or running == self._jobs or not tests: | |
2483 | try: |
|
2484 | try: | |
2484 | done.get(True, 1) |
|
2485 | done.get(True, 1) | |
2485 | running -= 1 |
|
2486 | running -= 1 | |
2486 | if result and result.shouldStop: |
|
2487 | if result and result.shouldStop: | |
2487 | stoppedearly = True |
|
2488 | stoppedearly = True | |
2488 | break |
|
2489 | break | |
2489 | except queue.Empty: |
|
2490 | except queue.Empty: | |
2490 | continue |
|
2491 | continue | |
2491 | if tests and not running == self._jobs: |
|
2492 | if tests and not running == self._jobs: | |
2492 | test = tests.pop(0) |
|
2493 | test = tests.pop(0) | |
2493 | if self._loop: |
|
2494 | if self._loop: | |
2494 | if getattr(test, 'should_reload', False): |
|
2495 | if getattr(test, 'should_reload', False): | |
2495 | num_tests[0] += 1 |
|
2496 | num_tests[0] += 1 | |
2496 | tests.append(self._loadtest(test, num_tests[0])) |
|
2497 | tests.append(self._loadtest(test, num_tests[0])) | |
2497 | else: |
|
2498 | else: | |
2498 | tests.append(test) |
|
2499 | tests.append(test) | |
2499 | if self._jobs == 1: |
|
2500 | if self._jobs == 1: | |
2500 | job(test, result) |
|
2501 | job(test, result) | |
2501 | else: |
|
2502 | else: | |
2502 | t = threading.Thread( |
|
2503 | t = threading.Thread( | |
2503 | target=job, name=test.name, args=(test, result) |
|
2504 | target=job, name=test.name, args=(test, result) | |
2504 | ) |
|
2505 | ) | |
2505 | t.start() |
|
2506 | t.start() | |
2506 | running += 1 |
|
2507 | running += 1 | |
2507 |
|
2508 | |||
2508 | # If we stop early we still need to wait on started tests to |
|
2509 | # If we stop early we still need to wait on started tests to | |
2509 | # finish. Otherwise, there is a race between the test completing |
|
2510 | # finish. Otherwise, there is a race between the test completing | |
2510 | # and the test's cleanup code running. This could result in the |
|
2511 | # and the test's cleanup code running. This could result in the | |
2511 | # test reporting incorrect. |
|
2512 | # test reporting incorrect. | |
2512 | if stoppedearly: |
|
2513 | if stoppedearly: | |
2513 | while running: |
|
2514 | while running: | |
2514 | try: |
|
2515 | try: | |
2515 | done.get(True, 1) |
|
2516 | done.get(True, 1) | |
2516 | running -= 1 |
|
2517 | running -= 1 | |
2517 | except queue.Empty: |
|
2518 | except queue.Empty: | |
2518 | continue |
|
2519 | continue | |
2519 | except KeyboardInterrupt: |
|
2520 | except KeyboardInterrupt: | |
2520 | for test in runtests: |
|
2521 | for test in runtests: | |
2521 | test.abort() |
|
2522 | test.abort() | |
2522 |
|
2523 | |||
2523 | channels = [] |
|
2524 | channels = [] | |
2524 |
|
2525 | |||
2525 | return result |
|
2526 | return result | |
2526 |
|
2527 | |||
2527 |
|
2528 | |||
2528 | # Save the most recent 5 wall-clock runtimes of each test to a |
|
2529 | # Save the most recent 5 wall-clock runtimes of each test to a | |
2529 | # human-readable text file named .testtimes. Tests are sorted |
|
2530 | # human-readable text file named .testtimes. Tests are sorted | |
2530 | # alphabetically, while times for each test are listed from oldest to |
|
2531 | # alphabetically, while times for each test are listed from oldest to | |
2531 | # newest. |
|
2532 | # newest. | |
2532 |
|
2533 | |||
2533 |
|
2534 | |||
2534 | def loadtimes(outputdir): |
|
2535 | def loadtimes(outputdir): | |
2535 | times = [] |
|
2536 | times = [] | |
2536 | try: |
|
2537 | try: | |
2537 | with open(os.path.join(outputdir, b'.testtimes')) as fp: |
|
2538 | with open(os.path.join(outputdir, b'.testtimes')) as fp: | |
2538 | for line in fp: |
|
2539 | for line in fp: | |
2539 | m = re.match('(.*?) ([0-9. ]+)', line) |
|
2540 | m = re.match('(.*?) ([0-9. ]+)', line) | |
2540 | times.append( |
|
2541 | times.append( | |
2541 | (m.group(1), [float(t) for t in m.group(2).split()]) |
|
2542 | (m.group(1), [float(t) for t in m.group(2).split()]) | |
2542 | ) |
|
2543 | ) | |
2543 | except IOError as err: |
|
2544 | except IOError as err: | |
2544 | if err.errno != errno.ENOENT: |
|
2545 | if err.errno != errno.ENOENT: | |
2545 | raise |
|
2546 | raise | |
2546 | return times |
|
2547 | return times | |
2547 |
|
2548 | |||
2548 |
|
2549 | |||
2549 | def savetimes(outputdir, result): |
|
2550 | def savetimes(outputdir, result): | |
2550 | saved = dict(loadtimes(outputdir)) |
|
2551 | saved = dict(loadtimes(outputdir)) | |
2551 | maxruns = 5 |
|
2552 | maxruns = 5 | |
2552 | skipped = {str(t[0]) for t in result.skipped} |
|
2553 | skipped = {str(t[0]) for t in result.skipped} | |
2553 | for tdata in result.times: |
|
2554 | for tdata in result.times: | |
2554 | test, real = tdata[0], tdata[3] |
|
2555 | test, real = tdata[0], tdata[3] | |
2555 | if test not in skipped: |
|
2556 | if test not in skipped: | |
2556 | ts = saved.setdefault(test, []) |
|
2557 | ts = saved.setdefault(test, []) | |
2557 | ts.append(real) |
|
2558 | ts.append(real) | |
2558 | ts[:] = ts[-maxruns:] |
|
2559 | ts[:] = ts[-maxruns:] | |
2559 |
|
2560 | |||
2560 | fd, tmpname = tempfile.mkstemp( |
|
2561 | fd, tmpname = tempfile.mkstemp( | |
2561 | prefix=b'.testtimes', dir=outputdir, text=True |
|
2562 | prefix=b'.testtimes', dir=outputdir, text=True | |
2562 | ) |
|
2563 | ) | |
2563 | with os.fdopen(fd, 'w') as fp: |
|
2564 | with os.fdopen(fd, 'w') as fp: | |
2564 | for name, ts in sorted(saved.items()): |
|
2565 | for name, ts in sorted(saved.items()): | |
2565 | fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts]))) |
|
2566 | fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts]))) | |
2566 | timepath = os.path.join(outputdir, b'.testtimes') |
|
2567 | timepath = os.path.join(outputdir, b'.testtimes') | |
2567 | try: |
|
2568 | try: | |
2568 | os.unlink(timepath) |
|
2569 | os.unlink(timepath) | |
2569 | except OSError: |
|
2570 | except OSError: | |
2570 | pass |
|
2571 | pass | |
2571 | try: |
|
2572 | try: | |
2572 | os.rename(tmpname, timepath) |
|
2573 | os.rename(tmpname, timepath) | |
2573 | except OSError: |
|
2574 | except OSError: | |
2574 | pass |
|
2575 | pass | |
2575 |
|
2576 | |||
2576 |
|
2577 | |||
2577 | class TextTestRunner(unittest.TextTestRunner): |
|
2578 | class TextTestRunner(unittest.TextTestRunner): | |
2578 | """Custom unittest test runner that uses appropriate settings.""" |
|
2579 | """Custom unittest test runner that uses appropriate settings.""" | |
2579 |
|
2580 | |||
2580 | def __init__(self, runner, *args, **kwargs): |
|
2581 | def __init__(self, runner, *args, **kwargs): | |
2581 | super(TextTestRunner, self).__init__(*args, **kwargs) |
|
2582 | super(TextTestRunner, self).__init__(*args, **kwargs) | |
2582 |
|
2583 | |||
2583 | self._runner = runner |
|
2584 | self._runner = runner | |
2584 |
|
2585 | |||
2585 | self._result = getTestResult()( |
|
2586 | self._result = getTestResult()( | |
2586 | self._runner.options, self.stream, self.descriptions, self.verbosity |
|
2587 | self._runner.options, self.stream, self.descriptions, self.verbosity | |
2587 | ) |
|
2588 | ) | |
2588 |
|
2589 | |||
2589 | def listtests(self, test): |
|
2590 | def listtests(self, test): | |
2590 | test = sorted(test, key=lambda t: t.name) |
|
2591 | test = sorted(test, key=lambda t: t.name) | |
2591 |
|
2592 | |||
2592 | self._result.onStart(test) |
|
2593 | self._result.onStart(test) | |
2593 |
|
2594 | |||
2594 | for t in test: |
|
2595 | for t in test: | |
2595 | print(t.name) |
|
2596 | print(t.name) | |
2596 | self._result.addSuccess(t) |
|
2597 | self._result.addSuccess(t) | |
2597 |
|
2598 | |||
2598 | if self._runner.options.xunit: |
|
2599 | if self._runner.options.xunit: | |
2599 | with open(self._runner.options.xunit, "wb") as xuf: |
|
2600 | with open(self._runner.options.xunit, "wb") as xuf: | |
2600 | self._writexunit(self._result, xuf) |
|
2601 | self._writexunit(self._result, xuf) | |
2601 |
|
2602 | |||
2602 | if self._runner.options.json: |
|
2603 | if self._runner.options.json: | |
2603 | jsonpath = os.path.join(self._runner._outputdir, b'report.json') |
|
2604 | jsonpath = os.path.join(self._runner._outputdir, b'report.json') | |
2604 | with open(jsonpath, 'w') as fp: |
|
2605 | with open(jsonpath, 'w') as fp: | |
2605 | self._writejson(self._result, fp) |
|
2606 | self._writejson(self._result, fp) | |
2606 |
|
2607 | |||
2607 | return self._result |
|
2608 | return self._result | |
2608 |
|
2609 | |||
2609 | def run(self, test): |
|
2610 | def run(self, test): | |
2610 | self._result.onStart(test) |
|
2611 | self._result.onStart(test) | |
2611 | test(self._result) |
|
2612 | test(self._result) | |
2612 |
|
2613 | |||
2613 | failed = len(self._result.failures) |
|
2614 | failed = len(self._result.failures) | |
2614 | skipped = len(self._result.skipped) |
|
2615 | skipped = len(self._result.skipped) | |
2615 | ignored = len(self._result.ignored) |
|
2616 | ignored = len(self._result.ignored) | |
2616 |
|
2617 | |||
2617 | with iolock: |
|
2618 | with iolock: | |
2618 | self.stream.writeln('') |
|
2619 | self.stream.writeln('') | |
2619 |
|
2620 | |||
2620 | if not self._runner.options.noskips: |
|
2621 | if not self._runner.options.noskips: | |
2621 | for test, msg in sorted( |
|
2622 | for test, msg in sorted( | |
2622 | self._result.skipped, key=lambda s: s[0].name |
|
2623 | self._result.skipped, key=lambda s: s[0].name | |
2623 | ): |
|
2624 | ): | |
2624 | formatted = 'Skipped %s: %s\n' % (test.name, msg) |
|
2625 | formatted = 'Skipped %s: %s\n' % (test.name, msg) | |
2625 | msg = highlightmsg(formatted, self._result.color) |
|
2626 | msg = highlightmsg(formatted, self._result.color) | |
2626 | self.stream.write(msg) |
|
2627 | self.stream.write(msg) | |
2627 | for test, msg in sorted( |
|
2628 | for test, msg in sorted( | |
2628 | self._result.failures, key=lambda f: f[0].name |
|
2629 | self._result.failures, key=lambda f: f[0].name | |
2629 | ): |
|
2630 | ): | |
2630 | formatted = 'Failed %s: %s\n' % (test.name, msg) |
|
2631 | formatted = 'Failed %s: %s\n' % (test.name, msg) | |
2631 | self.stream.write(highlightmsg(formatted, self._result.color)) |
|
2632 | self.stream.write(highlightmsg(formatted, self._result.color)) | |
2632 | for test, msg in sorted( |
|
2633 | for test, msg in sorted( | |
2633 | self._result.errors, key=lambda e: e[0].name |
|
2634 | self._result.errors, key=lambda e: e[0].name | |
2634 | ): |
|
2635 | ): | |
2635 | self.stream.writeln('Errored %s: %s' % (test.name, msg)) |
|
2636 | self.stream.writeln('Errored %s: %s' % (test.name, msg)) | |
2636 |
|
2637 | |||
2637 | if self._runner.options.xunit: |
|
2638 | if self._runner.options.xunit: | |
2638 | with open(self._runner.options.xunit, "wb") as xuf: |
|
2639 | with open(self._runner.options.xunit, "wb") as xuf: | |
2639 | self._writexunit(self._result, xuf) |
|
2640 | self._writexunit(self._result, xuf) | |
2640 |
|
2641 | |||
2641 | if self._runner.options.json: |
|
2642 | if self._runner.options.json: | |
2642 | jsonpath = os.path.join(self._runner._outputdir, b'report.json') |
|
2643 | jsonpath = os.path.join(self._runner._outputdir, b'report.json') | |
2643 | with open(jsonpath, 'w') as fp: |
|
2644 | with open(jsonpath, 'w') as fp: | |
2644 | self._writejson(self._result, fp) |
|
2645 | self._writejson(self._result, fp) | |
2645 |
|
2646 | |||
2646 | self._runner._checkhglib('Tested') |
|
2647 | self._runner._checkhglib('Tested') | |
2647 |
|
2648 | |||
2648 | savetimes(self._runner._outputdir, self._result) |
|
2649 | savetimes(self._runner._outputdir, self._result) | |
2649 |
|
2650 | |||
2650 | if failed and self._runner.options.known_good_rev: |
|
2651 | if failed and self._runner.options.known_good_rev: | |
2651 | self._bisecttests(t for t, m in self._result.failures) |
|
2652 | self._bisecttests(t for t, m in self._result.failures) | |
2652 | self.stream.writeln( |
|
2653 | self.stream.writeln( | |
2653 | '# Ran %d tests, %d skipped, %d failed.' |
|
2654 | '# Ran %d tests, %d skipped, %d failed.' | |
2654 | % (self._result.testsRun, skipped + ignored, failed) |
|
2655 | % (self._result.testsRun, skipped + ignored, failed) | |
2655 | ) |
|
2656 | ) | |
2656 | if failed: |
|
2657 | if failed: | |
2657 | self.stream.writeln( |
|
2658 | self.stream.writeln( | |
2658 | 'python hash seed: %s' % os.environ['PYTHONHASHSEED'] |
|
2659 | 'python hash seed: %s' % os.environ['PYTHONHASHSEED'] | |
2659 | ) |
|
2660 | ) | |
2660 | if self._runner.options.time: |
|
2661 | if self._runner.options.time: | |
2661 | self.printtimes(self._result.times) |
|
2662 | self.printtimes(self._result.times) | |
2662 |
|
2663 | |||
2663 | if self._runner.options.exceptions: |
|
2664 | if self._runner.options.exceptions: | |
2664 | exceptions = aggregateexceptions( |
|
2665 | exceptions = aggregateexceptions( | |
2665 | os.path.join(self._runner._outputdir, b'exceptions') |
|
2666 | os.path.join(self._runner._outputdir, b'exceptions') | |
2666 | ) |
|
2667 | ) | |
2667 |
|
2668 | |||
2668 | self.stream.writeln('Exceptions Report:') |
|
2669 | self.stream.writeln('Exceptions Report:') | |
2669 | self.stream.writeln( |
|
2670 | self.stream.writeln( | |
2670 | '%d total from %d frames' |
|
2671 | '%d total from %d frames' | |
2671 | % (exceptions['total'], len(exceptions['exceptioncounts'])) |
|
2672 | % (exceptions['total'], len(exceptions['exceptioncounts'])) | |
2672 | ) |
|
2673 | ) | |
2673 | combined = exceptions['combined'] |
|
2674 | combined = exceptions['combined'] | |
2674 | for key in sorted(combined, key=combined.get, reverse=True): |
|
2675 | for key in sorted(combined, key=combined.get, reverse=True): | |
2675 | frame, line, exc = key |
|
2676 | frame, line, exc = key | |
2676 | totalcount, testcount, leastcount, leasttest = combined[key] |
|
2677 | totalcount, testcount, leastcount, leasttest = combined[key] | |
2677 |
|
2678 | |||
2678 | self.stream.writeln( |
|
2679 | self.stream.writeln( | |
2679 | '%d (%d tests)\t%s: %s (%s - %d total)' |
|
2680 | '%d (%d tests)\t%s: %s (%s - %d total)' | |
2680 | % ( |
|
2681 | % ( | |
2681 | totalcount, |
|
2682 | totalcount, | |
2682 | testcount, |
|
2683 | testcount, | |
2683 | frame, |
|
2684 | frame, | |
2684 | exc, |
|
2685 | exc, | |
2685 | leasttest, |
|
2686 | leasttest, | |
2686 | leastcount, |
|
2687 | leastcount, | |
2687 | ) |
|
2688 | ) | |
2688 | ) |
|
2689 | ) | |
2689 |
|
2690 | |||
2690 | self.stream.flush() |
|
2691 | self.stream.flush() | |
2691 |
|
2692 | |||
2692 | return self._result |
|
2693 | return self._result | |
2693 |
|
2694 | |||
2694 | def _bisecttests(self, tests): |
|
2695 | def _bisecttests(self, tests): | |
2695 | bisectcmd = ['hg', 'bisect'] |
|
2696 | bisectcmd = ['hg', 'bisect'] | |
2696 | bisectrepo = self._runner.options.bisect_repo |
|
2697 | bisectrepo = self._runner.options.bisect_repo | |
2697 | if bisectrepo: |
|
2698 | if bisectrepo: | |
2698 | bisectcmd.extend(['-R', os.path.abspath(bisectrepo)]) |
|
2699 | bisectcmd.extend(['-R', os.path.abspath(bisectrepo)]) | |
2699 |
|
2700 | |||
2700 | def pread(args): |
|
2701 | def pread(args): | |
2701 | env = os.environ.copy() |
|
2702 | env = os.environ.copy() | |
2702 | env['HGPLAIN'] = '1' |
|
2703 | env['HGPLAIN'] = '1' | |
2703 | p = subprocess.Popen( |
|
2704 | p = subprocess.Popen( | |
2704 | args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env |
|
2705 | args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env | |
2705 | ) |
|
2706 | ) | |
2706 | data = p.stdout.read() |
|
2707 | data = p.stdout.read() | |
2707 | p.wait() |
|
2708 | p.wait() | |
2708 | return data |
|
2709 | return data | |
2709 |
|
2710 | |||
2710 | for test in tests: |
|
2711 | for test in tests: | |
2711 | pread(bisectcmd + ['--reset']), |
|
2712 | pread(bisectcmd + ['--reset']), | |
2712 | pread(bisectcmd + ['--bad', '.']) |
|
2713 | pread(bisectcmd + ['--bad', '.']) | |
2713 | pread(bisectcmd + ['--good', self._runner.options.known_good_rev]) |
|
2714 | pread(bisectcmd + ['--good', self._runner.options.known_good_rev]) | |
2714 | # TODO: we probably need to forward more options |
|
2715 | # TODO: we probably need to forward more options | |
2715 | # that alter hg's behavior inside the tests. |
|
2716 | # that alter hg's behavior inside the tests. | |
2716 | opts = '' |
|
2717 | opts = '' | |
2717 | withhg = self._runner.options.with_hg |
|
2718 | withhg = self._runner.options.with_hg | |
2718 | if withhg: |
|
2719 | if withhg: | |
2719 | opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg)) |
|
2720 | opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg)) | |
2720 | rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test) |
|
2721 | rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test) | |
2721 | data = pread(bisectcmd + ['--command', rtc]) |
|
2722 | data = pread(bisectcmd + ['--command', rtc]) | |
2722 | m = re.search( |
|
2723 | m = re.search( | |
2723 | ( |
|
2724 | ( | |
2724 | br'\nThe first (?P<goodbad>bad|good) revision ' |
|
2725 | br'\nThe first (?P<goodbad>bad|good) revision ' | |
2725 | br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n' |
|
2726 | br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n' | |
2726 | br'summary: +(?P<summary>[^\n]+)\n' |
|
2727 | br'summary: +(?P<summary>[^\n]+)\n' | |
2727 | ), |
|
2728 | ), | |
2728 | data, |
|
2729 | data, | |
2729 | (re.MULTILINE | re.DOTALL), |
|
2730 | (re.MULTILINE | re.DOTALL), | |
2730 | ) |
|
2731 | ) | |
2731 | if m is None: |
|
2732 | if m is None: | |
2732 | self.stream.writeln( |
|
2733 | self.stream.writeln( | |
2733 | 'Failed to identify failure point for %s' % test |
|
2734 | 'Failed to identify failure point for %s' % test | |
2734 | ) |
|
2735 | ) | |
2735 | continue |
|
2736 | continue | |
2736 | dat = m.groupdict() |
|
2737 | dat = m.groupdict() | |
2737 | verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed' |
|
2738 | verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed' | |
2738 | self.stream.writeln( |
|
2739 | self.stream.writeln( | |
2739 | '%s %s by %s (%s)' |
|
2740 | '%s %s by %s (%s)' | |
2740 | % ( |
|
2741 | % ( | |
2741 | test, |
|
2742 | test, | |
2742 | verb, |
|
2743 | verb, | |
2743 | dat['node'].decode('ascii'), |
|
2744 | dat['node'].decode('ascii'), | |
2744 | dat['summary'].decode('utf8', 'ignore'), |
|
2745 | dat['summary'].decode('utf8', 'ignore'), | |
2745 | ) |
|
2746 | ) | |
2746 | ) |
|
2747 | ) | |
2747 |
|
2748 | |||
2748 | def printtimes(self, times): |
|
2749 | def printtimes(self, times): | |
2749 | # iolock held by run |
|
2750 | # iolock held by run | |
2750 | self.stream.writeln('# Producing time report') |
|
2751 | self.stream.writeln('# Producing time report') | |
2751 | times.sort(key=lambda t: (t[3])) |
|
2752 | times.sort(key=lambda t: (t[3])) | |
2752 | cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s' |
|
2753 | cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s' | |
2753 | self.stream.writeln( |
|
2754 | self.stream.writeln( | |
2754 | '%-7s %-7s %-7s %-7s %-7s %s' |
|
2755 | '%-7s %-7s %-7s %-7s %-7s %s' | |
2755 | % ('start', 'end', 'cuser', 'csys', 'real', 'Test') |
|
2756 | % ('start', 'end', 'cuser', 'csys', 'real', 'Test') | |
2756 | ) |
|
2757 | ) | |
2757 | for tdata in times: |
|
2758 | for tdata in times: | |
2758 | test = tdata[0] |
|
2759 | test = tdata[0] | |
2759 | cuser, csys, real, start, end = tdata[1:6] |
|
2760 | cuser, csys, real, start, end = tdata[1:6] | |
2760 | self.stream.writeln(cols % (start, end, cuser, csys, real, test)) |
|
2761 | self.stream.writeln(cols % (start, end, cuser, csys, real, test)) | |
2761 |
|
2762 | |||
2762 | @staticmethod |
|
2763 | @staticmethod | |
2763 | def _writexunit(result, outf): |
|
2764 | def _writexunit(result, outf): | |
2764 | # See http://llg.cubic.org/docs/junit/ for a reference. |
|
2765 | # See http://llg.cubic.org/docs/junit/ for a reference. | |
2765 | timesd = {t[0]: t[3] for t in result.times} |
|
2766 | timesd = {t[0]: t[3] for t in result.times} | |
2766 | doc = minidom.Document() |
|
2767 | doc = minidom.Document() | |
2767 | s = doc.createElement('testsuite') |
|
2768 | s = doc.createElement('testsuite') | |
2768 | s.setAttribute('errors', "0") # TODO |
|
2769 | s.setAttribute('errors', "0") # TODO | |
2769 | s.setAttribute('failures', str(len(result.failures))) |
|
2770 | s.setAttribute('failures', str(len(result.failures))) | |
2770 | s.setAttribute('name', 'run-tests') |
|
2771 | s.setAttribute('name', 'run-tests') | |
2771 | s.setAttribute( |
|
2772 | s.setAttribute( | |
2772 | 'skipped', str(len(result.skipped) + len(result.ignored)) |
|
2773 | 'skipped', str(len(result.skipped) + len(result.ignored)) | |
2773 | ) |
|
2774 | ) | |
2774 | s.setAttribute('tests', str(result.testsRun)) |
|
2775 | s.setAttribute('tests', str(result.testsRun)) | |
2775 | doc.appendChild(s) |
|
2776 | doc.appendChild(s) | |
2776 | for tc in result.successes: |
|
2777 | for tc in result.successes: | |
2777 | t = doc.createElement('testcase') |
|
2778 | t = doc.createElement('testcase') | |
2778 | t.setAttribute('name', tc.name) |
|
2779 | t.setAttribute('name', tc.name) | |
2779 | tctime = timesd.get(tc.name) |
|
2780 | tctime = timesd.get(tc.name) | |
2780 | if tctime is not None: |
|
2781 | if tctime is not None: | |
2781 | t.setAttribute('time', '%.3f' % tctime) |
|
2782 | t.setAttribute('time', '%.3f' % tctime) | |
2782 | s.appendChild(t) |
|
2783 | s.appendChild(t) | |
2783 | for tc, err in sorted(result.faildata.items()): |
|
2784 | for tc, err in sorted(result.faildata.items()): | |
2784 | t = doc.createElement('testcase') |
|
2785 | t = doc.createElement('testcase') | |
2785 | t.setAttribute('name', tc) |
|
2786 | t.setAttribute('name', tc) | |
2786 | tctime = timesd.get(tc) |
|
2787 | tctime = timesd.get(tc) | |
2787 | if tctime is not None: |
|
2788 | if tctime is not None: | |
2788 | t.setAttribute('time', '%.3f' % tctime) |
|
2789 | t.setAttribute('time', '%.3f' % tctime) | |
2789 | # createCDATASection expects a unicode or it will |
|
2790 | # createCDATASection expects a unicode or it will | |
2790 | # convert using default conversion rules, which will |
|
2791 | # convert using default conversion rules, which will | |
2791 | # fail if string isn't ASCII. |
|
2792 | # fail if string isn't ASCII. | |
2792 | err = cdatasafe(err).decode('utf-8', 'replace') |
|
2793 | err = cdatasafe(err).decode('utf-8', 'replace') | |
2793 | cd = doc.createCDATASection(err) |
|
2794 | cd = doc.createCDATASection(err) | |
2794 | # Use 'failure' here instead of 'error' to match errors = 0, |
|
2795 | # Use 'failure' here instead of 'error' to match errors = 0, | |
2795 | # failures = len(result.failures) in the testsuite element. |
|
2796 | # failures = len(result.failures) in the testsuite element. | |
2796 | failelem = doc.createElement('failure') |
|
2797 | failelem = doc.createElement('failure') | |
2797 | failelem.setAttribute('message', 'output changed') |
|
2798 | failelem.setAttribute('message', 'output changed') | |
2798 | failelem.setAttribute('type', 'output-mismatch') |
|
2799 | failelem.setAttribute('type', 'output-mismatch') | |
2799 | failelem.appendChild(cd) |
|
2800 | failelem.appendChild(cd) | |
2800 | t.appendChild(failelem) |
|
2801 | t.appendChild(failelem) | |
2801 | s.appendChild(t) |
|
2802 | s.appendChild(t) | |
2802 | for tc, message in result.skipped: |
|
2803 | for tc, message in result.skipped: | |
2803 | # According to the schema, 'skipped' has no attributes. So store |
|
2804 | # According to the schema, 'skipped' has no attributes. So store | |
2804 | # the skip message as a text node instead. |
|
2805 | # the skip message as a text node instead. | |
2805 | t = doc.createElement('testcase') |
|
2806 | t = doc.createElement('testcase') | |
2806 | t.setAttribute('name', tc.name) |
|
2807 | t.setAttribute('name', tc.name) | |
2807 | binmessage = message.encode('utf-8') |
|
2808 | binmessage = message.encode('utf-8') | |
2808 | message = cdatasafe(binmessage).decode('utf-8', 'replace') |
|
2809 | message = cdatasafe(binmessage).decode('utf-8', 'replace') | |
2809 | cd = doc.createCDATASection(message) |
|
2810 | cd = doc.createCDATASection(message) | |
2810 | skipelem = doc.createElement('skipped') |
|
2811 | skipelem = doc.createElement('skipped') | |
2811 | skipelem.appendChild(cd) |
|
2812 | skipelem.appendChild(cd) | |
2812 | t.appendChild(skipelem) |
|
2813 | t.appendChild(skipelem) | |
2813 | s.appendChild(t) |
|
2814 | s.appendChild(t) | |
2814 | outf.write(doc.toprettyxml(indent=' ', encoding='utf-8')) |
|
2815 | outf.write(doc.toprettyxml(indent=' ', encoding='utf-8')) | |
2815 |
|
2816 | |||
2816 | @staticmethod |
|
2817 | @staticmethod | |
2817 | def _writejson(result, outf): |
|
2818 | def _writejson(result, outf): | |
2818 | timesd = {} |
|
2819 | timesd = {} | |
2819 | for tdata in result.times: |
|
2820 | for tdata in result.times: | |
2820 | test = tdata[0] |
|
2821 | test = tdata[0] | |
2821 | timesd[test] = tdata[1:] |
|
2822 | timesd[test] = tdata[1:] | |
2822 |
|
2823 | |||
2823 | outcome = {} |
|
2824 | outcome = {} | |
2824 | groups = [ |
|
2825 | groups = [ | |
2825 | ('success', ((tc, None) for tc in result.successes)), |
|
2826 | ('success', ((tc, None) for tc in result.successes)), | |
2826 | ('failure', result.failures), |
|
2827 | ('failure', result.failures), | |
2827 | ('skip', result.skipped), |
|
2828 | ('skip', result.skipped), | |
2828 | ] |
|
2829 | ] | |
2829 | for res, testcases in groups: |
|
2830 | for res, testcases in groups: | |
2830 | for tc, __ in testcases: |
|
2831 | for tc, __ in testcases: | |
2831 | if tc.name in timesd: |
|
2832 | if tc.name in timesd: | |
2832 | diff = result.faildata.get(tc.name, b'') |
|
2833 | diff = result.faildata.get(tc.name, b'') | |
2833 | try: |
|
2834 | try: | |
2834 | diff = diff.decode('unicode_escape') |
|
2835 | diff = diff.decode('unicode_escape') | |
2835 | except UnicodeDecodeError as e: |
|
2836 | except UnicodeDecodeError as e: | |
2836 | diff = '%r decoding diff, sorry' % e |
|
2837 | diff = '%r decoding diff, sorry' % e | |
2837 | tres = { |
|
2838 | tres = { | |
2838 | 'result': res, |
|
2839 | 'result': res, | |
2839 | 'time': ('%0.3f' % timesd[tc.name][2]), |
|
2840 | 'time': ('%0.3f' % timesd[tc.name][2]), | |
2840 | 'cuser': ('%0.3f' % timesd[tc.name][0]), |
|
2841 | 'cuser': ('%0.3f' % timesd[tc.name][0]), | |
2841 | 'csys': ('%0.3f' % timesd[tc.name][1]), |
|
2842 | 'csys': ('%0.3f' % timesd[tc.name][1]), | |
2842 | 'start': ('%0.3f' % timesd[tc.name][3]), |
|
2843 | 'start': ('%0.3f' % timesd[tc.name][3]), | |
2843 | 'end': ('%0.3f' % timesd[tc.name][4]), |
|
2844 | 'end': ('%0.3f' % timesd[tc.name][4]), | |
2844 | 'diff': diff, |
|
2845 | 'diff': diff, | |
2845 | } |
|
2846 | } | |
2846 | else: |
|
2847 | else: | |
2847 | # blacklisted test |
|
2848 | # blacklisted test | |
2848 | tres = {'result': res} |
|
2849 | tres = {'result': res} | |
2849 |
|
2850 | |||
2850 | outcome[tc.name] = tres |
|
2851 | outcome[tc.name] = tres | |
2851 | jsonout = json.dumps( |
|
2852 | jsonout = json.dumps( | |
2852 | outcome, sort_keys=True, indent=4, separators=(',', ': ') |
|
2853 | outcome, sort_keys=True, indent=4, separators=(',', ': ') | |
2853 | ) |
|
2854 | ) | |
2854 | outf.writelines(("testreport =", jsonout)) |
|
2855 | outf.writelines(("testreport =", jsonout)) | |
2855 |
|
2856 | |||
2856 |
|
2857 | |||
2857 | def sorttests(testdescs, previoustimes, shuffle=False): |
|
2858 | def sorttests(testdescs, previoustimes, shuffle=False): | |
2858 | """Do an in-place sort of tests.""" |
|
2859 | """Do an in-place sort of tests.""" | |
2859 | if shuffle: |
|
2860 | if shuffle: | |
2860 | random.shuffle(testdescs) |
|
2861 | random.shuffle(testdescs) | |
2861 | return |
|
2862 | return | |
2862 |
|
2863 | |||
2863 | if previoustimes: |
|
2864 | if previoustimes: | |
2864 |
|
2865 | |||
2865 | def sortkey(f): |
|
2866 | def sortkey(f): | |
2866 | f = f['path'] |
|
2867 | f = f['path'] | |
2867 | if f in previoustimes: |
|
2868 | if f in previoustimes: | |
2868 | # Use most recent time as estimate |
|
2869 | # Use most recent time as estimate | |
2869 | return -(previoustimes[f][-1]) |
|
2870 | return -(previoustimes[f][-1]) | |
2870 | else: |
|
2871 | else: | |
2871 | # Default to a rather arbitrary value of 1 second for new tests |
|
2872 | # Default to a rather arbitrary value of 1 second for new tests | |
2872 | return -1.0 |
|
2873 | return -1.0 | |
2873 |
|
2874 | |||
2874 | else: |
|
2875 | else: | |
2875 | # keywords for slow tests |
|
2876 | # keywords for slow tests | |
2876 | slow = { |
|
2877 | slow = { | |
2877 | b'svn': 10, |
|
2878 | b'svn': 10, | |
2878 | b'cvs': 10, |
|
2879 | b'cvs': 10, | |
2879 | b'hghave': 10, |
|
2880 | b'hghave': 10, | |
2880 | b'largefiles-update': 10, |
|
2881 | b'largefiles-update': 10, | |
2881 | b'run-tests': 10, |
|
2882 | b'run-tests': 10, | |
2882 | b'corruption': 10, |
|
2883 | b'corruption': 10, | |
2883 | b'race': 10, |
|
2884 | b'race': 10, | |
2884 | b'i18n': 10, |
|
2885 | b'i18n': 10, | |
2885 | b'check': 100, |
|
2886 | b'check': 100, | |
2886 | b'gendoc': 100, |
|
2887 | b'gendoc': 100, | |
2887 | b'contrib-perf': 200, |
|
2888 | b'contrib-perf': 200, | |
2888 | b'merge-combination': 100, |
|
2889 | b'merge-combination': 100, | |
2889 | } |
|
2890 | } | |
2890 | perf = {} |
|
2891 | perf = {} | |
2891 |
|
2892 | |||
2892 | def sortkey(f): |
|
2893 | def sortkey(f): | |
2893 | # run largest tests first, as they tend to take the longest |
|
2894 | # run largest tests first, as they tend to take the longest | |
2894 | f = f['path'] |
|
2895 | f = f['path'] | |
2895 | try: |
|
2896 | try: | |
2896 | return perf[f] |
|
2897 | return perf[f] | |
2897 | except KeyError: |
|
2898 | except KeyError: | |
2898 | try: |
|
2899 | try: | |
2899 | val = -os.stat(f).st_size |
|
2900 | val = -os.stat(f).st_size | |
2900 | except OSError as e: |
|
2901 | except OSError as e: | |
2901 | if e.errno != errno.ENOENT: |
|
2902 | if e.errno != errno.ENOENT: | |
2902 | raise |
|
2903 | raise | |
2903 | perf[f] = -1e9 # file does not exist, tell early |
|
2904 | perf[f] = -1e9 # file does not exist, tell early | |
2904 | return -1e9 |
|
2905 | return -1e9 | |
2905 | for kw, mul in slow.items(): |
|
2906 | for kw, mul in slow.items(): | |
2906 | if kw in f: |
|
2907 | if kw in f: | |
2907 | val *= mul |
|
2908 | val *= mul | |
2908 | if f.endswith(b'.py'): |
|
2909 | if f.endswith(b'.py'): | |
2909 | val /= 10.0 |
|
2910 | val /= 10.0 | |
2910 | perf[f] = val / 1000.0 |
|
2911 | perf[f] = val / 1000.0 | |
2911 | return perf[f] |
|
2912 | return perf[f] | |
2912 |
|
2913 | |||
2913 | testdescs.sort(key=sortkey) |
|
2914 | testdescs.sort(key=sortkey) | |
2914 |
|
2915 | |||
2915 |
|
2916 | |||
2916 | class TestRunner(object): |
|
2917 | class TestRunner(object): | |
2917 | """Holds context for executing tests. |
|
2918 | """Holds context for executing tests. | |
2918 |
|
2919 | |||
2919 | Tests rely on a lot of state. This object holds it for them. |
|
2920 | Tests rely on a lot of state. This object holds it for them. | |
2920 | """ |
|
2921 | """ | |
2921 |
|
2922 | |||
2922 | # Programs required to run tests. |
|
2923 | # Programs required to run tests. | |
2923 | REQUIREDTOOLS = [ |
|
2924 | REQUIREDTOOLS = [ | |
2924 | b'diff', |
|
2925 | b'diff', | |
2925 | b'grep', |
|
2926 | b'grep', | |
2926 | b'unzip', |
|
2927 | b'unzip', | |
2927 | b'gunzip', |
|
2928 | b'gunzip', | |
2928 | b'bunzip2', |
|
2929 | b'bunzip2', | |
2929 | b'sed', |
|
2930 | b'sed', | |
2930 | ] |
|
2931 | ] | |
2931 |
|
2932 | |||
2932 | # Maps file extensions to test class. |
|
2933 | # Maps file extensions to test class. | |
2933 | TESTTYPES = [ |
|
2934 | TESTTYPES = [ | |
2934 | (b'.py', PythonTest), |
|
2935 | (b'.py', PythonTest), | |
2935 | (b'.t', TTest), |
|
2936 | (b'.t', TTest), | |
2936 | ] |
|
2937 | ] | |
2937 |
|
2938 | |||
2938 | def __init__(self): |
|
2939 | def __init__(self): | |
2939 | self.options = None |
|
2940 | self.options = None | |
2940 | self._hgroot = None |
|
2941 | self._hgroot = None | |
2941 | self._testdir = None |
|
2942 | self._testdir = None | |
2942 | self._outputdir = None |
|
2943 | self._outputdir = None | |
2943 | self._hgtmp = None |
|
2944 | self._hgtmp = None | |
2944 | self._installdir = None |
|
2945 | self._installdir = None | |
2945 | self._bindir = None |
|
2946 | self._bindir = None | |
2946 | self._tmpbinddir = None |
|
2947 | self._tmpbinddir = None | |
2947 | self._pythondir = None |
|
2948 | self._pythondir = None | |
2948 | self._coveragefile = None |
|
2949 | self._coveragefile = None | |
2949 | self._createdfiles = [] |
|
2950 | self._createdfiles = [] | |
2950 | self._hgcommand = None |
|
2951 | self._hgcommand = None | |
2951 | self._hgpath = None |
|
2952 | self._hgpath = None | |
2952 | self._portoffset = 0 |
|
2953 | self._portoffset = 0 | |
2953 | self._ports = {} |
|
2954 | self._ports = {} | |
2954 |
|
2955 | |||
2955 | def run(self, args, parser=None): |
|
2956 | def run(self, args, parser=None): | |
2956 | """Run the test suite.""" |
|
2957 | """Run the test suite.""" | |
2957 | oldmask = os.umask(0o22) |
|
2958 | oldmask = os.umask(0o22) | |
2958 | try: |
|
2959 | try: | |
2959 | parser = parser or getparser() |
|
2960 | parser = parser or getparser() | |
2960 | options = parseargs(args, parser) |
|
2961 | options = parseargs(args, parser) | |
2961 | tests = [_sys2bytes(a) for a in options.tests] |
|
2962 | tests = [_sys2bytes(a) for a in options.tests] | |
2962 | if options.test_list is not None: |
|
2963 | if options.test_list is not None: | |
2963 | for listfile in options.test_list: |
|
2964 | for listfile in options.test_list: | |
2964 | with open(listfile, 'rb') as f: |
|
2965 | with open(listfile, 'rb') as f: | |
2965 | tests.extend(t for t in f.read().splitlines() if t) |
|
2966 | tests.extend(t for t in f.read().splitlines() if t) | |
2966 | self.options = options |
|
2967 | self.options = options | |
2967 |
|
2968 | |||
2968 | self._checktools() |
|
2969 | self._checktools() | |
2969 | testdescs = self.findtests(tests) |
|
2970 | testdescs = self.findtests(tests) | |
2970 | if options.profile_runner: |
|
2971 | if options.profile_runner: | |
2971 | import statprof |
|
2972 | import statprof | |
2972 |
|
2973 | |||
2973 | statprof.start() |
|
2974 | statprof.start() | |
2974 | result = self._run(testdescs) |
|
2975 | result = self._run(testdescs) | |
2975 | if options.profile_runner: |
|
2976 | if options.profile_runner: | |
2976 | statprof.stop() |
|
2977 | statprof.stop() | |
2977 | statprof.display() |
|
2978 | statprof.display() | |
2978 | return result |
|
2979 | return result | |
2979 |
|
2980 | |||
2980 | finally: |
|
2981 | finally: | |
2981 | os.umask(oldmask) |
|
2982 | os.umask(oldmask) | |
2982 |
|
2983 | |||
2983 | def _run(self, testdescs): |
|
2984 | def _run(self, testdescs): | |
2984 | testdir = getcwdb() |
|
2985 | testdir = getcwdb() | |
2985 | self._testdir = osenvironb[b'TESTDIR'] = getcwdb() |
|
2986 | self._testdir = osenvironb[b'TESTDIR'] = getcwdb() | |
2986 | # assume all tests in same folder for now |
|
2987 | # assume all tests in same folder for now | |
2987 | if testdescs: |
|
2988 | if testdescs: | |
2988 | pathname = os.path.dirname(testdescs[0]['path']) |
|
2989 | pathname = os.path.dirname(testdescs[0]['path']) | |
2989 | if pathname: |
|
2990 | if pathname: | |
2990 | testdir = os.path.join(testdir, pathname) |
|
2991 | testdir = os.path.join(testdir, pathname) | |
2991 | self._testdir = osenvironb[b'TESTDIR'] = testdir |
|
2992 | self._testdir = osenvironb[b'TESTDIR'] = testdir | |
2992 | if self.options.outputdir: |
|
2993 | if self.options.outputdir: | |
2993 | self._outputdir = canonpath(_sys2bytes(self.options.outputdir)) |
|
2994 | self._outputdir = canonpath(_sys2bytes(self.options.outputdir)) | |
2994 | else: |
|
2995 | else: | |
2995 | self._outputdir = getcwdb() |
|
2996 | self._outputdir = getcwdb() | |
2996 | if testdescs and pathname: |
|
2997 | if testdescs and pathname: | |
2997 | self._outputdir = os.path.join(self._outputdir, pathname) |
|
2998 | self._outputdir = os.path.join(self._outputdir, pathname) | |
2998 | previoustimes = {} |
|
2999 | previoustimes = {} | |
2999 | if self.options.order_by_runtime: |
|
3000 | if self.options.order_by_runtime: | |
3000 | previoustimes = dict(loadtimes(self._outputdir)) |
|
3001 | previoustimes = dict(loadtimes(self._outputdir)) | |
3001 | sorttests(testdescs, previoustimes, shuffle=self.options.random) |
|
3002 | sorttests(testdescs, previoustimes, shuffle=self.options.random) | |
3002 |
|
3003 | |||
3003 | if 'PYTHONHASHSEED' not in os.environ: |
|
3004 | if 'PYTHONHASHSEED' not in os.environ: | |
3004 | # use a random python hash seed all the time |
|
3005 | # use a random python hash seed all the time | |
3005 | # we do the randomness ourself to know what seed is used |
|
3006 | # we do the randomness ourself to know what seed is used | |
3006 | os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32)) |
|
3007 | os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32)) | |
3007 |
|
3008 | |||
3008 | # Rayon (Rust crate for multi-threading) will use all logical CPU cores |
|
3009 | # Rayon (Rust crate for multi-threading) will use all logical CPU cores | |
3009 | # by default, causing thrashing on high-cpu-count systems. |
|
3010 | # by default, causing thrashing on high-cpu-count systems. | |
3010 | # Setting its limit to 3 during tests should still let us uncover |
|
3011 | # Setting its limit to 3 during tests should still let us uncover | |
3011 | # multi-threading bugs while keeping the thrashing reasonable. |
|
3012 | # multi-threading bugs while keeping the thrashing reasonable. | |
3012 | os.environ.setdefault("RAYON_NUM_THREADS", "3") |
|
3013 | os.environ.setdefault("RAYON_NUM_THREADS", "3") | |
3013 |
|
3014 | |||
3014 | if self.options.tmpdir: |
|
3015 | if self.options.tmpdir: | |
3015 | self.options.keep_tmpdir = True |
|
3016 | self.options.keep_tmpdir = True | |
3016 | tmpdir = _sys2bytes(self.options.tmpdir) |
|
3017 | tmpdir = _sys2bytes(self.options.tmpdir) | |
3017 | if os.path.exists(tmpdir): |
|
3018 | if os.path.exists(tmpdir): | |
3018 | # Meaning of tmpdir has changed since 1.3: we used to create |
|
3019 | # Meaning of tmpdir has changed since 1.3: we used to create | |
3019 | # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if |
|
3020 | # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if | |
3020 | # tmpdir already exists. |
|
3021 | # tmpdir already exists. | |
3021 | print("error: temp dir %r already exists" % tmpdir) |
|
3022 | print("error: temp dir %r already exists" % tmpdir) | |
3022 | return 1 |
|
3023 | return 1 | |
3023 |
|
3024 | |||
3024 | os.makedirs(tmpdir) |
|
3025 | os.makedirs(tmpdir) | |
3025 | else: |
|
3026 | else: | |
3026 | d = None |
|
3027 | d = None | |
3027 | if os.name == 'nt': |
|
3028 | if os.name == 'nt': | |
3028 | # without this, we get the default temp dir location, but |
|
3029 | # without this, we get the default temp dir location, but | |
3029 | # in all lowercase, which causes troubles with paths (issue3490) |
|
3030 | # in all lowercase, which causes troubles with paths (issue3490) | |
3030 | d = osenvironb.get(b'TMP', None) |
|
3031 | d = osenvironb.get(b'TMP', None) | |
3031 | tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d) |
|
3032 | tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d) | |
3032 |
|
3033 | |||
3033 | self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir) |
|
3034 | self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir) | |
3034 |
|
3035 | |||
3035 | if self.options.with_hg: |
|
3036 | if self.options.with_hg: | |
3036 | self._installdir = None |
|
3037 | self._installdir = None | |
3037 | whg = self.options.with_hg |
|
3038 | whg = self.options.with_hg | |
3038 | self._bindir = os.path.dirname(os.path.realpath(whg)) |
|
3039 | self._bindir = os.path.dirname(os.path.realpath(whg)) | |
3039 | assert isinstance(self._bindir, bytes) |
|
3040 | assert isinstance(self._bindir, bytes) | |
3040 | self._hgcommand = os.path.basename(whg) |
|
3041 | self._hgcommand = os.path.basename(whg) | |
3041 | self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin') |
|
3042 | self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin') | |
3042 | os.makedirs(self._tmpbindir) |
|
3043 | os.makedirs(self._tmpbindir) | |
3043 |
|
3044 | |||
3044 | normbin = os.path.normpath(os.path.abspath(whg)) |
|
3045 | normbin = os.path.normpath(os.path.abspath(whg)) | |
3045 | normbin = normbin.replace(_sys2bytes(os.sep), b'/') |
|
3046 | normbin = normbin.replace(_sys2bytes(os.sep), b'/') | |
3046 |
|
3047 | |||
3047 | # Other Python scripts in the test harness need to |
|
3048 | # Other Python scripts in the test harness need to | |
3048 | # `import mercurial`. If `hg` is a Python script, we assume |
|
3049 | # `import mercurial`. If `hg` is a Python script, we assume | |
3049 | # the Mercurial modules are relative to its path and tell the tests |
|
3050 | # the Mercurial modules are relative to its path and tell the tests | |
3050 | # to load Python modules from its directory. |
|
3051 | # to load Python modules from its directory. | |
3051 | with open(whg, 'rb') as fh: |
|
3052 | with open(whg, 'rb') as fh: | |
3052 | initial = fh.read(1024) |
|
3053 | initial = fh.read(1024) | |
3053 |
|
3054 | |||
3054 | if re.match(b'#!.*python', initial): |
|
3055 | if re.match(b'#!.*python', initial): | |
3055 | self._pythondir = self._bindir |
|
3056 | self._pythondir = self._bindir | |
3056 | # If it looks like our in-repo Rust binary, use the source root. |
|
3057 | # If it looks like our in-repo Rust binary, use the source root. | |
3057 | # This is a bit hacky. But rhg is still not supported outside the |
|
3058 | # This is a bit hacky. But rhg is still not supported outside the | |
3058 | # source directory. So until it is, do the simple thing. |
|
3059 | # source directory. So until it is, do the simple thing. | |
3059 | elif re.search(b'/rust/target/[^/]+/hg', normbin): |
|
3060 | elif re.search(b'/rust/target/[^/]+/hg', normbin): | |
3060 | self._pythondir = os.path.dirname(self._testdir) |
|
3061 | self._pythondir = os.path.dirname(self._testdir) | |
3061 | # Fall back to the legacy behavior. |
|
3062 | # Fall back to the legacy behavior. | |
3062 | else: |
|
3063 | else: | |
3063 | self._pythondir = self._bindir |
|
3064 | self._pythondir = self._bindir | |
3064 |
|
3065 | |||
3065 | else: |
|
3066 | else: | |
3066 | self._installdir = os.path.join(self._hgtmp, b"install") |
|
3067 | self._installdir = os.path.join(self._hgtmp, b"install") | |
3067 | self._bindir = os.path.join(self._installdir, b"bin") |
|
3068 | self._bindir = os.path.join(self._installdir, b"bin") | |
3068 | self._hgcommand = b'hg' |
|
3069 | self._hgcommand = b'hg' | |
3069 | self._tmpbindir = self._bindir |
|
3070 | self._tmpbindir = self._bindir | |
3070 | self._pythondir = os.path.join(self._installdir, b"lib", b"python") |
|
3071 | self._pythondir = os.path.join(self._installdir, b"lib", b"python") | |
3071 |
|
3072 | |||
3072 | # Force the use of hg.exe instead of relying on MSYS to recognize hg is |
|
3073 | # Force the use of hg.exe instead of relying on MSYS to recognize hg is | |
3073 | # a python script and feed it to python.exe. Legacy stdio is force |
|
3074 | # a python script and feed it to python.exe. Legacy stdio is force | |
3074 | # enabled by hg.exe, and this is a more realistic way to launch hg |
|
3075 | # enabled by hg.exe, and this is a more realistic way to launch hg | |
3075 | # anyway. |
|
3076 | # anyway. | |
3076 | if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'): |
|
3077 | if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'): | |
3077 | self._hgcommand += b'.exe' |
|
3078 | self._hgcommand += b'.exe' | |
3078 |
|
3079 | |||
3079 | # set CHGHG, then replace "hg" command by "chg" |
|
3080 | # set CHGHG, then replace "hg" command by "chg" | |
3080 | chgbindir = self._bindir |
|
3081 | chgbindir = self._bindir | |
3081 | if self.options.chg or self.options.with_chg: |
|
3082 | if self.options.chg or self.options.with_chg: | |
3082 | osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand) |
|
3083 | osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand) | |
3083 | else: |
|
3084 | else: | |
3084 | osenvironb.pop(b'CHGHG', None) # drop flag for hghave |
|
3085 | osenvironb.pop(b'CHGHG', None) # drop flag for hghave | |
3085 | if self.options.chg: |
|
3086 | if self.options.chg: | |
3086 | self._hgcommand = b'chg' |
|
3087 | self._hgcommand = b'chg' | |
3087 | elif self.options.with_chg: |
|
3088 | elif self.options.with_chg: | |
3088 | chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg)) |
|
3089 | chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg)) | |
3089 | self._hgcommand = os.path.basename(self.options.with_chg) |
|
3090 | self._hgcommand = os.path.basename(self.options.with_chg) | |
3090 |
|
3091 | |||
3091 | osenvironb[b"BINDIR"] = self._bindir |
|
3092 | osenvironb[b"BINDIR"] = self._bindir | |
3092 | osenvironb[b"PYTHON"] = PYTHON |
|
3093 | osenvironb[b"PYTHON"] = PYTHON | |
3093 |
|
3094 | |||
3094 | fileb = _sys2bytes(__file__) |
|
3095 | fileb = _sys2bytes(__file__) | |
3095 | runtestdir = os.path.abspath(os.path.dirname(fileb)) |
|
3096 | runtestdir = os.path.abspath(os.path.dirname(fileb)) | |
3096 | osenvironb[b'RUNTESTDIR'] = runtestdir |
|
3097 | osenvironb[b'RUNTESTDIR'] = runtestdir | |
3097 | if PYTHON3: |
|
3098 | if PYTHON3: | |
3098 | sepb = _sys2bytes(os.pathsep) |
|
3099 | sepb = _sys2bytes(os.pathsep) | |
3099 | else: |
|
3100 | else: | |
3100 | sepb = os.pathsep |
|
3101 | sepb = os.pathsep | |
3101 | path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb) |
|
3102 | path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb) | |
3102 | if os.path.islink(__file__): |
|
3103 | if os.path.islink(__file__): | |
3103 | # test helper will likely be at the end of the symlink |
|
3104 | # test helper will likely be at the end of the symlink | |
3104 | realfile = os.path.realpath(fileb) |
|
3105 | realfile = os.path.realpath(fileb) | |
3105 | realdir = os.path.abspath(os.path.dirname(realfile)) |
|
3106 | realdir = os.path.abspath(os.path.dirname(realfile)) | |
3106 | path.insert(2, realdir) |
|
3107 | path.insert(2, realdir) | |
3107 | if chgbindir != self._bindir: |
|
3108 | if chgbindir != self._bindir: | |
3108 | path.insert(1, chgbindir) |
|
3109 | path.insert(1, chgbindir) | |
3109 | if self._testdir != runtestdir: |
|
3110 | if self._testdir != runtestdir: | |
3110 | path = [self._testdir] + path |
|
3111 | path = [self._testdir] + path | |
3111 | if self._tmpbindir != self._bindir: |
|
3112 | if self._tmpbindir != self._bindir: | |
3112 | path = [self._tmpbindir] + path |
|
3113 | path = [self._tmpbindir] + path | |
3113 | osenvironb[b"PATH"] = sepb.join(path) |
|
3114 | osenvironb[b"PATH"] = sepb.join(path) | |
3114 |
|
3115 | |||
3115 | # Include TESTDIR in PYTHONPATH so that out-of-tree extensions |
|
3116 | # Include TESTDIR in PYTHONPATH so that out-of-tree extensions | |
3116 | # can run .../tests/run-tests.py test-foo where test-foo |
|
3117 | # can run .../tests/run-tests.py test-foo where test-foo | |
3117 | # adds an extension to HGRC. Also include run-test.py directory to |
|
3118 | # adds an extension to HGRC. Also include run-test.py directory to | |
3118 | # import modules like heredoctest. |
|
3119 | # import modules like heredoctest. | |
3119 | pypath = [self._pythondir, self._testdir, runtestdir] |
|
3120 | pypath = [self._pythondir, self._testdir, runtestdir] | |
3120 | # We have to augment PYTHONPATH, rather than simply replacing |
|
3121 | # We have to augment PYTHONPATH, rather than simply replacing | |
3121 | # it, in case external libraries are only available via current |
|
3122 | # it, in case external libraries are only available via current | |
3122 | # PYTHONPATH. (In particular, the Subversion bindings on OS X |
|
3123 | # PYTHONPATH. (In particular, the Subversion bindings on OS X | |
3123 | # are in /opt/subversion.) |
|
3124 | # are in /opt/subversion.) | |
3124 | oldpypath = osenvironb.get(IMPL_PATH) |
|
3125 | oldpypath = osenvironb.get(IMPL_PATH) | |
3125 | if oldpypath: |
|
3126 | if oldpypath: | |
3126 | pypath.append(oldpypath) |
|
3127 | pypath.append(oldpypath) | |
3127 | osenvironb[IMPL_PATH] = sepb.join(pypath) |
|
3128 | osenvironb[IMPL_PATH] = sepb.join(pypath) | |
3128 |
|
3129 | |||
3129 | if self.options.pure: |
|
3130 | if self.options.pure: | |
3130 | os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure" |
|
3131 | os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure" | |
3131 | os.environ["HGMODULEPOLICY"] = "py" |
|
3132 | os.environ["HGMODULEPOLICY"] = "py" | |
3132 | if self.options.rust: |
|
3133 | if self.options.rust: | |
3133 | os.environ["HGMODULEPOLICY"] = "rust+c" |
|
3134 | os.environ["HGMODULEPOLICY"] = "rust+c" | |
3134 | if self.options.no_rust: |
|
3135 | if self.options.no_rust: | |
3135 | current_policy = os.environ.get("HGMODULEPOLICY", "") |
|
3136 | current_policy = os.environ.get("HGMODULEPOLICY", "") | |
3136 | if current_policy.startswith("rust+"): |
|
3137 | if current_policy.startswith("rust+"): | |
3137 | os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :] |
|
3138 | os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :] | |
3138 | os.environ.pop("HGWITHRUSTEXT", None) |
|
3139 | os.environ.pop("HGWITHRUSTEXT", None) | |
3139 |
|
3140 | |||
3140 | if self.options.allow_slow_tests: |
|
3141 | if self.options.allow_slow_tests: | |
3141 | os.environ["HGTEST_SLOW"] = "slow" |
|
3142 | os.environ["HGTEST_SLOW"] = "slow" | |
3142 | elif 'HGTEST_SLOW' in os.environ: |
|
3143 | elif 'HGTEST_SLOW' in os.environ: | |
3143 | del os.environ['HGTEST_SLOW'] |
|
3144 | del os.environ['HGTEST_SLOW'] | |
3144 |
|
3145 | |||
3145 | self._coveragefile = os.path.join(self._testdir, b'.coverage') |
|
3146 | self._coveragefile = os.path.join(self._testdir, b'.coverage') | |
3146 |
|
3147 | |||
3147 | if self.options.exceptions: |
|
3148 | if self.options.exceptions: | |
3148 | exceptionsdir = os.path.join(self._outputdir, b'exceptions') |
|
3149 | exceptionsdir = os.path.join(self._outputdir, b'exceptions') | |
3149 | try: |
|
3150 | try: | |
3150 | os.makedirs(exceptionsdir) |
|
3151 | os.makedirs(exceptionsdir) | |
3151 | except OSError as e: |
|
3152 | except OSError as e: | |
3152 | if e.errno != errno.EEXIST: |
|
3153 | if e.errno != errno.EEXIST: | |
3153 | raise |
|
3154 | raise | |
3154 |
|
3155 | |||
3155 | # Remove all existing exception reports. |
|
3156 | # Remove all existing exception reports. | |
3156 | for f in os.listdir(exceptionsdir): |
|
3157 | for f in os.listdir(exceptionsdir): | |
3157 | os.unlink(os.path.join(exceptionsdir, f)) |
|
3158 | os.unlink(os.path.join(exceptionsdir, f)) | |
3158 |
|
3159 | |||
3159 | osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir |
|
3160 | osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir | |
3160 | logexceptions = os.path.join(self._testdir, b'logexceptions.py') |
|
3161 | logexceptions = os.path.join(self._testdir, b'logexceptions.py') | |
3161 | self.options.extra_config_opt.append( |
|
3162 | self.options.extra_config_opt.append( | |
3162 | 'extensions.logexceptions=%s' % logexceptions.decode('utf-8') |
|
3163 | 'extensions.logexceptions=%s' % logexceptions.decode('utf-8') | |
3163 | ) |
|
3164 | ) | |
3164 |
|
3165 | |||
3165 | vlog("# Using TESTDIR", _bytes2sys(self._testdir)) |
|
3166 | vlog("# Using TESTDIR", _bytes2sys(self._testdir)) | |
3166 | vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR'])) |
|
3167 | vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR'])) | |
3167 | vlog("# Using HGTMP", _bytes2sys(self._hgtmp)) |
|
3168 | vlog("# Using HGTMP", _bytes2sys(self._hgtmp)) | |
3168 | vlog("# Using PATH", os.environ["PATH"]) |
|
3169 | vlog("# Using PATH", os.environ["PATH"]) | |
3169 | vlog( |
|
3170 | vlog( | |
3170 | "# Using", _bytes2sys(IMPL_PATH), _bytes2sys(osenvironb[IMPL_PATH]), |
|
3171 | "# Using", _bytes2sys(IMPL_PATH), _bytes2sys(osenvironb[IMPL_PATH]), | |
3171 | ) |
|
3172 | ) | |
3172 | vlog("# Writing to directory", _bytes2sys(self._outputdir)) |
|
3173 | vlog("# Writing to directory", _bytes2sys(self._outputdir)) | |
3173 |
|
3174 | |||
3174 | try: |
|
3175 | try: | |
3175 | return self._runtests(testdescs) or 0 |
|
3176 | return self._runtests(testdescs) or 0 | |
3176 | finally: |
|
3177 | finally: | |
3177 | time.sleep(0.1) |
|
3178 | time.sleep(0.1) | |
3178 | self._cleanup() |
|
3179 | self._cleanup() | |
3179 |
|
3180 | |||
3180 | def findtests(self, args): |
|
3181 | def findtests(self, args): | |
3181 | """Finds possible test files from arguments. |
|
3182 | """Finds possible test files from arguments. | |
3182 |
|
3183 | |||
3183 | If you wish to inject custom tests into the test harness, this would |
|
3184 | If you wish to inject custom tests into the test harness, this would | |
3184 | be a good function to monkeypatch or override in a derived class. |
|
3185 | be a good function to monkeypatch or override in a derived class. | |
3185 | """ |
|
3186 | """ | |
3186 | if not args: |
|
3187 | if not args: | |
3187 | if self.options.changed: |
|
3188 | if self.options.changed: | |
3188 | proc = Popen4( |
|
3189 | proc = Popen4( | |
3189 | b'hg st --rev "%s" -man0 .' |
|
3190 | b'hg st --rev "%s" -man0 .' | |
3190 | % _sys2bytes(self.options.changed), |
|
3191 | % _sys2bytes(self.options.changed), | |
3191 | None, |
|
3192 | None, | |
3192 | 0, |
|
3193 | 0, | |
3193 | ) |
|
3194 | ) | |
3194 | stdout, stderr = proc.communicate() |
|
3195 | stdout, stderr = proc.communicate() | |
3195 | args = stdout.strip(b'\0').split(b'\0') |
|
3196 | args = stdout.strip(b'\0').split(b'\0') | |
3196 | else: |
|
3197 | else: | |
3197 | args = os.listdir(b'.') |
|
3198 | args = os.listdir(b'.') | |
3198 |
|
3199 | |||
3199 | expanded_args = [] |
|
3200 | expanded_args = [] | |
3200 | for arg in args: |
|
3201 | for arg in args: | |
3201 | if os.path.isdir(arg): |
|
3202 | if os.path.isdir(arg): | |
3202 | if not arg.endswith(b'/'): |
|
3203 | if not arg.endswith(b'/'): | |
3203 | arg += b'/' |
|
3204 | arg += b'/' | |
3204 | expanded_args.extend([arg + a for a in os.listdir(arg)]) |
|
3205 | expanded_args.extend([arg + a for a in os.listdir(arg)]) | |
3205 | else: |
|
3206 | else: | |
3206 | expanded_args.append(arg) |
|
3207 | expanded_args.append(arg) | |
3207 | args = expanded_args |
|
3208 | args = expanded_args | |
3208 |
|
3209 | |||
3209 | testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))') |
|
3210 | testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))') | |
3210 | tests = [] |
|
3211 | tests = [] | |
3211 | for t in args: |
|
3212 | for t in args: | |
3212 | case = [] |
|
3213 | case = [] | |
3213 |
|
3214 | |||
3214 | if not ( |
|
3215 | if not ( | |
3215 | os.path.basename(t).startswith(b'test-') |
|
3216 | os.path.basename(t).startswith(b'test-') | |
3216 | and (t.endswith(b'.py') or t.endswith(b'.t')) |
|
3217 | and (t.endswith(b'.py') or t.endswith(b'.t')) | |
3217 | ): |
|
3218 | ): | |
3218 |
|
3219 | |||
3219 | m = testcasepattern.match(os.path.basename(t)) |
|
3220 | m = testcasepattern.match(os.path.basename(t)) | |
3220 | if m is not None: |
|
3221 | if m is not None: | |
3221 | t_basename, casestr = m.groups() |
|
3222 | t_basename, casestr = m.groups() | |
3222 | t = os.path.join(os.path.dirname(t), t_basename) |
|
3223 | t = os.path.join(os.path.dirname(t), t_basename) | |
3223 | if casestr: |
|
3224 | if casestr: | |
3224 | case = casestr.split(b'#') |
|
3225 | case = casestr.split(b'#') | |
3225 | else: |
|
3226 | else: | |
3226 | continue |
|
3227 | continue | |
3227 |
|
3228 | |||
3228 | if t.endswith(b'.t'): |
|
3229 | if t.endswith(b'.t'): | |
3229 | # .t file may contain multiple test cases |
|
3230 | # .t file may contain multiple test cases | |
3230 | casedimensions = parsettestcases(t) |
|
3231 | casedimensions = parsettestcases(t) | |
3231 | if casedimensions: |
|
3232 | if casedimensions: | |
3232 | cases = [] |
|
3233 | cases = [] | |
3233 |
|
3234 | |||
3234 | def addcases(case, casedimensions): |
|
3235 | def addcases(case, casedimensions): | |
3235 | if not casedimensions: |
|
3236 | if not casedimensions: | |
3236 | cases.append(case) |
|
3237 | cases.append(case) | |
3237 | else: |
|
3238 | else: | |
3238 | for c in casedimensions[0]: |
|
3239 | for c in casedimensions[0]: | |
3239 | addcases(case + [c], casedimensions[1:]) |
|
3240 | addcases(case + [c], casedimensions[1:]) | |
3240 |
|
3241 | |||
3241 | addcases([], casedimensions) |
|
3242 | addcases([], casedimensions) | |
3242 | if case and case in cases: |
|
3243 | if case and case in cases: | |
3243 | cases = [case] |
|
3244 | cases = [case] | |
3244 | elif case: |
|
3245 | elif case: | |
3245 | # Ignore invalid cases |
|
3246 | # Ignore invalid cases | |
3246 | cases = [] |
|
3247 | cases = [] | |
3247 | else: |
|
3248 | else: | |
3248 | pass |
|
3249 | pass | |
3249 | tests += [{'path': t, 'case': c} for c in sorted(cases)] |
|
3250 | tests += [{'path': t, 'case': c} for c in sorted(cases)] | |
3250 | else: |
|
3251 | else: | |
3251 | tests.append({'path': t}) |
|
3252 | tests.append({'path': t}) | |
3252 | else: |
|
3253 | else: | |
3253 | tests.append({'path': t}) |
|
3254 | tests.append({'path': t}) | |
3254 |
|
3255 | |||
3255 | if self.options.retest: |
|
3256 | if self.options.retest: | |
3256 | retest_args = [] |
|
3257 | retest_args = [] | |
3257 | for test in tests: |
|
3258 | for test in tests: | |
3258 | errpath = self._geterrpath(test) |
|
3259 | errpath = self._geterrpath(test) | |
3259 | if os.path.exists(errpath): |
|
3260 | if os.path.exists(errpath): | |
3260 | retest_args.append(test) |
|
3261 | retest_args.append(test) | |
3261 | tests = retest_args |
|
3262 | tests = retest_args | |
3262 | return tests |
|
3263 | return tests | |
3263 |
|
3264 | |||
3264 | def _runtests(self, testdescs): |
|
3265 | def _runtests(self, testdescs): | |
3265 | def _reloadtest(test, i): |
|
3266 | def _reloadtest(test, i): | |
3266 | # convert a test back to its description dict |
|
3267 | # convert a test back to its description dict | |
3267 | desc = {'path': test.path} |
|
3268 | desc = {'path': test.path} | |
3268 | case = getattr(test, '_case', []) |
|
3269 | case = getattr(test, '_case', []) | |
3269 | if case: |
|
3270 | if case: | |
3270 | desc['case'] = case |
|
3271 | desc['case'] = case | |
3271 | return self._gettest(desc, i) |
|
3272 | return self._gettest(desc, i) | |
3272 |
|
3273 | |||
3273 | try: |
|
3274 | try: | |
3274 | if self.options.restart: |
|
3275 | if self.options.restart: | |
3275 | orig = list(testdescs) |
|
3276 | orig = list(testdescs) | |
3276 | while testdescs: |
|
3277 | while testdescs: | |
3277 | desc = testdescs[0] |
|
3278 | desc = testdescs[0] | |
3278 | errpath = self._geterrpath(desc) |
|
3279 | errpath = self._geterrpath(desc) | |
3279 | if os.path.exists(errpath): |
|
3280 | if os.path.exists(errpath): | |
3280 | break |
|
3281 | break | |
3281 | testdescs.pop(0) |
|
3282 | testdescs.pop(0) | |
3282 | if not testdescs: |
|
3283 | if not testdescs: | |
3283 | print("running all tests") |
|
3284 | print("running all tests") | |
3284 | testdescs = orig |
|
3285 | testdescs = orig | |
3285 |
|
3286 | |||
3286 | tests = [self._gettest(d, i) for i, d in enumerate(testdescs)] |
|
3287 | tests = [self._gettest(d, i) for i, d in enumerate(testdescs)] | |
3287 | num_tests = len(tests) * self.options.runs_per_test |
|
3288 | num_tests = len(tests) * self.options.runs_per_test | |
3288 |
|
3289 | |||
3289 | jobs = min(num_tests, self.options.jobs) |
|
3290 | jobs = min(num_tests, self.options.jobs) | |
3290 |
|
3291 | |||
3291 | failed = False |
|
3292 | failed = False | |
3292 | kws = self.options.keywords |
|
3293 | kws = self.options.keywords | |
3293 | if kws is not None and PYTHON3: |
|
3294 | if kws is not None and PYTHON3: | |
3294 | kws = kws.encode('utf-8') |
|
3295 | kws = kws.encode('utf-8') | |
3295 |
|
3296 | |||
3296 | suite = TestSuite( |
|
3297 | suite = TestSuite( | |
3297 | self._testdir, |
|
3298 | self._testdir, | |
3298 | jobs=jobs, |
|
3299 | jobs=jobs, | |
3299 | whitelist=self.options.whitelisted, |
|
3300 | whitelist=self.options.whitelisted, | |
3300 | blacklist=self.options.blacklist, |
|
3301 | blacklist=self.options.blacklist, | |
3301 | keywords=kws, |
|
3302 | keywords=kws, | |
3302 | loop=self.options.loop, |
|
3303 | loop=self.options.loop, | |
3303 | runs_per_test=self.options.runs_per_test, |
|
3304 | runs_per_test=self.options.runs_per_test, | |
3304 | showchannels=self.options.showchannels, |
|
3305 | showchannels=self.options.showchannels, | |
3305 | tests=tests, |
|
3306 | tests=tests, | |
3306 | loadtest=_reloadtest, |
|
3307 | loadtest=_reloadtest, | |
3307 | ) |
|
3308 | ) | |
3308 | verbosity = 1 |
|
3309 | verbosity = 1 | |
3309 | if self.options.list_tests: |
|
3310 | if self.options.list_tests: | |
3310 | verbosity = 0 |
|
3311 | verbosity = 0 | |
3311 | elif self.options.verbose: |
|
3312 | elif self.options.verbose: | |
3312 | verbosity = 2 |
|
3313 | verbosity = 2 | |
3313 | runner = TextTestRunner(self, verbosity=verbosity) |
|
3314 | runner = TextTestRunner(self, verbosity=verbosity) | |
3314 |
|
3315 | |||
3315 | if self.options.list_tests: |
|
3316 | if self.options.list_tests: | |
3316 | result = runner.listtests(suite) |
|
3317 | result = runner.listtests(suite) | |
3317 | else: |
|
3318 | else: | |
3318 | if self._installdir: |
|
3319 | if self._installdir: | |
3319 | self._installhg() |
|
3320 | self._installhg() | |
3320 | self._checkhglib("Testing") |
|
3321 | self._checkhglib("Testing") | |
3321 | else: |
|
3322 | else: | |
3322 | self._usecorrectpython() |
|
3323 | self._usecorrectpython() | |
3323 | if self.options.chg: |
|
3324 | if self.options.chg: | |
3324 | assert self._installdir |
|
3325 | assert self._installdir | |
3325 | self._installchg() |
|
3326 | self._installchg() | |
3326 |
|
3327 | |||
3327 | log( |
|
3328 | log( | |
3328 | 'running %d tests using %d parallel processes' |
|
3329 | 'running %d tests using %d parallel processes' | |
3329 | % (num_tests, jobs) |
|
3330 | % (num_tests, jobs) | |
3330 | ) |
|
3331 | ) | |
3331 |
|
3332 | |||
3332 | result = runner.run(suite) |
|
3333 | result = runner.run(suite) | |
3333 |
|
3334 | |||
3334 | if result.failures or result.errors: |
|
3335 | if result.failures or result.errors: | |
3335 | failed = True |
|
3336 | failed = True | |
3336 |
|
3337 | |||
3337 | result.onEnd() |
|
3338 | result.onEnd() | |
3338 |
|
3339 | |||
3339 | if self.options.anycoverage: |
|
3340 | if self.options.anycoverage: | |
3340 | self._outputcoverage() |
|
3341 | self._outputcoverage() | |
3341 | except KeyboardInterrupt: |
|
3342 | except KeyboardInterrupt: | |
3342 | failed = True |
|
3343 | failed = True | |
3343 | print("\ninterrupted!") |
|
3344 | print("\ninterrupted!") | |
3344 |
|
3345 | |||
3345 | if failed: |
|
3346 | if failed: | |
3346 | return 1 |
|
3347 | return 1 | |
3347 |
|
3348 | |||
3348 | def _geterrpath(self, test): |
|
3349 | def _geterrpath(self, test): | |
3349 | # test['path'] is a relative path |
|
3350 | # test['path'] is a relative path | |
3350 | if 'case' in test: |
|
3351 | if 'case' in test: | |
3351 | # for multiple dimensions test cases |
|
3352 | # for multiple dimensions test cases | |
3352 | casestr = b'#'.join(test['case']) |
|
3353 | casestr = b'#'.join(test['case']) | |
3353 | errpath = b'%s#%s.err' % (test['path'], casestr) |
|
3354 | errpath = b'%s#%s.err' % (test['path'], casestr) | |
3354 | else: |
|
3355 | else: | |
3355 | errpath = b'%s.err' % test['path'] |
|
3356 | errpath = b'%s.err' % test['path'] | |
3356 | if self.options.outputdir: |
|
3357 | if self.options.outputdir: | |
3357 | self._outputdir = canonpath(_sys2bytes(self.options.outputdir)) |
|
3358 | self._outputdir = canonpath(_sys2bytes(self.options.outputdir)) | |
3358 | errpath = os.path.join(self._outputdir, errpath) |
|
3359 | errpath = os.path.join(self._outputdir, errpath) | |
3359 | return errpath |
|
3360 | return errpath | |
3360 |
|
3361 | |||
3361 | def _getport(self, count): |
|
3362 | def _getport(self, count): | |
3362 | port = self._ports.get(count) # do we have a cached entry? |
|
3363 | port = self._ports.get(count) # do we have a cached entry? | |
3363 | if port is None: |
|
3364 | if port is None: | |
3364 | portneeded = 3 |
|
3365 | portneeded = 3 | |
3365 | # above 100 tries we just give up and let test reports failure |
|
3366 | # above 100 tries we just give up and let test reports failure | |
3366 | for tries in xrange(100): |
|
3367 | for tries in xrange(100): | |
3367 | allfree = True |
|
3368 | allfree = True | |
3368 | port = self.options.port + self._portoffset |
|
3369 | port = self.options.port + self._portoffset | |
3369 | for idx in xrange(portneeded): |
|
3370 | for idx in xrange(portneeded): | |
3370 | if not checkportisavailable(port + idx): |
|
3371 | if not checkportisavailable(port + idx): | |
3371 | allfree = False |
|
3372 | allfree = False | |
3372 | break |
|
3373 | break | |
3373 | self._portoffset += portneeded |
|
3374 | self._portoffset += portneeded | |
3374 | if allfree: |
|
3375 | if allfree: | |
3375 | break |
|
3376 | break | |
3376 | self._ports[count] = port |
|
3377 | self._ports[count] = port | |
3377 | return port |
|
3378 | return port | |
3378 |
|
3379 | |||
3379 | def _gettest(self, testdesc, count): |
|
3380 | def _gettest(self, testdesc, count): | |
3380 | """Obtain a Test by looking at its filename. |
|
3381 | """Obtain a Test by looking at its filename. | |
3381 |
|
3382 | |||
3382 | Returns a Test instance. The Test may not be runnable if it doesn't |
|
3383 | Returns a Test instance. The Test may not be runnable if it doesn't | |
3383 | map to a known type. |
|
3384 | map to a known type. | |
3384 | """ |
|
3385 | """ | |
3385 | path = testdesc['path'] |
|
3386 | path = testdesc['path'] | |
3386 | lctest = path.lower() |
|
3387 | lctest = path.lower() | |
3387 | testcls = Test |
|
3388 | testcls = Test | |
3388 |
|
3389 | |||
3389 | for ext, cls in self.TESTTYPES: |
|
3390 | for ext, cls in self.TESTTYPES: | |
3390 | if lctest.endswith(ext): |
|
3391 | if lctest.endswith(ext): | |
3391 | testcls = cls |
|
3392 | testcls = cls | |
3392 | break |
|
3393 | break | |
3393 |
|
3394 | |||
3394 | refpath = os.path.join(getcwdb(), path) |
|
3395 | refpath = os.path.join(getcwdb(), path) | |
3395 | tmpdir = os.path.join(self._hgtmp, b'child%d' % count) |
|
3396 | tmpdir = os.path.join(self._hgtmp, b'child%d' % count) | |
3396 |
|
3397 | |||
3397 | # extra keyword parameters. 'case' is used by .t tests |
|
3398 | # extra keyword parameters. 'case' is used by .t tests | |
3398 | kwds = {k: testdesc[k] for k in ['case'] if k in testdesc} |
|
3399 | kwds = {k: testdesc[k] for k in ['case'] if k in testdesc} | |
3399 |
|
3400 | |||
3400 | t = testcls( |
|
3401 | t = testcls( | |
3401 | refpath, |
|
3402 | refpath, | |
3402 | self._outputdir, |
|
3403 | self._outputdir, | |
3403 | tmpdir, |
|
3404 | tmpdir, | |
3404 | keeptmpdir=self.options.keep_tmpdir, |
|
3405 | keeptmpdir=self.options.keep_tmpdir, | |
3405 | debug=self.options.debug, |
|
3406 | debug=self.options.debug, | |
3406 | first=self.options.first, |
|
3407 | first=self.options.first, | |
3407 | timeout=self.options.timeout, |
|
3408 | timeout=self.options.timeout, | |
3408 | startport=self._getport(count), |
|
3409 | startport=self._getport(count), | |
3409 | extraconfigopts=self.options.extra_config_opt, |
|
3410 | extraconfigopts=self.options.extra_config_opt, | |
3410 | shell=self.options.shell, |
|
3411 | shell=self.options.shell, | |
3411 | hgcommand=self._hgcommand, |
|
3412 | hgcommand=self._hgcommand, | |
3412 | usechg=bool(self.options.with_chg or self.options.chg), |
|
3413 | usechg=bool(self.options.with_chg or self.options.chg), | |
3413 | chgdebug=self.options.chg_debug, |
|
3414 | chgdebug=self.options.chg_debug, | |
3414 | useipv6=useipv6, |
|
3415 | useipv6=useipv6, | |
3415 | **kwds |
|
3416 | **kwds | |
3416 | ) |
|
3417 | ) | |
3417 | t.should_reload = True |
|
3418 | t.should_reload = True | |
3418 | return t |
|
3419 | return t | |
3419 |
|
3420 | |||
3420 | def _cleanup(self): |
|
3421 | def _cleanup(self): | |
3421 | """Clean up state from this test invocation.""" |
|
3422 | """Clean up state from this test invocation.""" | |
3422 | if self.options.keep_tmpdir: |
|
3423 | if self.options.keep_tmpdir: | |
3423 | return |
|
3424 | return | |
3424 |
|
3425 | |||
3425 | vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp)) |
|
3426 | vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp)) | |
3426 | shutil.rmtree(self._hgtmp, True) |
|
3427 | shutil.rmtree(self._hgtmp, True) | |
3427 | for f in self._createdfiles: |
|
3428 | for f in self._createdfiles: | |
3428 | try: |
|
3429 | try: | |
3429 | os.remove(f) |
|
3430 | os.remove(f) | |
3430 | except OSError: |
|
3431 | except OSError: | |
3431 | pass |
|
3432 | pass | |
3432 |
|
3433 | |||
3433 | def _usecorrectpython(self): |
|
3434 | def _usecorrectpython(self): | |
3434 | """Configure the environment to use the appropriate Python in tests.""" |
|
3435 | """Configure the environment to use the appropriate Python in tests.""" | |
3435 | # Tests must use the same interpreter as us or bad things will happen. |
|
3436 | # Tests must use the same interpreter as us or bad things will happen. | |
3436 | pyexename = sys.platform == 'win32' and b'python.exe' or b'python' |
|
3437 | pyexename = sys.platform == 'win32' and b'python.exe' or b'python' | |
3437 |
|
3438 | |||
3438 | # os.symlink() is a thing with py3 on Windows, but it requires |
|
3439 | # os.symlink() is a thing with py3 on Windows, but it requires | |
3439 | # Administrator rights. |
|
3440 | # Administrator rights. | |
3440 | if getattr(os, 'symlink', None) and os.name != 'nt': |
|
3441 | if getattr(os, 'symlink', None) and os.name != 'nt': | |
3441 | vlog( |
|
3442 | vlog( | |
3442 | "# Making python executable in test path a symlink to '%s'" |
|
3443 | "# Making python executable in test path a symlink to '%s'" | |
3443 | % sysexecutable |
|
3444 | % sysexecutable | |
3444 | ) |
|
3445 | ) | |
3445 | mypython = os.path.join(self._tmpbindir, pyexename) |
|
3446 | mypython = os.path.join(self._tmpbindir, pyexename) | |
3446 | try: |
|
3447 | try: | |
3447 | if os.readlink(mypython) == sysexecutable: |
|
3448 | if os.readlink(mypython) == sysexecutable: | |
3448 | return |
|
3449 | return | |
3449 | os.unlink(mypython) |
|
3450 | os.unlink(mypython) | |
3450 | except OSError as err: |
|
3451 | except OSError as err: | |
3451 | if err.errno != errno.ENOENT: |
|
3452 | if err.errno != errno.ENOENT: | |
3452 | raise |
|
3453 | raise | |
3453 | if self._findprogram(pyexename) != sysexecutable: |
|
3454 | if self._findprogram(pyexename) != sysexecutable: | |
3454 | try: |
|
3455 | try: | |
3455 | os.symlink(sysexecutable, mypython) |
|
3456 | os.symlink(sysexecutable, mypython) | |
3456 | self._createdfiles.append(mypython) |
|
3457 | self._createdfiles.append(mypython) | |
3457 | except OSError as err: |
|
3458 | except OSError as err: | |
3458 | # child processes may race, which is harmless |
|
3459 | # child processes may race, which is harmless | |
3459 | if err.errno != errno.EEXIST: |
|
3460 | if err.errno != errno.EEXIST: | |
3460 | raise |
|
3461 | raise | |
3461 | else: |
|
3462 | else: | |
3462 | exedir, exename = os.path.split(sysexecutable) |
|
3463 | exedir, exename = os.path.split(sysexecutable) | |
3463 | vlog( |
|
3464 | vlog( | |
3464 | "# Modifying search path to find %s as %s in '%s'" |
|
3465 | "# Modifying search path to find %s as %s in '%s'" | |
3465 | % (exename, pyexename, exedir) |
|
3466 | % (exename, pyexename, exedir) | |
3466 | ) |
|
3467 | ) | |
3467 | path = os.environ['PATH'].split(os.pathsep) |
|
3468 | path = os.environ['PATH'].split(os.pathsep) | |
3468 | while exedir in path: |
|
3469 | while exedir in path: | |
3469 | path.remove(exedir) |
|
3470 | path.remove(exedir) | |
3470 | os.environ['PATH'] = os.pathsep.join([exedir] + path) |
|
3471 | os.environ['PATH'] = os.pathsep.join([exedir] + path) | |
3471 | if not self._findprogram(pyexename): |
|
3472 | if not self._findprogram(pyexename): | |
3472 | print("WARNING: Cannot find %s in search path" % pyexename) |
|
3473 | print("WARNING: Cannot find %s in search path" % pyexename) | |
3473 |
|
3474 | |||
3474 | def _installhg(self): |
|
3475 | def _installhg(self): | |
3475 | """Install hg into the test environment. |
|
3476 | """Install hg into the test environment. | |
3476 |
|
3477 | |||
3477 | This will also configure hg with the appropriate testing settings. |
|
3478 | This will also configure hg with the appropriate testing settings. | |
3478 | """ |
|
3479 | """ | |
3479 | vlog("# Performing temporary installation of HG") |
|
3480 | vlog("# Performing temporary installation of HG") | |
3480 | installerrs = os.path.join(self._hgtmp, b"install.err") |
|
3481 | installerrs = os.path.join(self._hgtmp, b"install.err") | |
3481 | compiler = '' |
|
3482 | compiler = '' | |
3482 | if self.options.compiler: |
|
3483 | if self.options.compiler: | |
3483 | compiler = '--compiler ' + self.options.compiler |
|
3484 | compiler = '--compiler ' + self.options.compiler | |
3484 | setup_opts = b"" |
|
3485 | setup_opts = b"" | |
3485 | if self.options.pure: |
|
3486 | if self.options.pure: | |
3486 | setup_opts = b"--pure" |
|
3487 | setup_opts = b"--pure" | |
3487 | elif self.options.rust: |
|
3488 | elif self.options.rust: | |
3488 | setup_opts = b"--rust" |
|
3489 | setup_opts = b"--rust" | |
3489 | elif self.options.no_rust: |
|
3490 | elif self.options.no_rust: | |
3490 | setup_opts = b"--no-rust" |
|
3491 | setup_opts = b"--no-rust" | |
3491 |
|
3492 | |||
3492 | # Run installer in hg root |
|
3493 | # Run installer in hg root | |
3493 | script = os.path.realpath(sys.argv[0]) |
|
3494 | script = os.path.realpath(sys.argv[0]) | |
3494 | exe = sysexecutable |
|
3495 | exe = sysexecutable | |
3495 | if PYTHON3: |
|
3496 | if PYTHON3: | |
3496 | compiler = _sys2bytes(compiler) |
|
3497 | compiler = _sys2bytes(compiler) | |
3497 | script = _sys2bytes(script) |
|
3498 | script = _sys2bytes(script) | |
3498 | exe = _sys2bytes(exe) |
|
3499 | exe = _sys2bytes(exe) | |
3499 | hgroot = os.path.dirname(os.path.dirname(script)) |
|
3500 | hgroot = os.path.dirname(os.path.dirname(script)) | |
3500 | self._hgroot = hgroot |
|
3501 | self._hgroot = hgroot | |
3501 | os.chdir(hgroot) |
|
3502 | os.chdir(hgroot) | |
3502 | nohome = b'--home=""' |
|
3503 | nohome = b'--home=""' | |
3503 | if os.name == 'nt': |
|
3504 | if os.name == 'nt': | |
3504 | # The --home="" trick works only on OS where os.sep == '/' |
|
3505 | # The --home="" trick works only on OS where os.sep == '/' | |
3505 | # because of a distutils convert_path() fast-path. Avoid it at |
|
3506 | # because of a distutils convert_path() fast-path. Avoid it at | |
3506 | # least on Windows for now, deal with .pydistutils.cfg bugs |
|
3507 | # least on Windows for now, deal with .pydistutils.cfg bugs | |
3507 | # when they happen. |
|
3508 | # when they happen. | |
3508 | nohome = b'' |
|
3509 | nohome = b'' | |
3509 | cmd = ( |
|
3510 | cmd = ( | |
3510 | b'"%(exe)s" setup.py %(setup_opts)s clean --all' |
|
3511 | b'"%(exe)s" setup.py %(setup_opts)s clean --all' | |
3511 | b' build %(compiler)s --build-base="%(base)s"' |
|
3512 | b' build %(compiler)s --build-base="%(base)s"' | |
3512 | b' install --force --prefix="%(prefix)s"' |
|
3513 | b' install --force --prefix="%(prefix)s"' | |
3513 | b' --install-lib="%(libdir)s"' |
|
3514 | b' --install-lib="%(libdir)s"' | |
3514 | b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1' |
|
3515 | b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1' | |
3515 | % { |
|
3516 | % { | |
3516 | b'exe': exe, |
|
3517 | b'exe': exe, | |
3517 | b'setup_opts': setup_opts, |
|
3518 | b'setup_opts': setup_opts, | |
3518 | b'compiler': compiler, |
|
3519 | b'compiler': compiler, | |
3519 | b'base': os.path.join(self._hgtmp, b"build"), |
|
3520 | b'base': os.path.join(self._hgtmp, b"build"), | |
3520 | b'prefix': self._installdir, |
|
3521 | b'prefix': self._installdir, | |
3521 | b'libdir': self._pythondir, |
|
3522 | b'libdir': self._pythondir, | |
3522 | b'bindir': self._bindir, |
|
3523 | b'bindir': self._bindir, | |
3523 | b'nohome': nohome, |
|
3524 | b'nohome': nohome, | |
3524 | b'logfile': installerrs, |
|
3525 | b'logfile': installerrs, | |
3525 | } |
|
3526 | } | |
3526 | ) |
|
3527 | ) | |
3527 |
|
3528 | |||
3528 | # setuptools requires install directories to exist. |
|
3529 | # setuptools requires install directories to exist. | |
3529 | def makedirs(p): |
|
3530 | def makedirs(p): | |
3530 | try: |
|
3531 | try: | |
3531 | os.makedirs(p) |
|
3532 | os.makedirs(p) | |
3532 | except OSError as e: |
|
3533 | except OSError as e: | |
3533 | if e.errno != errno.EEXIST: |
|
3534 | if e.errno != errno.EEXIST: | |
3534 | raise |
|
3535 | raise | |
3535 |
|
3536 | |||
3536 | makedirs(self._pythondir) |
|
3537 | makedirs(self._pythondir) | |
3537 | makedirs(self._bindir) |
|
3538 | makedirs(self._bindir) | |
3538 |
|
3539 | |||
3539 | vlog("# Running", cmd.decode("utf-8")) |
|
3540 | vlog("# Running", cmd.decode("utf-8")) | |
3540 | if subprocess.call(_bytes2sys(cmd), shell=True) == 0: |
|
3541 | if subprocess.call(_bytes2sys(cmd), shell=True) == 0: | |
3541 | if not self.options.verbose: |
|
3542 | if not self.options.verbose: | |
3542 | try: |
|
3543 | try: | |
3543 | os.remove(installerrs) |
|
3544 | os.remove(installerrs) | |
3544 | except OSError as e: |
|
3545 | except OSError as e: | |
3545 | if e.errno != errno.ENOENT: |
|
3546 | if e.errno != errno.ENOENT: | |
3546 | raise |
|
3547 | raise | |
3547 | else: |
|
3548 | else: | |
3548 | with open(installerrs, 'rb') as f: |
|
3549 | with open(installerrs, 'rb') as f: | |
3549 | for line in f: |
|
3550 | for line in f: | |
3550 | if PYTHON3: |
|
3551 | if PYTHON3: | |
3551 | sys.stdout.buffer.write(line) |
|
3552 | sys.stdout.buffer.write(line) | |
3552 | else: |
|
3553 | else: | |
3553 | sys.stdout.write(line) |
|
3554 | sys.stdout.write(line) | |
3554 | sys.exit(1) |
|
3555 | sys.exit(1) | |
3555 | os.chdir(self._testdir) |
|
3556 | os.chdir(self._testdir) | |
3556 |
|
3557 | |||
3557 | self._usecorrectpython() |
|
3558 | self._usecorrectpython() | |
3558 |
|
3559 | |||
3559 | hgbat = os.path.join(self._bindir, b'hg.bat') |
|
3560 | hgbat = os.path.join(self._bindir, b'hg.bat') | |
3560 | if os.path.isfile(hgbat): |
|
3561 | if os.path.isfile(hgbat): | |
3561 | # hg.bat expects to be put in bin/scripts while run-tests.py |
|
3562 | # hg.bat expects to be put in bin/scripts while run-tests.py | |
3562 | # installation layout put it in bin/ directly. Fix it |
|
3563 | # installation layout put it in bin/ directly. Fix it | |
3563 | with open(hgbat, 'rb') as f: |
|
3564 | with open(hgbat, 'rb') as f: | |
3564 | data = f.read() |
|
3565 | data = f.read() | |
3565 | if br'"%~dp0..\python" "%~dp0hg" %*' in data: |
|
3566 | if br'"%~dp0..\python" "%~dp0hg" %*' in data: | |
3566 | data = data.replace( |
|
3567 | data = data.replace( | |
3567 | br'"%~dp0..\python" "%~dp0hg" %*', |
|
3568 | br'"%~dp0..\python" "%~dp0hg" %*', | |
3568 | b'"%~dp0python" "%~dp0hg" %*', |
|
3569 | b'"%~dp0python" "%~dp0hg" %*', | |
3569 | ) |
|
3570 | ) | |
3570 | with open(hgbat, 'wb') as f: |
|
3571 | with open(hgbat, 'wb') as f: | |
3571 | f.write(data) |
|
3572 | f.write(data) | |
3572 | else: |
|
3573 | else: | |
3573 | print('WARNING: cannot fix hg.bat reference to python.exe') |
|
3574 | print('WARNING: cannot fix hg.bat reference to python.exe') | |
3574 |
|
3575 | |||
3575 | if self.options.anycoverage: |
|
3576 | if self.options.anycoverage: | |
3576 | custom = os.path.join( |
|
3577 | custom = os.path.join( | |
3577 | osenvironb[b'RUNTESTDIR'], b'sitecustomize.py' |
|
3578 | osenvironb[b'RUNTESTDIR'], b'sitecustomize.py' | |
3578 | ) |
|
3579 | ) | |
3579 | target = os.path.join(self._pythondir, b'sitecustomize.py') |
|
3580 | target = os.path.join(self._pythondir, b'sitecustomize.py') | |
3580 | vlog('# Installing coverage trigger to %s' % target) |
|
3581 | vlog('# Installing coverage trigger to %s' % target) | |
3581 | shutil.copyfile(custom, target) |
|
3582 | shutil.copyfile(custom, target) | |
3582 | rc = os.path.join(self._testdir, b'.coveragerc') |
|
3583 | rc = os.path.join(self._testdir, b'.coveragerc') | |
3583 | vlog('# Installing coverage rc to %s' % rc) |
|
3584 | vlog('# Installing coverage rc to %s' % rc) | |
3584 | osenvironb[b'COVERAGE_PROCESS_START'] = rc |
|
3585 | osenvironb[b'COVERAGE_PROCESS_START'] = rc | |
3585 | covdir = os.path.join(self._installdir, b'..', b'coverage') |
|
3586 | covdir = os.path.join(self._installdir, b'..', b'coverage') | |
3586 | try: |
|
3587 | try: | |
3587 | os.mkdir(covdir) |
|
3588 | os.mkdir(covdir) | |
3588 | except OSError as e: |
|
3589 | except OSError as e: | |
3589 | if e.errno != errno.EEXIST: |
|
3590 | if e.errno != errno.EEXIST: | |
3590 | raise |
|
3591 | raise | |
3591 |
|
3592 | |||
3592 | osenvironb[b'COVERAGE_DIR'] = covdir |
|
3593 | osenvironb[b'COVERAGE_DIR'] = covdir | |
3593 |
|
3594 | |||
3594 | def _checkhglib(self, verb): |
|
3595 | def _checkhglib(self, verb): | |
3595 | """Ensure that the 'mercurial' package imported by python is |
|
3596 | """Ensure that the 'mercurial' package imported by python is | |
3596 | the one we expect it to be. If not, print a warning to stderr.""" |
|
3597 | the one we expect it to be. If not, print a warning to stderr.""" | |
3597 | if (self._bindir == self._pythondir) and ( |
|
3598 | if (self._bindir == self._pythondir) and ( | |
3598 | self._bindir != self._tmpbindir |
|
3599 | self._bindir != self._tmpbindir | |
3599 | ): |
|
3600 | ): | |
3600 | # The pythondir has been inferred from --with-hg flag. |
|
3601 | # The pythondir has been inferred from --with-hg flag. | |
3601 | # We cannot expect anything sensible here. |
|
3602 | # We cannot expect anything sensible here. | |
3602 | return |
|
3603 | return | |
3603 | expecthg = os.path.join(self._pythondir, b'mercurial') |
|
3604 | expecthg = os.path.join(self._pythondir, b'mercurial') | |
3604 | actualhg = self._gethgpath() |
|
3605 | actualhg = self._gethgpath() | |
3605 | if os.path.abspath(actualhg) != os.path.abspath(expecthg): |
|
3606 | if os.path.abspath(actualhg) != os.path.abspath(expecthg): | |
3606 | sys.stderr.write( |
|
3607 | sys.stderr.write( | |
3607 | 'warning: %s with unexpected mercurial lib: %s\n' |
|
3608 | 'warning: %s with unexpected mercurial lib: %s\n' | |
3608 | ' (expected %s)\n' % (verb, actualhg, expecthg) |
|
3609 | ' (expected %s)\n' % (verb, actualhg, expecthg) | |
3609 | ) |
|
3610 | ) | |
3610 |
|
3611 | |||
3611 | def _gethgpath(self): |
|
3612 | def _gethgpath(self): | |
3612 | """Return the path to the mercurial package that is actually found by |
|
3613 | """Return the path to the mercurial package that is actually found by | |
3613 | the current Python interpreter.""" |
|
3614 | the current Python interpreter.""" | |
3614 | if self._hgpath is not None: |
|
3615 | if self._hgpath is not None: | |
3615 | return self._hgpath |
|
3616 | return self._hgpath | |
3616 |
|
3617 | |||
3617 | cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"' |
|
3618 | cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"' | |
3618 | cmd = cmd % PYTHON |
|
3619 | cmd = cmd % PYTHON | |
3619 | if PYTHON3: |
|
3620 | if PYTHON3: | |
3620 | cmd = _bytes2sys(cmd) |
|
3621 | cmd = _bytes2sys(cmd) | |
3621 |
|
3622 | |||
3622 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) |
|
3623 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) | |
3623 | out, err = p.communicate() |
|
3624 | out, err = p.communicate() | |
3624 |
|
3625 | |||
3625 | self._hgpath = out.strip() |
|
3626 | self._hgpath = out.strip() | |
3626 |
|
3627 | |||
3627 | return self._hgpath |
|
3628 | return self._hgpath | |
3628 |
|
3629 | |||
3629 | def _installchg(self): |
|
3630 | def _installchg(self): | |
3630 | """Install chg into the test environment""" |
|
3631 | """Install chg into the test environment""" | |
3631 | vlog('# Performing temporary installation of CHG') |
|
3632 | vlog('# Performing temporary installation of CHG') | |
3632 | assert os.path.dirname(self._bindir) == self._installdir |
|
3633 | assert os.path.dirname(self._bindir) == self._installdir | |
3633 | assert self._hgroot, 'must be called after _installhg()' |
|
3634 | assert self._hgroot, 'must be called after _installhg()' | |
3634 | cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % { |
|
3635 | cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % { | |
3635 | b'make': b'make', # TODO: switch by option or environment? |
|
3636 | b'make': b'make', # TODO: switch by option or environment? | |
3636 | b'prefix': self._installdir, |
|
3637 | b'prefix': self._installdir, | |
3637 | } |
|
3638 | } | |
3638 | cwd = os.path.join(self._hgroot, b'contrib', b'chg') |
|
3639 | cwd = os.path.join(self._hgroot, b'contrib', b'chg') | |
3639 | vlog("# Running", cmd) |
|
3640 | vlog("# Running", cmd) | |
3640 | proc = subprocess.Popen( |
|
3641 | proc = subprocess.Popen( | |
3641 | cmd, |
|
3642 | cmd, | |
3642 | shell=True, |
|
3643 | shell=True, | |
3643 | cwd=cwd, |
|
3644 | cwd=cwd, | |
3644 | stdin=subprocess.PIPE, |
|
3645 | stdin=subprocess.PIPE, | |
3645 | stdout=subprocess.PIPE, |
|
3646 | stdout=subprocess.PIPE, | |
3646 | stderr=subprocess.STDOUT, |
|
3647 | stderr=subprocess.STDOUT, | |
3647 | ) |
|
3648 | ) | |
3648 | out, _err = proc.communicate() |
|
3649 | out, _err = proc.communicate() | |
3649 | if proc.returncode != 0: |
|
3650 | if proc.returncode != 0: | |
3650 | if PYTHON3: |
|
3651 | if PYTHON3: | |
3651 | sys.stdout.buffer.write(out) |
|
3652 | sys.stdout.buffer.write(out) | |
3652 | else: |
|
3653 | else: | |
3653 | sys.stdout.write(out) |
|
3654 | sys.stdout.write(out) | |
3654 | sys.exit(1) |
|
3655 | sys.exit(1) | |
3655 |
|
3656 | |||
3656 | def _outputcoverage(self): |
|
3657 | def _outputcoverage(self): | |
3657 | """Produce code coverage output.""" |
|
3658 | """Produce code coverage output.""" | |
3658 | import coverage |
|
3659 | import coverage | |
3659 |
|
3660 | |||
3660 | coverage = coverage.coverage |
|
3661 | coverage = coverage.coverage | |
3661 |
|
3662 | |||
3662 | vlog('# Producing coverage report') |
|
3663 | vlog('# Producing coverage report') | |
3663 | # chdir is the easiest way to get short, relative paths in the |
|
3664 | # chdir is the easiest way to get short, relative paths in the | |
3664 | # output. |
|
3665 | # output. | |
3665 | os.chdir(self._hgroot) |
|
3666 | os.chdir(self._hgroot) | |
3666 | covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage') |
|
3667 | covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage') | |
3667 | cov = coverage(data_file=os.path.join(covdir, 'cov')) |
|
3668 | cov = coverage(data_file=os.path.join(covdir, 'cov')) | |
3668 |
|
3669 | |||
3669 | # Map install directory paths back to source directory. |
|
3670 | # Map install directory paths back to source directory. | |
3670 | cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)] |
|
3671 | cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)] | |
3671 |
|
3672 | |||
3672 | cov.combine() |
|
3673 | cov.combine() | |
3673 |
|
3674 | |||
3674 | omit = [ |
|
3675 | omit = [ | |
3675 | _bytes2sys(os.path.join(x, b'*')) |
|
3676 | _bytes2sys(os.path.join(x, b'*')) | |
3676 | for x in [self._bindir, self._testdir] |
|
3677 | for x in [self._bindir, self._testdir] | |
3677 | ] |
|
3678 | ] | |
3678 | cov.report(ignore_errors=True, omit=omit) |
|
3679 | cov.report(ignore_errors=True, omit=omit) | |
3679 |
|
3680 | |||
3680 | if self.options.htmlcov: |
|
3681 | if self.options.htmlcov: | |
3681 | htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov') |
|
3682 | htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov') | |
3682 | cov.html_report(directory=htmldir, omit=omit) |
|
3683 | cov.html_report(directory=htmldir, omit=omit) | |
3683 | if self.options.annotate: |
|
3684 | if self.options.annotate: | |
3684 | adir = os.path.join(_bytes2sys(self._outputdir), 'annotated') |
|
3685 | adir = os.path.join(_bytes2sys(self._outputdir), 'annotated') | |
3685 | if not os.path.isdir(adir): |
|
3686 | if not os.path.isdir(adir): | |
3686 | os.mkdir(adir) |
|
3687 | os.mkdir(adir) | |
3687 | cov.annotate(directory=adir, omit=omit) |
|
3688 | cov.annotate(directory=adir, omit=omit) | |
3688 |
|
3689 | |||
3689 | def _findprogram(self, program): |
|
3690 | def _findprogram(self, program): | |
3690 | """Search PATH for a executable program""" |
|
3691 | """Search PATH for a executable program""" | |
3691 | dpb = _sys2bytes(os.defpath) |
|
3692 | dpb = _sys2bytes(os.defpath) | |
3692 | sepb = _sys2bytes(os.pathsep) |
|
3693 | sepb = _sys2bytes(os.pathsep) | |
3693 | for p in osenvironb.get(b'PATH', dpb).split(sepb): |
|
3694 | for p in osenvironb.get(b'PATH', dpb).split(sepb): | |
3694 | name = os.path.join(p, program) |
|
3695 | name = os.path.join(p, program) | |
3695 | if os.name == 'nt' or os.access(name, os.X_OK): |
|
3696 | if os.name == 'nt' or os.access(name, os.X_OK): | |
3696 | return _bytes2sys(name) |
|
3697 | return _bytes2sys(name) | |
3697 | return None |
|
3698 | return None | |
3698 |
|
3699 | |||
3699 | def _checktools(self): |
|
3700 | def _checktools(self): | |
3700 | """Ensure tools required to run tests are present.""" |
|
3701 | """Ensure tools required to run tests are present.""" | |
3701 | for p in self.REQUIREDTOOLS: |
|
3702 | for p in self.REQUIREDTOOLS: | |
3702 | if os.name == 'nt' and not p.endswith(b'.exe'): |
|
3703 | if os.name == 'nt' and not p.endswith(b'.exe'): | |
3703 | p += b'.exe' |
|
3704 | p += b'.exe' | |
3704 | found = self._findprogram(p) |
|
3705 | found = self._findprogram(p) | |
3705 | p = p.decode("utf-8") |
|
3706 | p = p.decode("utf-8") | |
3706 | if found: |
|
3707 | if found: | |
3707 | vlog("# Found prerequisite", p, "at", found) |
|
3708 | vlog("# Found prerequisite", p, "at", found) | |
3708 | else: |
|
3709 | else: | |
3709 | print("WARNING: Did not find prerequisite tool: %s " % p) |
|
3710 | print("WARNING: Did not find prerequisite tool: %s " % p) | |
3710 |
|
3711 | |||
3711 |
|
3712 | |||
3712 | def aggregateexceptions(path): |
|
3713 | def aggregateexceptions(path): | |
3713 | exceptioncounts = collections.Counter() |
|
3714 | exceptioncounts = collections.Counter() | |
3714 | testsbyfailure = collections.defaultdict(set) |
|
3715 | testsbyfailure = collections.defaultdict(set) | |
3715 | failuresbytest = collections.defaultdict(set) |
|
3716 | failuresbytest = collections.defaultdict(set) | |
3716 |
|
3717 | |||
3717 | for f in os.listdir(path): |
|
3718 | for f in os.listdir(path): | |
3718 | with open(os.path.join(path, f), 'rb') as fh: |
|
3719 | with open(os.path.join(path, f), 'rb') as fh: | |
3719 | data = fh.read().split(b'\0') |
|
3720 | data = fh.read().split(b'\0') | |
3720 | if len(data) != 5: |
|
3721 | if len(data) != 5: | |
3721 | continue |
|
3722 | continue | |
3722 |
|
3723 | |||
3723 | exc, mainframe, hgframe, hgline, testname = data |
|
3724 | exc, mainframe, hgframe, hgline, testname = data | |
3724 | exc = exc.decode('utf-8') |
|
3725 | exc = exc.decode('utf-8') | |
3725 | mainframe = mainframe.decode('utf-8') |
|
3726 | mainframe = mainframe.decode('utf-8') | |
3726 | hgframe = hgframe.decode('utf-8') |
|
3727 | hgframe = hgframe.decode('utf-8') | |
3727 | hgline = hgline.decode('utf-8') |
|
3728 | hgline = hgline.decode('utf-8') | |
3728 | testname = testname.decode('utf-8') |
|
3729 | testname = testname.decode('utf-8') | |
3729 |
|
3730 | |||
3730 | key = (hgframe, hgline, exc) |
|
3731 | key = (hgframe, hgline, exc) | |
3731 | exceptioncounts[key] += 1 |
|
3732 | exceptioncounts[key] += 1 | |
3732 | testsbyfailure[key].add(testname) |
|
3733 | testsbyfailure[key].add(testname) | |
3733 | failuresbytest[testname].add(key) |
|
3734 | failuresbytest[testname].add(key) | |
3734 |
|
3735 | |||
3735 | # Find test having fewest failures for each failure. |
|
3736 | # Find test having fewest failures for each failure. | |
3736 | leastfailing = {} |
|
3737 | leastfailing = {} | |
3737 | for key, tests in testsbyfailure.items(): |
|
3738 | for key, tests in testsbyfailure.items(): | |
3738 | fewesttest = None |
|
3739 | fewesttest = None | |
3739 | fewestcount = 99999999 |
|
3740 | fewestcount = 99999999 | |
3740 | for test in sorted(tests): |
|
3741 | for test in sorted(tests): | |
3741 | if len(failuresbytest[test]) < fewestcount: |
|
3742 | if len(failuresbytest[test]) < fewestcount: | |
3742 | fewesttest = test |
|
3743 | fewesttest = test | |
3743 | fewestcount = len(failuresbytest[test]) |
|
3744 | fewestcount = len(failuresbytest[test]) | |
3744 |
|
3745 | |||
3745 | leastfailing[key] = (fewestcount, fewesttest) |
|
3746 | leastfailing[key] = (fewestcount, fewesttest) | |
3746 |
|
3747 | |||
3747 | # Create a combined counter so we can sort by total occurrences and |
|
3748 | # Create a combined counter so we can sort by total occurrences and | |
3748 | # impacted tests. |
|
3749 | # impacted tests. | |
3749 | combined = {} |
|
3750 | combined = {} | |
3750 | for key in exceptioncounts: |
|
3751 | for key in exceptioncounts: | |
3751 | combined[key] = ( |
|
3752 | combined[key] = ( | |
3752 | exceptioncounts[key], |
|
3753 | exceptioncounts[key], | |
3753 | len(testsbyfailure[key]), |
|
3754 | len(testsbyfailure[key]), | |
3754 | leastfailing[key][0], |
|
3755 | leastfailing[key][0], | |
3755 | leastfailing[key][1], |
|
3756 | leastfailing[key][1], | |
3756 | ) |
|
3757 | ) | |
3757 |
|
3758 | |||
3758 | return { |
|
3759 | return { | |
3759 | 'exceptioncounts': exceptioncounts, |
|
3760 | 'exceptioncounts': exceptioncounts, | |
3760 | 'total': sum(exceptioncounts.values()), |
|
3761 | 'total': sum(exceptioncounts.values()), | |
3761 | 'combined': combined, |
|
3762 | 'combined': combined, | |
3762 | 'leastfailing': leastfailing, |
|
3763 | 'leastfailing': leastfailing, | |
3763 | 'byfailure': testsbyfailure, |
|
3764 | 'byfailure': testsbyfailure, | |
3764 | 'bytest': failuresbytest, |
|
3765 | 'bytest': failuresbytest, | |
3765 | } |
|
3766 | } | |
3766 |
|
3767 | |||
3767 |
|
3768 | |||
3768 | if __name__ == '__main__': |
|
3769 | if __name__ == '__main__': | |
3769 | runner = TestRunner() |
|
3770 | runner = TestRunner() | |
3770 |
|
3771 | |||
3771 | try: |
|
3772 | try: | |
3772 | import msvcrt |
|
3773 | import msvcrt | |
3773 |
|
3774 | |||
3774 | msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) |
|
3775 | msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) | |
3775 | msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) |
|
3776 | msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) | |
3776 | msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY) |
|
3777 | msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY) | |
3777 | except ImportError: |
|
3778 | except ImportError: | |
3778 | pass |
|
3779 | pass | |
3779 |
|
3780 | |||
3780 | sys.exit(runner.run(sys.argv[1:])) |
|
3781 | sys.exit(runner.run(sys.argv[1:])) |
@@ -1,30 +1,30 b'' | |||||
1 | $ cat >> $HGRCPATH << EOF |
|
1 | $ cat >> $HGRCPATH << EOF | |
2 | > [extensions] |
|
2 | > [extensions] | |
3 | > absorb= |
|
3 | > absorb= | |
4 | > EOF |
|
4 | > EOF | |
5 |
|
5 | |||
6 | Abort absorb if there is an unfinished operation. |
|
6 | Abort absorb if there is an unfinished operation. | |
7 |
|
7 | |||
8 | $ hg init abortunresolved |
|
8 | $ hg init abortunresolved | |
9 | $ cd abortunresolved |
|
9 | $ cd abortunresolved | |
10 |
|
10 | |||
11 | $ echo "foo1" > foo.whole |
|
11 | $ echo "foo1" > foo.whole | |
12 | $ hg commit -Aqm "foo 1" |
|
12 | $ hg commit -Aqm "foo 1" | |
13 |
|
13 | |||
14 | $ hg update null |
|
14 | $ hg update null | |
15 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved |
|
15 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved | |
16 | $ echo "foo2" > foo.whole |
|
16 | $ echo "foo2" > foo.whole | |
17 | $ hg commit -Aqm "foo 2" |
|
17 | $ hg commit -Aqm "foo 2" | |
18 |
|
18 | |||
19 | $ hg --config extensions.rebase= rebase -r 1 -d 0 |
|
19 | $ hg --config extensions.rebase= rebase -r 1 -d 0 | |
20 | rebasing 1:c3b6dc0e177a tip "foo 2" |
|
20 | rebasing 1:c3b6dc0e177a tip "foo 2" | |
21 | merging foo.whole |
|
21 | merging foo.whole | |
22 | warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark') |
|
22 | warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark') | |
23 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') |
|
23 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') | |
24 |
[ |
|
24 | [240] | |
25 |
|
25 | |||
26 | $ hg --config extensions.rebase= absorb |
|
26 | $ hg --config extensions.rebase= absorb | |
27 | abort: rebase in progress |
|
27 | abort: rebase in progress | |
28 | (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop') |
|
28 | (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop') | |
29 | [255] |
|
29 | [255] | |
30 |
|
30 |
@@ -1,227 +1,228 b'' | |||||
1 | Create a repository: |
|
1 | Create a repository: | |
2 |
|
2 | |||
3 | #if no-extraextensions |
|
3 | #if no-extraextensions | |
4 | $ hg config |
|
4 | $ hg config | |
5 | devel.all-warnings=true |
|
5 | devel.all-warnings=true | |
6 | devel.default-date=0 0 |
|
6 | devel.default-date=0 0 | |
7 | extensions.fsmonitor= (fsmonitor !) |
|
7 | extensions.fsmonitor= (fsmonitor !) | |
8 | largefiles.usercache=$TESTTMP/.cache/largefiles |
|
8 | largefiles.usercache=$TESTTMP/.cache/largefiles | |
9 | lfs.usercache=$TESTTMP/.cache/lfs |
|
9 | lfs.usercache=$TESTTMP/.cache/lfs | |
10 | ui.slash=True |
|
10 | ui.slash=True | |
11 | ui.interactive=False |
|
11 | ui.interactive=False | |
|
12 | ui.detailed-exit-code=True | |||
12 | ui.merge=internal:merge |
|
13 | ui.merge=internal:merge | |
13 | ui.mergemarkers=detailed |
|
14 | ui.mergemarkers=detailed | |
14 | ui.promptecho=True |
|
15 | ui.promptecho=True | |
15 | web.address=localhost |
|
16 | web.address=localhost | |
16 | web\.ipv6=(?:True|False) (re) |
|
17 | web\.ipv6=(?:True|False) (re) | |
17 | web.server-header=testing stub value |
|
18 | web.server-header=testing stub value | |
18 | #endif |
|
19 | #endif | |
19 |
|
20 | |||
20 | $ hg init t |
|
21 | $ hg init t | |
21 | $ cd t |
|
22 | $ cd t | |
22 |
|
23 | |||
23 | Prepare a changeset: |
|
24 | Prepare a changeset: | |
24 |
|
25 | |||
25 | $ echo a > a |
|
26 | $ echo a > a | |
26 | $ hg add a |
|
27 | $ hg add a | |
27 |
|
28 | |||
28 | $ hg status |
|
29 | $ hg status | |
29 | A a |
|
30 | A a | |
30 |
|
31 | |||
31 | Writes to stdio succeed and fail appropriately |
|
32 | Writes to stdio succeed and fail appropriately | |
32 |
|
33 | |||
33 | #if devfull |
|
34 | #if devfull | |
34 | $ hg status 2>/dev/full |
|
35 | $ hg status 2>/dev/full | |
35 | A a |
|
36 | A a | |
36 |
|
37 | |||
37 | $ hg status >/dev/full |
|
38 | $ hg status >/dev/full | |
38 | abort: No space left on device |
|
39 | abort: No space left on device | |
39 | [255] |
|
40 | [255] | |
40 | #endif |
|
41 | #endif | |
41 |
|
42 | |||
42 | #if devfull |
|
43 | #if devfull | |
43 | $ hg status >/dev/full 2>&1 |
|
44 | $ hg status >/dev/full 2>&1 | |
44 | [255] |
|
45 | [255] | |
45 |
|
46 | |||
46 | $ hg status ENOENT 2>/dev/full |
|
47 | $ hg status ENOENT 2>/dev/full | |
47 | [255] |
|
48 | [255] | |
48 | #endif |
|
49 | #endif | |
49 |
|
50 | |||
50 | $ hg commit -m test |
|
51 | $ hg commit -m test | |
51 |
|
52 | |||
52 | This command is ancient: |
|
53 | This command is ancient: | |
53 |
|
54 | |||
54 | $ hg history |
|
55 | $ hg history | |
55 | changeset: 0:acb14030fe0a |
|
56 | changeset: 0:acb14030fe0a | |
56 | tag: tip |
|
57 | tag: tip | |
57 | user: test |
|
58 | user: test | |
58 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
59 | date: Thu Jan 01 00:00:00 1970 +0000 | |
59 | summary: test |
|
60 | summary: test | |
60 |
|
61 | |||
61 |
|
62 | |||
62 | Verify that updating to revision 0 via commands.update() works properly |
|
63 | Verify that updating to revision 0 via commands.update() works properly | |
63 |
|
64 | |||
64 | $ cat <<EOF > update_to_rev0.py |
|
65 | $ cat <<EOF > update_to_rev0.py | |
65 | > from mercurial import commands, hg, ui as uimod |
|
66 | > from mercurial import commands, hg, ui as uimod | |
66 | > myui = uimod.ui.load() |
|
67 | > myui = uimod.ui.load() | |
67 | > repo = hg.repository(myui, path=b'.') |
|
68 | > repo = hg.repository(myui, path=b'.') | |
68 | > commands.update(myui, repo, rev=b"0") |
|
69 | > commands.update(myui, repo, rev=b"0") | |
69 | > EOF |
|
70 | > EOF | |
70 | $ hg up null |
|
71 | $ hg up null | |
71 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved |
|
72 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved | |
72 | $ "$PYTHON" ./update_to_rev0.py |
|
73 | $ "$PYTHON" ./update_to_rev0.py | |
73 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
74 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
74 | $ hg identify -n |
|
75 | $ hg identify -n | |
75 | 0 |
|
76 | 0 | |
76 |
|
77 | |||
77 |
|
78 | |||
78 | Poke around at hashes: |
|
79 | Poke around at hashes: | |
79 |
|
80 | |||
80 | $ hg manifest --debug |
|
81 | $ hg manifest --debug | |
81 | b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a |
|
82 | b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a | |
82 |
|
83 | |||
83 | $ hg cat a |
|
84 | $ hg cat a | |
84 | a |
|
85 | a | |
85 |
|
86 | |||
86 | Verify should succeed: |
|
87 | Verify should succeed: | |
87 |
|
88 | |||
88 | $ hg verify |
|
89 | $ hg verify | |
89 | checking changesets |
|
90 | checking changesets | |
90 | checking manifests |
|
91 | checking manifests | |
91 | crosschecking files in changesets and manifests |
|
92 | crosschecking files in changesets and manifests | |
92 | checking files |
|
93 | checking files | |
93 | checked 1 changesets with 1 changes to 1 files |
|
94 | checked 1 changesets with 1 changes to 1 files | |
94 |
|
95 | |||
95 | Repository root: |
|
96 | Repository root: | |
96 |
|
97 | |||
97 | $ hg root |
|
98 | $ hg root | |
98 | $TESTTMP/t |
|
99 | $TESTTMP/t | |
99 | $ hg log -l1 -T '{reporoot}\n' |
|
100 | $ hg log -l1 -T '{reporoot}\n' | |
100 | $TESTTMP/t |
|
101 | $TESTTMP/t | |
101 | $ hg root -Tjson | sed 's|\\\\|\\|g' |
|
102 | $ hg root -Tjson | sed 's|\\\\|\\|g' | |
102 | [ |
|
103 | [ | |
103 | { |
|
104 | { | |
104 | "hgpath": "$TESTTMP/t/.hg", |
|
105 | "hgpath": "$TESTTMP/t/.hg", | |
105 | "reporoot": "$TESTTMP/t", |
|
106 | "reporoot": "$TESTTMP/t", | |
106 | "storepath": "$TESTTMP/t/.hg/store" |
|
107 | "storepath": "$TESTTMP/t/.hg/store" | |
107 | } |
|
108 | } | |
108 | ] |
|
109 | ] | |
109 |
|
110 | |||
110 | At the end... |
|
111 | At the end... | |
111 |
|
112 | |||
112 | $ cd .. |
|
113 | $ cd .. | |
113 |
|
114 | |||
114 | Status message redirection: |
|
115 | Status message redirection: | |
115 |
|
116 | |||
116 | $ hg init empty |
|
117 | $ hg init empty | |
117 |
|
118 | |||
118 | status messages are sent to stdout by default: |
|
119 | status messages are sent to stdout by default: | |
119 |
|
120 | |||
120 | $ hg outgoing -R t empty -Tjson 2>/dev/null |
|
121 | $ hg outgoing -R t empty -Tjson 2>/dev/null | |
121 | comparing with empty |
|
122 | comparing with empty | |
122 | searching for changes |
|
123 | searching for changes | |
123 | [ |
|
124 | [ | |
124 | { |
|
125 | { | |
125 | "bookmarks": [], |
|
126 | "bookmarks": [], | |
126 | "branch": "default", |
|
127 | "branch": "default", | |
127 | "date": [0, 0], |
|
128 | "date": [0, 0], | |
128 | "desc": "test", |
|
129 | "desc": "test", | |
129 | "node": "acb14030fe0a21b60322c440ad2d20cf7685a376", |
|
130 | "node": "acb14030fe0a21b60322c440ad2d20cf7685a376", | |
130 | "parents": ["0000000000000000000000000000000000000000"], |
|
131 | "parents": ["0000000000000000000000000000000000000000"], | |
131 | "phase": "draft", |
|
132 | "phase": "draft", | |
132 | "rev": 0, |
|
133 | "rev": 0, | |
133 | "tags": ["tip"], |
|
134 | "tags": ["tip"], | |
134 | "user": "test" |
|
135 | "user": "test" | |
135 | } |
|
136 | } | |
136 | ] |
|
137 | ] | |
137 |
|
138 | |||
138 | which can be configured to send to stderr, so the output wouldn't be |
|
139 | which can be configured to send to stderr, so the output wouldn't be | |
139 | interleaved: |
|
140 | interleaved: | |
140 |
|
141 | |||
141 | $ cat <<'EOF' >> "$HGRCPATH" |
|
142 | $ cat <<'EOF' >> "$HGRCPATH" | |
142 | > [ui] |
|
143 | > [ui] | |
143 | > message-output = stderr |
|
144 | > message-output = stderr | |
144 | > EOF |
|
145 | > EOF | |
145 | $ hg outgoing -R t empty -Tjson 2>/dev/null |
|
146 | $ hg outgoing -R t empty -Tjson 2>/dev/null | |
146 | [ |
|
147 | [ | |
147 | { |
|
148 | { | |
148 | "bookmarks": [], |
|
149 | "bookmarks": [], | |
149 | "branch": "default", |
|
150 | "branch": "default", | |
150 | "date": [0, 0], |
|
151 | "date": [0, 0], | |
151 | "desc": "test", |
|
152 | "desc": "test", | |
152 | "node": "acb14030fe0a21b60322c440ad2d20cf7685a376", |
|
153 | "node": "acb14030fe0a21b60322c440ad2d20cf7685a376", | |
153 | "parents": ["0000000000000000000000000000000000000000"], |
|
154 | "parents": ["0000000000000000000000000000000000000000"], | |
154 | "phase": "draft", |
|
155 | "phase": "draft", | |
155 | "rev": 0, |
|
156 | "rev": 0, | |
156 | "tags": ["tip"], |
|
157 | "tags": ["tip"], | |
157 | "user": "test" |
|
158 | "user": "test" | |
158 | } |
|
159 | } | |
159 | ] |
|
160 | ] | |
160 | $ hg outgoing -R t empty -Tjson >/dev/null |
|
161 | $ hg outgoing -R t empty -Tjson >/dev/null | |
161 | comparing with empty |
|
162 | comparing with empty | |
162 | searching for changes |
|
163 | searching for changes | |
163 |
|
164 | |||
164 | this option should be turned off by HGPLAIN= since it may break scripting use: |
|
165 | this option should be turned off by HGPLAIN= since it may break scripting use: | |
165 |
|
166 | |||
166 | $ HGPLAIN= hg outgoing -R t empty -Tjson 2>/dev/null |
|
167 | $ HGPLAIN= hg outgoing -R t empty -Tjson 2>/dev/null | |
167 | comparing with empty |
|
168 | comparing with empty | |
168 | searching for changes |
|
169 | searching for changes | |
169 | [ |
|
170 | [ | |
170 | { |
|
171 | { | |
171 | "bookmarks": [], |
|
172 | "bookmarks": [], | |
172 | "branch": "default", |
|
173 | "branch": "default", | |
173 | "date": [0, 0], |
|
174 | "date": [0, 0], | |
174 | "desc": "test", |
|
175 | "desc": "test", | |
175 | "node": "acb14030fe0a21b60322c440ad2d20cf7685a376", |
|
176 | "node": "acb14030fe0a21b60322c440ad2d20cf7685a376", | |
176 | "parents": ["0000000000000000000000000000000000000000"], |
|
177 | "parents": ["0000000000000000000000000000000000000000"], | |
177 | "phase": "draft", |
|
178 | "phase": "draft", | |
178 | "rev": 0, |
|
179 | "rev": 0, | |
179 | "tags": ["tip"], |
|
180 | "tags": ["tip"], | |
180 | "user": "test" |
|
181 | "user": "test" | |
181 | } |
|
182 | } | |
182 | ] |
|
183 | ] | |
183 |
|
184 | |||
184 | but still overridden by --config: |
|
185 | but still overridden by --config: | |
185 |
|
186 | |||
186 | $ HGPLAIN= hg outgoing -R t empty -Tjson --config ui.message-output=stderr \ |
|
187 | $ HGPLAIN= hg outgoing -R t empty -Tjson --config ui.message-output=stderr \ | |
187 | > 2>/dev/null |
|
188 | > 2>/dev/null | |
188 | [ |
|
189 | [ | |
189 | { |
|
190 | { | |
190 | "bookmarks": [], |
|
191 | "bookmarks": [], | |
191 | "branch": "default", |
|
192 | "branch": "default", | |
192 | "date": [0, 0], |
|
193 | "date": [0, 0], | |
193 | "desc": "test", |
|
194 | "desc": "test", | |
194 | "node": "acb14030fe0a21b60322c440ad2d20cf7685a376", |
|
195 | "node": "acb14030fe0a21b60322c440ad2d20cf7685a376", | |
195 | "parents": ["0000000000000000000000000000000000000000"], |
|
196 | "parents": ["0000000000000000000000000000000000000000"], | |
196 | "phase": "draft", |
|
197 | "phase": "draft", | |
197 | "rev": 0, |
|
198 | "rev": 0, | |
198 | "tags": ["tip"], |
|
199 | "tags": ["tip"], | |
199 | "user": "test" |
|
200 | "user": "test" | |
200 | } |
|
201 | } | |
201 | ] |
|
202 | ] | |
202 |
|
203 | |||
203 | Invalid ui.message-output option: |
|
204 | Invalid ui.message-output option: | |
204 |
|
205 | |||
205 | $ hg log -R t --config ui.message-output=bad |
|
206 | $ hg log -R t --config ui.message-output=bad | |
206 | abort: invalid ui.message-output destination: bad |
|
207 | abort: invalid ui.message-output destination: bad | |
207 | [255] |
|
208 | [255] | |
208 |
|
209 | |||
209 | Underlying message streams should be updated when ui.fout/ferr are set: |
|
210 | Underlying message streams should be updated when ui.fout/ferr are set: | |
210 |
|
211 | |||
211 | $ cat <<'EOF' > capui.py |
|
212 | $ cat <<'EOF' > capui.py | |
212 | > from mercurial import pycompat, registrar |
|
213 | > from mercurial import pycompat, registrar | |
213 | > cmdtable = {} |
|
214 | > cmdtable = {} | |
214 | > command = registrar.command(cmdtable) |
|
215 | > command = registrar.command(cmdtable) | |
215 | > @command(b'capui', norepo=True) |
|
216 | > @command(b'capui', norepo=True) | |
216 | > def capui(ui): |
|
217 | > def capui(ui): | |
217 | > out = ui.fout |
|
218 | > out = ui.fout | |
218 | > ui.fout = pycompat.bytesio() |
|
219 | > ui.fout = pycompat.bytesio() | |
219 | > ui.status(b'status\n') |
|
220 | > ui.status(b'status\n') | |
220 | > ui.ferr = pycompat.bytesio() |
|
221 | > ui.ferr = pycompat.bytesio() | |
221 | > ui.warn(b'warn\n') |
|
222 | > ui.warn(b'warn\n') | |
222 | > out.write(b'stdout: %s' % ui.fout.getvalue()) |
|
223 | > out.write(b'stdout: %s' % ui.fout.getvalue()) | |
223 | > out.write(b'stderr: %s' % ui.ferr.getvalue()) |
|
224 | > out.write(b'stderr: %s' % ui.ferr.getvalue()) | |
224 | > EOF |
|
225 | > EOF | |
225 | $ hg --config extensions.capui=capui.py --config ui.message-output=stdio capui |
|
226 | $ hg --config extensions.capui=capui.py --config ui.message-output=stdio capui | |
226 | stdout: status |
|
227 | stdout: status | |
227 | stderr: warn |
|
228 | stderr: warn |
@@ -1,105 +1,105 b'' | |||||
1 | $ echo "[extensions]" >> $HGRCPATH |
|
1 | $ echo "[extensions]" >> $HGRCPATH | |
2 | $ echo "rebase=" >> $HGRCPATH |
|
2 | $ echo "rebase=" >> $HGRCPATH | |
3 |
|
3 | |||
4 | initialize repository |
|
4 | initialize repository | |
5 |
|
5 | |||
6 | $ hg init |
|
6 | $ hg init | |
7 |
|
7 | |||
8 | $ echo 'a' > a |
|
8 | $ echo 'a' > a | |
9 | $ hg ci -A -m "0" |
|
9 | $ hg ci -A -m "0" | |
10 | adding a |
|
10 | adding a | |
11 |
|
11 | |||
12 | $ echo 'b' > b |
|
12 | $ echo 'b' > b | |
13 | $ hg ci -A -m "1" |
|
13 | $ hg ci -A -m "1" | |
14 | adding b |
|
14 | adding b | |
15 |
|
15 | |||
16 | $ hg up 0 |
|
16 | $ hg up 0 | |
17 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved |
|
17 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved | |
18 | $ echo 'c' > c |
|
18 | $ echo 'c' > c | |
19 | $ hg ci -A -m "2" |
|
19 | $ hg ci -A -m "2" | |
20 | adding c |
|
20 | adding c | |
21 | created new head |
|
21 | created new head | |
22 |
|
22 | |||
23 | $ echo 'd' > d |
|
23 | $ echo 'd' > d | |
24 | $ hg ci -A -m "3" |
|
24 | $ hg ci -A -m "3" | |
25 | adding d |
|
25 | adding d | |
26 |
|
26 | |||
27 | $ hg bookmark -r 1 one |
|
27 | $ hg bookmark -r 1 one | |
28 | $ hg bookmark -r 3 two |
|
28 | $ hg bookmark -r 3 two | |
29 | $ hg up -q two |
|
29 | $ hg up -q two | |
30 |
|
30 | |||
31 | bookmark list |
|
31 | bookmark list | |
32 |
|
32 | |||
33 | $ hg bookmark |
|
33 | $ hg bookmark | |
34 | one 1:925d80f479bb |
|
34 | one 1:925d80f479bb | |
35 | * two 3:2ae46b1d99a7 |
|
35 | * two 3:2ae46b1d99a7 | |
36 |
|
36 | |||
37 | rebase |
|
37 | rebase | |
38 |
|
38 | |||
39 | $ hg rebase -s two -d one |
|
39 | $ hg rebase -s two -d one | |
40 | rebasing 3:2ae46b1d99a7 two tip "3" |
|
40 | rebasing 3:2ae46b1d99a7 two tip "3" | |
41 | saved backup bundle to $TESTTMP/.hg/strip-backup/2ae46b1d99a7-e6b057bc-rebase.hg |
|
41 | saved backup bundle to $TESTTMP/.hg/strip-backup/2ae46b1d99a7-e6b057bc-rebase.hg | |
42 |
|
42 | |||
43 | $ hg log |
|
43 | $ hg log | |
44 | changeset: 3:42e5ed2cdcf4 |
|
44 | changeset: 3:42e5ed2cdcf4 | |
45 | bookmark: two |
|
45 | bookmark: two | |
46 | tag: tip |
|
46 | tag: tip | |
47 | parent: 1:925d80f479bb |
|
47 | parent: 1:925d80f479bb | |
48 | user: test |
|
48 | user: test | |
49 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
49 | date: Thu Jan 01 00:00:00 1970 +0000 | |
50 | summary: 3 |
|
50 | summary: 3 | |
51 |
|
51 | |||
52 | changeset: 2:db815d6d32e6 |
|
52 | changeset: 2:db815d6d32e6 | |
53 | parent: 0:f7b1eb17ad24 |
|
53 | parent: 0:f7b1eb17ad24 | |
54 | user: test |
|
54 | user: test | |
55 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
55 | date: Thu Jan 01 00:00:00 1970 +0000 | |
56 | summary: 2 |
|
56 | summary: 2 | |
57 |
|
57 | |||
58 | changeset: 1:925d80f479bb |
|
58 | changeset: 1:925d80f479bb | |
59 | bookmark: one |
|
59 | bookmark: one | |
60 | user: test |
|
60 | user: test | |
61 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
61 | date: Thu Jan 01 00:00:00 1970 +0000 | |
62 | summary: 1 |
|
62 | summary: 1 | |
63 |
|
63 | |||
64 | changeset: 0:f7b1eb17ad24 |
|
64 | changeset: 0:f7b1eb17ad24 | |
65 | user: test |
|
65 | user: test | |
66 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
66 | date: Thu Jan 01 00:00:00 1970 +0000 | |
67 | summary: 0 |
|
67 | summary: 0 | |
68 |
|
68 | |||
69 | aborted rebase should restore active bookmark. |
|
69 | aborted rebase should restore active bookmark. | |
70 |
|
70 | |||
71 | $ hg up 1 |
|
71 | $ hg up 1 | |
72 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved |
|
72 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved | |
73 | (leaving bookmark two) |
|
73 | (leaving bookmark two) | |
74 | $ echo 'e' > d |
|
74 | $ echo 'e' > d | |
75 | $ hg ci -A -m "4" |
|
75 | $ hg ci -A -m "4" | |
76 | adding d |
|
76 | adding d | |
77 | created new head |
|
77 | created new head | |
78 | $ hg bookmark three |
|
78 | $ hg bookmark three | |
79 | $ hg rebase -s three -d two |
|
79 | $ hg rebase -s three -d two | |
80 | rebasing 4:dd7c838e8362 three tip "4" |
|
80 | rebasing 4:dd7c838e8362 three tip "4" | |
81 | merging d |
|
81 | merging d | |
82 | warning: conflicts while merging d! (edit, then use 'hg resolve --mark') |
|
82 | warning: conflicts while merging d! (edit, then use 'hg resolve --mark') | |
83 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') |
|
83 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') | |
84 |
[ |
|
84 | [240] | |
85 | $ hg rebase --abort |
|
85 | $ hg rebase --abort | |
86 | rebase aborted |
|
86 | rebase aborted | |
87 | $ hg bookmark |
|
87 | $ hg bookmark | |
88 | one 1:925d80f479bb |
|
88 | one 1:925d80f479bb | |
89 | * three 4:dd7c838e8362 |
|
89 | * three 4:dd7c838e8362 | |
90 | two 3:42e5ed2cdcf4 |
|
90 | two 3:42e5ed2cdcf4 | |
91 |
|
91 | |||
92 | after aborted rebase, restoring a bookmark that has been removed should not fail |
|
92 | after aborted rebase, restoring a bookmark that has been removed should not fail | |
93 |
|
93 | |||
94 | $ hg rebase -s three -d two |
|
94 | $ hg rebase -s three -d two | |
95 | rebasing 4:dd7c838e8362 three tip "4" |
|
95 | rebasing 4:dd7c838e8362 three tip "4" | |
96 | merging d |
|
96 | merging d | |
97 | warning: conflicts while merging d! (edit, then use 'hg resolve --mark') |
|
97 | warning: conflicts while merging d! (edit, then use 'hg resolve --mark') | |
98 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') |
|
98 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') | |
99 |
[ |
|
99 | [240] | |
100 | $ hg bookmark -d three |
|
100 | $ hg bookmark -d three | |
101 | $ hg rebase --abort |
|
101 | $ hg rebase --abort | |
102 | rebase aborted |
|
102 | rebase aborted | |
103 | $ hg bookmark |
|
103 | $ hg bookmark | |
104 | one 1:925d80f479bb |
|
104 | one 1:925d80f479bb | |
105 | two 3:42e5ed2cdcf4 |
|
105 | two 3:42e5ed2cdcf4 |
@@ -1,1156 +1,1158 b'' | |||||
1 | #if windows |
|
1 | #if windows | |
2 | $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH" |
|
2 | $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH" | |
3 | #else |
|
3 | #else | |
4 | $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH" |
|
4 | $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH" | |
5 | #endif |
|
5 | #endif | |
6 | $ export PYTHONPATH |
|
6 | $ export PYTHONPATH | |
7 |
|
7 | |||
8 | typical client does not want echo-back messages, so test without it: |
|
8 | typical client does not want echo-back messages, so test without it: | |
9 |
|
9 | |||
10 | $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new |
|
10 | $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new | |
11 | $ mv $HGRCPATH.new $HGRCPATH |
|
11 | $ mv $HGRCPATH.new $HGRCPATH | |
12 |
|
12 | |||
13 | $ hg init repo |
|
13 | $ hg init repo | |
14 | $ cd repo |
|
14 | $ cd repo | |
15 |
|
15 | |||
16 | >>> from __future__ import absolute_import |
|
16 | >>> from __future__ import absolute_import | |
17 | >>> import os |
|
17 | >>> import os | |
18 | >>> import sys |
|
18 | >>> import sys | |
19 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
19 | >>> from hgclient import bprint, check, readchannel, runcommand | |
20 | >>> @check |
|
20 | >>> @check | |
21 | ... def hellomessage(server): |
|
21 | ... def hellomessage(server): | |
22 | ... ch, data = readchannel(server) |
|
22 | ... ch, data = readchannel(server) | |
23 | ... bprint(b'%c, %r' % (ch, data)) |
|
23 | ... bprint(b'%c, %r' % (ch, data)) | |
24 | ... # run an arbitrary command to make sure the next thing the server |
|
24 | ... # run an arbitrary command to make sure the next thing the server | |
25 | ... # sends isn't part of the hello message |
|
25 | ... # sends isn't part of the hello message | |
26 | ... runcommand(server, [b'id']) |
|
26 | ... runcommand(server, [b'id']) | |
27 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) |
|
27 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) | |
28 | *** runcommand id |
|
28 | *** runcommand id | |
29 | 000000000000 tip |
|
29 | 000000000000 tip | |
30 |
|
30 | |||
31 | >>> from hgclient import check |
|
31 | >>> from hgclient import check | |
32 | >>> @check |
|
32 | >>> @check | |
33 | ... def unknowncommand(server): |
|
33 | ... def unknowncommand(server): | |
34 | ... server.stdin.write(b'unknowncommand\n') |
|
34 | ... server.stdin.write(b'unknowncommand\n') | |
35 | abort: unknown command unknowncommand |
|
35 | abort: unknown command unknowncommand | |
36 |
|
36 | |||
37 | >>> from hgclient import check, readchannel, runcommand |
|
37 | >>> from hgclient import check, readchannel, runcommand | |
38 | >>> @check |
|
38 | >>> @check | |
39 | ... def checkruncommand(server): |
|
39 | ... def checkruncommand(server): | |
40 | ... # hello block |
|
40 | ... # hello block | |
41 | ... readchannel(server) |
|
41 | ... readchannel(server) | |
42 | ... |
|
42 | ... | |
43 | ... # no args |
|
43 | ... # no args | |
44 | ... runcommand(server, []) |
|
44 | ... runcommand(server, []) | |
45 | ... |
|
45 | ... | |
46 | ... # global options |
|
46 | ... # global options | |
47 | ... runcommand(server, [b'id', b'--quiet']) |
|
47 | ... runcommand(server, [b'id', b'--quiet']) | |
48 | ... |
|
48 | ... | |
49 | ... # make sure global options don't stick through requests |
|
49 | ... # make sure global options don't stick through requests | |
50 | ... runcommand(server, [b'id']) |
|
50 | ... runcommand(server, [b'id']) | |
51 | ... |
|
51 | ... | |
52 | ... # --config |
|
52 | ... # --config | |
53 | ... runcommand(server, [b'id', b'--config', b'ui.quiet=True']) |
|
53 | ... runcommand(server, [b'id', b'--config', b'ui.quiet=True']) | |
54 | ... |
|
54 | ... | |
55 | ... # make sure --config doesn't stick |
|
55 | ... # make sure --config doesn't stick | |
56 | ... runcommand(server, [b'id']) |
|
56 | ... runcommand(server, [b'id']) | |
57 | ... |
|
57 | ... | |
58 | ... # negative return code should be masked |
|
58 | ... # negative return code should be masked | |
59 | ... runcommand(server, [b'id', b'-runknown']) |
|
59 | ... runcommand(server, [b'id', b'-runknown']) | |
60 | *** runcommand |
|
60 | *** runcommand | |
61 | Mercurial Distributed SCM |
|
61 | Mercurial Distributed SCM | |
62 |
|
62 | |||
63 | basic commands: |
|
63 | basic commands: | |
64 |
|
64 | |||
65 | add add the specified files on the next commit |
|
65 | add add the specified files on the next commit | |
66 | annotate show changeset information by line for each file |
|
66 | annotate show changeset information by line for each file | |
67 | clone make a copy of an existing repository |
|
67 | clone make a copy of an existing repository | |
68 | commit commit the specified files or all outstanding changes |
|
68 | commit commit the specified files or all outstanding changes | |
69 | diff diff repository (or selected files) |
|
69 | diff diff repository (or selected files) | |
70 | export dump the header and diffs for one or more changesets |
|
70 | export dump the header and diffs for one or more changesets | |
71 | forget forget the specified files on the next commit |
|
71 | forget forget the specified files on the next commit | |
72 | init create a new repository in the given directory |
|
72 | init create a new repository in the given directory | |
73 | log show revision history of entire repository or files |
|
73 | log show revision history of entire repository or files | |
74 | merge merge another revision into working directory |
|
74 | merge merge another revision into working directory | |
75 | pull pull changes from the specified source |
|
75 | pull pull changes from the specified source | |
76 | push push changes to the specified destination |
|
76 | push push changes to the specified destination | |
77 | remove remove the specified files on the next commit |
|
77 | remove remove the specified files on the next commit | |
78 | serve start stand-alone webserver |
|
78 | serve start stand-alone webserver | |
79 | status show changed files in the working directory |
|
79 | status show changed files in the working directory | |
80 | summary summarize working directory state |
|
80 | summary summarize working directory state | |
81 | update update working directory (or switch revisions) |
|
81 | update update working directory (or switch revisions) | |
82 |
|
82 | |||
83 | (use 'hg help' for the full list of commands or 'hg -v' for details) |
|
83 | (use 'hg help' for the full list of commands or 'hg -v' for details) | |
84 | *** runcommand id --quiet |
|
84 | *** runcommand id --quiet | |
85 | 000000000000 |
|
85 | 000000000000 | |
86 | *** runcommand id |
|
86 | *** runcommand id | |
87 | 000000000000 tip |
|
87 | 000000000000 tip | |
88 | *** runcommand id --config ui.quiet=True |
|
88 | *** runcommand id --config ui.quiet=True | |
89 | 000000000000 |
|
89 | 000000000000 | |
90 | *** runcommand id |
|
90 | *** runcommand id | |
91 | 000000000000 tip |
|
91 | 000000000000 tip | |
92 | *** runcommand id -runknown |
|
92 | *** runcommand id -runknown | |
93 | abort: unknown revision 'unknown'! |
|
93 | abort: unknown revision 'unknown'! | |
94 | [255] |
|
94 | [255] | |
95 |
|
95 | |||
96 | >>> from hgclient import bprint, check, readchannel |
|
96 | >>> from hgclient import bprint, check, readchannel | |
97 | >>> @check |
|
97 | >>> @check | |
98 | ... def inputeof(server): |
|
98 | ... def inputeof(server): | |
99 | ... readchannel(server) |
|
99 | ... readchannel(server) | |
100 | ... server.stdin.write(b'runcommand\n') |
|
100 | ... server.stdin.write(b'runcommand\n') | |
101 | ... # close stdin while server is waiting for input |
|
101 | ... # close stdin while server is waiting for input | |
102 | ... server.stdin.close() |
|
102 | ... server.stdin.close() | |
103 | ... |
|
103 | ... | |
104 | ... # server exits with 1 if the pipe closed while reading the command |
|
104 | ... # server exits with 1 if the pipe closed while reading the command | |
105 | ... bprint(b'server exit code =', b'%d' % server.wait()) |
|
105 | ... bprint(b'server exit code =', b'%d' % server.wait()) | |
106 | server exit code = 1 |
|
106 | server exit code = 1 | |
107 |
|
107 | |||
108 | >>> from hgclient import check, readchannel, runcommand, stringio |
|
108 | >>> from hgclient import check, readchannel, runcommand, stringio | |
109 | >>> @check |
|
109 | >>> @check | |
110 | ... def serverinput(server): |
|
110 | ... def serverinput(server): | |
111 | ... readchannel(server) |
|
111 | ... readchannel(server) | |
112 | ... |
|
112 | ... | |
113 | ... patch = b""" |
|
113 | ... patch = b""" | |
114 | ... # HG changeset patch |
|
114 | ... # HG changeset patch | |
115 | ... # User test |
|
115 | ... # User test | |
116 | ... # Date 0 0 |
|
116 | ... # Date 0 0 | |
117 | ... # Node ID c103a3dec114d882c98382d684d8af798d09d857 |
|
117 | ... # Node ID c103a3dec114d882c98382d684d8af798d09d857 | |
118 | ... # Parent 0000000000000000000000000000000000000000 |
|
118 | ... # Parent 0000000000000000000000000000000000000000 | |
119 | ... 1 |
|
119 | ... 1 | |
120 | ... |
|
120 | ... | |
121 | ... diff -r 000000000000 -r c103a3dec114 a |
|
121 | ... diff -r 000000000000 -r c103a3dec114 a | |
122 | ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000 |
|
122 | ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000 | |
123 | ... +++ b/a Thu Jan 01 00:00:00 1970 +0000 |
|
123 | ... +++ b/a Thu Jan 01 00:00:00 1970 +0000 | |
124 | ... @@ -0,0 +1,1 @@ |
|
124 | ... @@ -0,0 +1,1 @@ | |
125 | ... +1 |
|
125 | ... +1 | |
126 | ... """ |
|
126 | ... """ | |
127 | ... |
|
127 | ... | |
128 | ... runcommand(server, [b'import', b'-'], input=stringio(patch)) |
|
128 | ... runcommand(server, [b'import', b'-'], input=stringio(patch)) | |
129 | ... runcommand(server, [b'log']) |
|
129 | ... runcommand(server, [b'log']) | |
130 | *** runcommand import - |
|
130 | *** runcommand import - | |
131 | applying patch from stdin |
|
131 | applying patch from stdin | |
132 | *** runcommand log |
|
132 | *** runcommand log | |
133 | changeset: 0:eff892de26ec |
|
133 | changeset: 0:eff892de26ec | |
134 | tag: tip |
|
134 | tag: tip | |
135 | user: test |
|
135 | user: test | |
136 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
136 | date: Thu Jan 01 00:00:00 1970 +0000 | |
137 | summary: 1 |
|
137 | summary: 1 | |
138 |
|
138 | |||
139 |
|
139 | |||
140 | check strict parsing of early options: |
|
140 | check strict parsing of early options: | |
141 |
|
141 | |||
142 | >>> import os |
|
142 | >>> import os | |
143 | >>> from hgclient import check, readchannel, runcommand |
|
143 | >>> from hgclient import check, readchannel, runcommand | |
144 | >>> os.environ['HGPLAIN'] = '+strictflags' |
|
144 | >>> os.environ['HGPLAIN'] = '+strictflags' | |
145 | >>> @check |
|
145 | >>> @check | |
146 | ... def cwd(server): |
|
146 | ... def cwd(server): | |
147 | ... readchannel(server) |
|
147 | ... readchannel(server) | |
148 | ... runcommand(server, [b'log', b'-b', b'--config=alias.log=!echo pwned', |
|
148 | ... runcommand(server, [b'log', b'-b', b'--config=alias.log=!echo pwned', | |
149 | ... b'default']) |
|
149 | ... b'default']) | |
150 | *** runcommand log -b --config=alias.log=!echo pwned default |
|
150 | *** runcommand log -b --config=alias.log=!echo pwned default | |
151 | abort: unknown revision '--config=alias.log=!echo pwned'! |
|
151 | abort: unknown revision '--config=alias.log=!echo pwned'! | |
152 | [255] |
|
152 | [255] | |
153 |
|
153 | |||
154 | check that "histedit --commands=-" can read rules from the input channel: |
|
154 | check that "histedit --commands=-" can read rules from the input channel: | |
155 |
|
155 | |||
156 | >>> from hgclient import check, readchannel, runcommand, stringio |
|
156 | >>> from hgclient import check, readchannel, runcommand, stringio | |
157 | >>> @check |
|
157 | >>> @check | |
158 | ... def serverinput(server): |
|
158 | ... def serverinput(server): | |
159 | ... readchannel(server) |
|
159 | ... readchannel(server) | |
160 | ... rules = b'pick eff892de26ec\n' |
|
160 | ... rules = b'pick eff892de26ec\n' | |
161 | ... runcommand(server, [b'histedit', b'0', b'--commands=-', |
|
161 | ... runcommand(server, [b'histedit', b'0', b'--commands=-', | |
162 | ... b'--config', b'extensions.histedit='], |
|
162 | ... b'--config', b'extensions.histedit='], | |
163 | ... input=stringio(rules)) |
|
163 | ... input=stringio(rules)) | |
164 | *** runcommand histedit 0 --commands=- --config extensions.histedit= |
|
164 | *** runcommand histedit 0 --commands=- --config extensions.histedit= | |
165 |
|
165 | |||
166 | check that --cwd doesn't persist between requests: |
|
166 | check that --cwd doesn't persist between requests: | |
167 |
|
167 | |||
168 | $ mkdir foo |
|
168 | $ mkdir foo | |
169 | $ touch foo/bar |
|
169 | $ touch foo/bar | |
170 | >>> from hgclient import check, readchannel, runcommand |
|
170 | >>> from hgclient import check, readchannel, runcommand | |
171 | >>> @check |
|
171 | >>> @check | |
172 | ... def cwd(server): |
|
172 | ... def cwd(server): | |
173 | ... readchannel(server) |
|
173 | ... readchannel(server) | |
174 | ... runcommand(server, [b'--cwd', b'foo', b'st', b'bar']) |
|
174 | ... runcommand(server, [b'--cwd', b'foo', b'st', b'bar']) | |
175 | ... runcommand(server, [b'st', b'foo/bar']) |
|
175 | ... runcommand(server, [b'st', b'foo/bar']) | |
176 | *** runcommand --cwd foo st bar |
|
176 | *** runcommand --cwd foo st bar | |
177 | ? bar |
|
177 | ? bar | |
178 | *** runcommand st foo/bar |
|
178 | *** runcommand st foo/bar | |
179 | ? foo/bar |
|
179 | ? foo/bar | |
180 |
|
180 | |||
181 | $ rm foo/bar |
|
181 | $ rm foo/bar | |
182 |
|
182 | |||
183 |
|
183 | |||
184 | check that local configs for the cached repo aren't inherited when -R is used: |
|
184 | check that local configs for the cached repo aren't inherited when -R is used: | |
185 |
|
185 | |||
186 | $ cat <<EOF >> .hg/hgrc |
|
186 | $ cat <<EOF >> .hg/hgrc | |
187 | > [ui] |
|
187 | > [ui] | |
188 | > foo = bar |
|
188 | > foo = bar | |
189 | > EOF |
|
189 | > EOF | |
190 |
|
190 | |||
191 | #if no-extraextensions |
|
191 | #if no-extraextensions | |
192 |
|
192 | |||
193 | >>> from hgclient import check, readchannel, runcommand, sep |
|
193 | >>> from hgclient import check, readchannel, runcommand, sep | |
194 | >>> @check |
|
194 | >>> @check | |
195 | ... def localhgrc(server): |
|
195 | ... def localhgrc(server): | |
196 | ... readchannel(server) |
|
196 | ... readchannel(server) | |
197 | ... |
|
197 | ... | |
198 | ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should |
|
198 | ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should | |
199 | ... # show it |
|
199 | ... # show it | |
200 | ... runcommand(server, [b'showconfig'], outfilter=sep) |
|
200 | ... runcommand(server, [b'showconfig'], outfilter=sep) | |
201 | ... |
|
201 | ... | |
202 | ... # but not for this repo |
|
202 | ... # but not for this repo | |
203 | ... runcommand(server, [b'init', b'foo']) |
|
203 | ... runcommand(server, [b'init', b'foo']) | |
204 | ... runcommand(server, [b'-R', b'foo', b'showconfig', b'ui', b'defaults']) |
|
204 | ... runcommand(server, [b'-R', b'foo', b'showconfig', b'ui', b'defaults']) | |
205 | *** runcommand showconfig |
|
205 | *** runcommand showconfig | |
206 | bundle.mainreporoot=$TESTTMP/repo |
|
206 | bundle.mainreporoot=$TESTTMP/repo | |
207 | devel.all-warnings=true |
|
207 | devel.all-warnings=true | |
208 | devel.default-date=0 0 |
|
208 | devel.default-date=0 0 | |
209 | extensions.fsmonitor= (fsmonitor !) |
|
209 | extensions.fsmonitor= (fsmonitor !) | |
210 | largefiles.usercache=$TESTTMP/.cache/largefiles |
|
210 | largefiles.usercache=$TESTTMP/.cache/largefiles | |
211 | lfs.usercache=$TESTTMP/.cache/lfs |
|
211 | lfs.usercache=$TESTTMP/.cache/lfs | |
212 | ui.slash=True |
|
212 | ui.slash=True | |
213 | ui.interactive=False |
|
213 | ui.interactive=False | |
|
214 | ui.detailed-exit-code=True | |||
214 | ui.merge=internal:merge |
|
215 | ui.merge=internal:merge | |
215 | ui.mergemarkers=detailed |
|
216 | ui.mergemarkers=detailed | |
216 | ui.foo=bar |
|
217 | ui.foo=bar | |
217 | ui.nontty=true |
|
218 | ui.nontty=true | |
218 | web.address=localhost |
|
219 | web.address=localhost | |
219 | web\.ipv6=(?:True|False) (re) |
|
220 | web\.ipv6=(?:True|False) (re) | |
220 | web.server-header=testing stub value |
|
221 | web.server-header=testing stub value | |
221 | *** runcommand init foo |
|
222 | *** runcommand init foo | |
222 | *** runcommand -R foo showconfig ui defaults |
|
223 | *** runcommand -R foo showconfig ui defaults | |
223 | ui.slash=True |
|
224 | ui.slash=True | |
224 | ui.interactive=False |
|
225 | ui.interactive=False | |
|
226 | ui.detailed-exit-code=True | |||
225 | ui.merge=internal:merge |
|
227 | ui.merge=internal:merge | |
226 | ui.mergemarkers=detailed |
|
228 | ui.mergemarkers=detailed | |
227 | ui.nontty=true |
|
229 | ui.nontty=true | |
228 | #endif |
|
230 | #endif | |
229 |
|
231 | |||
230 | $ rm -R foo |
|
232 | $ rm -R foo | |
231 |
|
233 | |||
232 | #if windows |
|
234 | #if windows | |
233 | $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH" |
|
235 | $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH" | |
234 | #else |
|
236 | #else | |
235 | $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH" |
|
237 | $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH" | |
236 | #endif |
|
238 | #endif | |
237 |
|
239 | |||
238 | $ cat <<EOF > hook.py |
|
240 | $ cat <<EOF > hook.py | |
239 | > import sys |
|
241 | > import sys | |
240 | > from hgclient import bprint |
|
242 | > from hgclient import bprint | |
241 | > def hook(**args): |
|
243 | > def hook(**args): | |
242 | > bprint(b'hook talking') |
|
244 | > bprint(b'hook talking') | |
243 | > bprint(b'now try to read something: %r' % sys.stdin.read()) |
|
245 | > bprint(b'now try to read something: %r' % sys.stdin.read()) | |
244 | > EOF |
|
246 | > EOF | |
245 |
|
247 | |||
246 | >>> from hgclient import check, readchannel, runcommand, stringio |
|
248 | >>> from hgclient import check, readchannel, runcommand, stringio | |
247 | >>> @check |
|
249 | >>> @check | |
248 | ... def hookoutput(server): |
|
250 | ... def hookoutput(server): | |
249 | ... readchannel(server) |
|
251 | ... readchannel(server) | |
250 | ... runcommand(server, [b'--config', |
|
252 | ... runcommand(server, [b'--config', | |
251 | ... b'hooks.pre-identify=python:hook.hook', |
|
253 | ... b'hooks.pre-identify=python:hook.hook', | |
252 | ... b'id'], |
|
254 | ... b'id'], | |
253 | ... input=stringio(b'some input')) |
|
255 | ... input=stringio(b'some input')) | |
254 | *** runcommand --config hooks.pre-identify=python:hook.hook id |
|
256 | *** runcommand --config hooks.pre-identify=python:hook.hook id | |
255 | eff892de26ec tip |
|
257 | eff892de26ec tip | |
256 | hook talking |
|
258 | hook talking | |
257 | now try to read something: '' |
|
259 | now try to read something: '' | |
258 |
|
260 | |||
259 | Clean hook cached version |
|
261 | Clean hook cached version | |
260 | $ rm hook.py* |
|
262 | $ rm hook.py* | |
261 | $ rm -Rf __pycache__ |
|
263 | $ rm -Rf __pycache__ | |
262 |
|
264 | |||
263 | $ echo a >> a |
|
265 | $ echo a >> a | |
264 | >>> import os |
|
266 | >>> import os | |
265 | >>> from hgclient import check, readchannel, runcommand |
|
267 | >>> from hgclient import check, readchannel, runcommand | |
266 | >>> @check |
|
268 | >>> @check | |
267 | ... def outsidechanges(server): |
|
269 | ... def outsidechanges(server): | |
268 | ... readchannel(server) |
|
270 | ... readchannel(server) | |
269 | ... runcommand(server, [b'status']) |
|
271 | ... runcommand(server, [b'status']) | |
270 | ... os.system('hg ci -Am2') |
|
272 | ... os.system('hg ci -Am2') | |
271 | ... runcommand(server, [b'tip']) |
|
273 | ... runcommand(server, [b'tip']) | |
272 | ... runcommand(server, [b'status']) |
|
274 | ... runcommand(server, [b'status']) | |
273 | *** runcommand status |
|
275 | *** runcommand status | |
274 | M a |
|
276 | M a | |
275 | *** runcommand tip |
|
277 | *** runcommand tip | |
276 | changeset: 1:d3a0a68be6de |
|
278 | changeset: 1:d3a0a68be6de | |
277 | tag: tip |
|
279 | tag: tip | |
278 | user: test |
|
280 | user: test | |
279 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
281 | date: Thu Jan 01 00:00:00 1970 +0000 | |
280 | summary: 2 |
|
282 | summary: 2 | |
281 |
|
283 | |||
282 | *** runcommand status |
|
284 | *** runcommand status | |
283 |
|
285 | |||
284 | >>> import os |
|
286 | >>> import os | |
285 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
287 | >>> from hgclient import bprint, check, readchannel, runcommand | |
286 | >>> @check |
|
288 | >>> @check | |
287 | ... def bookmarks(server): |
|
289 | ... def bookmarks(server): | |
288 | ... readchannel(server) |
|
290 | ... readchannel(server) | |
289 | ... runcommand(server, [b'bookmarks']) |
|
291 | ... runcommand(server, [b'bookmarks']) | |
290 | ... |
|
292 | ... | |
291 | ... # changes .hg/bookmarks |
|
293 | ... # changes .hg/bookmarks | |
292 | ... os.system('hg bookmark -i bm1') |
|
294 | ... os.system('hg bookmark -i bm1') | |
293 | ... os.system('hg bookmark -i bm2') |
|
295 | ... os.system('hg bookmark -i bm2') | |
294 | ... runcommand(server, [b'bookmarks']) |
|
296 | ... runcommand(server, [b'bookmarks']) | |
295 | ... |
|
297 | ... | |
296 | ... # changes .hg/bookmarks.current |
|
298 | ... # changes .hg/bookmarks.current | |
297 | ... os.system('hg upd bm1 -q') |
|
299 | ... os.system('hg upd bm1 -q') | |
298 | ... runcommand(server, [b'bookmarks']) |
|
300 | ... runcommand(server, [b'bookmarks']) | |
299 | ... |
|
301 | ... | |
300 | ... runcommand(server, [b'bookmarks', b'bm3']) |
|
302 | ... runcommand(server, [b'bookmarks', b'bm3']) | |
301 | ... f = open('a', 'ab') |
|
303 | ... f = open('a', 'ab') | |
302 | ... f.write(b'a\n') and None |
|
304 | ... f.write(b'a\n') and None | |
303 | ... f.close() |
|
305 | ... f.close() | |
304 | ... runcommand(server, [b'commit', b'-Amm']) |
|
306 | ... runcommand(server, [b'commit', b'-Amm']) | |
305 | ... runcommand(server, [b'bookmarks']) |
|
307 | ... runcommand(server, [b'bookmarks']) | |
306 | ... bprint(b'') |
|
308 | ... bprint(b'') | |
307 | *** runcommand bookmarks |
|
309 | *** runcommand bookmarks | |
308 | no bookmarks set |
|
310 | no bookmarks set | |
309 | *** runcommand bookmarks |
|
311 | *** runcommand bookmarks | |
310 | bm1 1:d3a0a68be6de |
|
312 | bm1 1:d3a0a68be6de | |
311 | bm2 1:d3a0a68be6de |
|
313 | bm2 1:d3a0a68be6de | |
312 | *** runcommand bookmarks |
|
314 | *** runcommand bookmarks | |
313 | * bm1 1:d3a0a68be6de |
|
315 | * bm1 1:d3a0a68be6de | |
314 | bm2 1:d3a0a68be6de |
|
316 | bm2 1:d3a0a68be6de | |
315 | *** runcommand bookmarks bm3 |
|
317 | *** runcommand bookmarks bm3 | |
316 | *** runcommand commit -Amm |
|
318 | *** runcommand commit -Amm | |
317 | *** runcommand bookmarks |
|
319 | *** runcommand bookmarks | |
318 | bm1 1:d3a0a68be6de |
|
320 | bm1 1:d3a0a68be6de | |
319 | bm2 1:d3a0a68be6de |
|
321 | bm2 1:d3a0a68be6de | |
320 | * bm3 2:aef17e88f5f0 |
|
322 | * bm3 2:aef17e88f5f0 | |
321 |
|
323 | |||
322 |
|
324 | |||
323 | >>> import os |
|
325 | >>> import os | |
324 | >>> from hgclient import check, readchannel, runcommand |
|
326 | >>> from hgclient import check, readchannel, runcommand | |
325 | >>> @check |
|
327 | >>> @check | |
326 | ... def tagscache(server): |
|
328 | ... def tagscache(server): | |
327 | ... readchannel(server) |
|
329 | ... readchannel(server) | |
328 | ... runcommand(server, [b'id', b'-t', b'-r', b'0']) |
|
330 | ... runcommand(server, [b'id', b'-t', b'-r', b'0']) | |
329 | ... os.system('hg tag -r 0 foo') |
|
331 | ... os.system('hg tag -r 0 foo') | |
330 | ... runcommand(server, [b'id', b'-t', b'-r', b'0']) |
|
332 | ... runcommand(server, [b'id', b'-t', b'-r', b'0']) | |
331 | *** runcommand id -t -r 0 |
|
333 | *** runcommand id -t -r 0 | |
332 |
|
334 | |||
333 | *** runcommand id -t -r 0 |
|
335 | *** runcommand id -t -r 0 | |
334 | foo |
|
336 | foo | |
335 |
|
337 | |||
336 | >>> import os |
|
338 | >>> import os | |
337 | >>> from hgclient import check, readchannel, runcommand |
|
339 | >>> from hgclient import check, readchannel, runcommand | |
338 | >>> @check |
|
340 | >>> @check | |
339 | ... def setphase(server): |
|
341 | ... def setphase(server): | |
340 | ... readchannel(server) |
|
342 | ... readchannel(server) | |
341 | ... runcommand(server, [b'phase', b'-r', b'.']) |
|
343 | ... runcommand(server, [b'phase', b'-r', b'.']) | |
342 | ... os.system('hg phase -r . -p') |
|
344 | ... os.system('hg phase -r . -p') | |
343 | ... runcommand(server, [b'phase', b'-r', b'.']) |
|
345 | ... runcommand(server, [b'phase', b'-r', b'.']) | |
344 | *** runcommand phase -r . |
|
346 | *** runcommand phase -r . | |
345 | 3: draft |
|
347 | 3: draft | |
346 | *** runcommand phase -r . |
|
348 | *** runcommand phase -r . | |
347 | 3: public |
|
349 | 3: public | |
348 |
|
350 | |||
349 | $ echo a >> a |
|
351 | $ echo a >> a | |
350 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
352 | >>> from hgclient import bprint, check, readchannel, runcommand | |
351 | >>> @check |
|
353 | >>> @check | |
352 | ... def rollback(server): |
|
354 | ... def rollback(server): | |
353 | ... readchannel(server) |
|
355 | ... readchannel(server) | |
354 | ... runcommand(server, [b'phase', b'-r', b'.', b'-p']) |
|
356 | ... runcommand(server, [b'phase', b'-r', b'.', b'-p']) | |
355 | ... runcommand(server, [b'commit', b'-Am.']) |
|
357 | ... runcommand(server, [b'commit', b'-Am.']) | |
356 | ... runcommand(server, [b'rollback']) |
|
358 | ... runcommand(server, [b'rollback']) | |
357 | ... runcommand(server, [b'phase', b'-r', b'.']) |
|
359 | ... runcommand(server, [b'phase', b'-r', b'.']) | |
358 | ... bprint(b'') |
|
360 | ... bprint(b'') | |
359 | *** runcommand phase -r . -p |
|
361 | *** runcommand phase -r . -p | |
360 | no phases changed |
|
362 | no phases changed | |
361 | *** runcommand commit -Am. |
|
363 | *** runcommand commit -Am. | |
362 | *** runcommand rollback |
|
364 | *** runcommand rollback | |
363 | repository tip rolled back to revision 3 (undo commit) |
|
365 | repository tip rolled back to revision 3 (undo commit) | |
364 | working directory now based on revision 3 |
|
366 | working directory now based on revision 3 | |
365 | *** runcommand phase -r . |
|
367 | *** runcommand phase -r . | |
366 | 3: public |
|
368 | 3: public | |
367 |
|
369 | |||
368 |
|
370 | |||
369 | >>> import os |
|
371 | >>> import os | |
370 | >>> from hgclient import check, readchannel, runcommand |
|
372 | >>> from hgclient import check, readchannel, runcommand | |
371 | >>> @check |
|
373 | >>> @check | |
372 | ... def branch(server): |
|
374 | ... def branch(server): | |
373 | ... readchannel(server) |
|
375 | ... readchannel(server) | |
374 | ... runcommand(server, [b'branch']) |
|
376 | ... runcommand(server, [b'branch']) | |
375 | ... os.system('hg branch foo') |
|
377 | ... os.system('hg branch foo') | |
376 | ... runcommand(server, [b'branch']) |
|
378 | ... runcommand(server, [b'branch']) | |
377 | ... os.system('hg branch default') |
|
379 | ... os.system('hg branch default') | |
378 | *** runcommand branch |
|
380 | *** runcommand branch | |
379 | default |
|
381 | default | |
380 | marked working directory as branch foo |
|
382 | marked working directory as branch foo | |
381 | (branches are permanent and global, did you want a bookmark?) |
|
383 | (branches are permanent and global, did you want a bookmark?) | |
382 | *** runcommand branch |
|
384 | *** runcommand branch | |
383 | foo |
|
385 | foo | |
384 | marked working directory as branch default |
|
386 | marked working directory as branch default | |
385 | (branches are permanent and global, did you want a bookmark?) |
|
387 | (branches are permanent and global, did you want a bookmark?) | |
386 |
|
388 | |||
387 | $ touch .hgignore |
|
389 | $ touch .hgignore | |
388 | >>> import os |
|
390 | >>> import os | |
389 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
391 | >>> from hgclient import bprint, check, readchannel, runcommand | |
390 | >>> @check |
|
392 | >>> @check | |
391 | ... def hgignore(server): |
|
393 | ... def hgignore(server): | |
392 | ... readchannel(server) |
|
394 | ... readchannel(server) | |
393 | ... runcommand(server, [b'commit', b'-Am.']) |
|
395 | ... runcommand(server, [b'commit', b'-Am.']) | |
394 | ... f = open('ignored-file', 'ab') |
|
396 | ... f = open('ignored-file', 'ab') | |
395 | ... f.write(b'') and None |
|
397 | ... f.write(b'') and None | |
396 | ... f.close() |
|
398 | ... f.close() | |
397 | ... f = open('.hgignore', 'ab') |
|
399 | ... f = open('.hgignore', 'ab') | |
398 | ... f.write(b'ignored-file') |
|
400 | ... f.write(b'ignored-file') | |
399 | ... f.close() |
|
401 | ... f.close() | |
400 | ... runcommand(server, [b'status', b'-i', b'-u']) |
|
402 | ... runcommand(server, [b'status', b'-i', b'-u']) | |
401 | ... bprint(b'') |
|
403 | ... bprint(b'') | |
402 | *** runcommand commit -Am. |
|
404 | *** runcommand commit -Am. | |
403 | adding .hgignore |
|
405 | adding .hgignore | |
404 | *** runcommand status -i -u |
|
406 | *** runcommand status -i -u | |
405 | I ignored-file |
|
407 | I ignored-file | |
406 |
|
408 | |||
407 |
|
409 | |||
408 | cache of non-public revisions should be invalidated on repository change |
|
410 | cache of non-public revisions should be invalidated on repository change | |
409 | (issue4855): |
|
411 | (issue4855): | |
410 |
|
412 | |||
411 | >>> import os |
|
413 | >>> import os | |
412 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
414 | >>> from hgclient import bprint, check, readchannel, runcommand | |
413 | >>> @check |
|
415 | >>> @check | |
414 | ... def phasesetscacheaftercommit(server): |
|
416 | ... def phasesetscacheaftercommit(server): | |
415 | ... readchannel(server) |
|
417 | ... readchannel(server) | |
416 | ... # load _phasecache._phaserevs and _phasesets |
|
418 | ... # load _phasecache._phaserevs and _phasesets | |
417 | ... runcommand(server, [b'log', b'-qr', b'draft()']) |
|
419 | ... runcommand(server, [b'log', b'-qr', b'draft()']) | |
418 | ... # create draft commits by another process |
|
420 | ... # create draft commits by another process | |
419 | ... for i in range(5, 7): |
|
421 | ... for i in range(5, 7): | |
420 | ... f = open('a', 'ab') |
|
422 | ... f = open('a', 'ab') | |
421 | ... f.seek(0, os.SEEK_END) |
|
423 | ... f.seek(0, os.SEEK_END) | |
422 | ... f.write(b'a\n') and None |
|
424 | ... f.write(b'a\n') and None | |
423 | ... f.close() |
|
425 | ... f.close() | |
424 | ... os.system('hg commit -Aqm%d' % i) |
|
426 | ... os.system('hg commit -Aqm%d' % i) | |
425 | ... # new commits should be listed as draft revisions |
|
427 | ... # new commits should be listed as draft revisions | |
426 | ... runcommand(server, [b'log', b'-qr', b'draft()']) |
|
428 | ... runcommand(server, [b'log', b'-qr', b'draft()']) | |
427 | ... bprint(b'') |
|
429 | ... bprint(b'') | |
428 | *** runcommand log -qr draft() |
|
430 | *** runcommand log -qr draft() | |
429 | 4:7966c8e3734d |
|
431 | 4:7966c8e3734d | |
430 | *** runcommand log -qr draft() |
|
432 | *** runcommand log -qr draft() | |
431 | 4:7966c8e3734d |
|
433 | 4:7966c8e3734d | |
432 | 5:41f6602d1c4f |
|
434 | 5:41f6602d1c4f | |
433 | 6:10501e202c35 |
|
435 | 6:10501e202c35 | |
434 |
|
436 | |||
435 |
|
437 | |||
436 | >>> import os |
|
438 | >>> import os | |
437 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
439 | >>> from hgclient import bprint, check, readchannel, runcommand | |
438 | >>> @check |
|
440 | >>> @check | |
439 | ... def phasesetscacheafterstrip(server): |
|
441 | ... def phasesetscacheafterstrip(server): | |
440 | ... readchannel(server) |
|
442 | ... readchannel(server) | |
441 | ... # load _phasecache._phaserevs and _phasesets |
|
443 | ... # load _phasecache._phaserevs and _phasesets | |
442 | ... runcommand(server, [b'log', b'-qr', b'draft()']) |
|
444 | ... runcommand(server, [b'log', b'-qr', b'draft()']) | |
443 | ... # strip cached revisions by another process |
|
445 | ... # strip cached revisions by another process | |
444 | ... os.system('hg --config extensions.strip= strip -q 5') |
|
446 | ... os.system('hg --config extensions.strip= strip -q 5') | |
445 | ... # shouldn't abort by "unknown revision '6'" |
|
447 | ... # shouldn't abort by "unknown revision '6'" | |
446 | ... runcommand(server, [b'log', b'-qr', b'draft()']) |
|
448 | ... runcommand(server, [b'log', b'-qr', b'draft()']) | |
447 | ... bprint(b'') |
|
449 | ... bprint(b'') | |
448 | *** runcommand log -qr draft() |
|
450 | *** runcommand log -qr draft() | |
449 | 4:7966c8e3734d |
|
451 | 4:7966c8e3734d | |
450 | 5:41f6602d1c4f |
|
452 | 5:41f6602d1c4f | |
451 | 6:10501e202c35 |
|
453 | 6:10501e202c35 | |
452 | *** runcommand log -qr draft() |
|
454 | *** runcommand log -qr draft() | |
453 | 4:7966c8e3734d |
|
455 | 4:7966c8e3734d | |
454 |
|
456 | |||
455 |
|
457 | |||
456 | cache of phase roots should be invalidated on strip (issue3827): |
|
458 | cache of phase roots should be invalidated on strip (issue3827): | |
457 |
|
459 | |||
458 | >>> import os |
|
460 | >>> import os | |
459 | >>> from hgclient import check, readchannel, runcommand, sep |
|
461 | >>> from hgclient import check, readchannel, runcommand, sep | |
460 | >>> @check |
|
462 | >>> @check | |
461 | ... def phasecacheafterstrip(server): |
|
463 | ... def phasecacheafterstrip(server): | |
462 | ... readchannel(server) |
|
464 | ... readchannel(server) | |
463 | ... |
|
465 | ... | |
464 | ... # create new head, 5:731265503d86 |
|
466 | ... # create new head, 5:731265503d86 | |
465 | ... runcommand(server, [b'update', b'-C', b'0']) |
|
467 | ... runcommand(server, [b'update', b'-C', b'0']) | |
466 | ... f = open('a', 'ab') |
|
468 | ... f = open('a', 'ab') | |
467 | ... f.write(b'a\n') and None |
|
469 | ... f.write(b'a\n') and None | |
468 | ... f.close() |
|
470 | ... f.close() | |
469 | ... runcommand(server, [b'commit', b'-Am.', b'a']) |
|
471 | ... runcommand(server, [b'commit', b'-Am.', b'a']) | |
470 | ... runcommand(server, [b'log', b'-Gq']) |
|
472 | ... runcommand(server, [b'log', b'-Gq']) | |
471 | ... |
|
473 | ... | |
472 | ... # make it public; draft marker moves to 4:7966c8e3734d |
|
474 | ... # make it public; draft marker moves to 4:7966c8e3734d | |
473 | ... runcommand(server, [b'phase', b'-p', b'.']) |
|
475 | ... runcommand(server, [b'phase', b'-p', b'.']) | |
474 | ... # load _phasecache.phaseroots |
|
476 | ... # load _phasecache.phaseroots | |
475 | ... runcommand(server, [b'phase', b'.'], outfilter=sep) |
|
477 | ... runcommand(server, [b'phase', b'.'], outfilter=sep) | |
476 | ... |
|
478 | ... | |
477 | ... # strip 1::4 outside server |
|
479 | ... # strip 1::4 outside server | |
478 | ... os.system('hg -q --config extensions.mq= strip 1') |
|
480 | ... os.system('hg -q --config extensions.mq= strip 1') | |
479 | ... |
|
481 | ... | |
480 | ... # shouldn't raise "7966c8e3734d: no node!" |
|
482 | ... # shouldn't raise "7966c8e3734d: no node!" | |
481 | ... runcommand(server, [b'branches']) |
|
483 | ... runcommand(server, [b'branches']) | |
482 | *** runcommand update -C 0 |
|
484 | *** runcommand update -C 0 | |
483 | 1 files updated, 0 files merged, 2 files removed, 0 files unresolved |
|
485 | 1 files updated, 0 files merged, 2 files removed, 0 files unresolved | |
484 | (leaving bookmark bm3) |
|
486 | (leaving bookmark bm3) | |
485 | *** runcommand commit -Am. a |
|
487 | *** runcommand commit -Am. a | |
486 | created new head |
|
488 | created new head | |
487 | *** runcommand log -Gq |
|
489 | *** runcommand log -Gq | |
488 | @ 5:731265503d86 |
|
490 | @ 5:731265503d86 | |
489 | | |
|
491 | | | |
490 | | o 4:7966c8e3734d |
|
492 | | o 4:7966c8e3734d | |
491 | | | |
|
493 | | | | |
492 | | o 3:b9b85890c400 |
|
494 | | o 3:b9b85890c400 | |
493 | | | |
|
495 | | | | |
494 | | o 2:aef17e88f5f0 |
|
496 | | o 2:aef17e88f5f0 | |
495 | | | |
|
497 | | | | |
496 | | o 1:d3a0a68be6de |
|
498 | | o 1:d3a0a68be6de | |
497 | |/ |
|
499 | |/ | |
498 | o 0:eff892de26ec |
|
500 | o 0:eff892de26ec | |
499 |
|
501 | |||
500 | *** runcommand phase -p . |
|
502 | *** runcommand phase -p . | |
501 | *** runcommand phase . |
|
503 | *** runcommand phase . | |
502 | 5: public |
|
504 | 5: public | |
503 | *** runcommand branches |
|
505 | *** runcommand branches | |
504 | default 1:731265503d86 |
|
506 | default 1:731265503d86 | |
505 |
|
507 | |||
506 | in-memory cache must be reloaded if transaction is aborted. otherwise |
|
508 | in-memory cache must be reloaded if transaction is aborted. otherwise | |
507 | changelog and manifest would have invalid node: |
|
509 | changelog and manifest would have invalid node: | |
508 |
|
510 | |||
509 | $ echo a >> a |
|
511 | $ echo a >> a | |
510 | >>> from hgclient import check, readchannel, runcommand |
|
512 | >>> from hgclient import check, readchannel, runcommand | |
511 | >>> @check |
|
513 | >>> @check | |
512 | ... def txabort(server): |
|
514 | ... def txabort(server): | |
513 | ... readchannel(server) |
|
515 | ... readchannel(server) | |
514 | ... runcommand(server, [b'commit', b'--config', b'hooks.pretxncommit=false', |
|
516 | ... runcommand(server, [b'commit', b'--config', b'hooks.pretxncommit=false', | |
515 | ... b'-mfoo']) |
|
517 | ... b'-mfoo']) | |
516 | ... runcommand(server, [b'verify']) |
|
518 | ... runcommand(server, [b'verify']) | |
517 | *** runcommand commit --config hooks.pretxncommit=false -mfoo |
|
519 | *** runcommand commit --config hooks.pretxncommit=false -mfoo | |
518 | transaction abort! |
|
520 | transaction abort! | |
519 | rollback completed |
|
521 | rollback completed | |
520 | abort: pretxncommit hook exited with status 1 |
|
522 | abort: pretxncommit hook exited with status 1 | |
521 | [255] |
|
523 | [255] | |
522 | *** runcommand verify |
|
524 | *** runcommand verify | |
523 | checking changesets |
|
525 | checking changesets | |
524 | checking manifests |
|
526 | checking manifests | |
525 | crosschecking files in changesets and manifests |
|
527 | crosschecking files in changesets and manifests | |
526 | checking files |
|
528 | checking files | |
527 | checked 2 changesets with 2 changes to 1 files |
|
529 | checked 2 changesets with 2 changes to 1 files | |
528 | $ hg revert --no-backup -aq |
|
530 | $ hg revert --no-backup -aq | |
529 |
|
531 | |||
530 | $ cat >> .hg/hgrc << EOF |
|
532 | $ cat >> .hg/hgrc << EOF | |
531 | > [experimental] |
|
533 | > [experimental] | |
532 | > evolution.createmarkers=True |
|
534 | > evolution.createmarkers=True | |
533 | > EOF |
|
535 | > EOF | |
534 |
|
536 | |||
535 | >>> import os |
|
537 | >>> import os | |
536 | >>> from hgclient import check, readchannel, runcommand |
|
538 | >>> from hgclient import check, readchannel, runcommand | |
537 | >>> @check |
|
539 | >>> @check | |
538 | ... def obsolete(server): |
|
540 | ... def obsolete(server): | |
539 | ... readchannel(server) |
|
541 | ... readchannel(server) | |
540 | ... |
|
542 | ... | |
541 | ... runcommand(server, [b'up', b'null']) |
|
543 | ... runcommand(server, [b'up', b'null']) | |
542 | ... runcommand(server, [b'phase', b'-df', b'tip']) |
|
544 | ... runcommand(server, [b'phase', b'-df', b'tip']) | |
543 | ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`' |
|
545 | ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`' | |
544 | ... if os.name == 'nt': |
|
546 | ... if os.name == 'nt': | |
545 | ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe |
|
547 | ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe | |
546 | ... os.system(cmd) |
|
548 | ... os.system(cmd) | |
547 | ... runcommand(server, [b'log', b'--hidden']) |
|
549 | ... runcommand(server, [b'log', b'--hidden']) | |
548 | ... runcommand(server, [b'log']) |
|
550 | ... runcommand(server, [b'log']) | |
549 | *** runcommand up null |
|
551 | *** runcommand up null | |
550 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved |
|
552 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved | |
551 | *** runcommand phase -df tip |
|
553 | *** runcommand phase -df tip | |
552 | 1 new obsolescence markers |
|
554 | 1 new obsolescence markers | |
553 | obsoleted 1 changesets |
|
555 | obsoleted 1 changesets | |
554 | *** runcommand log --hidden |
|
556 | *** runcommand log --hidden | |
555 | changeset: 1:731265503d86 |
|
557 | changeset: 1:731265503d86 | |
556 | tag: tip |
|
558 | tag: tip | |
557 | user: test |
|
559 | user: test | |
558 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
560 | date: Thu Jan 01 00:00:00 1970 +0000 | |
559 | obsolete: pruned |
|
561 | obsolete: pruned | |
560 | summary: . |
|
562 | summary: . | |
561 |
|
563 | |||
562 | changeset: 0:eff892de26ec |
|
564 | changeset: 0:eff892de26ec | |
563 | bookmark: bm1 |
|
565 | bookmark: bm1 | |
564 | bookmark: bm2 |
|
566 | bookmark: bm2 | |
565 | bookmark: bm3 |
|
567 | bookmark: bm3 | |
566 | user: test |
|
568 | user: test | |
567 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
569 | date: Thu Jan 01 00:00:00 1970 +0000 | |
568 | summary: 1 |
|
570 | summary: 1 | |
569 |
|
571 | |||
570 | *** runcommand log |
|
572 | *** runcommand log | |
571 | changeset: 0:eff892de26ec |
|
573 | changeset: 0:eff892de26ec | |
572 | bookmark: bm1 |
|
574 | bookmark: bm1 | |
573 | bookmark: bm2 |
|
575 | bookmark: bm2 | |
574 | bookmark: bm3 |
|
576 | bookmark: bm3 | |
575 | tag: tip |
|
577 | tag: tip | |
576 | user: test |
|
578 | user: test | |
577 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
579 | date: Thu Jan 01 00:00:00 1970 +0000 | |
578 | summary: 1 |
|
580 | summary: 1 | |
579 |
|
581 | |||
580 |
|
582 | |||
581 | $ cat <<EOF >> .hg/hgrc |
|
583 | $ cat <<EOF >> .hg/hgrc | |
582 | > [extensions] |
|
584 | > [extensions] | |
583 | > mq = |
|
585 | > mq = | |
584 | > EOF |
|
586 | > EOF | |
585 |
|
587 | |||
586 | >>> import os |
|
588 | >>> import os | |
587 | >>> from hgclient import check, readchannel, runcommand |
|
589 | >>> from hgclient import check, readchannel, runcommand | |
588 | >>> @check |
|
590 | >>> @check | |
589 | ... def mqoutsidechanges(server): |
|
591 | ... def mqoutsidechanges(server): | |
590 | ... readchannel(server) |
|
592 | ... readchannel(server) | |
591 | ... |
|
593 | ... | |
592 | ... # load repo.mq |
|
594 | ... # load repo.mq | |
593 | ... runcommand(server, [b'qapplied']) |
|
595 | ... runcommand(server, [b'qapplied']) | |
594 | ... os.system('hg qnew 0.diff') |
|
596 | ... os.system('hg qnew 0.diff') | |
595 | ... # repo.mq should be invalidated |
|
597 | ... # repo.mq should be invalidated | |
596 | ... runcommand(server, [b'qapplied']) |
|
598 | ... runcommand(server, [b'qapplied']) | |
597 | ... |
|
599 | ... | |
598 | ... runcommand(server, [b'qpop', b'--all']) |
|
600 | ... runcommand(server, [b'qpop', b'--all']) | |
599 | ... os.system('hg qqueue --create foo') |
|
601 | ... os.system('hg qqueue --create foo') | |
600 | ... # repo.mq should be recreated to point to new queue |
|
602 | ... # repo.mq should be recreated to point to new queue | |
601 | ... runcommand(server, [b'qqueue', b'--active']) |
|
603 | ... runcommand(server, [b'qqueue', b'--active']) | |
602 | *** runcommand qapplied |
|
604 | *** runcommand qapplied | |
603 | *** runcommand qapplied |
|
605 | *** runcommand qapplied | |
604 | 0.diff |
|
606 | 0.diff | |
605 | *** runcommand qpop --all |
|
607 | *** runcommand qpop --all | |
606 | popping 0.diff |
|
608 | popping 0.diff | |
607 | patch queue now empty |
|
609 | patch queue now empty | |
608 | *** runcommand qqueue --active |
|
610 | *** runcommand qqueue --active | |
609 | foo |
|
611 | foo | |
610 |
|
612 | |||
611 | $ cat <<'EOF' > ../dbgui.py |
|
613 | $ cat <<'EOF' > ../dbgui.py | |
612 | > import os |
|
614 | > import os | |
613 | > import sys |
|
615 | > import sys | |
614 | > from mercurial import commands, registrar |
|
616 | > from mercurial import commands, registrar | |
615 | > cmdtable = {} |
|
617 | > cmdtable = {} | |
616 | > command = registrar.command(cmdtable) |
|
618 | > command = registrar.command(cmdtable) | |
617 | > @command(b"debuggetpass", norepo=True) |
|
619 | > @command(b"debuggetpass", norepo=True) | |
618 | > def debuggetpass(ui): |
|
620 | > def debuggetpass(ui): | |
619 | > ui.write(b"%s\n" % ui.getpass()) |
|
621 | > ui.write(b"%s\n" % ui.getpass()) | |
620 | > @command(b"debugprompt", norepo=True) |
|
622 | > @command(b"debugprompt", norepo=True) | |
621 | > def debugprompt(ui): |
|
623 | > def debugprompt(ui): | |
622 | > ui.write(b"%s\n" % ui.prompt(b"prompt:")) |
|
624 | > ui.write(b"%s\n" % ui.prompt(b"prompt:")) | |
623 | > @command(b"debugpromptchoice", norepo=True) |
|
625 | > @command(b"debugpromptchoice", norepo=True) | |
624 | > def debugpromptchoice(ui): |
|
626 | > def debugpromptchoice(ui): | |
625 | > msg = b"promptchoice (y/n)? $$ &Yes $$ &No" |
|
627 | > msg = b"promptchoice (y/n)? $$ &Yes $$ &No" | |
626 | > ui.write(b"%d\n" % ui.promptchoice(msg)) |
|
628 | > ui.write(b"%d\n" % ui.promptchoice(msg)) | |
627 | > @command(b"debugreadstdin", norepo=True) |
|
629 | > @command(b"debugreadstdin", norepo=True) | |
628 | > def debugreadstdin(ui): |
|
630 | > def debugreadstdin(ui): | |
629 | > ui.write(b"read: %r\n" % sys.stdin.read(1)) |
|
631 | > ui.write(b"read: %r\n" % sys.stdin.read(1)) | |
630 | > @command(b"debugwritestdout", norepo=True) |
|
632 | > @command(b"debugwritestdout", norepo=True) | |
631 | > def debugwritestdout(ui): |
|
633 | > def debugwritestdout(ui): | |
632 | > os.write(1, b"low-level stdout fd and\n") |
|
634 | > os.write(1, b"low-level stdout fd and\n") | |
633 | > sys.stdout.write("stdout should be redirected to stderr\n") |
|
635 | > sys.stdout.write("stdout should be redirected to stderr\n") | |
634 | > sys.stdout.flush() |
|
636 | > sys.stdout.flush() | |
635 | > EOF |
|
637 | > EOF | |
636 | $ cat <<EOF >> .hg/hgrc |
|
638 | $ cat <<EOF >> .hg/hgrc | |
637 | > [extensions] |
|
639 | > [extensions] | |
638 | > dbgui = ../dbgui.py |
|
640 | > dbgui = ../dbgui.py | |
639 | > EOF |
|
641 | > EOF | |
640 |
|
642 | |||
641 | >>> from hgclient import check, readchannel, runcommand, stringio |
|
643 | >>> from hgclient import check, readchannel, runcommand, stringio | |
642 | >>> @check |
|
644 | >>> @check | |
643 | ... def getpass(server): |
|
645 | ... def getpass(server): | |
644 | ... readchannel(server) |
|
646 | ... readchannel(server) | |
645 | ... runcommand(server, [b'debuggetpass', b'--config', |
|
647 | ... runcommand(server, [b'debuggetpass', b'--config', | |
646 | ... b'ui.interactive=True'], |
|
648 | ... b'ui.interactive=True'], | |
647 | ... input=stringio(b'1234\n')) |
|
649 | ... input=stringio(b'1234\n')) | |
648 | ... runcommand(server, [b'debuggetpass', b'--config', |
|
650 | ... runcommand(server, [b'debuggetpass', b'--config', | |
649 | ... b'ui.interactive=True'], |
|
651 | ... b'ui.interactive=True'], | |
650 | ... input=stringio(b'\n')) |
|
652 | ... input=stringio(b'\n')) | |
651 | ... runcommand(server, [b'debuggetpass', b'--config', |
|
653 | ... runcommand(server, [b'debuggetpass', b'--config', | |
652 | ... b'ui.interactive=True'], |
|
654 | ... b'ui.interactive=True'], | |
653 | ... input=stringio(b'')) |
|
655 | ... input=stringio(b'')) | |
654 | ... runcommand(server, [b'debugprompt', b'--config', |
|
656 | ... runcommand(server, [b'debugprompt', b'--config', | |
655 | ... b'ui.interactive=True'], |
|
657 | ... b'ui.interactive=True'], | |
656 | ... input=stringio(b'5678\n')) |
|
658 | ... input=stringio(b'5678\n')) | |
657 | ... runcommand(server, [b'debugprompt', b'--config', |
|
659 | ... runcommand(server, [b'debugprompt', b'--config', | |
658 | ... b'ui.interactive=True'], |
|
660 | ... b'ui.interactive=True'], | |
659 | ... input=stringio(b'\nremainder\nshould\nnot\nbe\nread\n')) |
|
661 | ... input=stringio(b'\nremainder\nshould\nnot\nbe\nread\n')) | |
660 | ... runcommand(server, [b'debugreadstdin']) |
|
662 | ... runcommand(server, [b'debugreadstdin']) | |
661 | ... runcommand(server, [b'debugwritestdout']) |
|
663 | ... runcommand(server, [b'debugwritestdout']) | |
662 | *** runcommand debuggetpass --config ui.interactive=True |
|
664 | *** runcommand debuggetpass --config ui.interactive=True | |
663 | password: 1234 |
|
665 | password: 1234 | |
664 | *** runcommand debuggetpass --config ui.interactive=True |
|
666 | *** runcommand debuggetpass --config ui.interactive=True | |
665 | password: |
|
667 | password: | |
666 | *** runcommand debuggetpass --config ui.interactive=True |
|
668 | *** runcommand debuggetpass --config ui.interactive=True | |
667 | password: abort: response expected |
|
669 | password: abort: response expected | |
668 | [255] |
|
670 | [255] | |
669 | *** runcommand debugprompt --config ui.interactive=True |
|
671 | *** runcommand debugprompt --config ui.interactive=True | |
670 | prompt: 5678 |
|
672 | prompt: 5678 | |
671 | *** runcommand debugprompt --config ui.interactive=True |
|
673 | *** runcommand debugprompt --config ui.interactive=True | |
672 | prompt: y |
|
674 | prompt: y | |
673 | *** runcommand debugreadstdin |
|
675 | *** runcommand debugreadstdin | |
674 | read: '' |
|
676 | read: '' | |
675 | *** runcommand debugwritestdout |
|
677 | *** runcommand debugwritestdout | |
676 | low-level stdout fd and |
|
678 | low-level stdout fd and | |
677 | stdout should be redirected to stderr |
|
679 | stdout should be redirected to stderr | |
678 |
|
680 | |||
679 |
|
681 | |||
680 | run commandserver in commandserver, which is silly but should work: |
|
682 | run commandserver in commandserver, which is silly but should work: | |
681 |
|
683 | |||
682 | >>> from hgclient import bprint, check, readchannel, runcommand, stringio |
|
684 | >>> from hgclient import bprint, check, readchannel, runcommand, stringio | |
683 | >>> @check |
|
685 | >>> @check | |
684 | ... def nested(server): |
|
686 | ... def nested(server): | |
685 | ... bprint(b'%c, %r' % readchannel(server)) |
|
687 | ... bprint(b'%c, %r' % readchannel(server)) | |
686 | ... class nestedserver(object): |
|
688 | ... class nestedserver(object): | |
687 | ... stdin = stringio(b'getencoding\n') |
|
689 | ... stdin = stringio(b'getencoding\n') | |
688 | ... stdout = stringio() |
|
690 | ... stdout = stringio() | |
689 | ... runcommand(server, [b'serve', b'--cmdserver', b'pipe'], |
|
691 | ... runcommand(server, [b'serve', b'--cmdserver', b'pipe'], | |
690 | ... output=nestedserver.stdout, input=nestedserver.stdin) |
|
692 | ... output=nestedserver.stdout, input=nestedserver.stdin) | |
691 | ... nestedserver.stdout.seek(0) |
|
693 | ... nestedserver.stdout.seek(0) | |
692 | ... bprint(b'%c, %r' % readchannel(nestedserver)) # hello |
|
694 | ... bprint(b'%c, %r' % readchannel(nestedserver)) # hello | |
693 | ... bprint(b'%c, %r' % readchannel(nestedserver)) # getencoding |
|
695 | ... bprint(b'%c, %r' % readchannel(nestedserver)) # getencoding | |
694 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) |
|
696 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) | |
695 | *** runcommand serve --cmdserver pipe |
|
697 | *** runcommand serve --cmdserver pipe | |
696 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) |
|
698 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) | |
697 | r, '*' (glob) |
|
699 | r, '*' (glob) | |
698 |
|
700 | |||
699 |
|
701 | |||
700 | start without repository: |
|
702 | start without repository: | |
701 |
|
703 | |||
702 | $ cd .. |
|
704 | $ cd .. | |
703 |
|
705 | |||
704 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
706 | >>> from hgclient import bprint, check, readchannel, runcommand | |
705 | >>> @check |
|
707 | >>> @check | |
706 | ... def hellomessage(server): |
|
708 | ... def hellomessage(server): | |
707 | ... ch, data = readchannel(server) |
|
709 | ... ch, data = readchannel(server) | |
708 | ... bprint(b'%c, %r' % (ch, data)) |
|
710 | ... bprint(b'%c, %r' % (ch, data)) | |
709 | ... # run an arbitrary command to make sure the next thing the server |
|
711 | ... # run an arbitrary command to make sure the next thing the server | |
710 | ... # sends isn't part of the hello message |
|
712 | ... # sends isn't part of the hello message | |
711 | ... runcommand(server, [b'id']) |
|
713 | ... runcommand(server, [b'id']) | |
712 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) |
|
714 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) | |
713 | *** runcommand id |
|
715 | *** runcommand id | |
714 | abort: there is no Mercurial repository here (.hg not found) |
|
716 | abort: there is no Mercurial repository here (.hg not found) | |
715 | [255] |
|
717 | [255] | |
716 |
|
718 | |||
717 | >>> from hgclient import check, readchannel, runcommand |
|
719 | >>> from hgclient import check, readchannel, runcommand | |
718 | >>> @check |
|
720 | >>> @check | |
719 | ... def startwithoutrepo(server): |
|
721 | ... def startwithoutrepo(server): | |
720 | ... readchannel(server) |
|
722 | ... readchannel(server) | |
721 | ... runcommand(server, [b'init', b'repo2']) |
|
723 | ... runcommand(server, [b'init', b'repo2']) | |
722 | ... runcommand(server, [b'id', b'-R', b'repo2']) |
|
724 | ... runcommand(server, [b'id', b'-R', b'repo2']) | |
723 | *** runcommand init repo2 |
|
725 | *** runcommand init repo2 | |
724 | *** runcommand id -R repo2 |
|
726 | *** runcommand id -R repo2 | |
725 | 000000000000 tip |
|
727 | 000000000000 tip | |
726 |
|
728 | |||
727 |
|
729 | |||
728 | don't fall back to cwd if invalid -R path is specified (issue4805): |
|
730 | don't fall back to cwd if invalid -R path is specified (issue4805): | |
729 |
|
731 | |||
730 | $ cd repo |
|
732 | $ cd repo | |
731 | $ hg serve --cmdserver pipe -R ../nonexistent |
|
733 | $ hg serve --cmdserver pipe -R ../nonexistent | |
732 | abort: repository ../nonexistent not found! |
|
734 | abort: repository ../nonexistent not found! | |
733 | [255] |
|
735 | [255] | |
734 | $ cd .. |
|
736 | $ cd .. | |
735 |
|
737 | |||
736 |
|
738 | |||
737 | #if no-windows |
|
739 | #if no-windows | |
738 |
|
740 | |||
739 | option to not shutdown on SIGINT: |
|
741 | option to not shutdown on SIGINT: | |
740 |
|
742 | |||
741 | $ cat <<'EOF' > dbgint.py |
|
743 | $ cat <<'EOF' > dbgint.py | |
742 | > import os |
|
744 | > import os | |
743 | > import signal |
|
745 | > import signal | |
744 | > import time |
|
746 | > import time | |
745 | > from mercurial import commands, registrar |
|
747 | > from mercurial import commands, registrar | |
746 | > cmdtable = {} |
|
748 | > cmdtable = {} | |
747 | > command = registrar.command(cmdtable) |
|
749 | > command = registrar.command(cmdtable) | |
748 | > @command(b"debugsleep", norepo=True) |
|
750 | > @command(b"debugsleep", norepo=True) | |
749 | > def debugsleep(ui): |
|
751 | > def debugsleep(ui): | |
750 | > time.sleep(1) |
|
752 | > time.sleep(1) | |
751 | > @command(b"debugsuicide", norepo=True) |
|
753 | > @command(b"debugsuicide", norepo=True) | |
752 | > def debugsuicide(ui): |
|
754 | > def debugsuicide(ui): | |
753 | > os.kill(os.getpid(), signal.SIGINT) |
|
755 | > os.kill(os.getpid(), signal.SIGINT) | |
754 | > time.sleep(1) |
|
756 | > time.sleep(1) | |
755 | > EOF |
|
757 | > EOF | |
756 |
|
758 | |||
757 | >>> import signal |
|
759 | >>> import signal | |
758 | >>> import time |
|
760 | >>> import time | |
759 | >>> from hgclient import checkwith, readchannel, runcommand |
|
761 | >>> from hgclient import checkwith, readchannel, runcommand | |
760 | >>> @checkwith(extraargs=[b'--config', b'cmdserver.shutdown-on-interrupt=False', |
|
762 | >>> @checkwith(extraargs=[b'--config', b'cmdserver.shutdown-on-interrupt=False', | |
761 | ... b'--config', b'extensions.dbgint=dbgint.py']) |
|
763 | ... b'--config', b'extensions.dbgint=dbgint.py']) | |
762 | ... def nointr(server): |
|
764 | ... def nointr(server): | |
763 | ... readchannel(server) |
|
765 | ... readchannel(server) | |
764 | ... server.send_signal(signal.SIGINT) # server won't be terminated |
|
766 | ... server.send_signal(signal.SIGINT) # server won't be terminated | |
765 | ... time.sleep(1) |
|
767 | ... time.sleep(1) | |
766 | ... runcommand(server, [b'debugsleep']) |
|
768 | ... runcommand(server, [b'debugsleep']) | |
767 | ... server.send_signal(signal.SIGINT) # server won't be terminated |
|
769 | ... server.send_signal(signal.SIGINT) # server won't be terminated | |
768 | ... runcommand(server, [b'debugsleep']) |
|
770 | ... runcommand(server, [b'debugsleep']) | |
769 | ... runcommand(server, [b'debugsuicide']) # command can be interrupted |
|
771 | ... runcommand(server, [b'debugsuicide']) # command can be interrupted | |
770 | ... server.send_signal(signal.SIGTERM) # server will be terminated |
|
772 | ... server.send_signal(signal.SIGTERM) # server will be terminated | |
771 | ... time.sleep(1) |
|
773 | ... time.sleep(1) | |
772 | *** runcommand debugsleep |
|
774 | *** runcommand debugsleep | |
773 | *** runcommand debugsleep |
|
775 | *** runcommand debugsleep | |
774 | *** runcommand debugsuicide |
|
776 | *** runcommand debugsuicide | |
775 | interrupted! |
|
777 | interrupted! | |
776 | killed! |
|
778 | killed! | |
777 | [255] |
|
779 | [255] | |
778 |
|
780 | |||
779 | #endif |
|
781 | #endif | |
780 |
|
782 | |||
781 |
|
783 | |||
782 | structured message channel: |
|
784 | structured message channel: | |
783 |
|
785 | |||
784 | $ cat <<'EOF' >> repo2/.hg/hgrc |
|
786 | $ cat <<'EOF' >> repo2/.hg/hgrc | |
785 | > [ui] |
|
787 | > [ui] | |
786 | > # server --config should precede repository option |
|
788 | > # server --config should precede repository option | |
787 | > message-output = stdio |
|
789 | > message-output = stdio | |
788 | > EOF |
|
790 | > EOF | |
789 |
|
791 | |||
790 | >>> from hgclient import bprint, checkwith, readchannel, runcommand |
|
792 | >>> from hgclient import bprint, checkwith, readchannel, runcommand | |
791 | >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel', |
|
793 | >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel', | |
792 | ... b'--config', b'cmdserver.message-encodings=foo cbor']) |
|
794 | ... b'--config', b'cmdserver.message-encodings=foo cbor']) | |
793 | ... def verify(server): |
|
795 | ... def verify(server): | |
794 | ... _ch, data = readchannel(server) |
|
796 | ... _ch, data = readchannel(server) | |
795 | ... bprint(data) |
|
797 | ... bprint(data) | |
796 | ... runcommand(server, [b'-R', b'repo2', b'verify']) |
|
798 | ... runcommand(server, [b'-R', b'repo2', b'verify']) | |
797 | capabilities: getencoding runcommand |
|
799 | capabilities: getencoding runcommand | |
798 | encoding: ascii |
|
800 | encoding: ascii | |
799 | message-encoding: cbor |
|
801 | message-encoding: cbor | |
800 | pid: * (glob) |
|
802 | pid: * (glob) | |
801 | pgid: * (glob) (no-windows !) |
|
803 | pgid: * (glob) (no-windows !) | |
802 | *** runcommand -R repo2 verify |
|
804 | *** runcommand -R repo2 verify | |
803 | message: '\xa2DdataTchecking changesets\nDtypeFstatus' |
|
805 | message: '\xa2DdataTchecking changesets\nDtypeFstatus' | |
804 | message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@' |
|
806 | message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@' | |
805 | message: '\xa2DdataSchecking manifests\nDtypeFstatus' |
|
807 | message: '\xa2DdataSchecking manifests\nDtypeFstatus' | |
806 | message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@' |
|
808 | message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@' | |
807 | message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus' |
|
809 | message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus' | |
808 | message: '\xa6Ditem@Cpos\xf6EtopicMcrosscheckingEtotal\xf6DtypeHprogressDunit@' |
|
810 | message: '\xa6Ditem@Cpos\xf6EtopicMcrosscheckingEtotal\xf6DtypeHprogressDunit@' | |
809 | message: '\xa2DdataOchecking files\nDtypeFstatus' |
|
811 | message: '\xa2DdataOchecking files\nDtypeFstatus' | |
810 | message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@' |
|
812 | message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@' | |
811 | message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus' |
|
813 | message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus' | |
812 |
|
814 | |||
813 | >>> from hgclient import checkwith, readchannel, runcommand, stringio |
|
815 | >>> from hgclient import checkwith, readchannel, runcommand, stringio | |
814 | >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel', |
|
816 | >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel', | |
815 | ... b'--config', b'cmdserver.message-encodings=cbor', |
|
817 | ... b'--config', b'cmdserver.message-encodings=cbor', | |
816 | ... b'--config', b'extensions.dbgui=dbgui.py']) |
|
818 | ... b'--config', b'extensions.dbgui=dbgui.py']) | |
817 | ... def prompt(server): |
|
819 | ... def prompt(server): | |
818 | ... readchannel(server) |
|
820 | ... readchannel(server) | |
819 | ... interactive = [b'--config', b'ui.interactive=True'] |
|
821 | ... interactive = [b'--config', b'ui.interactive=True'] | |
820 | ... runcommand(server, [b'debuggetpass'] + interactive, |
|
822 | ... runcommand(server, [b'debuggetpass'] + interactive, | |
821 | ... input=stringio(b'1234\n')) |
|
823 | ... input=stringio(b'1234\n')) | |
822 | ... runcommand(server, [b'debugprompt'] + interactive, |
|
824 | ... runcommand(server, [b'debugprompt'] + interactive, | |
823 | ... input=stringio(b'5678\n')) |
|
825 | ... input=stringio(b'5678\n')) | |
824 | ... runcommand(server, [b'debugpromptchoice'] + interactive, |
|
826 | ... runcommand(server, [b'debugpromptchoice'] + interactive, | |
825 | ... input=stringio(b'n\n')) |
|
827 | ... input=stringio(b'n\n')) | |
826 | *** runcommand debuggetpass --config ui.interactive=True |
|
828 | *** runcommand debuggetpass --config ui.interactive=True | |
827 | message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt' |
|
829 | message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt' | |
828 | 1234 |
|
830 | 1234 | |
829 | *** runcommand debugprompt --config ui.interactive=True |
|
831 | *** runcommand debugprompt --config ui.interactive=True | |
830 | message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt' |
|
832 | message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt' | |
831 | 5678 |
|
833 | 5678 | |
832 | *** runcommand debugpromptchoice --config ui.interactive=True |
|
834 | *** runcommand debugpromptchoice --config ui.interactive=True | |
833 | message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt' |
|
835 | message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt' | |
834 | 1 |
|
836 | 1 | |
835 |
|
837 | |||
836 | bad message encoding: |
|
838 | bad message encoding: | |
837 |
|
839 | |||
838 | $ hg serve --cmdserver pipe --config ui.message-output=channel |
|
840 | $ hg serve --cmdserver pipe --config ui.message-output=channel | |
839 | abort: no supported message encodings: |
|
841 | abort: no supported message encodings: | |
840 | [255] |
|
842 | [255] | |
841 | $ hg serve --cmdserver pipe --config ui.message-output=channel \ |
|
843 | $ hg serve --cmdserver pipe --config ui.message-output=channel \ | |
842 | > --config cmdserver.message-encodings='foo bar' |
|
844 | > --config cmdserver.message-encodings='foo bar' | |
843 | abort: no supported message encodings: foo bar |
|
845 | abort: no supported message encodings: foo bar | |
844 | [255] |
|
846 | [255] | |
845 |
|
847 | |||
846 | unix domain socket: |
|
848 | unix domain socket: | |
847 |
|
849 | |||
848 | $ cd repo |
|
850 | $ cd repo | |
849 | $ hg update -q |
|
851 | $ hg update -q | |
850 |
|
852 | |||
851 | #if unix-socket unix-permissions |
|
853 | #if unix-socket unix-permissions | |
852 |
|
854 | |||
853 | >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver |
|
855 | >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver | |
854 | >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log') |
|
856 | >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log') | |
855 | >>> def hellomessage(conn): |
|
857 | >>> def hellomessage(conn): | |
856 | ... ch, data = readchannel(conn) |
|
858 | ... ch, data = readchannel(conn) | |
857 | ... bprint(b'%c, %r' % (ch, data)) |
|
859 | ... bprint(b'%c, %r' % (ch, data)) | |
858 | ... runcommand(conn, [b'id']) |
|
860 | ... runcommand(conn, [b'id']) | |
859 | >>> check(hellomessage, server.connect) |
|
861 | >>> check(hellomessage, server.connect) | |
860 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) |
|
862 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) | |
861 | *** runcommand id |
|
863 | *** runcommand id | |
862 | eff892de26ec tip bm1/bm2/bm3 |
|
864 | eff892de26ec tip bm1/bm2/bm3 | |
863 | >>> def unknowncommand(conn): |
|
865 | >>> def unknowncommand(conn): | |
864 | ... readchannel(conn) |
|
866 | ... readchannel(conn) | |
865 | ... conn.stdin.write(b'unknowncommand\n') |
|
867 | ... conn.stdin.write(b'unknowncommand\n') | |
866 | >>> check(unknowncommand, server.connect) # error sent to server.log |
|
868 | >>> check(unknowncommand, server.connect) # error sent to server.log | |
867 | >>> def serverinput(conn): |
|
869 | >>> def serverinput(conn): | |
868 | ... readchannel(conn) |
|
870 | ... readchannel(conn) | |
869 | ... patch = b""" |
|
871 | ... patch = b""" | |
870 | ... # HG changeset patch |
|
872 | ... # HG changeset patch | |
871 | ... # User test |
|
873 | ... # User test | |
872 | ... # Date 0 0 |
|
874 | ... # Date 0 0 | |
873 | ... 2 |
|
875 | ... 2 | |
874 | ... |
|
876 | ... | |
875 | ... diff -r eff892de26ec -r 1ed24be7e7a0 a |
|
877 | ... diff -r eff892de26ec -r 1ed24be7e7a0 a | |
876 | ... --- a/a |
|
878 | ... --- a/a | |
877 | ... +++ b/a |
|
879 | ... +++ b/a | |
878 | ... @@ -1,1 +1,2 @@ |
|
880 | ... @@ -1,1 +1,2 @@ | |
879 | ... 1 |
|
881 | ... 1 | |
880 | ... +2 |
|
882 | ... +2 | |
881 | ... """ |
|
883 | ... """ | |
882 | ... runcommand(conn, [b'import', b'-'], input=stringio(patch)) |
|
884 | ... runcommand(conn, [b'import', b'-'], input=stringio(patch)) | |
883 | ... runcommand(conn, [b'log', b'-rtip', b'-q']) |
|
885 | ... runcommand(conn, [b'log', b'-rtip', b'-q']) | |
884 | >>> check(serverinput, server.connect) |
|
886 | >>> check(serverinput, server.connect) | |
885 | *** runcommand import - |
|
887 | *** runcommand import - | |
886 | applying patch from stdin |
|
888 | applying patch from stdin | |
887 | *** runcommand log -rtip -q |
|
889 | *** runcommand log -rtip -q | |
888 | 2:1ed24be7e7a0 |
|
890 | 2:1ed24be7e7a0 | |
889 | >>> server.shutdown() |
|
891 | >>> server.shutdown() | |
890 |
|
892 | |||
891 | $ cat .hg/server.log |
|
893 | $ cat .hg/server.log | |
892 | listening at .hg/server.sock |
|
894 | listening at .hg/server.sock | |
893 | abort: unknown command unknowncommand |
|
895 | abort: unknown command unknowncommand | |
894 | killed! |
|
896 | killed! | |
895 | $ rm .hg/server.log |
|
897 | $ rm .hg/server.log | |
896 |
|
898 | |||
897 | if server crashed before hello, traceback will be sent to 'e' channel as |
|
899 | if server crashed before hello, traceback will be sent to 'e' channel as | |
898 | last ditch: |
|
900 | last ditch: | |
899 |
|
901 | |||
900 | $ cat <<'EOF' > ../earlycrasher.py |
|
902 | $ cat <<'EOF' > ../earlycrasher.py | |
901 | > from mercurial import commandserver, extensions |
|
903 | > from mercurial import commandserver, extensions | |
902 | > def _serverequest(orig, ui, repo, conn, createcmdserver, prereposetups): |
|
904 | > def _serverequest(orig, ui, repo, conn, createcmdserver, prereposetups): | |
903 | > def createcmdserver(*args, **kwargs): |
|
905 | > def createcmdserver(*args, **kwargs): | |
904 | > raise Exception('crash') |
|
906 | > raise Exception('crash') | |
905 | > return orig(ui, repo, conn, createcmdserver, prereposetups) |
|
907 | > return orig(ui, repo, conn, createcmdserver, prereposetups) | |
906 | > def extsetup(ui): |
|
908 | > def extsetup(ui): | |
907 | > extensions.wrapfunction(commandserver, b'_serverequest', _serverequest) |
|
909 | > extensions.wrapfunction(commandserver, b'_serverequest', _serverequest) | |
908 | > EOF |
|
910 | > EOF | |
909 | $ cat <<EOF >> .hg/hgrc |
|
911 | $ cat <<EOF >> .hg/hgrc | |
910 | > [extensions] |
|
912 | > [extensions] | |
911 | > earlycrasher = ../earlycrasher.py |
|
913 | > earlycrasher = ../earlycrasher.py | |
912 | > EOF |
|
914 | > EOF | |
913 | >>> from hgclient import bprint, check, readchannel, unixserver |
|
915 | >>> from hgclient import bprint, check, readchannel, unixserver | |
914 | >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log') |
|
916 | >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log') | |
915 | >>> def earlycrash(conn): |
|
917 | >>> def earlycrash(conn): | |
916 | ... while True: |
|
918 | ... while True: | |
917 | ... try: |
|
919 | ... try: | |
918 | ... ch, data = readchannel(conn) |
|
920 | ... ch, data = readchannel(conn) | |
919 | ... for l in data.splitlines(True): |
|
921 | ... for l in data.splitlines(True): | |
920 | ... if not l.startswith(b' '): |
|
922 | ... if not l.startswith(b' '): | |
921 | ... bprint(b'%c, %r' % (ch, l)) |
|
923 | ... bprint(b'%c, %r' % (ch, l)) | |
922 | ... except EOFError: |
|
924 | ... except EOFError: | |
923 | ... break |
|
925 | ... break | |
924 | >>> check(earlycrash, server.connect) |
|
926 | >>> check(earlycrash, server.connect) | |
925 | e, 'Traceback (most recent call last):\n' |
|
927 | e, 'Traceback (most recent call last):\n' | |
926 | e, 'Exception: crash\n' |
|
928 | e, 'Exception: crash\n' | |
927 | >>> server.shutdown() |
|
929 | >>> server.shutdown() | |
928 |
|
930 | |||
929 | $ cat .hg/server.log | grep -v '^ ' |
|
931 | $ cat .hg/server.log | grep -v '^ ' | |
930 | listening at .hg/server.sock |
|
932 | listening at .hg/server.sock | |
931 | Traceback (most recent call last): |
|
933 | Traceback (most recent call last): | |
932 | Exception: crash |
|
934 | Exception: crash | |
933 | killed! |
|
935 | killed! | |
934 | #endif |
|
936 | #endif | |
935 | #if no-unix-socket |
|
937 | #if no-unix-socket | |
936 |
|
938 | |||
937 | $ hg serve --cmdserver unix -a .hg/server.sock |
|
939 | $ hg serve --cmdserver unix -a .hg/server.sock | |
938 | abort: unsupported platform |
|
940 | abort: unsupported platform | |
939 | [255] |
|
941 | [255] | |
940 |
|
942 | |||
941 | #endif |
|
943 | #endif | |
942 |
|
944 | |||
943 | $ cd .. |
|
945 | $ cd .. | |
944 |
|
946 | |||
945 | Test that accessing to invalid changelog cache is avoided at |
|
947 | Test that accessing to invalid changelog cache is avoided at | |
946 | subsequent operations even if repo object is reused even after failure |
|
948 | subsequent operations even if repo object is reused even after failure | |
947 | of transaction (see 0a7610758c42 also) |
|
949 | of transaction (see 0a7610758c42 also) | |
948 |
|
950 | |||
949 | "hg log" after failure of transaction is needed to detect invalid |
|
951 | "hg log" after failure of transaction is needed to detect invalid | |
950 | cache in repoview: this can't detect by "hg verify" only. |
|
952 | cache in repoview: this can't detect by "hg verify" only. | |
951 |
|
953 | |||
952 | Combination of "finalization" and "empty-ness of changelog" (2 x 2 = |
|
954 | Combination of "finalization" and "empty-ness of changelog" (2 x 2 = | |
953 | 4) are tested, because '00changelog.i' are differently changed in each |
|
955 | 4) are tested, because '00changelog.i' are differently changed in each | |
954 | cases. |
|
956 | cases. | |
955 |
|
957 | |||
956 | $ cat > $TESTTMP/failafterfinalize.py <<EOF |
|
958 | $ cat > $TESTTMP/failafterfinalize.py <<EOF | |
957 | > # extension to abort transaction after finalization forcibly |
|
959 | > # extension to abort transaction after finalization forcibly | |
958 | > from mercurial import commands, error, extensions, lock as lockmod |
|
960 | > from mercurial import commands, error, extensions, lock as lockmod | |
959 | > from mercurial import registrar |
|
961 | > from mercurial import registrar | |
960 | > cmdtable = {} |
|
962 | > cmdtable = {} | |
961 | > command = registrar.command(cmdtable) |
|
963 | > command = registrar.command(cmdtable) | |
962 | > configtable = {} |
|
964 | > configtable = {} | |
963 | > configitem = registrar.configitem(configtable) |
|
965 | > configitem = registrar.configitem(configtable) | |
964 | > configitem(b'failafterfinalize', b'fail', |
|
966 | > configitem(b'failafterfinalize', b'fail', | |
965 | > default=None, |
|
967 | > default=None, | |
966 | > ) |
|
968 | > ) | |
967 | > def fail(tr): |
|
969 | > def fail(tr): | |
968 | > raise error.Abort(b'fail after finalization') |
|
970 | > raise error.Abort(b'fail after finalization') | |
969 | > def reposetup(ui, repo): |
|
971 | > def reposetup(ui, repo): | |
970 | > class failrepo(repo.__class__): |
|
972 | > class failrepo(repo.__class__): | |
971 | > def commitctx(self, ctx, error=False, origctx=None): |
|
973 | > def commitctx(self, ctx, error=False, origctx=None): | |
972 | > if self.ui.configbool(b'failafterfinalize', b'fail'): |
|
974 | > if self.ui.configbool(b'failafterfinalize', b'fail'): | |
973 | > # 'sorted()' by ASCII code on category names causes |
|
975 | > # 'sorted()' by ASCII code on category names causes | |
974 | > # invoking 'fail' after finalization of changelog |
|
976 | > # invoking 'fail' after finalization of changelog | |
975 | > # using "'cl-%i' % id(self)" as category name |
|
977 | > # using "'cl-%i' % id(self)" as category name | |
976 | > self.currenttransaction().addfinalize(b'zzzzzzzz', fail) |
|
978 | > self.currenttransaction().addfinalize(b'zzzzzzzz', fail) | |
977 | > return super(failrepo, self).commitctx(ctx, error, origctx) |
|
979 | > return super(failrepo, self).commitctx(ctx, error, origctx) | |
978 | > repo.__class__ = failrepo |
|
980 | > repo.__class__ = failrepo | |
979 | > EOF |
|
981 | > EOF | |
980 |
|
982 | |||
981 | $ hg init repo3 |
|
983 | $ hg init repo3 | |
982 | $ cd repo3 |
|
984 | $ cd repo3 | |
983 |
|
985 | |||
984 | $ cat <<EOF >> $HGRCPATH |
|
986 | $ cat <<EOF >> $HGRCPATH | |
985 | > [command-templates] |
|
987 | > [command-templates] | |
986 | > log = {rev} {desc|firstline} ({files})\n |
|
988 | > log = {rev} {desc|firstline} ({files})\n | |
987 | > |
|
989 | > | |
988 | > [extensions] |
|
990 | > [extensions] | |
989 | > failafterfinalize = $TESTTMP/failafterfinalize.py |
|
991 | > failafterfinalize = $TESTTMP/failafterfinalize.py | |
990 | > EOF |
|
992 | > EOF | |
991 |
|
993 | |||
992 | - test failure with "empty changelog" |
|
994 | - test failure with "empty changelog" | |
993 |
|
995 | |||
994 | $ echo foo > foo |
|
996 | $ echo foo > foo | |
995 | $ hg add foo |
|
997 | $ hg add foo | |
996 |
|
998 | |||
997 | (failure before finalization) |
|
999 | (failure before finalization) | |
998 |
|
1000 | |||
999 | >>> from hgclient import check, readchannel, runcommand |
|
1001 | >>> from hgclient import check, readchannel, runcommand | |
1000 | >>> @check |
|
1002 | >>> @check | |
1001 | ... def abort(server): |
|
1003 | ... def abort(server): | |
1002 | ... readchannel(server) |
|
1004 | ... readchannel(server) | |
1003 | ... runcommand(server, [b'commit', |
|
1005 | ... runcommand(server, [b'commit', | |
1004 | ... b'--config', b'hooks.pretxncommit=false', |
|
1006 | ... b'--config', b'hooks.pretxncommit=false', | |
1005 | ... b'-mfoo']) |
|
1007 | ... b'-mfoo']) | |
1006 | ... runcommand(server, [b'log']) |
|
1008 | ... runcommand(server, [b'log']) | |
1007 | ... runcommand(server, [b'verify', b'-q']) |
|
1009 | ... runcommand(server, [b'verify', b'-q']) | |
1008 | *** runcommand commit --config hooks.pretxncommit=false -mfoo |
|
1010 | *** runcommand commit --config hooks.pretxncommit=false -mfoo | |
1009 | transaction abort! |
|
1011 | transaction abort! | |
1010 | rollback completed |
|
1012 | rollback completed | |
1011 | abort: pretxncommit hook exited with status 1 |
|
1013 | abort: pretxncommit hook exited with status 1 | |
1012 | [255] |
|
1014 | [255] | |
1013 | *** runcommand log |
|
1015 | *** runcommand log | |
1014 | *** runcommand verify -q |
|
1016 | *** runcommand verify -q | |
1015 |
|
1017 | |||
1016 | (failure after finalization) |
|
1018 | (failure after finalization) | |
1017 |
|
1019 | |||
1018 | >>> from hgclient import check, readchannel, runcommand |
|
1020 | >>> from hgclient import check, readchannel, runcommand | |
1019 | >>> @check |
|
1021 | >>> @check | |
1020 | ... def abort(server): |
|
1022 | ... def abort(server): | |
1021 | ... readchannel(server) |
|
1023 | ... readchannel(server) | |
1022 | ... runcommand(server, [b'commit', |
|
1024 | ... runcommand(server, [b'commit', | |
1023 | ... b'--config', b'failafterfinalize.fail=true', |
|
1025 | ... b'--config', b'failafterfinalize.fail=true', | |
1024 | ... b'-mfoo']) |
|
1026 | ... b'-mfoo']) | |
1025 | ... runcommand(server, [b'log']) |
|
1027 | ... runcommand(server, [b'log']) | |
1026 | ... runcommand(server, [b'verify', b'-q']) |
|
1028 | ... runcommand(server, [b'verify', b'-q']) | |
1027 | *** runcommand commit --config failafterfinalize.fail=true -mfoo |
|
1029 | *** runcommand commit --config failafterfinalize.fail=true -mfoo | |
1028 | transaction abort! |
|
1030 | transaction abort! | |
1029 | rollback completed |
|
1031 | rollback completed | |
1030 | abort: fail after finalization |
|
1032 | abort: fail after finalization | |
1031 | [255] |
|
1033 | [255] | |
1032 | *** runcommand log |
|
1034 | *** runcommand log | |
1033 | *** runcommand verify -q |
|
1035 | *** runcommand verify -q | |
1034 |
|
1036 | |||
1035 | - test failure with "not-empty changelog" |
|
1037 | - test failure with "not-empty changelog" | |
1036 |
|
1038 | |||
1037 | $ echo bar > bar |
|
1039 | $ echo bar > bar | |
1038 | $ hg add bar |
|
1040 | $ hg add bar | |
1039 | $ hg commit -mbar bar |
|
1041 | $ hg commit -mbar bar | |
1040 |
|
1042 | |||
1041 | (failure before finalization) |
|
1043 | (failure before finalization) | |
1042 |
|
1044 | |||
1043 | >>> from hgclient import check, readchannel, runcommand |
|
1045 | >>> from hgclient import check, readchannel, runcommand | |
1044 | >>> @check |
|
1046 | >>> @check | |
1045 | ... def abort(server): |
|
1047 | ... def abort(server): | |
1046 | ... readchannel(server) |
|
1048 | ... readchannel(server) | |
1047 | ... runcommand(server, [b'commit', |
|
1049 | ... runcommand(server, [b'commit', | |
1048 | ... b'--config', b'hooks.pretxncommit=false', |
|
1050 | ... b'--config', b'hooks.pretxncommit=false', | |
1049 | ... b'-mfoo', b'foo']) |
|
1051 | ... b'-mfoo', b'foo']) | |
1050 | ... runcommand(server, [b'log']) |
|
1052 | ... runcommand(server, [b'log']) | |
1051 | ... runcommand(server, [b'verify', b'-q']) |
|
1053 | ... runcommand(server, [b'verify', b'-q']) | |
1052 | *** runcommand commit --config hooks.pretxncommit=false -mfoo foo |
|
1054 | *** runcommand commit --config hooks.pretxncommit=false -mfoo foo | |
1053 | transaction abort! |
|
1055 | transaction abort! | |
1054 | rollback completed |
|
1056 | rollback completed | |
1055 | abort: pretxncommit hook exited with status 1 |
|
1057 | abort: pretxncommit hook exited with status 1 | |
1056 | [255] |
|
1058 | [255] | |
1057 | *** runcommand log |
|
1059 | *** runcommand log | |
1058 | 0 bar (bar) |
|
1060 | 0 bar (bar) | |
1059 | *** runcommand verify -q |
|
1061 | *** runcommand verify -q | |
1060 |
|
1062 | |||
1061 | (failure after finalization) |
|
1063 | (failure after finalization) | |
1062 |
|
1064 | |||
1063 | >>> from hgclient import check, readchannel, runcommand |
|
1065 | >>> from hgclient import check, readchannel, runcommand | |
1064 | >>> @check |
|
1066 | >>> @check | |
1065 | ... def abort(server): |
|
1067 | ... def abort(server): | |
1066 | ... readchannel(server) |
|
1068 | ... readchannel(server) | |
1067 | ... runcommand(server, [b'commit', |
|
1069 | ... runcommand(server, [b'commit', | |
1068 | ... b'--config', b'failafterfinalize.fail=true', |
|
1070 | ... b'--config', b'failafterfinalize.fail=true', | |
1069 | ... b'-mfoo', b'foo']) |
|
1071 | ... b'-mfoo', b'foo']) | |
1070 | ... runcommand(server, [b'log']) |
|
1072 | ... runcommand(server, [b'log']) | |
1071 | ... runcommand(server, [b'verify', b'-q']) |
|
1073 | ... runcommand(server, [b'verify', b'-q']) | |
1072 | *** runcommand commit --config failafterfinalize.fail=true -mfoo foo |
|
1074 | *** runcommand commit --config failafterfinalize.fail=true -mfoo foo | |
1073 | transaction abort! |
|
1075 | transaction abort! | |
1074 | rollback completed |
|
1076 | rollback completed | |
1075 | abort: fail after finalization |
|
1077 | abort: fail after finalization | |
1076 | [255] |
|
1078 | [255] | |
1077 | *** runcommand log |
|
1079 | *** runcommand log | |
1078 | 0 bar (bar) |
|
1080 | 0 bar (bar) | |
1079 | *** runcommand verify -q |
|
1081 | *** runcommand verify -q | |
1080 |
|
1082 | |||
1081 | $ cd .. |
|
1083 | $ cd .. | |
1082 |
|
1084 | |||
1083 | Test symlink traversal over cached audited paths: |
|
1085 | Test symlink traversal over cached audited paths: | |
1084 | ------------------------------------------------- |
|
1086 | ------------------------------------------------- | |
1085 |
|
1087 | |||
1086 | #if symlink |
|
1088 | #if symlink | |
1087 |
|
1089 | |||
1088 | set up symlink hell |
|
1090 | set up symlink hell | |
1089 |
|
1091 | |||
1090 | $ mkdir merge-symlink-out |
|
1092 | $ mkdir merge-symlink-out | |
1091 | $ hg init merge-symlink |
|
1093 | $ hg init merge-symlink | |
1092 | $ cd merge-symlink |
|
1094 | $ cd merge-symlink | |
1093 | $ touch base |
|
1095 | $ touch base | |
1094 | $ hg commit -qAm base |
|
1096 | $ hg commit -qAm base | |
1095 | $ ln -s ../merge-symlink-out a |
|
1097 | $ ln -s ../merge-symlink-out a | |
1096 | $ hg commit -qAm 'symlink a -> ../merge-symlink-out' |
|
1098 | $ hg commit -qAm 'symlink a -> ../merge-symlink-out' | |
1097 | $ hg up -q 0 |
|
1099 | $ hg up -q 0 | |
1098 | $ mkdir a |
|
1100 | $ mkdir a | |
1099 | $ touch a/poisoned |
|
1101 | $ touch a/poisoned | |
1100 | $ hg commit -qAm 'file a/poisoned' |
|
1102 | $ hg commit -qAm 'file a/poisoned' | |
1101 | $ hg log -G -T '{rev}: {desc}\n' |
|
1103 | $ hg log -G -T '{rev}: {desc}\n' | |
1102 | @ 2: file a/poisoned |
|
1104 | @ 2: file a/poisoned | |
1103 | | |
|
1105 | | | |
1104 | | o 1: symlink a -> ../merge-symlink-out |
|
1106 | | o 1: symlink a -> ../merge-symlink-out | |
1105 | |/ |
|
1107 | |/ | |
1106 | o 0: base |
|
1108 | o 0: base | |
1107 |
|
1109 | |||
1108 |
|
1110 | |||
1109 | try trivial merge after update: cache of audited paths should be discarded, |
|
1111 | try trivial merge after update: cache of audited paths should be discarded, | |
1110 | and the merge should fail (issue5628) |
|
1112 | and the merge should fail (issue5628) | |
1111 |
|
1113 | |||
1112 | $ hg up -q null |
|
1114 | $ hg up -q null | |
1113 | >>> from hgclient import check, readchannel, runcommand |
|
1115 | >>> from hgclient import check, readchannel, runcommand | |
1114 | >>> @check |
|
1116 | >>> @check | |
1115 | ... def merge(server): |
|
1117 | ... def merge(server): | |
1116 | ... readchannel(server) |
|
1118 | ... readchannel(server) | |
1117 | ... # audit a/poisoned as a good path |
|
1119 | ... # audit a/poisoned as a good path | |
1118 | ... runcommand(server, [b'up', b'-qC', b'2']) |
|
1120 | ... runcommand(server, [b'up', b'-qC', b'2']) | |
1119 | ... runcommand(server, [b'up', b'-qC', b'1']) |
|
1121 | ... runcommand(server, [b'up', b'-qC', b'1']) | |
1120 | ... # here a is a symlink, so a/poisoned is bad |
|
1122 | ... # here a is a symlink, so a/poisoned is bad | |
1121 | ... runcommand(server, [b'merge', b'2']) |
|
1123 | ... runcommand(server, [b'merge', b'2']) | |
1122 | *** runcommand up -qC 2 |
|
1124 | *** runcommand up -qC 2 | |
1123 | *** runcommand up -qC 1 |
|
1125 | *** runcommand up -qC 1 | |
1124 | *** runcommand merge 2 |
|
1126 | *** runcommand merge 2 | |
1125 | abort: path 'a/poisoned' traverses symbolic link 'a' |
|
1127 | abort: path 'a/poisoned' traverses symbolic link 'a' | |
1126 | [255] |
|
1128 | [255] | |
1127 | $ ls ../merge-symlink-out |
|
1129 | $ ls ../merge-symlink-out | |
1128 |
|
1130 | |||
1129 | cache of repo.auditor should be discarded, so matcher would never traverse |
|
1131 | cache of repo.auditor should be discarded, so matcher would never traverse | |
1130 | symlinks: |
|
1132 | symlinks: | |
1131 |
|
1133 | |||
1132 | $ hg up -qC 0 |
|
1134 | $ hg up -qC 0 | |
1133 | $ touch ../merge-symlink-out/poisoned |
|
1135 | $ touch ../merge-symlink-out/poisoned | |
1134 | >>> from hgclient import check, readchannel, runcommand |
|
1136 | >>> from hgclient import check, readchannel, runcommand | |
1135 | >>> @check |
|
1137 | >>> @check | |
1136 | ... def files(server): |
|
1138 | ... def files(server): | |
1137 | ... readchannel(server) |
|
1139 | ... readchannel(server) | |
1138 | ... runcommand(server, [b'up', b'-qC', b'2']) |
|
1140 | ... runcommand(server, [b'up', b'-qC', b'2']) | |
1139 | ... # audit a/poisoned as a good path |
|
1141 | ... # audit a/poisoned as a good path | |
1140 | ... runcommand(server, [b'files', b'a/poisoned']) |
|
1142 | ... runcommand(server, [b'files', b'a/poisoned']) | |
1141 | ... runcommand(server, [b'up', b'-qC', b'0']) |
|
1143 | ... runcommand(server, [b'up', b'-qC', b'0']) | |
1142 | ... runcommand(server, [b'up', b'-qC', b'1']) |
|
1144 | ... runcommand(server, [b'up', b'-qC', b'1']) | |
1143 | ... # here 'a' is a symlink, so a/poisoned should be warned |
|
1145 | ... # here 'a' is a symlink, so a/poisoned should be warned | |
1144 | ... runcommand(server, [b'files', b'a/poisoned']) |
|
1146 | ... runcommand(server, [b'files', b'a/poisoned']) | |
1145 | *** runcommand up -qC 2 |
|
1147 | *** runcommand up -qC 2 | |
1146 | *** runcommand files a/poisoned |
|
1148 | *** runcommand files a/poisoned | |
1147 | a/poisoned |
|
1149 | a/poisoned | |
1148 | *** runcommand up -qC 0 |
|
1150 | *** runcommand up -qC 0 | |
1149 | *** runcommand up -qC 1 |
|
1151 | *** runcommand up -qC 1 | |
1150 | *** runcommand files a/poisoned |
|
1152 | *** runcommand files a/poisoned | |
1151 | abort: path 'a/poisoned' traverses symbolic link 'a' |
|
1153 | abort: path 'a/poisoned' traverses symbolic link 'a' | |
1152 | [255] |
|
1154 | [255] | |
1153 |
|
1155 | |||
1154 | $ cd .. |
|
1156 | $ cd .. | |
1155 |
|
1157 | |||
1156 | #endif |
|
1158 | #endif |
@@ -1,726 +1,726 b'' | |||||
1 | Test for the heuristic copytracing algorithm |
|
1 | Test for the heuristic copytracing algorithm | |
2 | ============================================ |
|
2 | ============================================ | |
3 |
|
3 | |||
4 | $ cat >> $TESTTMP/copytrace.sh << '__EOF__' |
|
4 | $ cat >> $TESTTMP/copytrace.sh << '__EOF__' | |
5 | > initclient() { |
|
5 | > initclient() { | |
6 | > cat >> $1/.hg/hgrc <<EOF |
|
6 | > cat >> $1/.hg/hgrc <<EOF | |
7 | > [experimental] |
|
7 | > [experimental] | |
8 | > copytrace = heuristics |
|
8 | > copytrace = heuristics | |
9 | > copytrace.sourcecommitlimit = -1 |
|
9 | > copytrace.sourcecommitlimit = -1 | |
10 | > EOF |
|
10 | > EOF | |
11 | > } |
|
11 | > } | |
12 | > __EOF__ |
|
12 | > __EOF__ | |
13 | $ . "$TESTTMP/copytrace.sh" |
|
13 | $ . "$TESTTMP/copytrace.sh" | |
14 |
|
14 | |||
15 | $ cat >> $HGRCPATH << EOF |
|
15 | $ cat >> $HGRCPATH << EOF | |
16 | > [extensions] |
|
16 | > [extensions] | |
17 | > rebase= |
|
17 | > rebase= | |
18 | > [alias] |
|
18 | > [alias] | |
19 | > l = log -G -T 'rev: {rev}\ndesc: {desc}\n' |
|
19 | > l = log -G -T 'rev: {rev}\ndesc: {desc}\n' | |
20 | > pl = log -G -T 'rev: {rev}, phase: {phase}\ndesc: {desc}\n' |
|
20 | > pl = log -G -T 'rev: {rev}, phase: {phase}\ndesc: {desc}\n' | |
21 | > EOF |
|
21 | > EOF | |
22 |
|
22 | |||
23 | NOTE: calling initclient() set copytrace.sourcecommitlimit=-1 as we want to |
|
23 | NOTE: calling initclient() set copytrace.sourcecommitlimit=-1 as we want to | |
24 | prevent the full copytrace algorithm to run and test the heuristic algorithm |
|
24 | prevent the full copytrace algorithm to run and test the heuristic algorithm | |
25 | without complexing the test cases with public and draft commits. |
|
25 | without complexing the test cases with public and draft commits. | |
26 |
|
26 | |||
27 | Check filename heuristics (same dirname and same basename) |
|
27 | Check filename heuristics (same dirname and same basename) | |
28 | ---------------------------------------------------------- |
|
28 | ---------------------------------------------------------- | |
29 |
|
29 | |||
30 | $ hg init repo |
|
30 | $ hg init repo | |
31 | $ initclient repo |
|
31 | $ initclient repo | |
32 | $ cd repo |
|
32 | $ cd repo | |
33 | $ echo a > a |
|
33 | $ echo a > a | |
34 | $ mkdir dir |
|
34 | $ mkdir dir | |
35 | $ echo a > dir/file.txt |
|
35 | $ echo a > dir/file.txt | |
36 | $ hg addremove |
|
36 | $ hg addremove | |
37 | adding a |
|
37 | adding a | |
38 | adding dir/file.txt |
|
38 | adding dir/file.txt | |
39 | $ hg ci -m initial |
|
39 | $ hg ci -m initial | |
40 | $ hg mv a b |
|
40 | $ hg mv a b | |
41 | $ hg mv -q dir dir2 |
|
41 | $ hg mv -q dir dir2 | |
42 | $ hg ci -m 'mv a b, mv dir/ dir2/' |
|
42 | $ hg ci -m 'mv a b, mv dir/ dir2/' | |
43 | $ hg up -q 0 |
|
43 | $ hg up -q 0 | |
44 | $ echo b > a |
|
44 | $ echo b > a | |
45 | $ echo b > dir/file.txt |
|
45 | $ echo b > dir/file.txt | |
46 | $ hg ci -qm 'mod a, mod dir/file.txt' |
|
46 | $ hg ci -qm 'mod a, mod dir/file.txt' | |
47 |
|
47 | |||
48 | $ hg l |
|
48 | $ hg l | |
49 | @ rev: 2 |
|
49 | @ rev: 2 | |
50 | | desc: mod a, mod dir/file.txt |
|
50 | | desc: mod a, mod dir/file.txt | |
51 | | o rev: 1 |
|
51 | | o rev: 1 | |
52 | |/ desc: mv a b, mv dir/ dir2/ |
|
52 | |/ desc: mv a b, mv dir/ dir2/ | |
53 | o rev: 0 |
|
53 | o rev: 0 | |
54 | desc: initial |
|
54 | desc: initial | |
55 |
|
55 | |||
56 | $ hg rebase -s . -d 1 |
|
56 | $ hg rebase -s . -d 1 | |
57 | rebasing 2:557f403c0afd tip "mod a, mod dir/file.txt" |
|
57 | rebasing 2:557f403c0afd tip "mod a, mod dir/file.txt" | |
58 | merging b and a to b |
|
58 | merging b and a to b | |
59 | merging dir2/file.txt and dir/file.txt to dir2/file.txt |
|
59 | merging dir2/file.txt and dir/file.txt to dir2/file.txt | |
60 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/557f403c0afd-9926eeff-rebase.hg |
|
60 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/557f403c0afd-9926eeff-rebase.hg | |
61 | $ cd .. |
|
61 | $ cd .. | |
62 | $ rm -rf repo |
|
62 | $ rm -rf repo | |
63 |
|
63 | |||
64 | Make sure filename heuristics do not when they are not related |
|
64 | Make sure filename heuristics do not when they are not related | |
65 | -------------------------------------------------------------- |
|
65 | -------------------------------------------------------------- | |
66 |
|
66 | |||
67 | $ hg init repo |
|
67 | $ hg init repo | |
68 | $ initclient repo |
|
68 | $ initclient repo | |
69 | $ cd repo |
|
69 | $ cd repo | |
70 | $ echo 'somecontent' > a |
|
70 | $ echo 'somecontent' > a | |
71 | $ hg add a |
|
71 | $ hg add a | |
72 | $ hg ci -m initial |
|
72 | $ hg ci -m initial | |
73 | $ hg rm a |
|
73 | $ hg rm a | |
74 | $ echo 'completelydifferentcontext' > b |
|
74 | $ echo 'completelydifferentcontext' > b | |
75 | $ hg add b |
|
75 | $ hg add b | |
76 | $ hg ci -m 'rm a, add b' |
|
76 | $ hg ci -m 'rm a, add b' | |
77 | $ hg up -q 0 |
|
77 | $ hg up -q 0 | |
78 | $ printf 'somecontent\nmoarcontent' > a |
|
78 | $ printf 'somecontent\nmoarcontent' > a | |
79 | $ hg ci -qm 'mode a' |
|
79 | $ hg ci -qm 'mode a' | |
80 |
|
80 | |||
81 | $ hg l |
|
81 | $ hg l | |
82 | @ rev: 2 |
|
82 | @ rev: 2 | |
83 | | desc: mode a |
|
83 | | desc: mode a | |
84 | | o rev: 1 |
|
84 | | o rev: 1 | |
85 | |/ desc: rm a, add b |
|
85 | |/ desc: rm a, add b | |
86 | o rev: 0 |
|
86 | o rev: 0 | |
87 | desc: initial |
|
87 | desc: initial | |
88 |
|
88 | |||
89 | $ hg rebase -s . -d 1 |
|
89 | $ hg rebase -s . -d 1 | |
90 | rebasing 2:d526312210b9 tip "mode a" |
|
90 | rebasing 2:d526312210b9 tip "mode a" | |
91 | file 'a' was deleted in local [dest] but was modified in other [source]. |
|
91 | file 'a' was deleted in local [dest] but was modified in other [source]. | |
92 | You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. |
|
92 | You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. | |
93 | What do you want to do? u |
|
93 | What do you want to do? u | |
94 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') |
|
94 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') | |
95 |
[ |
|
95 | [240] | |
96 |
|
96 | |||
97 | $ cd .. |
|
97 | $ cd .. | |
98 | $ rm -rf repo |
|
98 | $ rm -rf repo | |
99 |
|
99 | |||
100 | Test when lca didn't modified the file that was moved |
|
100 | Test when lca didn't modified the file that was moved | |
101 | ----------------------------------------------------- |
|
101 | ----------------------------------------------------- | |
102 |
|
102 | |||
103 | $ hg init repo |
|
103 | $ hg init repo | |
104 | $ initclient repo |
|
104 | $ initclient repo | |
105 | $ cd repo |
|
105 | $ cd repo | |
106 | $ echo 'somecontent' > a |
|
106 | $ echo 'somecontent' > a | |
107 | $ hg add a |
|
107 | $ hg add a | |
108 | $ hg ci -m initial |
|
108 | $ hg ci -m initial | |
109 | $ echo c > c |
|
109 | $ echo c > c | |
110 | $ hg add c |
|
110 | $ hg add c | |
111 | $ hg ci -m randomcommit |
|
111 | $ hg ci -m randomcommit | |
112 | $ hg mv a b |
|
112 | $ hg mv a b | |
113 | $ hg ci -m 'mv a b' |
|
113 | $ hg ci -m 'mv a b' | |
114 | $ hg up -q 1 |
|
114 | $ hg up -q 1 | |
115 | $ echo b > a |
|
115 | $ echo b > a | |
116 | $ hg ci -qm 'mod a' |
|
116 | $ hg ci -qm 'mod a' | |
117 |
|
117 | |||
118 | $ hg pl |
|
118 | $ hg pl | |
119 | @ rev: 3, phase: draft |
|
119 | @ rev: 3, phase: draft | |
120 | | desc: mod a |
|
120 | | desc: mod a | |
121 | | o rev: 2, phase: draft |
|
121 | | o rev: 2, phase: draft | |
122 | |/ desc: mv a b |
|
122 | |/ desc: mv a b | |
123 | o rev: 1, phase: draft |
|
123 | o rev: 1, phase: draft | |
124 | | desc: randomcommit |
|
124 | | desc: randomcommit | |
125 | o rev: 0, phase: draft |
|
125 | o rev: 0, phase: draft | |
126 | desc: initial |
|
126 | desc: initial | |
127 |
|
127 | |||
128 | $ hg rebase -s . -d 2 |
|
128 | $ hg rebase -s . -d 2 | |
129 | rebasing 3:9d5cf99c3d9f tip "mod a" |
|
129 | rebasing 3:9d5cf99c3d9f tip "mod a" | |
130 | merging b and a to b |
|
130 | merging b and a to b | |
131 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9d5cf99c3d9f-f02358cc-rebase.hg |
|
131 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9d5cf99c3d9f-f02358cc-rebase.hg | |
132 | $ cd .. |
|
132 | $ cd .. | |
133 | $ rm -rf repo |
|
133 | $ rm -rf repo | |
134 |
|
134 | |||
135 | Rebase "backwards" |
|
135 | Rebase "backwards" | |
136 | ------------------ |
|
136 | ------------------ | |
137 |
|
137 | |||
138 | $ hg init repo |
|
138 | $ hg init repo | |
139 | $ initclient repo |
|
139 | $ initclient repo | |
140 | $ cd repo |
|
140 | $ cd repo | |
141 | $ echo 'somecontent' > a |
|
141 | $ echo 'somecontent' > a | |
142 | $ hg add a |
|
142 | $ hg add a | |
143 | $ hg ci -m initial |
|
143 | $ hg ci -m initial | |
144 | $ echo c > c |
|
144 | $ echo c > c | |
145 | $ hg add c |
|
145 | $ hg add c | |
146 | $ hg ci -m randomcommit |
|
146 | $ hg ci -m randomcommit | |
147 | $ hg mv a b |
|
147 | $ hg mv a b | |
148 | $ hg ci -m 'mv a b' |
|
148 | $ hg ci -m 'mv a b' | |
149 | $ hg up -q 2 |
|
149 | $ hg up -q 2 | |
150 | $ echo b > b |
|
150 | $ echo b > b | |
151 | $ hg ci -qm 'mod b' |
|
151 | $ hg ci -qm 'mod b' | |
152 |
|
152 | |||
153 | $ hg l |
|
153 | $ hg l | |
154 | @ rev: 3 |
|
154 | @ rev: 3 | |
155 | | desc: mod b |
|
155 | | desc: mod b | |
156 | o rev: 2 |
|
156 | o rev: 2 | |
157 | | desc: mv a b |
|
157 | | desc: mv a b | |
158 | o rev: 1 |
|
158 | o rev: 1 | |
159 | | desc: randomcommit |
|
159 | | desc: randomcommit | |
160 | o rev: 0 |
|
160 | o rev: 0 | |
161 | desc: initial |
|
161 | desc: initial | |
162 |
|
162 | |||
163 | $ hg rebase -s . -d 0 |
|
163 | $ hg rebase -s . -d 0 | |
164 | rebasing 3:fbe97126b396 tip "mod b" |
|
164 | rebasing 3:fbe97126b396 tip "mod b" | |
165 | merging a and b to a |
|
165 | merging a and b to a | |
166 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/fbe97126b396-cf5452a1-rebase.hg |
|
166 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/fbe97126b396-cf5452a1-rebase.hg | |
167 | $ cd .. |
|
167 | $ cd .. | |
168 | $ rm -rf repo |
|
168 | $ rm -rf repo | |
169 |
|
169 | |||
170 | Check a few potential move candidates |
|
170 | Check a few potential move candidates | |
171 | ------------------------------------- |
|
171 | ------------------------------------- | |
172 |
|
172 | |||
173 | $ hg init repo |
|
173 | $ hg init repo | |
174 | $ initclient repo |
|
174 | $ initclient repo | |
175 | $ cd repo |
|
175 | $ cd repo | |
176 | $ mkdir dir |
|
176 | $ mkdir dir | |
177 | $ echo a > dir/a |
|
177 | $ echo a > dir/a | |
178 | $ hg add dir/a |
|
178 | $ hg add dir/a | |
179 | $ hg ci -qm initial |
|
179 | $ hg ci -qm initial | |
180 | $ hg mv dir/a dir/b |
|
180 | $ hg mv dir/a dir/b | |
181 | $ hg ci -qm 'mv dir/a dir/b' |
|
181 | $ hg ci -qm 'mv dir/a dir/b' | |
182 | $ mkdir dir2 |
|
182 | $ mkdir dir2 | |
183 | $ echo b > dir2/a |
|
183 | $ echo b > dir2/a | |
184 | $ hg add dir2/a |
|
184 | $ hg add dir2/a | |
185 | $ hg ci -qm 'create dir2/a' |
|
185 | $ hg ci -qm 'create dir2/a' | |
186 | $ hg up -q 0 |
|
186 | $ hg up -q 0 | |
187 | $ echo b > dir/a |
|
187 | $ echo b > dir/a | |
188 | $ hg ci -qm 'mod dir/a' |
|
188 | $ hg ci -qm 'mod dir/a' | |
189 |
|
189 | |||
190 | $ hg l |
|
190 | $ hg l | |
191 | @ rev: 3 |
|
191 | @ rev: 3 | |
192 | | desc: mod dir/a |
|
192 | | desc: mod dir/a | |
193 | | o rev: 2 |
|
193 | | o rev: 2 | |
194 | | | desc: create dir2/a |
|
194 | | | desc: create dir2/a | |
195 | | o rev: 1 |
|
195 | | o rev: 1 | |
196 | |/ desc: mv dir/a dir/b |
|
196 | |/ desc: mv dir/a dir/b | |
197 | o rev: 0 |
|
197 | o rev: 0 | |
198 | desc: initial |
|
198 | desc: initial | |
199 |
|
199 | |||
200 | $ hg rebase -s . -d 2 |
|
200 | $ hg rebase -s . -d 2 | |
201 | rebasing 3:6b2f4cece40f tip "mod dir/a" |
|
201 | rebasing 3:6b2f4cece40f tip "mod dir/a" | |
202 | merging dir/b and dir/a to dir/b |
|
202 | merging dir/b and dir/a to dir/b | |
203 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/6b2f4cece40f-503efe60-rebase.hg |
|
203 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/6b2f4cece40f-503efe60-rebase.hg | |
204 | $ cd .. |
|
204 | $ cd .. | |
205 | $ rm -rf repo |
|
205 | $ rm -rf repo | |
206 |
|
206 | |||
207 | Test the copytrace.movecandidateslimit with many move candidates |
|
207 | Test the copytrace.movecandidateslimit with many move candidates | |
208 | ---------------------------------------------------------------- |
|
208 | ---------------------------------------------------------------- | |
209 |
|
209 | |||
210 | $ hg init repo |
|
210 | $ hg init repo | |
211 | $ initclient repo |
|
211 | $ initclient repo | |
212 | $ cd repo |
|
212 | $ cd repo | |
213 | $ echo a > a |
|
213 | $ echo a > a | |
214 | $ hg add a |
|
214 | $ hg add a | |
215 | $ hg ci -m initial |
|
215 | $ hg ci -m initial | |
216 | $ hg mv a foo |
|
216 | $ hg mv a foo | |
217 | $ echo a > b |
|
217 | $ echo a > b | |
218 | $ echo a > c |
|
218 | $ echo a > c | |
219 | $ echo a > d |
|
219 | $ echo a > d | |
220 | $ echo a > e |
|
220 | $ echo a > e | |
221 | $ echo a > f |
|
221 | $ echo a > f | |
222 | $ echo a > g |
|
222 | $ echo a > g | |
223 | $ hg add b |
|
223 | $ hg add b | |
224 | $ hg add c |
|
224 | $ hg add c | |
225 | $ hg add d |
|
225 | $ hg add d | |
226 | $ hg add e |
|
226 | $ hg add e | |
227 | $ hg add f |
|
227 | $ hg add f | |
228 | $ hg add g |
|
228 | $ hg add g | |
229 | $ hg ci -m 'mv a foo, add many files' |
|
229 | $ hg ci -m 'mv a foo, add many files' | |
230 | $ hg up -q ".^" |
|
230 | $ hg up -q ".^" | |
231 | $ echo b > a |
|
231 | $ echo b > a | |
232 | $ hg ci -m 'mod a' |
|
232 | $ hg ci -m 'mod a' | |
233 | created new head |
|
233 | created new head | |
234 |
|
234 | |||
235 | $ hg l |
|
235 | $ hg l | |
236 | @ rev: 2 |
|
236 | @ rev: 2 | |
237 | | desc: mod a |
|
237 | | desc: mod a | |
238 | | o rev: 1 |
|
238 | | o rev: 1 | |
239 | |/ desc: mv a foo, add many files |
|
239 | |/ desc: mv a foo, add many files | |
240 | o rev: 0 |
|
240 | o rev: 0 | |
241 | desc: initial |
|
241 | desc: initial | |
242 |
|
242 | |||
243 | With small limit |
|
243 | With small limit | |
244 |
|
244 | |||
245 | $ hg rebase -s 2 -d 1 --config experimental.copytrace.movecandidateslimit=0 |
|
245 | $ hg rebase -s 2 -d 1 --config experimental.copytrace.movecandidateslimit=0 | |
246 | rebasing 2:ef716627c70b tip "mod a" |
|
246 | rebasing 2:ef716627c70b tip "mod a" | |
247 | skipping copytracing for 'a', more candidates than the limit: 7 |
|
247 | skipping copytracing for 'a', more candidates than the limit: 7 | |
248 | file 'a' was deleted in local [dest] but was modified in other [source]. |
|
248 | file 'a' was deleted in local [dest] but was modified in other [source]. | |
249 | You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. |
|
249 | You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. | |
250 | What do you want to do? u |
|
250 | What do you want to do? u | |
251 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') |
|
251 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') | |
252 |
[ |
|
252 | [240] | |
253 |
|
253 | |||
254 | $ hg rebase --abort |
|
254 | $ hg rebase --abort | |
255 | rebase aborted |
|
255 | rebase aborted | |
256 |
|
256 | |||
257 | With default limit which is 100 |
|
257 | With default limit which is 100 | |
258 |
|
258 | |||
259 | $ hg rebase -s 2 -d 1 |
|
259 | $ hg rebase -s 2 -d 1 | |
260 | rebasing 2:ef716627c70b tip "mod a" |
|
260 | rebasing 2:ef716627c70b tip "mod a" | |
261 | merging foo and a to foo |
|
261 | merging foo and a to foo | |
262 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg |
|
262 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg | |
263 |
|
263 | |||
264 | $ cd .. |
|
264 | $ cd .. | |
265 | $ rm -rf repo |
|
265 | $ rm -rf repo | |
266 |
|
266 | |||
267 | Move file in one branch and delete it in another |
|
267 | Move file in one branch and delete it in another | |
268 | ----------------------------------------------- |
|
268 | ----------------------------------------------- | |
269 |
|
269 | |||
270 | $ hg init repo |
|
270 | $ hg init repo | |
271 | $ initclient repo |
|
271 | $ initclient repo | |
272 | $ cd repo |
|
272 | $ cd repo | |
273 | $ echo a > a |
|
273 | $ echo a > a | |
274 | $ hg add a |
|
274 | $ hg add a | |
275 | $ hg ci -m initial |
|
275 | $ hg ci -m initial | |
276 | $ hg mv a b |
|
276 | $ hg mv a b | |
277 | $ hg ci -m 'mv a b' |
|
277 | $ hg ci -m 'mv a b' | |
278 | $ hg up -q ".^" |
|
278 | $ hg up -q ".^" | |
279 | $ hg rm a |
|
279 | $ hg rm a | |
280 | $ hg ci -m 'del a' |
|
280 | $ hg ci -m 'del a' | |
281 | created new head |
|
281 | created new head | |
282 |
|
282 | |||
283 | $ hg pl |
|
283 | $ hg pl | |
284 | @ rev: 2, phase: draft |
|
284 | @ rev: 2, phase: draft | |
285 | | desc: del a |
|
285 | | desc: del a | |
286 | | o rev: 1, phase: draft |
|
286 | | o rev: 1, phase: draft | |
287 | |/ desc: mv a b |
|
287 | |/ desc: mv a b | |
288 | o rev: 0, phase: draft |
|
288 | o rev: 0, phase: draft | |
289 | desc: initial |
|
289 | desc: initial | |
290 |
|
290 | |||
291 | $ hg rebase -s 1 -d 2 |
|
291 | $ hg rebase -s 1 -d 2 | |
292 | rebasing 1:472e38d57782 "mv a b" |
|
292 | rebasing 1:472e38d57782 "mv a b" | |
293 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-17d50e29-rebase.hg |
|
293 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-17d50e29-rebase.hg | |
294 | $ hg up -q c492ed3c7e35dcd1dc938053b8adf56e2cfbd062 |
|
294 | $ hg up -q c492ed3c7e35dcd1dc938053b8adf56e2cfbd062 | |
295 | $ ls -A |
|
295 | $ ls -A | |
296 | .hg |
|
296 | .hg | |
297 | b |
|
297 | b | |
298 | $ cd .. |
|
298 | $ cd .. | |
299 | $ rm -rf repo |
|
299 | $ rm -rf repo | |
300 |
|
300 | |||
301 | Move a directory in draft branch |
|
301 | Move a directory in draft branch | |
302 | -------------------------------- |
|
302 | -------------------------------- | |
303 |
|
303 | |||
304 | $ hg init repo |
|
304 | $ hg init repo | |
305 | $ initclient repo |
|
305 | $ initclient repo | |
306 | $ cd repo |
|
306 | $ cd repo | |
307 | $ mkdir dir |
|
307 | $ mkdir dir | |
308 | $ echo a > dir/a |
|
308 | $ echo a > dir/a | |
309 | $ hg add dir/a |
|
309 | $ hg add dir/a | |
310 | $ hg ci -qm initial |
|
310 | $ hg ci -qm initial | |
311 | $ echo b > dir/a |
|
311 | $ echo b > dir/a | |
312 | $ hg ci -qm 'mod dir/a' |
|
312 | $ hg ci -qm 'mod dir/a' | |
313 | $ hg up -q ".^" |
|
313 | $ hg up -q ".^" | |
314 | $ hg mv -q dir/ dir2 |
|
314 | $ hg mv -q dir/ dir2 | |
315 | $ hg ci -qm 'mv dir/ dir2/' |
|
315 | $ hg ci -qm 'mv dir/ dir2/' | |
316 |
|
316 | |||
317 | $ hg l |
|
317 | $ hg l | |
318 | @ rev: 2 |
|
318 | @ rev: 2 | |
319 | | desc: mv dir/ dir2/ |
|
319 | | desc: mv dir/ dir2/ | |
320 | | o rev: 1 |
|
320 | | o rev: 1 | |
321 | |/ desc: mod dir/a |
|
321 | |/ desc: mod dir/a | |
322 | o rev: 0 |
|
322 | o rev: 0 | |
323 | desc: initial |
|
323 | desc: initial | |
324 |
|
324 | |||
325 | $ hg rebase -s . -d 1 |
|
325 | $ hg rebase -s . -d 1 | |
326 | rebasing 2:a33d80b6e352 tip "mv dir/ dir2/" |
|
326 | rebasing 2:a33d80b6e352 tip "mv dir/ dir2/" | |
327 | merging dir/a and dir2/a to dir2/a |
|
327 | merging dir/a and dir2/a to dir2/a | |
328 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/a33d80b6e352-fecb9ada-rebase.hg |
|
328 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/a33d80b6e352-fecb9ada-rebase.hg | |
329 | $ cd .. |
|
329 | $ cd .. | |
330 | $ rm -rf server |
|
330 | $ rm -rf server | |
331 | $ rm -rf repo |
|
331 | $ rm -rf repo | |
332 |
|
332 | |||
333 | Move file twice and rebase mod on top of moves |
|
333 | Move file twice and rebase mod on top of moves | |
334 | ---------------------------------------------- |
|
334 | ---------------------------------------------- | |
335 |
|
335 | |||
336 | $ hg init repo |
|
336 | $ hg init repo | |
337 | $ initclient repo |
|
337 | $ initclient repo | |
338 | $ cd repo |
|
338 | $ cd repo | |
339 | $ echo a > a |
|
339 | $ echo a > a | |
340 | $ hg add a |
|
340 | $ hg add a | |
341 | $ hg ci -m initial |
|
341 | $ hg ci -m initial | |
342 | $ hg mv a b |
|
342 | $ hg mv a b | |
343 | $ hg ci -m 'mv a b' |
|
343 | $ hg ci -m 'mv a b' | |
344 | $ hg mv b c |
|
344 | $ hg mv b c | |
345 | $ hg ci -m 'mv b c' |
|
345 | $ hg ci -m 'mv b c' | |
346 | $ hg up -q 0 |
|
346 | $ hg up -q 0 | |
347 | $ echo c > a |
|
347 | $ echo c > a | |
348 | $ hg ci -m 'mod a' |
|
348 | $ hg ci -m 'mod a' | |
349 | created new head |
|
349 | created new head | |
350 |
|
350 | |||
351 | $ hg l |
|
351 | $ hg l | |
352 | @ rev: 3 |
|
352 | @ rev: 3 | |
353 | | desc: mod a |
|
353 | | desc: mod a | |
354 | | o rev: 2 |
|
354 | | o rev: 2 | |
355 | | | desc: mv b c |
|
355 | | | desc: mv b c | |
356 | | o rev: 1 |
|
356 | | o rev: 1 | |
357 | |/ desc: mv a b |
|
357 | |/ desc: mv a b | |
358 | o rev: 0 |
|
358 | o rev: 0 | |
359 | desc: initial |
|
359 | desc: initial | |
360 | $ hg rebase -s . -d 2 |
|
360 | $ hg rebase -s . -d 2 | |
361 | rebasing 3:d41316942216 tip "mod a" |
|
361 | rebasing 3:d41316942216 tip "mod a" | |
362 | merging c and a to c |
|
362 | merging c and a to c | |
363 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d41316942216-2b5949bc-rebase.hg |
|
363 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d41316942216-2b5949bc-rebase.hg | |
364 |
|
364 | |||
365 | $ cd .. |
|
365 | $ cd .. | |
366 | $ rm -rf repo |
|
366 | $ rm -rf repo | |
367 |
|
367 | |||
368 | Move file twice and rebase moves on top of mods |
|
368 | Move file twice and rebase moves on top of mods | |
369 | ----------------------------------------------- |
|
369 | ----------------------------------------------- | |
370 |
|
370 | |||
371 | $ hg init repo |
|
371 | $ hg init repo | |
372 | $ initclient repo |
|
372 | $ initclient repo | |
373 | $ cd repo |
|
373 | $ cd repo | |
374 | $ echo a > a |
|
374 | $ echo a > a | |
375 | $ hg add a |
|
375 | $ hg add a | |
376 | $ hg ci -m initial |
|
376 | $ hg ci -m initial | |
377 | $ hg mv a b |
|
377 | $ hg mv a b | |
378 | $ hg ci -m 'mv a b' |
|
378 | $ hg ci -m 'mv a b' | |
379 | $ hg mv b c |
|
379 | $ hg mv b c | |
380 | $ hg ci -m 'mv b c' |
|
380 | $ hg ci -m 'mv b c' | |
381 | $ hg up -q 0 |
|
381 | $ hg up -q 0 | |
382 | $ echo c > a |
|
382 | $ echo c > a | |
383 | $ hg ci -m 'mod a' |
|
383 | $ hg ci -m 'mod a' | |
384 | created new head |
|
384 | created new head | |
385 | $ hg l |
|
385 | $ hg l | |
386 | @ rev: 3 |
|
386 | @ rev: 3 | |
387 | | desc: mod a |
|
387 | | desc: mod a | |
388 | | o rev: 2 |
|
388 | | o rev: 2 | |
389 | | | desc: mv b c |
|
389 | | | desc: mv b c | |
390 | | o rev: 1 |
|
390 | | o rev: 1 | |
391 | |/ desc: mv a b |
|
391 | |/ desc: mv a b | |
392 | o rev: 0 |
|
392 | o rev: 0 | |
393 | desc: initial |
|
393 | desc: initial | |
394 | $ hg rebase -s 1 -d . |
|
394 | $ hg rebase -s 1 -d . | |
395 | rebasing 1:472e38d57782 "mv a b" |
|
395 | rebasing 1:472e38d57782 "mv a b" | |
396 | merging a and b to b |
|
396 | merging a and b to b | |
397 | rebasing 2:d3efd280421d "mv b c" |
|
397 | rebasing 2:d3efd280421d "mv b c" | |
398 | merging b and c to c |
|
398 | merging b and c to c | |
399 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-ab8d3c58-rebase.hg |
|
399 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-ab8d3c58-rebase.hg | |
400 |
|
400 | |||
401 | $ cd .. |
|
401 | $ cd .. | |
402 | $ rm -rf repo |
|
402 | $ rm -rf repo | |
403 |
|
403 | |||
404 | Move one file and add another file in the same folder in one branch, modify file in another branch |
|
404 | Move one file and add another file in the same folder in one branch, modify file in another branch | |
405 | -------------------------------------------------------------------------------------------------- |
|
405 | -------------------------------------------------------------------------------------------------- | |
406 |
|
406 | |||
407 | $ hg init repo |
|
407 | $ hg init repo | |
408 | $ initclient repo |
|
408 | $ initclient repo | |
409 | $ cd repo |
|
409 | $ cd repo | |
410 | $ echo a > a |
|
410 | $ echo a > a | |
411 | $ hg add a |
|
411 | $ hg add a | |
412 | $ hg ci -m initial |
|
412 | $ hg ci -m initial | |
413 | $ hg mv a b |
|
413 | $ hg mv a b | |
414 | $ hg ci -m 'mv a b' |
|
414 | $ hg ci -m 'mv a b' | |
415 | $ echo c > c |
|
415 | $ echo c > c | |
416 | $ hg add c |
|
416 | $ hg add c | |
417 | $ hg ci -m 'add c' |
|
417 | $ hg ci -m 'add c' | |
418 | $ hg up -q 0 |
|
418 | $ hg up -q 0 | |
419 | $ echo b > a |
|
419 | $ echo b > a | |
420 | $ hg ci -m 'mod a' |
|
420 | $ hg ci -m 'mod a' | |
421 | created new head |
|
421 | created new head | |
422 |
|
422 | |||
423 | $ hg l |
|
423 | $ hg l | |
424 | @ rev: 3 |
|
424 | @ rev: 3 | |
425 | | desc: mod a |
|
425 | | desc: mod a | |
426 | | o rev: 2 |
|
426 | | o rev: 2 | |
427 | | | desc: add c |
|
427 | | | desc: add c | |
428 | | o rev: 1 |
|
428 | | o rev: 1 | |
429 | |/ desc: mv a b |
|
429 | |/ desc: mv a b | |
430 | o rev: 0 |
|
430 | o rev: 0 | |
431 | desc: initial |
|
431 | desc: initial | |
432 |
|
432 | |||
433 | $ hg rebase -s . -d 2 |
|
433 | $ hg rebase -s . -d 2 | |
434 | rebasing 3:ef716627c70b tip "mod a" |
|
434 | rebasing 3:ef716627c70b tip "mod a" | |
435 | merging b and a to b |
|
435 | merging b and a to b | |
436 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg |
|
436 | saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg | |
437 | $ ls -A |
|
437 | $ ls -A | |
438 | .hg |
|
438 | .hg | |
439 | b |
|
439 | b | |
440 | c |
|
440 | c | |
441 | $ cat b |
|
441 | $ cat b | |
442 | b |
|
442 | b | |
443 | $ rm -rf repo |
|
443 | $ rm -rf repo | |
444 |
|
444 | |||
445 | Merge test |
|
445 | Merge test | |
446 | ---------- |
|
446 | ---------- | |
447 |
|
447 | |||
448 | $ hg init repo |
|
448 | $ hg init repo | |
449 | $ initclient repo |
|
449 | $ initclient repo | |
450 | $ cd repo |
|
450 | $ cd repo | |
451 | $ echo a > a |
|
451 | $ echo a > a | |
452 | $ hg add a |
|
452 | $ hg add a | |
453 | $ hg ci -m initial |
|
453 | $ hg ci -m initial | |
454 | $ echo b > a |
|
454 | $ echo b > a | |
455 | $ hg ci -m 'modify a' |
|
455 | $ hg ci -m 'modify a' | |
456 | $ hg up -q 0 |
|
456 | $ hg up -q 0 | |
457 | $ hg mv a b |
|
457 | $ hg mv a b | |
458 | $ hg ci -m 'mv a b' |
|
458 | $ hg ci -m 'mv a b' | |
459 | created new head |
|
459 | created new head | |
460 | $ hg up -q 2 |
|
460 | $ hg up -q 2 | |
461 |
|
461 | |||
462 | $ hg l |
|
462 | $ hg l | |
463 | @ rev: 2 |
|
463 | @ rev: 2 | |
464 | | desc: mv a b |
|
464 | | desc: mv a b | |
465 | | o rev: 1 |
|
465 | | o rev: 1 | |
466 | |/ desc: modify a |
|
466 | |/ desc: modify a | |
467 | o rev: 0 |
|
467 | o rev: 0 | |
468 | desc: initial |
|
468 | desc: initial | |
469 |
|
469 | |||
470 | $ hg merge 1 |
|
470 | $ hg merge 1 | |
471 | merging b and a to b |
|
471 | merging b and a to b | |
472 | 0 files updated, 1 files merged, 0 files removed, 0 files unresolved |
|
472 | 0 files updated, 1 files merged, 0 files removed, 0 files unresolved | |
473 | (branch merge, don't forget to commit) |
|
473 | (branch merge, don't forget to commit) | |
474 | $ hg ci -m merge |
|
474 | $ hg ci -m merge | |
475 | $ ls -A |
|
475 | $ ls -A | |
476 | .hg |
|
476 | .hg | |
477 | b |
|
477 | b | |
478 | $ cd .. |
|
478 | $ cd .. | |
479 | $ rm -rf repo |
|
479 | $ rm -rf repo | |
480 |
|
480 | |||
481 | Copy and move file |
|
481 | Copy and move file | |
482 | ------------------ |
|
482 | ------------------ | |
483 |
|
483 | |||
484 | $ hg init repo |
|
484 | $ hg init repo | |
485 | $ initclient repo |
|
485 | $ initclient repo | |
486 | $ cd repo |
|
486 | $ cd repo | |
487 | $ echo a > a |
|
487 | $ echo a > a | |
488 | $ hg add a |
|
488 | $ hg add a | |
489 | $ hg ci -m initial |
|
489 | $ hg ci -m initial | |
490 | $ hg cp a c |
|
490 | $ hg cp a c | |
491 | $ hg mv a b |
|
491 | $ hg mv a b | |
492 | $ hg ci -m 'cp a c, mv a b' |
|
492 | $ hg ci -m 'cp a c, mv a b' | |
493 | $ hg up -q 0 |
|
493 | $ hg up -q 0 | |
494 | $ echo b > a |
|
494 | $ echo b > a | |
495 | $ hg ci -m 'mod a' |
|
495 | $ hg ci -m 'mod a' | |
496 | created new head |
|
496 | created new head | |
497 |
|
497 | |||
498 | $ hg l |
|
498 | $ hg l | |
499 | @ rev: 2 |
|
499 | @ rev: 2 | |
500 | | desc: mod a |
|
500 | | desc: mod a | |
501 | | o rev: 1 |
|
501 | | o rev: 1 | |
502 | |/ desc: cp a c, mv a b |
|
502 | |/ desc: cp a c, mv a b | |
503 | o rev: 0 |
|
503 | o rev: 0 | |
504 | desc: initial |
|
504 | desc: initial | |
505 |
|
505 | |||
506 | $ hg rebase -s . -d 1 |
|
506 | $ hg rebase -s . -d 1 | |
507 | rebasing 2:ef716627c70b tip "mod a" |
|
507 | rebasing 2:ef716627c70b tip "mod a" | |
508 | merging b and a to b |
|
508 | merging b and a to b | |
509 | merging c and a to c |
|
509 | merging c and a to c | |
510 | saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg |
|
510 | saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg | |
511 | $ ls -A |
|
511 | $ ls -A | |
512 | .hg |
|
512 | .hg | |
513 | b |
|
513 | b | |
514 | c |
|
514 | c | |
515 | $ cat b |
|
515 | $ cat b | |
516 | b |
|
516 | b | |
517 | $ cat c |
|
517 | $ cat c | |
518 | b |
|
518 | b | |
519 | $ cd .. |
|
519 | $ cd .. | |
520 | $ rm -rf repo |
|
520 | $ rm -rf repo | |
521 |
|
521 | |||
522 | Do a merge commit with many consequent moves in one branch |
|
522 | Do a merge commit with many consequent moves in one branch | |
523 | ---------------------------------------------------------- |
|
523 | ---------------------------------------------------------- | |
524 |
|
524 | |||
525 | $ hg init repo |
|
525 | $ hg init repo | |
526 | $ initclient repo |
|
526 | $ initclient repo | |
527 | $ cd repo |
|
527 | $ cd repo | |
528 | $ echo a > a |
|
528 | $ echo a > a | |
529 | $ hg add a |
|
529 | $ hg add a | |
530 | $ hg ci -m initial |
|
530 | $ hg ci -m initial | |
531 | $ echo b > a |
|
531 | $ echo b > a | |
532 | $ hg ci -qm 'mod a' |
|
532 | $ hg ci -qm 'mod a' | |
533 | $ hg up -q ".^" |
|
533 | $ hg up -q ".^" | |
534 | $ hg mv a b |
|
534 | $ hg mv a b | |
535 | $ hg ci -qm 'mv a b' |
|
535 | $ hg ci -qm 'mv a b' | |
536 | $ hg mv b c |
|
536 | $ hg mv b c | |
537 | $ hg ci -qm 'mv b c' |
|
537 | $ hg ci -qm 'mv b c' | |
538 | $ hg up -q 1 |
|
538 | $ hg up -q 1 | |
539 | $ hg l |
|
539 | $ hg l | |
540 | o rev: 3 |
|
540 | o rev: 3 | |
541 | | desc: mv b c |
|
541 | | desc: mv b c | |
542 | o rev: 2 |
|
542 | o rev: 2 | |
543 | | desc: mv a b |
|
543 | | desc: mv a b | |
544 | | @ rev: 1 |
|
544 | | @ rev: 1 | |
545 | |/ desc: mod a |
|
545 | |/ desc: mod a | |
546 | o rev: 0 |
|
546 | o rev: 0 | |
547 | desc: initial |
|
547 | desc: initial | |
548 |
|
548 | |||
549 | $ hg merge 3 |
|
549 | $ hg merge 3 | |
550 | merging a and c to c |
|
550 | merging a and c to c | |
551 | 0 files updated, 1 files merged, 0 files removed, 0 files unresolved |
|
551 | 0 files updated, 1 files merged, 0 files removed, 0 files unresolved | |
552 | (branch merge, don't forget to commit) |
|
552 | (branch merge, don't forget to commit) | |
553 | $ hg ci -qm 'merge' |
|
553 | $ hg ci -qm 'merge' | |
554 | $ hg pl |
|
554 | $ hg pl | |
555 | @ rev: 4, phase: draft |
|
555 | @ rev: 4, phase: draft | |
556 | |\ desc: merge |
|
556 | |\ desc: merge | |
557 | | o rev: 3, phase: draft |
|
557 | | o rev: 3, phase: draft | |
558 | | | desc: mv b c |
|
558 | | | desc: mv b c | |
559 | | o rev: 2, phase: draft |
|
559 | | o rev: 2, phase: draft | |
560 | | | desc: mv a b |
|
560 | | | desc: mv a b | |
561 | o | rev: 1, phase: draft |
|
561 | o | rev: 1, phase: draft | |
562 | |/ desc: mod a |
|
562 | |/ desc: mod a | |
563 | o rev: 0, phase: draft |
|
563 | o rev: 0, phase: draft | |
564 | desc: initial |
|
564 | desc: initial | |
565 | $ ls -A |
|
565 | $ ls -A | |
566 | .hg |
|
566 | .hg | |
567 | c |
|
567 | c | |
568 | $ cd .. |
|
568 | $ cd .. | |
569 | $ rm -rf repo |
|
569 | $ rm -rf repo | |
570 |
|
570 | |||
571 | Test shelve/unshelve |
|
571 | Test shelve/unshelve | |
572 | ------------------- |
|
572 | ------------------- | |
573 |
|
573 | |||
574 | $ hg init repo |
|
574 | $ hg init repo | |
575 | $ initclient repo |
|
575 | $ initclient repo | |
576 | $ cd repo |
|
576 | $ cd repo | |
577 | $ echo a > a |
|
577 | $ echo a > a | |
578 | $ hg add a |
|
578 | $ hg add a | |
579 | $ hg ci -m initial |
|
579 | $ hg ci -m initial | |
580 | $ echo b > a |
|
580 | $ echo b > a | |
581 | $ hg shelve |
|
581 | $ hg shelve | |
582 | shelved as default |
|
582 | shelved as default | |
583 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
583 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
584 | $ hg mv a b |
|
584 | $ hg mv a b | |
585 | $ hg ci -m 'mv a b' |
|
585 | $ hg ci -m 'mv a b' | |
586 |
|
586 | |||
587 | $ hg l |
|
587 | $ hg l | |
588 | @ rev: 1 |
|
588 | @ rev: 1 | |
589 | | desc: mv a b |
|
589 | | desc: mv a b | |
590 | o rev: 0 |
|
590 | o rev: 0 | |
591 | desc: initial |
|
591 | desc: initial | |
592 | $ hg unshelve |
|
592 | $ hg unshelve | |
593 | unshelving change 'default' |
|
593 | unshelving change 'default' | |
594 | rebasing shelved changes |
|
594 | rebasing shelved changes | |
595 | merging b and a to b |
|
595 | merging b and a to b | |
596 | $ ls -A |
|
596 | $ ls -A | |
597 | .hg |
|
597 | .hg | |
598 | b |
|
598 | b | |
599 | $ cat b |
|
599 | $ cat b | |
600 | b |
|
600 | b | |
601 | $ cd .. |
|
601 | $ cd .. | |
602 | $ rm -rf repo |
|
602 | $ rm -rf repo | |
603 |
|
603 | |||
604 | Test full copytrace ability on draft branch |
|
604 | Test full copytrace ability on draft branch | |
605 | ------------------------------------------- |
|
605 | ------------------------------------------- | |
606 |
|
606 | |||
607 | File directory and base name changed in same move |
|
607 | File directory and base name changed in same move | |
608 | $ hg init repo |
|
608 | $ hg init repo | |
609 | $ initclient repo |
|
609 | $ initclient repo | |
610 | $ mkdir repo/dir1 |
|
610 | $ mkdir repo/dir1 | |
611 | $ cd repo/dir1 |
|
611 | $ cd repo/dir1 | |
612 | $ echo a > a |
|
612 | $ echo a > a | |
613 | $ hg add a |
|
613 | $ hg add a | |
614 | $ hg ci -qm initial |
|
614 | $ hg ci -qm initial | |
615 | $ cd .. |
|
615 | $ cd .. | |
616 | $ hg mv -q dir1 dir2 |
|
616 | $ hg mv -q dir1 dir2 | |
617 | $ hg mv dir2/a dir2/b |
|
617 | $ hg mv dir2/a dir2/b | |
618 | $ hg ci -qm 'mv a b; mv dir1 dir2' |
|
618 | $ hg ci -qm 'mv a b; mv dir1 dir2' | |
619 | $ hg up -q '.^' |
|
619 | $ hg up -q '.^' | |
620 | $ cd dir1 |
|
620 | $ cd dir1 | |
621 | $ echo b >> a |
|
621 | $ echo b >> a | |
622 | $ cd .. |
|
622 | $ cd .. | |
623 | $ hg ci -qm 'mod a' |
|
623 | $ hg ci -qm 'mod a' | |
624 |
|
624 | |||
625 | $ hg pl |
|
625 | $ hg pl | |
626 | @ rev: 2, phase: draft |
|
626 | @ rev: 2, phase: draft | |
627 | | desc: mod a |
|
627 | | desc: mod a | |
628 | | o rev: 1, phase: draft |
|
628 | | o rev: 1, phase: draft | |
629 | |/ desc: mv a b; mv dir1 dir2 |
|
629 | |/ desc: mv a b; mv dir1 dir2 | |
630 | o rev: 0, phase: draft |
|
630 | o rev: 0, phase: draft | |
631 | desc: initial |
|
631 | desc: initial | |
632 |
|
632 | |||
633 | $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100 |
|
633 | $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100 | |
634 | rebasing 2:6207d2d318e7 tip "mod a" |
|
634 | rebasing 2:6207d2d318e7 tip "mod a" | |
635 | merging dir2/b and dir1/a to dir2/b |
|
635 | merging dir2/b and dir1/a to dir2/b | |
636 | saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/6207d2d318e7-1c9779ad-rebase.hg |
|
636 | saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/6207d2d318e7-1c9779ad-rebase.hg | |
637 | $ cat dir2/b |
|
637 | $ cat dir2/b | |
638 | a |
|
638 | a | |
639 | b |
|
639 | b | |
640 | $ cd .. |
|
640 | $ cd .. | |
641 | $ rm -rf repo |
|
641 | $ rm -rf repo | |
642 |
|
642 | |||
643 | Move directory in one merge parent, while adding file to original directory |
|
643 | Move directory in one merge parent, while adding file to original directory | |
644 | in other merge parent. File moved on rebase. |
|
644 | in other merge parent. File moved on rebase. | |
645 |
|
645 | |||
646 | $ hg init repo |
|
646 | $ hg init repo | |
647 | $ initclient repo |
|
647 | $ initclient repo | |
648 | $ mkdir repo/dir1 |
|
648 | $ mkdir repo/dir1 | |
649 | $ cd repo/dir1 |
|
649 | $ cd repo/dir1 | |
650 | $ echo dummy > dummy |
|
650 | $ echo dummy > dummy | |
651 | $ hg add dummy |
|
651 | $ hg add dummy | |
652 | $ cd .. |
|
652 | $ cd .. | |
653 | $ hg ci -qm initial |
|
653 | $ hg ci -qm initial | |
654 | $ cd dir1 |
|
654 | $ cd dir1 | |
655 | $ echo a > a |
|
655 | $ echo a > a | |
656 | $ hg add a |
|
656 | $ hg add a | |
657 | $ cd .. |
|
657 | $ cd .. | |
658 | $ hg ci -qm 'hg add dir1/a' |
|
658 | $ hg ci -qm 'hg add dir1/a' | |
659 | $ hg up -q '.^' |
|
659 | $ hg up -q '.^' | |
660 | $ hg mv -q dir1 dir2 |
|
660 | $ hg mv -q dir1 dir2 | |
661 | $ hg ci -qm 'mv dir1 dir2' |
|
661 | $ hg ci -qm 'mv dir1 dir2' | |
662 |
|
662 | |||
663 | $ hg pl |
|
663 | $ hg pl | |
664 | @ rev: 2, phase: draft |
|
664 | @ rev: 2, phase: draft | |
665 | | desc: mv dir1 dir2 |
|
665 | | desc: mv dir1 dir2 | |
666 | | o rev: 1, phase: draft |
|
666 | | o rev: 1, phase: draft | |
667 | |/ desc: hg add dir1/a |
|
667 | |/ desc: hg add dir1/a | |
668 | o rev: 0, phase: draft |
|
668 | o rev: 0, phase: draft | |
669 | desc: initial |
|
669 | desc: initial | |
670 |
|
670 | |||
671 | $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100 |
|
671 | $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100 | |
672 | rebasing 2:e8919e7df8d0 tip "mv dir1 dir2" |
|
672 | rebasing 2:e8919e7df8d0 tip "mv dir1 dir2" | |
673 | saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/e8919e7df8d0-f62fab62-rebase.hg |
|
673 | saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/e8919e7df8d0-f62fab62-rebase.hg | |
674 | $ ls dir2 |
|
674 | $ ls dir2 | |
675 | a |
|
675 | a | |
676 | dummy |
|
676 | dummy | |
677 | $ rm -rf repo |
|
677 | $ rm -rf repo | |
678 |
|
678 | |||
679 | Testing the sourcecommitlimit config |
|
679 | Testing the sourcecommitlimit config | |
680 | ----------------------------------- |
|
680 | ----------------------------------- | |
681 |
|
681 | |||
682 | $ hg init repo |
|
682 | $ hg init repo | |
683 | $ initclient repo |
|
683 | $ initclient repo | |
684 | $ cd repo |
|
684 | $ cd repo | |
685 | $ echo a > a |
|
685 | $ echo a > a | |
686 | $ hg ci -Aqm "added a" |
|
686 | $ hg ci -Aqm "added a" | |
687 | $ echo "more things" >> a |
|
687 | $ echo "more things" >> a | |
688 | $ hg ci -qm "added more things to a" |
|
688 | $ hg ci -qm "added more things to a" | |
689 | $ hg up 0 |
|
689 | $ hg up 0 | |
690 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
690 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
691 | $ echo b > b |
|
691 | $ echo b > b | |
692 | $ hg ci -Aqm "added b" |
|
692 | $ hg ci -Aqm "added b" | |
693 | $ mkdir foo |
|
693 | $ mkdir foo | |
694 | $ hg mv a foo/bar |
|
694 | $ hg mv a foo/bar | |
695 | $ hg ci -m "Moved a to foo/bar" |
|
695 | $ hg ci -m "Moved a to foo/bar" | |
696 | $ hg pl |
|
696 | $ hg pl | |
697 | @ rev: 3, phase: draft |
|
697 | @ rev: 3, phase: draft | |
698 | | desc: Moved a to foo/bar |
|
698 | | desc: Moved a to foo/bar | |
699 | o rev: 2, phase: draft |
|
699 | o rev: 2, phase: draft | |
700 | | desc: added b |
|
700 | | desc: added b | |
701 | | o rev: 1, phase: draft |
|
701 | | o rev: 1, phase: draft | |
702 | |/ desc: added more things to a |
|
702 | |/ desc: added more things to a | |
703 | o rev: 0, phase: draft |
|
703 | o rev: 0, phase: draft | |
704 | desc: added a |
|
704 | desc: added a | |
705 |
|
705 | |||
706 | When the sourcecommitlimit is small and we have more drafts, we use heuristics only |
|
706 | When the sourcecommitlimit is small and we have more drafts, we use heuristics only | |
707 |
|
707 | |||
708 | $ hg rebase -s 1 -d . |
|
708 | $ hg rebase -s 1 -d . | |
709 | rebasing 1:8b6e13696c38 "added more things to a" |
|
709 | rebasing 1:8b6e13696c38 "added more things to a" | |
710 | file 'a' was deleted in local [dest] but was modified in other [source]. |
|
710 | file 'a' was deleted in local [dest] but was modified in other [source]. | |
711 | You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. |
|
711 | You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. | |
712 | What do you want to do? u |
|
712 | What do you want to do? u | |
713 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') |
|
713 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') | |
714 |
[ |
|
714 | [240] | |
715 |
|
715 | |||
716 | But when we have "sourcecommitlimit > (no. of drafts from base to c1)", we do |
|
716 | But when we have "sourcecommitlimit > (no. of drafts from base to c1)", we do | |
717 | fullcopytracing |
|
717 | fullcopytracing | |
718 |
|
718 | |||
719 | $ hg rebase --abort |
|
719 | $ hg rebase --abort | |
720 | rebase aborted |
|
720 | rebase aborted | |
721 | $ hg rebase -s 1 -d . --config experimental.copytrace.sourcecommitlimit=100 |
|
721 | $ hg rebase -s 1 -d . --config experimental.copytrace.sourcecommitlimit=100 | |
722 | rebasing 1:8b6e13696c38 "added more things to a" |
|
722 | rebasing 1:8b6e13696c38 "added more things to a" | |
723 | merging foo/bar and a to foo/bar |
|
723 | merging foo/bar and a to foo/bar | |
724 | saved backup bundle to $TESTTMP/repo/repo/repo/.hg/strip-backup/8b6e13696c38-fc14ac83-rebase.hg |
|
724 | saved backup bundle to $TESTTMP/repo/repo/repo/.hg/strip-backup/8b6e13696c38-fc14ac83-rebase.hg | |
725 | $ cd .. |
|
725 | $ cd .. | |
726 | $ rm -rf repo |
|
726 | $ rm -rf repo |
@@ -1,1693 +1,1693 b'' | |||||
1 | A script that implements uppercasing of specific lines in a file. This |
|
1 | A script that implements uppercasing of specific lines in a file. This | |
2 | approximates the behavior of code formatters well enough for our tests. |
|
2 | approximates the behavior of code formatters well enough for our tests. | |
3 |
|
3 | |||
4 | $ UPPERCASEPY="$TESTTMP/uppercase.py" |
|
4 | $ UPPERCASEPY="$TESTTMP/uppercase.py" | |
5 | $ cat > $UPPERCASEPY <<EOF |
|
5 | $ cat > $UPPERCASEPY <<EOF | |
6 | > import sys |
|
6 | > import sys | |
7 | > from mercurial.utils.procutil import setbinary |
|
7 | > from mercurial.utils.procutil import setbinary | |
8 | > setbinary(sys.stdin) |
|
8 | > setbinary(sys.stdin) | |
9 | > setbinary(sys.stdout) |
|
9 | > setbinary(sys.stdout) | |
10 | > lines = set() |
|
10 | > lines = set() | |
11 | > for arg in sys.argv[1:]: |
|
11 | > for arg in sys.argv[1:]: | |
12 | > if arg == 'all': |
|
12 | > if arg == 'all': | |
13 | > sys.stdout.write(sys.stdin.read().upper()) |
|
13 | > sys.stdout.write(sys.stdin.read().upper()) | |
14 | > sys.exit(0) |
|
14 | > sys.exit(0) | |
15 | > else: |
|
15 | > else: | |
16 | > first, last = arg.split('-') |
|
16 | > first, last = arg.split('-') | |
17 | > lines.update(range(int(first), int(last) + 1)) |
|
17 | > lines.update(range(int(first), int(last) + 1)) | |
18 | > for i, line in enumerate(sys.stdin.readlines()): |
|
18 | > for i, line in enumerate(sys.stdin.readlines()): | |
19 | > if i + 1 in lines: |
|
19 | > if i + 1 in lines: | |
20 | > sys.stdout.write(line.upper()) |
|
20 | > sys.stdout.write(line.upper()) | |
21 | > else: |
|
21 | > else: | |
22 | > sys.stdout.write(line) |
|
22 | > sys.stdout.write(line) | |
23 | > EOF |
|
23 | > EOF | |
24 | $ TESTLINES="foo\nbar\nbaz\nqux\n" |
|
24 | $ TESTLINES="foo\nbar\nbaz\nqux\n" | |
25 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY |
|
25 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY | |
26 | foo |
|
26 | foo | |
27 | bar |
|
27 | bar | |
28 | baz |
|
28 | baz | |
29 | qux |
|
29 | qux | |
30 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY all |
|
30 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY all | |
31 | FOO |
|
31 | FOO | |
32 | BAR |
|
32 | BAR | |
33 | BAZ |
|
33 | BAZ | |
34 | QUX |
|
34 | QUX | |
35 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-1 |
|
35 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-1 | |
36 | FOO |
|
36 | FOO | |
37 | bar |
|
37 | bar | |
38 | baz |
|
38 | baz | |
39 | qux |
|
39 | qux | |
40 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-2 |
|
40 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-2 | |
41 | FOO |
|
41 | FOO | |
42 | BAR |
|
42 | BAR | |
43 | baz |
|
43 | baz | |
44 | qux |
|
44 | qux | |
45 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-3 |
|
45 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-3 | |
46 | foo |
|
46 | foo | |
47 | BAR |
|
47 | BAR | |
48 | BAZ |
|
48 | BAZ | |
49 | qux |
|
49 | qux | |
50 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-2 4-4 |
|
50 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-2 4-4 | |
51 | foo |
|
51 | foo | |
52 | BAR |
|
52 | BAR | |
53 | baz |
|
53 | baz | |
54 | QUX |
|
54 | QUX | |
55 |
|
55 | |||
56 | Set up the config with two simple fixers: one that fixes specific line ranges, |
|
56 | Set up the config with two simple fixers: one that fixes specific line ranges, | |
57 | and one that always fixes the whole file. They both "fix" files by converting |
|
57 | and one that always fixes the whole file. They both "fix" files by converting | |
58 | letters to uppercase. They use different file extensions, so each test case can |
|
58 | letters to uppercase. They use different file extensions, so each test case can | |
59 | choose which behavior to use by naming files. |
|
59 | choose which behavior to use by naming files. | |
60 |
|
60 | |||
61 | $ cat >> $HGRCPATH <<EOF |
|
61 | $ cat >> $HGRCPATH <<EOF | |
62 | > [extensions] |
|
62 | > [extensions] | |
63 | > fix = |
|
63 | > fix = | |
64 | > [experimental] |
|
64 | > [experimental] | |
65 | > evolution.createmarkers=True |
|
65 | > evolution.createmarkers=True | |
66 | > evolution.allowunstable=True |
|
66 | > evolution.allowunstable=True | |
67 | > [fix] |
|
67 | > [fix] | |
68 | > uppercase-whole-file:command="$PYTHON" $UPPERCASEPY all |
|
68 | > uppercase-whole-file:command="$PYTHON" $UPPERCASEPY all | |
69 | > uppercase-whole-file:pattern=set:**.whole |
|
69 | > uppercase-whole-file:pattern=set:**.whole | |
70 | > uppercase-changed-lines:command="$PYTHON" $UPPERCASEPY |
|
70 | > uppercase-changed-lines:command="$PYTHON" $UPPERCASEPY | |
71 | > uppercase-changed-lines:linerange={first}-{last} |
|
71 | > uppercase-changed-lines:linerange={first}-{last} | |
72 | > uppercase-changed-lines:pattern=set:**.changed |
|
72 | > uppercase-changed-lines:pattern=set:**.changed | |
73 | > EOF |
|
73 | > EOF | |
74 |
|
74 | |||
75 | Help text for fix. |
|
75 | Help text for fix. | |
76 |
|
76 | |||
77 | $ hg help fix |
|
77 | $ hg help fix | |
78 | hg fix [OPTION]... [FILE]... |
|
78 | hg fix [OPTION]... [FILE]... | |
79 |
|
79 | |||
80 | rewrite file content in changesets or working directory |
|
80 | rewrite file content in changesets or working directory | |
81 |
|
81 | |||
82 | Runs any configured tools to fix the content of files. Only affects files |
|
82 | Runs any configured tools to fix the content of files. Only affects files | |
83 | with changes, unless file arguments are provided. Only affects changed |
|
83 | with changes, unless file arguments are provided. Only affects changed | |
84 | lines of files, unless the --whole flag is used. Some tools may always |
|
84 | lines of files, unless the --whole flag is used. Some tools may always | |
85 | affect the whole file regardless of --whole. |
|
85 | affect the whole file regardless of --whole. | |
86 |
|
86 | |||
87 | If --working-dir is used, files with uncommitted changes in the working |
|
87 | If --working-dir is used, files with uncommitted changes in the working | |
88 | copy will be fixed. Note that no backup are made. |
|
88 | copy will be fixed. Note that no backup are made. | |
89 |
|
89 | |||
90 | If revisions are specified with --source, those revisions and their |
|
90 | If revisions are specified with --source, those revisions and their | |
91 | descendants will be checked, and they may be replaced with new revisions |
|
91 | descendants will be checked, and they may be replaced with new revisions | |
92 | that have fixed file content. By automatically including the descendants, |
|
92 | that have fixed file content. By automatically including the descendants, | |
93 | no merging, rebasing, or evolution will be required. If an ancestor of the |
|
93 | no merging, rebasing, or evolution will be required. If an ancestor of the | |
94 | working copy is included, then the working copy itself will also be fixed, |
|
94 | working copy is included, then the working copy itself will also be fixed, | |
95 | and the working copy will be updated to the fixed parent. |
|
95 | and the working copy will be updated to the fixed parent. | |
96 |
|
96 | |||
97 | When determining what lines of each file to fix at each revision, the |
|
97 | When determining what lines of each file to fix at each revision, the | |
98 | whole set of revisions being fixed is considered, so that fixes to earlier |
|
98 | whole set of revisions being fixed is considered, so that fixes to earlier | |
99 | revisions are not forgotten in later ones. The --base flag can be used to |
|
99 | revisions are not forgotten in later ones. The --base flag can be used to | |
100 | override this default behavior, though it is not usually desirable to do |
|
100 | override this default behavior, though it is not usually desirable to do | |
101 | so. |
|
101 | so. | |
102 |
|
102 | |||
103 | (use 'hg help -e fix' to show help for the fix extension) |
|
103 | (use 'hg help -e fix' to show help for the fix extension) | |
104 |
|
104 | |||
105 | options ([+] can be repeated): |
|
105 | options ([+] can be repeated): | |
106 |
|
106 | |||
107 | --all fix all non-public non-obsolete revisions |
|
107 | --all fix all non-public non-obsolete revisions | |
108 | --base REV [+] revisions to diff against (overrides automatic selection, |
|
108 | --base REV [+] revisions to diff against (overrides automatic selection, | |
109 | and applies to every revision being fixed) |
|
109 | and applies to every revision being fixed) | |
110 | -s --source REV [+] fix the specified revisions and their descendants |
|
110 | -s --source REV [+] fix the specified revisions and their descendants | |
111 | -w --working-dir fix the working directory |
|
111 | -w --working-dir fix the working directory | |
112 | --whole always fix every line of a file |
|
112 | --whole always fix every line of a file | |
113 |
|
113 | |||
114 | (some details hidden, use --verbose to show complete help) |
|
114 | (some details hidden, use --verbose to show complete help) | |
115 |
|
115 | |||
116 | $ hg help -e fix |
|
116 | $ hg help -e fix | |
117 | fix extension - rewrite file content in changesets or working copy |
|
117 | fix extension - rewrite file content in changesets or working copy | |
118 | (EXPERIMENTAL) |
|
118 | (EXPERIMENTAL) | |
119 |
|
119 | |||
120 | Provides a command that runs configured tools on the contents of modified |
|
120 | Provides a command that runs configured tools on the contents of modified | |
121 | files, writing back any fixes to the working copy or replacing changesets. |
|
121 | files, writing back any fixes to the working copy or replacing changesets. | |
122 |
|
122 | |||
123 | Here is an example configuration that causes 'hg fix' to apply automatic |
|
123 | Here is an example configuration that causes 'hg fix' to apply automatic | |
124 | formatting fixes to modified lines in C++ code: |
|
124 | formatting fixes to modified lines in C++ code: | |
125 |
|
125 | |||
126 | [fix] |
|
126 | [fix] | |
127 | clang-format:command=clang-format --assume-filename={rootpath} |
|
127 | clang-format:command=clang-format --assume-filename={rootpath} | |
128 | clang-format:linerange=--lines={first}:{last} |
|
128 | clang-format:linerange=--lines={first}:{last} | |
129 | clang-format:pattern=set:**.cpp or **.hpp |
|
129 | clang-format:pattern=set:**.cpp or **.hpp | |
130 |
|
130 | |||
131 | The :command suboption forms the first part of the shell command that will be |
|
131 | The :command suboption forms the first part of the shell command that will be | |
132 | used to fix a file. The content of the file is passed on standard input, and |
|
132 | used to fix a file. The content of the file is passed on standard input, and | |
133 | the fixed file content is expected on standard output. Any output on standard |
|
133 | the fixed file content is expected on standard output. Any output on standard | |
134 | error will be displayed as a warning. If the exit status is not zero, the file |
|
134 | error will be displayed as a warning. If the exit status is not zero, the file | |
135 | will not be affected. A placeholder warning is displayed if there is a non- |
|
135 | will not be affected. A placeholder warning is displayed if there is a non- | |
136 | zero exit status but no standard error output. Some values may be substituted |
|
136 | zero exit status but no standard error output. Some values may be substituted | |
137 | into the command: |
|
137 | into the command: | |
138 |
|
138 | |||
139 | {rootpath} The path of the file being fixed, relative to the repo root |
|
139 | {rootpath} The path of the file being fixed, relative to the repo root | |
140 | {basename} The name of the file being fixed, without the directory path |
|
140 | {basename} The name of the file being fixed, without the directory path | |
141 |
|
141 | |||
142 | If the :linerange suboption is set, the tool will only be run if there are |
|
142 | If the :linerange suboption is set, the tool will only be run if there are | |
143 | changed lines in a file. The value of this suboption is appended to the shell |
|
143 | changed lines in a file. The value of this suboption is appended to the shell | |
144 | command once for every range of changed lines in the file. Some values may be |
|
144 | command once for every range of changed lines in the file. Some values may be | |
145 | substituted into the command: |
|
145 | substituted into the command: | |
146 |
|
146 | |||
147 | {first} The 1-based line number of the first line in the modified range |
|
147 | {first} The 1-based line number of the first line in the modified range | |
148 | {last} The 1-based line number of the last line in the modified range |
|
148 | {last} The 1-based line number of the last line in the modified range | |
149 |
|
149 | |||
150 | Deleted sections of a file will be ignored by :linerange, because there is no |
|
150 | Deleted sections of a file will be ignored by :linerange, because there is no | |
151 | corresponding line range in the version being fixed. |
|
151 | corresponding line range in the version being fixed. | |
152 |
|
152 | |||
153 | By default, tools that set :linerange will only be executed if there is at |
|
153 | By default, tools that set :linerange will only be executed if there is at | |
154 | least one changed line range. This is meant to prevent accidents like running |
|
154 | least one changed line range. This is meant to prevent accidents like running | |
155 | a code formatter in such a way that it unexpectedly reformats the whole file. |
|
155 | a code formatter in such a way that it unexpectedly reformats the whole file. | |
156 | If such a tool needs to operate on unchanged files, it should set the |
|
156 | If such a tool needs to operate on unchanged files, it should set the | |
157 | :skipclean suboption to false. |
|
157 | :skipclean suboption to false. | |
158 |
|
158 | |||
159 | The :pattern suboption determines which files will be passed through each |
|
159 | The :pattern suboption determines which files will be passed through each | |
160 | configured tool. See 'hg help patterns' for possible values. However, all |
|
160 | configured tool. See 'hg help patterns' for possible values. However, all | |
161 | patterns are relative to the repo root, even if that text says they are |
|
161 | patterns are relative to the repo root, even if that text says they are | |
162 | relative to the current working directory. If there are file arguments to 'hg |
|
162 | relative to the current working directory. If there are file arguments to 'hg | |
163 | fix', the intersection of these patterns is used. |
|
163 | fix', the intersection of these patterns is used. | |
164 |
|
164 | |||
165 | There is also a configurable limit for the maximum size of file that will be |
|
165 | There is also a configurable limit for the maximum size of file that will be | |
166 | processed by 'hg fix': |
|
166 | processed by 'hg fix': | |
167 |
|
167 | |||
168 | [fix] |
|
168 | [fix] | |
169 | maxfilesize = 2MB |
|
169 | maxfilesize = 2MB | |
170 |
|
170 | |||
171 | Normally, execution of configured tools will continue after a failure |
|
171 | Normally, execution of configured tools will continue after a failure | |
172 | (indicated by a non-zero exit status). It can also be configured to abort |
|
172 | (indicated by a non-zero exit status). It can also be configured to abort | |
173 | after the first such failure, so that no files will be affected if any tool |
|
173 | after the first such failure, so that no files will be affected if any tool | |
174 | fails. This abort will also cause 'hg fix' to exit with a non-zero status: |
|
174 | fails. This abort will also cause 'hg fix' to exit with a non-zero status: | |
175 |
|
175 | |||
176 | [fix] |
|
176 | [fix] | |
177 | failure = abort |
|
177 | failure = abort | |
178 |
|
178 | |||
179 | When multiple tools are configured to affect a file, they execute in an order |
|
179 | When multiple tools are configured to affect a file, they execute in an order | |
180 | defined by the :priority suboption. The priority suboption has a default value |
|
180 | defined by the :priority suboption. The priority suboption has a default value | |
181 | of zero for each tool. Tools are executed in order of descending priority. The |
|
181 | of zero for each tool. Tools are executed in order of descending priority. The | |
182 | execution order of tools with equal priority is unspecified. For example, you |
|
182 | execution order of tools with equal priority is unspecified. For example, you | |
183 | could use the 'sort' and 'head' utilities to keep only the 10 smallest numbers |
|
183 | could use the 'sort' and 'head' utilities to keep only the 10 smallest numbers | |
184 | in a text file by ensuring that 'sort' runs before 'head': |
|
184 | in a text file by ensuring that 'sort' runs before 'head': | |
185 |
|
185 | |||
186 | [fix] |
|
186 | [fix] | |
187 | sort:command = sort -n |
|
187 | sort:command = sort -n | |
188 | head:command = head -n 10 |
|
188 | head:command = head -n 10 | |
189 | sort:pattern = numbers.txt |
|
189 | sort:pattern = numbers.txt | |
190 | head:pattern = numbers.txt |
|
190 | head:pattern = numbers.txt | |
191 | sort:priority = 2 |
|
191 | sort:priority = 2 | |
192 | head:priority = 1 |
|
192 | head:priority = 1 | |
193 |
|
193 | |||
194 | To account for changes made by each tool, the line numbers used for |
|
194 | To account for changes made by each tool, the line numbers used for | |
195 | incremental formatting are recomputed before executing the next tool. So, each |
|
195 | incremental formatting are recomputed before executing the next tool. So, each | |
196 | tool may see different values for the arguments added by the :linerange |
|
196 | tool may see different values for the arguments added by the :linerange | |
197 | suboption. |
|
197 | suboption. | |
198 |
|
198 | |||
199 | Each fixer tool is allowed to return some metadata in addition to the fixed |
|
199 | Each fixer tool is allowed to return some metadata in addition to the fixed | |
200 | file content. The metadata must be placed before the file content on stdout, |
|
200 | file content. The metadata must be placed before the file content on stdout, | |
201 | separated from the file content by a zero byte. The metadata is parsed as a |
|
201 | separated from the file content by a zero byte. The metadata is parsed as a | |
202 | JSON value (so, it should be UTF-8 encoded and contain no zero bytes). A fixer |
|
202 | JSON value (so, it should be UTF-8 encoded and contain no zero bytes). A fixer | |
203 | tool is expected to produce this metadata encoding if and only if the |
|
203 | tool is expected to produce this metadata encoding if and only if the | |
204 | :metadata suboption is true: |
|
204 | :metadata suboption is true: | |
205 |
|
205 | |||
206 | [fix] |
|
206 | [fix] | |
207 | tool:command = tool --prepend-json-metadata |
|
207 | tool:command = tool --prepend-json-metadata | |
208 | tool:metadata = true |
|
208 | tool:metadata = true | |
209 |
|
209 | |||
210 | The metadata values are passed to hooks, which can be used to print summaries |
|
210 | The metadata values are passed to hooks, which can be used to print summaries | |
211 | or perform other post-fixing work. The supported hooks are: |
|
211 | or perform other post-fixing work. The supported hooks are: | |
212 |
|
212 | |||
213 | "postfixfile" |
|
213 | "postfixfile" | |
214 | Run once for each file in each revision where any fixer tools made changes |
|
214 | Run once for each file in each revision where any fixer tools made changes | |
215 | to the file content. Provides "$HG_REV" and "$HG_PATH" to identify the file, |
|
215 | to the file content. Provides "$HG_REV" and "$HG_PATH" to identify the file, | |
216 | and "$HG_METADATA" with a map of fixer names to metadata values from fixer |
|
216 | and "$HG_METADATA" with a map of fixer names to metadata values from fixer | |
217 | tools that affected the file. Fixer tools that didn't affect the file have a |
|
217 | tools that affected the file. Fixer tools that didn't affect the file have a | |
218 | value of None. Only fixer tools that executed are present in the metadata. |
|
218 | value of None. Only fixer tools that executed are present in the metadata. | |
219 |
|
219 | |||
220 | "postfix" |
|
220 | "postfix" | |
221 | Run once after all files and revisions have been handled. Provides |
|
221 | Run once after all files and revisions have been handled. Provides | |
222 | "$HG_REPLACEMENTS" with information about what revisions were created and |
|
222 | "$HG_REPLACEMENTS" with information about what revisions were created and | |
223 | made obsolete. Provides a boolean "$HG_WDIRWRITTEN" to indicate whether any |
|
223 | made obsolete. Provides a boolean "$HG_WDIRWRITTEN" to indicate whether any | |
224 | files in the working copy were updated. Provides a list "$HG_METADATA" |
|
224 | files in the working copy were updated. Provides a list "$HG_METADATA" | |
225 | mapping fixer tool names to lists of metadata values returned from |
|
225 | mapping fixer tool names to lists of metadata values returned from | |
226 | executions that modified a file. This aggregates the same metadata |
|
226 | executions that modified a file. This aggregates the same metadata | |
227 | previously passed to the "postfixfile" hook. |
|
227 | previously passed to the "postfixfile" hook. | |
228 |
|
228 | |||
229 | Fixer tools are run in the repository's root directory. This allows them to |
|
229 | Fixer tools are run in the repository's root directory. This allows them to | |
230 | read configuration files from the working copy, or even write to the working |
|
230 | read configuration files from the working copy, or even write to the working | |
231 | copy. The working copy is not updated to match the revision being fixed. In |
|
231 | copy. The working copy is not updated to match the revision being fixed. In | |
232 | fact, several revisions may be fixed in parallel. Writes to the working copy |
|
232 | fact, several revisions may be fixed in parallel. Writes to the working copy | |
233 | are not amended into the revision being fixed; fixer tools should always write |
|
233 | are not amended into the revision being fixed; fixer tools should always write | |
234 | fixed file content back to stdout as documented above. |
|
234 | fixed file content back to stdout as documented above. | |
235 |
|
235 | |||
236 | list of commands: |
|
236 | list of commands: | |
237 |
|
237 | |||
238 | fix rewrite file content in changesets or working directory |
|
238 | fix rewrite file content in changesets or working directory | |
239 |
|
239 | |||
240 | (use 'hg help -v -e fix' to show built-in aliases and global options) |
|
240 | (use 'hg help -v -e fix' to show built-in aliases and global options) | |
241 |
|
241 | |||
242 | There is no default behavior in the absence of --rev and --working-dir. |
|
242 | There is no default behavior in the absence of --rev and --working-dir. | |
243 |
|
243 | |||
244 | $ hg init badusage |
|
244 | $ hg init badusage | |
245 | $ cd badusage |
|
245 | $ cd badusage | |
246 |
|
246 | |||
247 | $ hg fix |
|
247 | $ hg fix | |
248 | abort: no changesets specified |
|
248 | abort: no changesets specified | |
249 | (use --source or --working-dir) |
|
249 | (use --source or --working-dir) | |
250 | [255] |
|
250 | [255] | |
251 | $ hg fix --whole |
|
251 | $ hg fix --whole | |
252 | abort: no changesets specified |
|
252 | abort: no changesets specified | |
253 | (use --source or --working-dir) |
|
253 | (use --source or --working-dir) | |
254 | [255] |
|
254 | [255] | |
255 | $ hg fix --base 0 |
|
255 | $ hg fix --base 0 | |
256 | abort: no changesets specified |
|
256 | abort: no changesets specified | |
257 | (use --source or --working-dir) |
|
257 | (use --source or --working-dir) | |
258 | [255] |
|
258 | [255] | |
259 |
|
259 | |||
260 | Fixing a public revision isn't allowed. It should abort early enough that |
|
260 | Fixing a public revision isn't allowed. It should abort early enough that | |
261 | nothing happens, even to the working directory. |
|
261 | nothing happens, even to the working directory. | |
262 |
|
262 | |||
263 | $ printf "hello\n" > hello.whole |
|
263 | $ printf "hello\n" > hello.whole | |
264 | $ hg commit -Aqm "hello" |
|
264 | $ hg commit -Aqm "hello" | |
265 | $ hg phase -r 0 --public |
|
265 | $ hg phase -r 0 --public | |
266 | $ hg fix -r 0 |
|
266 | $ hg fix -r 0 | |
267 | abort: cannot fix public changesets |
|
267 | abort: cannot fix public changesets | |
268 | (see 'hg help phases' for details) |
|
268 | (see 'hg help phases' for details) | |
269 | [255] |
|
269 | [255] | |
270 | $ hg fix -r 0 --working-dir |
|
270 | $ hg fix -r 0 --working-dir | |
271 | abort: cannot fix public changesets |
|
271 | abort: cannot fix public changesets | |
272 | (see 'hg help phases' for details) |
|
272 | (see 'hg help phases' for details) | |
273 | [255] |
|
273 | [255] | |
274 | $ hg cat -r tip hello.whole |
|
274 | $ hg cat -r tip hello.whole | |
275 | hello |
|
275 | hello | |
276 | $ cat hello.whole |
|
276 | $ cat hello.whole | |
277 | hello |
|
277 | hello | |
278 |
|
278 | |||
279 | $ cd .. |
|
279 | $ cd .. | |
280 |
|
280 | |||
281 | Fixing a clean working directory should do nothing. Even the --whole flag |
|
281 | Fixing a clean working directory should do nothing. Even the --whole flag | |
282 | shouldn't cause any clean files to be fixed. Specifying a clean file explicitly |
|
282 | shouldn't cause any clean files to be fixed. Specifying a clean file explicitly | |
283 | should only fix it if the fixer always fixes the whole file. The combination of |
|
283 | should only fix it if the fixer always fixes the whole file. The combination of | |
284 | an explicit filename and --whole should format the entire file regardless. |
|
284 | an explicit filename and --whole should format the entire file regardless. | |
285 |
|
285 | |||
286 | $ hg init fixcleanwdir |
|
286 | $ hg init fixcleanwdir | |
287 | $ cd fixcleanwdir |
|
287 | $ cd fixcleanwdir | |
288 |
|
288 | |||
289 | $ printf "hello\n" > hello.changed |
|
289 | $ printf "hello\n" > hello.changed | |
290 | $ printf "world\n" > hello.whole |
|
290 | $ printf "world\n" > hello.whole | |
291 | $ hg commit -Aqm "foo" |
|
291 | $ hg commit -Aqm "foo" | |
292 | $ hg fix --working-dir |
|
292 | $ hg fix --working-dir | |
293 | $ hg diff |
|
293 | $ hg diff | |
294 | $ hg fix --working-dir --whole |
|
294 | $ hg fix --working-dir --whole | |
295 | $ hg diff |
|
295 | $ hg diff | |
296 | $ hg fix --working-dir * |
|
296 | $ hg fix --working-dir * | |
297 | $ cat * |
|
297 | $ cat * | |
298 | hello |
|
298 | hello | |
299 | WORLD |
|
299 | WORLD | |
300 | $ hg revert --all --no-backup |
|
300 | $ hg revert --all --no-backup | |
301 | reverting hello.whole |
|
301 | reverting hello.whole | |
302 | $ hg fix --working-dir * --whole |
|
302 | $ hg fix --working-dir * --whole | |
303 | $ cat * |
|
303 | $ cat * | |
304 | HELLO |
|
304 | HELLO | |
305 | WORLD |
|
305 | WORLD | |
306 |
|
306 | |||
307 | The same ideas apply to fixing a revision, so we create a revision that doesn't |
|
307 | The same ideas apply to fixing a revision, so we create a revision that doesn't | |
308 | modify either of the files in question and try fixing it. This also tests that |
|
308 | modify either of the files in question and try fixing it. This also tests that | |
309 | we ignore a file that doesn't match any configured fixer. |
|
309 | we ignore a file that doesn't match any configured fixer. | |
310 |
|
310 | |||
311 | $ hg revert --all --no-backup |
|
311 | $ hg revert --all --no-backup | |
312 | reverting hello.changed |
|
312 | reverting hello.changed | |
313 | reverting hello.whole |
|
313 | reverting hello.whole | |
314 | $ printf "unimportant\n" > some.file |
|
314 | $ printf "unimportant\n" > some.file | |
315 | $ hg commit -Aqm "some other file" |
|
315 | $ hg commit -Aqm "some other file" | |
316 |
|
316 | |||
317 | $ hg fix -r . |
|
317 | $ hg fix -r . | |
318 | $ hg cat -r tip * |
|
318 | $ hg cat -r tip * | |
319 | hello |
|
319 | hello | |
320 | world |
|
320 | world | |
321 | unimportant |
|
321 | unimportant | |
322 | $ hg fix -r . --whole |
|
322 | $ hg fix -r . --whole | |
323 | $ hg cat -r tip * |
|
323 | $ hg cat -r tip * | |
324 | hello |
|
324 | hello | |
325 | world |
|
325 | world | |
326 | unimportant |
|
326 | unimportant | |
327 | $ hg fix -r . * |
|
327 | $ hg fix -r . * | |
328 | $ hg cat -r tip * |
|
328 | $ hg cat -r tip * | |
329 | hello |
|
329 | hello | |
330 | WORLD |
|
330 | WORLD | |
331 | unimportant |
|
331 | unimportant | |
332 | $ hg fix -r . * --whole --config experimental.evolution.allowdivergence=true |
|
332 | $ hg fix -r . * --whole --config experimental.evolution.allowdivergence=true | |
333 | 2 new content-divergent changesets |
|
333 | 2 new content-divergent changesets | |
334 | $ hg cat -r tip * |
|
334 | $ hg cat -r tip * | |
335 | HELLO |
|
335 | HELLO | |
336 | WORLD |
|
336 | WORLD | |
337 | unimportant |
|
337 | unimportant | |
338 |
|
338 | |||
339 | $ cd .. |
|
339 | $ cd .. | |
340 |
|
340 | |||
341 | Fixing the working directory should still work if there are no revisions. |
|
341 | Fixing the working directory should still work if there are no revisions. | |
342 |
|
342 | |||
343 | $ hg init norevisions |
|
343 | $ hg init norevisions | |
344 | $ cd norevisions |
|
344 | $ cd norevisions | |
345 |
|
345 | |||
346 | $ printf "something\n" > something.whole |
|
346 | $ printf "something\n" > something.whole | |
347 | $ hg add |
|
347 | $ hg add | |
348 | adding something.whole |
|
348 | adding something.whole | |
349 | $ hg fix --working-dir |
|
349 | $ hg fix --working-dir | |
350 | $ cat something.whole |
|
350 | $ cat something.whole | |
351 | SOMETHING |
|
351 | SOMETHING | |
352 |
|
352 | |||
353 | $ cd .. |
|
353 | $ cd .. | |
354 |
|
354 | |||
355 | Test the effect of fixing the working directory for each possible status, with |
|
355 | Test the effect of fixing the working directory for each possible status, with | |
356 | and without providing explicit file arguments. |
|
356 | and without providing explicit file arguments. | |
357 |
|
357 | |||
358 | $ hg init implicitlyfixstatus |
|
358 | $ hg init implicitlyfixstatus | |
359 | $ cd implicitlyfixstatus |
|
359 | $ cd implicitlyfixstatus | |
360 |
|
360 | |||
361 | $ printf "modified\n" > modified.whole |
|
361 | $ printf "modified\n" > modified.whole | |
362 | $ printf "removed\n" > removed.whole |
|
362 | $ printf "removed\n" > removed.whole | |
363 | $ printf "deleted\n" > deleted.whole |
|
363 | $ printf "deleted\n" > deleted.whole | |
364 | $ printf "clean\n" > clean.whole |
|
364 | $ printf "clean\n" > clean.whole | |
365 | $ printf "ignored.whole" > .hgignore |
|
365 | $ printf "ignored.whole" > .hgignore | |
366 | $ hg commit -Aqm "stuff" |
|
366 | $ hg commit -Aqm "stuff" | |
367 |
|
367 | |||
368 | $ printf "modified!!!\n" > modified.whole |
|
368 | $ printf "modified!!!\n" > modified.whole | |
369 | $ printf "unknown\n" > unknown.whole |
|
369 | $ printf "unknown\n" > unknown.whole | |
370 | $ printf "ignored\n" > ignored.whole |
|
370 | $ printf "ignored\n" > ignored.whole | |
371 | $ printf "added\n" > added.whole |
|
371 | $ printf "added\n" > added.whole | |
372 | $ hg add added.whole |
|
372 | $ hg add added.whole | |
373 | $ hg remove removed.whole |
|
373 | $ hg remove removed.whole | |
374 | $ rm deleted.whole |
|
374 | $ rm deleted.whole | |
375 |
|
375 | |||
376 | $ hg status --all |
|
376 | $ hg status --all | |
377 | M modified.whole |
|
377 | M modified.whole | |
378 | A added.whole |
|
378 | A added.whole | |
379 | R removed.whole |
|
379 | R removed.whole | |
380 | ! deleted.whole |
|
380 | ! deleted.whole | |
381 | ? unknown.whole |
|
381 | ? unknown.whole | |
382 | I ignored.whole |
|
382 | I ignored.whole | |
383 | C .hgignore |
|
383 | C .hgignore | |
384 | C clean.whole |
|
384 | C clean.whole | |
385 |
|
385 | |||
386 | $ hg fix --working-dir |
|
386 | $ hg fix --working-dir | |
387 |
|
387 | |||
388 | $ hg status --all |
|
388 | $ hg status --all | |
389 | M modified.whole |
|
389 | M modified.whole | |
390 | A added.whole |
|
390 | A added.whole | |
391 | R removed.whole |
|
391 | R removed.whole | |
392 | ! deleted.whole |
|
392 | ! deleted.whole | |
393 | ? unknown.whole |
|
393 | ? unknown.whole | |
394 | I ignored.whole |
|
394 | I ignored.whole | |
395 | C .hgignore |
|
395 | C .hgignore | |
396 | C clean.whole |
|
396 | C clean.whole | |
397 |
|
397 | |||
398 | $ cat *.whole |
|
398 | $ cat *.whole | |
399 | ADDED |
|
399 | ADDED | |
400 | clean |
|
400 | clean | |
401 | ignored |
|
401 | ignored | |
402 | MODIFIED!!! |
|
402 | MODIFIED!!! | |
403 | unknown |
|
403 | unknown | |
404 |
|
404 | |||
405 | $ printf "modified!!!\n" > modified.whole |
|
405 | $ printf "modified!!!\n" > modified.whole | |
406 | $ printf "added\n" > added.whole |
|
406 | $ printf "added\n" > added.whole | |
407 |
|
407 | |||
408 | Listing the files explicitly causes untracked files to also be fixed, but |
|
408 | Listing the files explicitly causes untracked files to also be fixed, but | |
409 | ignored files are still unaffected. |
|
409 | ignored files are still unaffected. | |
410 |
|
410 | |||
411 | $ hg fix --working-dir *.whole |
|
411 | $ hg fix --working-dir *.whole | |
412 |
|
412 | |||
413 | $ hg status --all |
|
413 | $ hg status --all | |
414 | M clean.whole |
|
414 | M clean.whole | |
415 | M modified.whole |
|
415 | M modified.whole | |
416 | A added.whole |
|
416 | A added.whole | |
417 | R removed.whole |
|
417 | R removed.whole | |
418 | ! deleted.whole |
|
418 | ! deleted.whole | |
419 | ? unknown.whole |
|
419 | ? unknown.whole | |
420 | I ignored.whole |
|
420 | I ignored.whole | |
421 | C .hgignore |
|
421 | C .hgignore | |
422 |
|
422 | |||
423 | $ cat *.whole |
|
423 | $ cat *.whole | |
424 | ADDED |
|
424 | ADDED | |
425 | CLEAN |
|
425 | CLEAN | |
426 | ignored |
|
426 | ignored | |
427 | MODIFIED!!! |
|
427 | MODIFIED!!! | |
428 | UNKNOWN |
|
428 | UNKNOWN | |
429 |
|
429 | |||
430 | $ cd .. |
|
430 | $ cd .. | |
431 |
|
431 | |||
432 | Test that incremental fixing works on files with additions, deletions, and |
|
432 | Test that incremental fixing works on files with additions, deletions, and | |
433 | changes in multiple line ranges. Note that deletions do not generally cause |
|
433 | changes in multiple line ranges. Note that deletions do not generally cause | |
434 | neighboring lines to be fixed, so we don't return a line range for purely |
|
434 | neighboring lines to be fixed, so we don't return a line range for purely | |
435 | deleted sections. In the future we should support a :deletion config that |
|
435 | deleted sections. In the future we should support a :deletion config that | |
436 | allows fixers to know where deletions are located. |
|
436 | allows fixers to know where deletions are located. | |
437 |
|
437 | |||
438 | $ hg init incrementalfixedlines |
|
438 | $ hg init incrementalfixedlines | |
439 | $ cd incrementalfixedlines |
|
439 | $ cd incrementalfixedlines | |
440 |
|
440 | |||
441 | $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt |
|
441 | $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt | |
442 | $ hg commit -Aqm "foo" |
|
442 | $ hg commit -Aqm "foo" | |
443 | $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt |
|
443 | $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt | |
444 |
|
444 | |||
445 | $ hg --config "fix.fail:command=echo" \ |
|
445 | $ hg --config "fix.fail:command=echo" \ | |
446 | > --config "fix.fail:linerange={first}:{last}" \ |
|
446 | > --config "fix.fail:linerange={first}:{last}" \ | |
447 | > --config "fix.fail:pattern=foo.txt" \ |
|
447 | > --config "fix.fail:pattern=foo.txt" \ | |
448 | > fix --working-dir |
|
448 | > fix --working-dir | |
449 | $ cat foo.txt |
|
449 | $ cat foo.txt | |
450 | 1:1 4:6 8:8 |
|
450 | 1:1 4:6 8:8 | |
451 |
|
451 | |||
452 | $ cd .. |
|
452 | $ cd .. | |
453 |
|
453 | |||
454 | Test that --whole fixes all lines regardless of the diffs present. |
|
454 | Test that --whole fixes all lines regardless of the diffs present. | |
455 |
|
455 | |||
456 | $ hg init wholeignoresdiffs |
|
456 | $ hg init wholeignoresdiffs | |
457 | $ cd wholeignoresdiffs |
|
457 | $ cd wholeignoresdiffs | |
458 |
|
458 | |||
459 | $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed |
|
459 | $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed | |
460 | $ hg commit -Aqm "foo" |
|
460 | $ hg commit -Aqm "foo" | |
461 | $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed |
|
461 | $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed | |
462 |
|
462 | |||
463 | $ hg fix --working-dir |
|
463 | $ hg fix --working-dir | |
464 | $ cat foo.changed |
|
464 | $ cat foo.changed | |
465 | ZZ |
|
465 | ZZ | |
466 | a |
|
466 | a | |
467 | c |
|
467 | c | |
468 | DD |
|
468 | DD | |
469 | EE |
|
469 | EE | |
470 | FF |
|
470 | FF | |
471 | f |
|
471 | f | |
472 | GG |
|
472 | GG | |
473 |
|
473 | |||
474 | $ hg fix --working-dir --whole |
|
474 | $ hg fix --working-dir --whole | |
475 | $ cat foo.changed |
|
475 | $ cat foo.changed | |
476 | ZZ |
|
476 | ZZ | |
477 | A |
|
477 | A | |
478 | C |
|
478 | C | |
479 | DD |
|
479 | DD | |
480 | EE |
|
480 | EE | |
481 | FF |
|
481 | FF | |
482 | F |
|
482 | F | |
483 | GG |
|
483 | GG | |
484 |
|
484 | |||
485 | $ cd .. |
|
485 | $ cd .. | |
486 |
|
486 | |||
487 | We should do nothing with symlinks, and their targets should be unaffected. Any |
|
487 | We should do nothing with symlinks, and their targets should be unaffected. Any | |
488 | other behavior would be more complicated to implement and harder to document. |
|
488 | other behavior would be more complicated to implement and harder to document. | |
489 |
|
489 | |||
490 | #if symlink |
|
490 | #if symlink | |
491 | $ hg init dontmesswithsymlinks |
|
491 | $ hg init dontmesswithsymlinks | |
492 | $ cd dontmesswithsymlinks |
|
492 | $ cd dontmesswithsymlinks | |
493 |
|
493 | |||
494 | $ printf "hello\n" > hello.whole |
|
494 | $ printf "hello\n" > hello.whole | |
495 | $ ln -s hello.whole hellolink |
|
495 | $ ln -s hello.whole hellolink | |
496 | $ hg add |
|
496 | $ hg add | |
497 | adding hello.whole |
|
497 | adding hello.whole | |
498 | adding hellolink |
|
498 | adding hellolink | |
499 | $ hg fix --working-dir hellolink |
|
499 | $ hg fix --working-dir hellolink | |
500 | $ hg status |
|
500 | $ hg status | |
501 | A hello.whole |
|
501 | A hello.whole | |
502 | A hellolink |
|
502 | A hellolink | |
503 |
|
503 | |||
504 | $ cd .. |
|
504 | $ cd .. | |
505 | #endif |
|
505 | #endif | |
506 |
|
506 | |||
507 | We should allow fixers to run on binary files, even though this doesn't sound |
|
507 | We should allow fixers to run on binary files, even though this doesn't sound | |
508 | like a common use case. There's not much benefit to disallowing it, and users |
|
508 | like a common use case. There's not much benefit to disallowing it, and users | |
509 | can add "and not binary()" to their filesets if needed. The Mercurial |
|
509 | can add "and not binary()" to their filesets if needed. The Mercurial | |
510 | philosophy is generally to not handle binary files specially anyway. |
|
510 | philosophy is generally to not handle binary files specially anyway. | |
511 |
|
511 | |||
512 | $ hg init cantouchbinaryfiles |
|
512 | $ hg init cantouchbinaryfiles | |
513 | $ cd cantouchbinaryfiles |
|
513 | $ cd cantouchbinaryfiles | |
514 |
|
514 | |||
515 | $ printf "hello\0\n" > hello.whole |
|
515 | $ printf "hello\0\n" > hello.whole | |
516 | $ hg add |
|
516 | $ hg add | |
517 | adding hello.whole |
|
517 | adding hello.whole | |
518 | $ hg fix --working-dir 'set:binary()' |
|
518 | $ hg fix --working-dir 'set:binary()' | |
519 | $ cat hello.whole |
|
519 | $ cat hello.whole | |
520 | HELLO\x00 (esc) |
|
520 | HELLO\x00 (esc) | |
521 |
|
521 | |||
522 | $ cd .. |
|
522 | $ cd .. | |
523 |
|
523 | |||
524 | We have a config for the maximum size of file we will attempt to fix. This can |
|
524 | We have a config for the maximum size of file we will attempt to fix. This can | |
525 | be helpful to avoid running unsuspecting fixer tools on huge inputs, which |
|
525 | be helpful to avoid running unsuspecting fixer tools on huge inputs, which | |
526 | could happen by accident without a well considered configuration. A more |
|
526 | could happen by accident without a well considered configuration. A more | |
527 | precise configuration could use the size() fileset function if one global limit |
|
527 | precise configuration could use the size() fileset function if one global limit | |
528 | is undesired. |
|
528 | is undesired. | |
529 |
|
529 | |||
530 | $ hg init maxfilesize |
|
530 | $ hg init maxfilesize | |
531 | $ cd maxfilesize |
|
531 | $ cd maxfilesize | |
532 |
|
532 | |||
533 | $ printf "this file is huge\n" > hello.whole |
|
533 | $ printf "this file is huge\n" > hello.whole | |
534 | $ hg add |
|
534 | $ hg add | |
535 | adding hello.whole |
|
535 | adding hello.whole | |
536 | $ hg --config fix.maxfilesize=10 fix --working-dir |
|
536 | $ hg --config fix.maxfilesize=10 fix --working-dir | |
537 | ignoring file larger than 10 bytes: hello.whole |
|
537 | ignoring file larger than 10 bytes: hello.whole | |
538 | $ cat hello.whole |
|
538 | $ cat hello.whole | |
539 | this file is huge |
|
539 | this file is huge | |
540 |
|
540 | |||
541 | $ cd .. |
|
541 | $ cd .. | |
542 |
|
542 | |||
543 | If we specify a file to fix, other files should be left alone, even if they |
|
543 | If we specify a file to fix, other files should be left alone, even if they | |
544 | have changes. |
|
544 | have changes. | |
545 |
|
545 | |||
546 | $ hg init fixonlywhatitellyouto |
|
546 | $ hg init fixonlywhatitellyouto | |
547 | $ cd fixonlywhatitellyouto |
|
547 | $ cd fixonlywhatitellyouto | |
548 |
|
548 | |||
549 | $ printf "fix me!\n" > fixme.whole |
|
549 | $ printf "fix me!\n" > fixme.whole | |
550 | $ printf "not me.\n" > notme.whole |
|
550 | $ printf "not me.\n" > notme.whole | |
551 | $ hg add |
|
551 | $ hg add | |
552 | adding fixme.whole |
|
552 | adding fixme.whole | |
553 | adding notme.whole |
|
553 | adding notme.whole | |
554 | $ hg fix --working-dir fixme.whole |
|
554 | $ hg fix --working-dir fixme.whole | |
555 | $ cat *.whole |
|
555 | $ cat *.whole | |
556 | FIX ME! |
|
556 | FIX ME! | |
557 | not me. |
|
557 | not me. | |
558 |
|
558 | |||
559 | $ cd .. |
|
559 | $ cd .. | |
560 |
|
560 | |||
561 | If we try to fix a missing file, we still fix other files. |
|
561 | If we try to fix a missing file, we still fix other files. | |
562 |
|
562 | |||
563 | $ hg init fixmissingfile |
|
563 | $ hg init fixmissingfile | |
564 | $ cd fixmissingfile |
|
564 | $ cd fixmissingfile | |
565 |
|
565 | |||
566 | $ printf "fix me!\n" > foo.whole |
|
566 | $ printf "fix me!\n" > foo.whole | |
567 | $ hg add |
|
567 | $ hg add | |
568 | adding foo.whole |
|
568 | adding foo.whole | |
569 | $ hg fix --working-dir foo.whole bar.whole |
|
569 | $ hg fix --working-dir foo.whole bar.whole | |
570 | bar.whole: $ENOENT$ |
|
570 | bar.whole: $ENOENT$ | |
571 | $ cat *.whole |
|
571 | $ cat *.whole | |
572 | FIX ME! |
|
572 | FIX ME! | |
573 |
|
573 | |||
574 | $ cd .. |
|
574 | $ cd .. | |
575 |
|
575 | |||
576 | Specifying a directory name should fix all its files and subdirectories. |
|
576 | Specifying a directory name should fix all its files and subdirectories. | |
577 |
|
577 | |||
578 | $ hg init fixdirectory |
|
578 | $ hg init fixdirectory | |
579 | $ cd fixdirectory |
|
579 | $ cd fixdirectory | |
580 |
|
580 | |||
581 | $ mkdir -p dir1/dir2 |
|
581 | $ mkdir -p dir1/dir2 | |
582 | $ printf "foo\n" > foo.whole |
|
582 | $ printf "foo\n" > foo.whole | |
583 | $ printf "bar\n" > dir1/bar.whole |
|
583 | $ printf "bar\n" > dir1/bar.whole | |
584 | $ printf "baz\n" > dir1/dir2/baz.whole |
|
584 | $ printf "baz\n" > dir1/dir2/baz.whole | |
585 | $ hg add |
|
585 | $ hg add | |
586 | adding dir1/bar.whole |
|
586 | adding dir1/bar.whole | |
587 | adding dir1/dir2/baz.whole |
|
587 | adding dir1/dir2/baz.whole | |
588 | adding foo.whole |
|
588 | adding foo.whole | |
589 | $ hg fix --working-dir dir1 |
|
589 | $ hg fix --working-dir dir1 | |
590 | $ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole |
|
590 | $ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole | |
591 | foo |
|
591 | foo | |
592 | BAR |
|
592 | BAR | |
593 | BAZ |
|
593 | BAZ | |
594 |
|
594 | |||
595 | $ cd .. |
|
595 | $ cd .. | |
596 |
|
596 | |||
597 | Fixing a file in the working directory that needs no fixes should not actually |
|
597 | Fixing a file in the working directory that needs no fixes should not actually | |
598 | write back to the file, so for example the mtime shouldn't change. |
|
598 | write back to the file, so for example the mtime shouldn't change. | |
599 |
|
599 | |||
600 | $ hg init donttouchunfixedfiles |
|
600 | $ hg init donttouchunfixedfiles | |
601 | $ cd donttouchunfixedfiles |
|
601 | $ cd donttouchunfixedfiles | |
602 |
|
602 | |||
603 | $ printf "NO FIX NEEDED\n" > foo.whole |
|
603 | $ printf "NO FIX NEEDED\n" > foo.whole | |
604 | $ hg add |
|
604 | $ hg add | |
605 | adding foo.whole |
|
605 | adding foo.whole | |
606 | $ cp -p foo.whole foo.whole.orig |
|
606 | $ cp -p foo.whole foo.whole.orig | |
607 | $ cp -p foo.whole.orig foo.whole |
|
607 | $ cp -p foo.whole.orig foo.whole | |
608 | $ sleep 2 # mtime has a resolution of one or two seconds. |
|
608 | $ sleep 2 # mtime has a resolution of one or two seconds. | |
609 | $ hg fix --working-dir |
|
609 | $ hg fix --working-dir | |
610 | $ f foo.whole.orig --newer foo.whole |
|
610 | $ f foo.whole.orig --newer foo.whole | |
611 | foo.whole.orig: newer than foo.whole |
|
611 | foo.whole.orig: newer than foo.whole | |
612 |
|
612 | |||
613 | $ cd .. |
|
613 | $ cd .. | |
614 |
|
614 | |||
615 | When a fixer prints to stderr, we don't assume that it has failed. We show the |
|
615 | When a fixer prints to stderr, we don't assume that it has failed. We show the | |
616 | error messages to the user, and we still let the fixer affect the file it was |
|
616 | error messages to the user, and we still let the fixer affect the file it was | |
617 | fixing if its exit code is zero. Some code formatters might emit error messages |
|
617 | fixing if its exit code is zero. Some code formatters might emit error messages | |
618 | on stderr and nothing on stdout, which would cause us the clear the file, |
|
618 | on stderr and nothing on stdout, which would cause us the clear the file, | |
619 | except that they also exit with a non-zero code. We show the user which fixer |
|
619 | except that they also exit with a non-zero code. We show the user which fixer | |
620 | emitted the stderr, and which revision, but we assume that the fixer will print |
|
620 | emitted the stderr, and which revision, but we assume that the fixer will print | |
621 | the filename if it is relevant (since the issue may be non-specific). There is |
|
621 | the filename if it is relevant (since the issue may be non-specific). There is | |
622 | also a config to abort (without affecting any files whatsoever) if we see any |
|
622 | also a config to abort (without affecting any files whatsoever) if we see any | |
623 | tool with a non-zero exit status. |
|
623 | tool with a non-zero exit status. | |
624 |
|
624 | |||
625 | $ hg init showstderr |
|
625 | $ hg init showstderr | |
626 | $ cd showstderr |
|
626 | $ cd showstderr | |
627 |
|
627 | |||
628 | $ printf "hello\n" > hello.txt |
|
628 | $ printf "hello\n" > hello.txt | |
629 | $ hg add |
|
629 | $ hg add | |
630 | adding hello.txt |
|
630 | adding hello.txt | |
631 | $ cat > $TESTTMP/work.sh <<'EOF' |
|
631 | $ cat > $TESTTMP/work.sh <<'EOF' | |
632 | > printf 'HELLO\n' |
|
632 | > printf 'HELLO\n' | |
633 | > printf "$@: some\nerror that didn't stop the tool" >&2 |
|
633 | > printf "$@: some\nerror that didn't stop the tool" >&2 | |
634 | > exit 0 # success despite the stderr output |
|
634 | > exit 0 # success despite the stderr output | |
635 | > EOF |
|
635 | > EOF | |
636 | $ hg --config "fix.work:command=sh $TESTTMP/work.sh {rootpath}" \ |
|
636 | $ hg --config "fix.work:command=sh $TESTTMP/work.sh {rootpath}" \ | |
637 | > --config "fix.work:pattern=hello.txt" \ |
|
637 | > --config "fix.work:pattern=hello.txt" \ | |
638 | > fix --working-dir |
|
638 | > fix --working-dir | |
639 | [wdir] work: hello.txt: some |
|
639 | [wdir] work: hello.txt: some | |
640 | [wdir] work: error that didn't stop the tool |
|
640 | [wdir] work: error that didn't stop the tool | |
641 | $ cat hello.txt |
|
641 | $ cat hello.txt | |
642 | HELLO |
|
642 | HELLO | |
643 |
|
643 | |||
644 | $ printf "goodbye\n" > hello.txt |
|
644 | $ printf "goodbye\n" > hello.txt | |
645 | $ printf "foo\n" > foo.whole |
|
645 | $ printf "foo\n" > foo.whole | |
646 | $ hg add |
|
646 | $ hg add | |
647 | adding foo.whole |
|
647 | adding foo.whole | |
648 | $ cat > $TESTTMP/fail.sh <<'EOF' |
|
648 | $ cat > $TESTTMP/fail.sh <<'EOF' | |
649 | > printf 'GOODBYE\n' |
|
649 | > printf 'GOODBYE\n' | |
650 | > printf "$@: some\nerror that did stop the tool\n" >&2 |
|
650 | > printf "$@: some\nerror that did stop the tool\n" >&2 | |
651 | > exit 42 # success despite the stdout output |
|
651 | > exit 42 # success despite the stdout output | |
652 | > EOF |
|
652 | > EOF | |
653 | $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \ |
|
653 | $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \ | |
654 | > --config "fix.fail:pattern=hello.txt" \ |
|
654 | > --config "fix.fail:pattern=hello.txt" \ | |
655 | > --config "fix.failure=abort" \ |
|
655 | > --config "fix.failure=abort" \ | |
656 | > fix --working-dir |
|
656 | > fix --working-dir | |
657 | [wdir] fail: hello.txt: some |
|
657 | [wdir] fail: hello.txt: some | |
658 | [wdir] fail: error that did stop the tool |
|
658 | [wdir] fail: error that did stop the tool | |
659 | abort: no fixes will be applied |
|
659 | abort: no fixes will be applied | |
660 | (use --config fix.failure=continue to apply any successful fixes anyway) |
|
660 | (use --config fix.failure=continue to apply any successful fixes anyway) | |
661 | [255] |
|
661 | [255] | |
662 | $ cat hello.txt |
|
662 | $ cat hello.txt | |
663 | goodbye |
|
663 | goodbye | |
664 | $ cat foo.whole |
|
664 | $ cat foo.whole | |
665 | foo |
|
665 | foo | |
666 |
|
666 | |||
667 | $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \ |
|
667 | $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \ | |
668 | > --config "fix.fail:pattern=hello.txt" \ |
|
668 | > --config "fix.fail:pattern=hello.txt" \ | |
669 | > fix --working-dir |
|
669 | > fix --working-dir | |
670 | [wdir] fail: hello.txt: some |
|
670 | [wdir] fail: hello.txt: some | |
671 | [wdir] fail: error that did stop the tool |
|
671 | [wdir] fail: error that did stop the tool | |
672 | $ cat hello.txt |
|
672 | $ cat hello.txt | |
673 | goodbye |
|
673 | goodbye | |
674 | $ cat foo.whole |
|
674 | $ cat foo.whole | |
675 | FOO |
|
675 | FOO | |
676 |
|
676 | |||
677 | $ hg --config "fix.fail:command=exit 42" \ |
|
677 | $ hg --config "fix.fail:command=exit 42" \ | |
678 | > --config "fix.fail:pattern=hello.txt" \ |
|
678 | > --config "fix.fail:pattern=hello.txt" \ | |
679 | > fix --working-dir |
|
679 | > fix --working-dir | |
680 | [wdir] fail: exited with status 42 |
|
680 | [wdir] fail: exited with status 42 | |
681 |
|
681 | |||
682 | $ cd .. |
|
682 | $ cd .. | |
683 |
|
683 | |||
684 | Fixing the working directory and its parent revision at the same time should |
|
684 | Fixing the working directory and its parent revision at the same time should | |
685 | check out the replacement revision for the parent. This prevents any new |
|
685 | check out the replacement revision for the parent. This prevents any new | |
686 | uncommitted changes from appearing. We test this for a clean working directory |
|
686 | uncommitted changes from appearing. We test this for a clean working directory | |
687 | and a dirty one. In both cases, all lines/files changed since the grandparent |
|
687 | and a dirty one. In both cases, all lines/files changed since the grandparent | |
688 | will be fixed. The grandparent is the "baserev" for both the parent and the |
|
688 | will be fixed. The grandparent is the "baserev" for both the parent and the | |
689 | working copy. |
|
689 | working copy. | |
690 |
|
690 | |||
691 | $ hg init fixdotandcleanwdir |
|
691 | $ hg init fixdotandcleanwdir | |
692 | $ cd fixdotandcleanwdir |
|
692 | $ cd fixdotandcleanwdir | |
693 |
|
693 | |||
694 | $ printf "hello\n" > hello.whole |
|
694 | $ printf "hello\n" > hello.whole | |
695 | $ printf "world\n" > world.whole |
|
695 | $ printf "world\n" > world.whole | |
696 | $ hg commit -Aqm "the parent commit" |
|
696 | $ hg commit -Aqm "the parent commit" | |
697 |
|
697 | |||
698 | $ hg parents --template '{rev} {desc}\n' |
|
698 | $ hg parents --template '{rev} {desc}\n' | |
699 | 0 the parent commit |
|
699 | 0 the parent commit | |
700 | $ hg fix --working-dir -r . |
|
700 | $ hg fix --working-dir -r . | |
701 | $ hg parents --template '{rev} {desc}\n' |
|
701 | $ hg parents --template '{rev} {desc}\n' | |
702 | 1 the parent commit |
|
702 | 1 the parent commit | |
703 | $ hg cat -r . *.whole |
|
703 | $ hg cat -r . *.whole | |
704 | HELLO |
|
704 | HELLO | |
705 | WORLD |
|
705 | WORLD | |
706 | $ cat *.whole |
|
706 | $ cat *.whole | |
707 | HELLO |
|
707 | HELLO | |
708 | WORLD |
|
708 | WORLD | |
709 | $ hg status |
|
709 | $ hg status | |
710 |
|
710 | |||
711 | $ cd .. |
|
711 | $ cd .. | |
712 |
|
712 | |||
713 | Same test with a dirty working copy. |
|
713 | Same test with a dirty working copy. | |
714 |
|
714 | |||
715 | $ hg init fixdotanddirtywdir |
|
715 | $ hg init fixdotanddirtywdir | |
716 | $ cd fixdotanddirtywdir |
|
716 | $ cd fixdotanddirtywdir | |
717 |
|
717 | |||
718 | $ printf "hello\n" > hello.whole |
|
718 | $ printf "hello\n" > hello.whole | |
719 | $ printf "world\n" > world.whole |
|
719 | $ printf "world\n" > world.whole | |
720 | $ hg commit -Aqm "the parent commit" |
|
720 | $ hg commit -Aqm "the parent commit" | |
721 |
|
721 | |||
722 | $ printf "hello,\n" > hello.whole |
|
722 | $ printf "hello,\n" > hello.whole | |
723 | $ printf "world!\n" > world.whole |
|
723 | $ printf "world!\n" > world.whole | |
724 |
|
724 | |||
725 | $ hg parents --template '{rev} {desc}\n' |
|
725 | $ hg parents --template '{rev} {desc}\n' | |
726 | 0 the parent commit |
|
726 | 0 the parent commit | |
727 | $ hg fix --working-dir -r . |
|
727 | $ hg fix --working-dir -r . | |
728 | $ hg parents --template '{rev} {desc}\n' |
|
728 | $ hg parents --template '{rev} {desc}\n' | |
729 | 1 the parent commit |
|
729 | 1 the parent commit | |
730 | $ hg cat -r . *.whole |
|
730 | $ hg cat -r . *.whole | |
731 | HELLO |
|
731 | HELLO | |
732 | WORLD |
|
732 | WORLD | |
733 | $ cat *.whole |
|
733 | $ cat *.whole | |
734 | HELLO, |
|
734 | HELLO, | |
735 | WORLD! |
|
735 | WORLD! | |
736 | $ hg status |
|
736 | $ hg status | |
737 | M hello.whole |
|
737 | M hello.whole | |
738 | M world.whole |
|
738 | M world.whole | |
739 |
|
739 | |||
740 | $ cd .. |
|
740 | $ cd .. | |
741 |
|
741 | |||
742 | When we have a chain of commits that change mutually exclusive lines of code, |
|
742 | When we have a chain of commits that change mutually exclusive lines of code, | |
743 | we should be able to do incremental fixing that causes each commit in the chain |
|
743 | we should be able to do incremental fixing that causes each commit in the chain | |
744 | to include fixes made to the previous commits. This prevents children from |
|
744 | to include fixes made to the previous commits. This prevents children from | |
745 | backing out the fixes made in their parents. A dirty working directory is |
|
745 | backing out the fixes made in their parents. A dirty working directory is | |
746 | conceptually similar to another commit in the chain. |
|
746 | conceptually similar to another commit in the chain. | |
747 |
|
747 | |||
748 | $ hg init incrementallyfixchain |
|
748 | $ hg init incrementallyfixchain | |
749 | $ cd incrementallyfixchain |
|
749 | $ cd incrementallyfixchain | |
750 |
|
750 | |||
751 | $ cat > file.changed <<EOF |
|
751 | $ cat > file.changed <<EOF | |
752 | > first |
|
752 | > first | |
753 | > second |
|
753 | > second | |
754 | > third |
|
754 | > third | |
755 | > fourth |
|
755 | > fourth | |
756 | > fifth |
|
756 | > fifth | |
757 | > EOF |
|
757 | > EOF | |
758 | $ hg commit -Aqm "the common ancestor (the baserev)" |
|
758 | $ hg commit -Aqm "the common ancestor (the baserev)" | |
759 | $ cat > file.changed <<EOF |
|
759 | $ cat > file.changed <<EOF | |
760 | > first (changed) |
|
760 | > first (changed) | |
761 | > second |
|
761 | > second | |
762 | > third |
|
762 | > third | |
763 | > fourth |
|
763 | > fourth | |
764 | > fifth |
|
764 | > fifth | |
765 | > EOF |
|
765 | > EOF | |
766 | $ hg commit -Aqm "the first commit to fix" |
|
766 | $ hg commit -Aqm "the first commit to fix" | |
767 | $ cat > file.changed <<EOF |
|
767 | $ cat > file.changed <<EOF | |
768 | > first (changed) |
|
768 | > first (changed) | |
769 | > second |
|
769 | > second | |
770 | > third (changed) |
|
770 | > third (changed) | |
771 | > fourth |
|
771 | > fourth | |
772 | > fifth |
|
772 | > fifth | |
773 | > EOF |
|
773 | > EOF | |
774 | $ hg commit -Aqm "the second commit to fix" |
|
774 | $ hg commit -Aqm "the second commit to fix" | |
775 | $ cat > file.changed <<EOF |
|
775 | $ cat > file.changed <<EOF | |
776 | > first (changed) |
|
776 | > first (changed) | |
777 | > second |
|
777 | > second | |
778 | > third (changed) |
|
778 | > third (changed) | |
779 | > fourth |
|
779 | > fourth | |
780 | > fifth (changed) |
|
780 | > fifth (changed) | |
781 | > EOF |
|
781 | > EOF | |
782 |
|
782 | |||
783 | $ hg fix -r . -r '.^' --working-dir |
|
783 | $ hg fix -r . -r '.^' --working-dir | |
784 |
|
784 | |||
785 | $ hg parents --template '{rev}\n' |
|
785 | $ hg parents --template '{rev}\n' | |
786 | 4 |
|
786 | 4 | |
787 | $ hg cat -r '.^^' file.changed |
|
787 | $ hg cat -r '.^^' file.changed | |
788 | first |
|
788 | first | |
789 | second |
|
789 | second | |
790 | third |
|
790 | third | |
791 | fourth |
|
791 | fourth | |
792 | fifth |
|
792 | fifth | |
793 | $ hg cat -r '.^' file.changed |
|
793 | $ hg cat -r '.^' file.changed | |
794 | FIRST (CHANGED) |
|
794 | FIRST (CHANGED) | |
795 | second |
|
795 | second | |
796 | third |
|
796 | third | |
797 | fourth |
|
797 | fourth | |
798 | fifth |
|
798 | fifth | |
799 | $ hg cat -r . file.changed |
|
799 | $ hg cat -r . file.changed | |
800 | FIRST (CHANGED) |
|
800 | FIRST (CHANGED) | |
801 | second |
|
801 | second | |
802 | THIRD (CHANGED) |
|
802 | THIRD (CHANGED) | |
803 | fourth |
|
803 | fourth | |
804 | fifth |
|
804 | fifth | |
805 | $ cat file.changed |
|
805 | $ cat file.changed | |
806 | FIRST (CHANGED) |
|
806 | FIRST (CHANGED) | |
807 | second |
|
807 | second | |
808 | THIRD (CHANGED) |
|
808 | THIRD (CHANGED) | |
809 | fourth |
|
809 | fourth | |
810 | FIFTH (CHANGED) |
|
810 | FIFTH (CHANGED) | |
811 |
|
811 | |||
812 | $ cd .. |
|
812 | $ cd .. | |
813 |
|
813 | |||
814 | If we incrementally fix a merge commit, we should fix any lines that changed |
|
814 | If we incrementally fix a merge commit, we should fix any lines that changed | |
815 | versus either parent. You could imagine only fixing the intersection or some |
|
815 | versus either parent. You could imagine only fixing the intersection or some | |
816 | other subset, but this is necessary if either parent is being fixed. It |
|
816 | other subset, but this is necessary if either parent is being fixed. It | |
817 | prevents us from forgetting fixes made in either parent. |
|
817 | prevents us from forgetting fixes made in either parent. | |
818 |
|
818 | |||
819 | $ hg init incrementallyfixmergecommit |
|
819 | $ hg init incrementallyfixmergecommit | |
820 | $ cd incrementallyfixmergecommit |
|
820 | $ cd incrementallyfixmergecommit | |
821 |
|
821 | |||
822 | $ printf "a\nb\nc\n" > file.changed |
|
822 | $ printf "a\nb\nc\n" > file.changed | |
823 | $ hg commit -Aqm "ancestor" |
|
823 | $ hg commit -Aqm "ancestor" | |
824 |
|
824 | |||
825 | $ printf "aa\nb\nc\n" > file.changed |
|
825 | $ printf "aa\nb\nc\n" > file.changed | |
826 | $ hg commit -m "change a" |
|
826 | $ hg commit -m "change a" | |
827 |
|
827 | |||
828 | $ hg checkout '.^' |
|
828 | $ hg checkout '.^' | |
829 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
829 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
830 | $ printf "a\nb\ncc\n" > file.changed |
|
830 | $ printf "a\nb\ncc\n" > file.changed | |
831 | $ hg commit -m "change c" |
|
831 | $ hg commit -m "change c" | |
832 | created new head |
|
832 | created new head | |
833 |
|
833 | |||
834 | $ hg merge |
|
834 | $ hg merge | |
835 | merging file.changed |
|
835 | merging file.changed | |
836 | 0 files updated, 1 files merged, 0 files removed, 0 files unresolved |
|
836 | 0 files updated, 1 files merged, 0 files removed, 0 files unresolved | |
837 | (branch merge, don't forget to commit) |
|
837 | (branch merge, don't forget to commit) | |
838 | $ hg commit -m "merge" |
|
838 | $ hg commit -m "merge" | |
839 | $ hg cat -r . file.changed |
|
839 | $ hg cat -r . file.changed | |
840 | aa |
|
840 | aa | |
841 | b |
|
841 | b | |
842 | cc |
|
842 | cc | |
843 |
|
843 | |||
844 | $ hg fix -r . --working-dir |
|
844 | $ hg fix -r . --working-dir | |
845 | $ hg cat -r . file.changed |
|
845 | $ hg cat -r . file.changed | |
846 | AA |
|
846 | AA | |
847 | b |
|
847 | b | |
848 | CC |
|
848 | CC | |
849 |
|
849 | |||
850 | $ cd .. |
|
850 | $ cd .. | |
851 |
|
851 | |||
852 | Abort fixing revisions if there is an unfinished operation. We don't want to |
|
852 | Abort fixing revisions if there is an unfinished operation. We don't want to | |
853 | make things worse by editing files or stripping/obsoleting things. Also abort |
|
853 | make things worse by editing files or stripping/obsoleting things. Also abort | |
854 | fixing the working directory if there are unresolved merge conflicts. |
|
854 | fixing the working directory if there are unresolved merge conflicts. | |
855 |
|
855 | |||
856 | $ hg init abortunresolved |
|
856 | $ hg init abortunresolved | |
857 | $ cd abortunresolved |
|
857 | $ cd abortunresolved | |
858 |
|
858 | |||
859 | $ echo "foo1" > foo.whole |
|
859 | $ echo "foo1" > foo.whole | |
860 | $ hg commit -Aqm "foo 1" |
|
860 | $ hg commit -Aqm "foo 1" | |
861 |
|
861 | |||
862 | $ hg update null |
|
862 | $ hg update null | |
863 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved |
|
863 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved | |
864 | $ echo "foo2" > foo.whole |
|
864 | $ echo "foo2" > foo.whole | |
865 | $ hg commit -Aqm "foo 2" |
|
865 | $ hg commit -Aqm "foo 2" | |
866 |
|
866 | |||
867 | $ hg --config extensions.rebase= rebase -r 1 -d 0 |
|
867 | $ hg --config extensions.rebase= rebase -r 1 -d 0 | |
868 | rebasing 1:c3b6dc0e177a tip "foo 2" |
|
868 | rebasing 1:c3b6dc0e177a tip "foo 2" | |
869 | merging foo.whole |
|
869 | merging foo.whole | |
870 | warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark') |
|
870 | warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark') | |
871 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') |
|
871 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') | |
872 |
[ |
|
872 | [240] | |
873 |
|
873 | |||
874 | $ hg --config extensions.rebase= fix --working-dir |
|
874 | $ hg --config extensions.rebase= fix --working-dir | |
875 | abort: unresolved conflicts |
|
875 | abort: unresolved conflicts | |
876 | (use 'hg resolve') |
|
876 | (use 'hg resolve') | |
877 | [255] |
|
877 | [255] | |
878 |
|
878 | |||
879 | $ hg --config extensions.rebase= fix -r . |
|
879 | $ hg --config extensions.rebase= fix -r . | |
880 | abort: rebase in progress |
|
880 | abort: rebase in progress | |
881 | (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop') |
|
881 | (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop') | |
882 | [255] |
|
882 | [255] | |
883 |
|
883 | |||
884 | $ cd .. |
|
884 | $ cd .. | |
885 |
|
885 | |||
886 | When fixing a file that was renamed, we should diff against the source of the |
|
886 | When fixing a file that was renamed, we should diff against the source of the | |
887 | rename for incremental fixing and we should correctly reproduce the rename in |
|
887 | rename for incremental fixing and we should correctly reproduce the rename in | |
888 | the replacement revision. |
|
888 | the replacement revision. | |
889 |
|
889 | |||
890 | $ hg init fixrenamecommit |
|
890 | $ hg init fixrenamecommit | |
891 | $ cd fixrenamecommit |
|
891 | $ cd fixrenamecommit | |
892 |
|
892 | |||
893 | $ printf "a\nb\nc\n" > source.changed |
|
893 | $ printf "a\nb\nc\n" > source.changed | |
894 | $ hg commit -Aqm "source revision" |
|
894 | $ hg commit -Aqm "source revision" | |
895 | $ hg move source.changed dest.changed |
|
895 | $ hg move source.changed dest.changed | |
896 | $ printf "a\nb\ncc\n" > dest.changed |
|
896 | $ printf "a\nb\ncc\n" > dest.changed | |
897 | $ hg commit -m "dest revision" |
|
897 | $ hg commit -m "dest revision" | |
898 |
|
898 | |||
899 | $ hg fix -r . |
|
899 | $ hg fix -r . | |
900 | $ hg log -r tip --copies --template "{file_copies}\n" |
|
900 | $ hg log -r tip --copies --template "{file_copies}\n" | |
901 | dest.changed (source.changed) |
|
901 | dest.changed (source.changed) | |
902 | $ hg cat -r tip dest.changed |
|
902 | $ hg cat -r tip dest.changed | |
903 | a |
|
903 | a | |
904 | b |
|
904 | b | |
905 | CC |
|
905 | CC | |
906 |
|
906 | |||
907 | $ cd .. |
|
907 | $ cd .. | |
908 |
|
908 | |||
909 | When fixing revisions that remove files we must ensure that the replacement |
|
909 | When fixing revisions that remove files we must ensure that the replacement | |
910 | actually removes the file, whereas it could accidentally leave it unchanged or |
|
910 | actually removes the file, whereas it could accidentally leave it unchanged or | |
911 | write an empty string to it. |
|
911 | write an empty string to it. | |
912 |
|
912 | |||
913 | $ hg init fixremovedfile |
|
913 | $ hg init fixremovedfile | |
914 | $ cd fixremovedfile |
|
914 | $ cd fixremovedfile | |
915 |
|
915 | |||
916 | $ printf "foo\n" > foo.whole |
|
916 | $ printf "foo\n" > foo.whole | |
917 | $ printf "bar\n" > bar.whole |
|
917 | $ printf "bar\n" > bar.whole | |
918 | $ hg commit -Aqm "add files" |
|
918 | $ hg commit -Aqm "add files" | |
919 | $ hg remove bar.whole |
|
919 | $ hg remove bar.whole | |
920 | $ hg commit -m "remove file" |
|
920 | $ hg commit -m "remove file" | |
921 | $ hg status --change . |
|
921 | $ hg status --change . | |
922 | R bar.whole |
|
922 | R bar.whole | |
923 | $ hg fix -r . foo.whole |
|
923 | $ hg fix -r . foo.whole | |
924 | $ hg status --change tip |
|
924 | $ hg status --change tip | |
925 | M foo.whole |
|
925 | M foo.whole | |
926 | R bar.whole |
|
926 | R bar.whole | |
927 |
|
927 | |||
928 | $ cd .. |
|
928 | $ cd .. | |
929 |
|
929 | |||
930 | If fixing a revision finds no fixes to make, no replacement revision should be |
|
930 | If fixing a revision finds no fixes to make, no replacement revision should be | |
931 | created. |
|
931 | created. | |
932 |
|
932 | |||
933 | $ hg init nofixesneeded |
|
933 | $ hg init nofixesneeded | |
934 | $ cd nofixesneeded |
|
934 | $ cd nofixesneeded | |
935 |
|
935 | |||
936 | $ printf "FOO\n" > foo.whole |
|
936 | $ printf "FOO\n" > foo.whole | |
937 | $ hg commit -Aqm "add file" |
|
937 | $ hg commit -Aqm "add file" | |
938 | $ hg log --template '{rev}\n' |
|
938 | $ hg log --template '{rev}\n' | |
939 | 0 |
|
939 | 0 | |
940 | $ hg fix -r . |
|
940 | $ hg fix -r . | |
941 | $ hg log --template '{rev}\n' |
|
941 | $ hg log --template '{rev}\n' | |
942 | 0 |
|
942 | 0 | |
943 |
|
943 | |||
944 | $ cd .. |
|
944 | $ cd .. | |
945 |
|
945 | |||
946 | If fixing a commit reverts all the changes in the commit, we replace it with a |
|
946 | If fixing a commit reverts all the changes in the commit, we replace it with a | |
947 | commit that changes no files. |
|
947 | commit that changes no files. | |
948 |
|
948 | |||
949 | $ hg init nochangesleft |
|
949 | $ hg init nochangesleft | |
950 | $ cd nochangesleft |
|
950 | $ cd nochangesleft | |
951 |
|
951 | |||
952 | $ printf "FOO\n" > foo.whole |
|
952 | $ printf "FOO\n" > foo.whole | |
953 | $ hg commit -Aqm "add file" |
|
953 | $ hg commit -Aqm "add file" | |
954 | $ printf "foo\n" > foo.whole |
|
954 | $ printf "foo\n" > foo.whole | |
955 | $ hg commit -m "edit file" |
|
955 | $ hg commit -m "edit file" | |
956 | $ hg status --change . |
|
956 | $ hg status --change . | |
957 | M foo.whole |
|
957 | M foo.whole | |
958 | $ hg fix -r . |
|
958 | $ hg fix -r . | |
959 | $ hg status --change tip |
|
959 | $ hg status --change tip | |
960 |
|
960 | |||
961 | $ cd .. |
|
961 | $ cd .. | |
962 |
|
962 | |||
963 | If we fix a parent and child revision together, the child revision must be |
|
963 | If we fix a parent and child revision together, the child revision must be | |
964 | replaced if the parent is replaced, even if the diffs of the child needed no |
|
964 | replaced if the parent is replaced, even if the diffs of the child needed no | |
965 | fixes. However, we're free to not replace revisions that need no fixes and have |
|
965 | fixes. However, we're free to not replace revisions that need no fixes and have | |
966 | no ancestors that are replaced. |
|
966 | no ancestors that are replaced. | |
967 |
|
967 | |||
968 | $ hg init mustreplacechild |
|
968 | $ hg init mustreplacechild | |
969 | $ cd mustreplacechild |
|
969 | $ cd mustreplacechild | |
970 |
|
970 | |||
971 | $ printf "FOO\n" > foo.whole |
|
971 | $ printf "FOO\n" > foo.whole | |
972 | $ hg commit -Aqm "add foo" |
|
972 | $ hg commit -Aqm "add foo" | |
973 | $ printf "foo\n" > foo.whole |
|
973 | $ printf "foo\n" > foo.whole | |
974 | $ hg commit -m "edit foo" |
|
974 | $ hg commit -m "edit foo" | |
975 | $ printf "BAR\n" > bar.whole |
|
975 | $ printf "BAR\n" > bar.whole | |
976 | $ hg commit -Aqm "add bar" |
|
976 | $ hg commit -Aqm "add bar" | |
977 |
|
977 | |||
978 | $ hg log --graph --template '{rev} {files}' |
|
978 | $ hg log --graph --template '{rev} {files}' | |
979 | @ 2 bar.whole |
|
979 | @ 2 bar.whole | |
980 | | |
|
980 | | | |
981 | o 1 foo.whole |
|
981 | o 1 foo.whole | |
982 | | |
|
982 | | | |
983 | o 0 foo.whole |
|
983 | o 0 foo.whole | |
984 |
|
984 | |||
985 | $ hg fix -r 0:2 |
|
985 | $ hg fix -r 0:2 | |
986 | $ hg log --graph --template '{rev} {files}' |
|
986 | $ hg log --graph --template '{rev} {files}' | |
987 | o 4 bar.whole |
|
987 | o 4 bar.whole | |
988 | | |
|
988 | | | |
989 | o 3 |
|
989 | o 3 | |
990 | | |
|
990 | | | |
991 | | @ 2 bar.whole |
|
991 | | @ 2 bar.whole | |
992 | | | |
|
992 | | | | |
993 | | x 1 foo.whole |
|
993 | | x 1 foo.whole | |
994 | |/ |
|
994 | |/ | |
995 | o 0 foo.whole |
|
995 | o 0 foo.whole | |
996 |
|
996 | |||
997 |
|
997 | |||
998 | $ cd .. |
|
998 | $ cd .. | |
999 |
|
999 | |||
1000 | It's also possible that the child needs absolutely no changes, but we still |
|
1000 | It's also possible that the child needs absolutely no changes, but we still | |
1001 | need to replace it to update its parent. If we skipped replacing the child |
|
1001 | need to replace it to update its parent. If we skipped replacing the child | |
1002 | because it had no file content changes, it would become an orphan for no good |
|
1002 | because it had no file content changes, it would become an orphan for no good | |
1003 | reason. |
|
1003 | reason. | |
1004 |
|
1004 | |||
1005 | $ hg init mustreplacechildevenifnop |
|
1005 | $ hg init mustreplacechildevenifnop | |
1006 | $ cd mustreplacechildevenifnop |
|
1006 | $ cd mustreplacechildevenifnop | |
1007 |
|
1007 | |||
1008 | $ printf "Foo\n" > foo.whole |
|
1008 | $ printf "Foo\n" > foo.whole | |
1009 | $ hg commit -Aqm "add a bad foo" |
|
1009 | $ hg commit -Aqm "add a bad foo" | |
1010 | $ printf "FOO\n" > foo.whole |
|
1010 | $ printf "FOO\n" > foo.whole | |
1011 | $ hg commit -m "add a good foo" |
|
1011 | $ hg commit -m "add a good foo" | |
1012 | $ hg fix -r . -r '.^' |
|
1012 | $ hg fix -r . -r '.^' | |
1013 | $ hg log --graph --template '{rev} {desc}' |
|
1013 | $ hg log --graph --template '{rev} {desc}' | |
1014 | o 3 add a good foo |
|
1014 | o 3 add a good foo | |
1015 | | |
|
1015 | | | |
1016 | o 2 add a bad foo |
|
1016 | o 2 add a bad foo | |
1017 |
|
1017 | |||
1018 | @ 1 add a good foo |
|
1018 | @ 1 add a good foo | |
1019 | | |
|
1019 | | | |
1020 | x 0 add a bad foo |
|
1020 | x 0 add a bad foo | |
1021 |
|
1021 | |||
1022 |
|
1022 | |||
1023 | $ cd .. |
|
1023 | $ cd .. | |
1024 |
|
1024 | |||
1025 | Similar to the case above, the child revision may become empty as a result of |
|
1025 | Similar to the case above, the child revision may become empty as a result of | |
1026 | fixing its parent. We should still create an empty replacement child. |
|
1026 | fixing its parent. We should still create an empty replacement child. | |
1027 | TODO: determine how this should interact with ui.allowemptycommit given that |
|
1027 | TODO: determine how this should interact with ui.allowemptycommit given that | |
1028 | the empty replacement could have children. |
|
1028 | the empty replacement could have children. | |
1029 |
|
1029 | |||
1030 | $ hg init mustreplacechildevenifempty |
|
1030 | $ hg init mustreplacechildevenifempty | |
1031 | $ cd mustreplacechildevenifempty |
|
1031 | $ cd mustreplacechildevenifempty | |
1032 |
|
1032 | |||
1033 | $ printf "foo\n" > foo.whole |
|
1033 | $ printf "foo\n" > foo.whole | |
1034 | $ hg commit -Aqm "add foo" |
|
1034 | $ hg commit -Aqm "add foo" | |
1035 | $ printf "Foo\n" > foo.whole |
|
1035 | $ printf "Foo\n" > foo.whole | |
1036 | $ hg commit -m "edit foo" |
|
1036 | $ hg commit -m "edit foo" | |
1037 | $ hg fix -r . -r '.^' |
|
1037 | $ hg fix -r . -r '.^' | |
1038 | $ hg log --graph --template '{rev} {desc}\n' --stat |
|
1038 | $ hg log --graph --template '{rev} {desc}\n' --stat | |
1039 | o 3 edit foo |
|
1039 | o 3 edit foo | |
1040 | | |
|
1040 | | | |
1041 | o 2 add foo |
|
1041 | o 2 add foo | |
1042 | foo.whole | 1 + |
|
1042 | foo.whole | 1 + | |
1043 | 1 files changed, 1 insertions(+), 0 deletions(-) |
|
1043 | 1 files changed, 1 insertions(+), 0 deletions(-) | |
1044 |
|
1044 | |||
1045 | @ 1 edit foo |
|
1045 | @ 1 edit foo | |
1046 | | foo.whole | 2 +- |
|
1046 | | foo.whole | 2 +- | |
1047 | | 1 files changed, 1 insertions(+), 1 deletions(-) |
|
1047 | | 1 files changed, 1 insertions(+), 1 deletions(-) | |
1048 | | |
|
1048 | | | |
1049 | x 0 add foo |
|
1049 | x 0 add foo | |
1050 | foo.whole | 1 + |
|
1050 | foo.whole | 1 + | |
1051 | 1 files changed, 1 insertions(+), 0 deletions(-) |
|
1051 | 1 files changed, 1 insertions(+), 0 deletions(-) | |
1052 |
|
1052 | |||
1053 |
|
1053 | |||
1054 | $ cd .. |
|
1054 | $ cd .. | |
1055 |
|
1055 | |||
1056 | Fixing a secret commit should replace it with another secret commit. |
|
1056 | Fixing a secret commit should replace it with another secret commit. | |
1057 |
|
1057 | |||
1058 | $ hg init fixsecretcommit |
|
1058 | $ hg init fixsecretcommit | |
1059 | $ cd fixsecretcommit |
|
1059 | $ cd fixsecretcommit | |
1060 |
|
1060 | |||
1061 | $ printf "foo\n" > foo.whole |
|
1061 | $ printf "foo\n" > foo.whole | |
1062 | $ hg commit -Aqm "add foo" --secret |
|
1062 | $ hg commit -Aqm "add foo" --secret | |
1063 | $ hg fix -r . |
|
1063 | $ hg fix -r . | |
1064 | $ hg log --template '{rev} {phase}\n' |
|
1064 | $ hg log --template '{rev} {phase}\n' | |
1065 | 1 secret |
|
1065 | 1 secret | |
1066 | 0 secret |
|
1066 | 0 secret | |
1067 |
|
1067 | |||
1068 | $ cd .. |
|
1068 | $ cd .. | |
1069 |
|
1069 | |||
1070 | We should also preserve phase when fixing a draft commit while the user has |
|
1070 | We should also preserve phase when fixing a draft commit while the user has | |
1071 | their default set to secret. |
|
1071 | their default set to secret. | |
1072 |
|
1072 | |||
1073 | $ hg init respectphasesnewcommit |
|
1073 | $ hg init respectphasesnewcommit | |
1074 | $ cd respectphasesnewcommit |
|
1074 | $ cd respectphasesnewcommit | |
1075 |
|
1075 | |||
1076 | $ printf "foo\n" > foo.whole |
|
1076 | $ printf "foo\n" > foo.whole | |
1077 | $ hg commit -Aqm "add foo" |
|
1077 | $ hg commit -Aqm "add foo" | |
1078 | $ hg --config phases.newcommit=secret fix -r . |
|
1078 | $ hg --config phases.newcommit=secret fix -r . | |
1079 | $ hg log --template '{rev} {phase}\n' |
|
1079 | $ hg log --template '{rev} {phase}\n' | |
1080 | 1 draft |
|
1080 | 1 draft | |
1081 | 0 draft |
|
1081 | 0 draft | |
1082 |
|
1082 | |||
1083 | $ cd .. |
|
1083 | $ cd .. | |
1084 |
|
1084 | |||
1085 | Debug output should show what fixer commands are being subprocessed, which is |
|
1085 | Debug output should show what fixer commands are being subprocessed, which is | |
1086 | useful for anyone trying to set up a new config. |
|
1086 | useful for anyone trying to set up a new config. | |
1087 |
|
1087 | |||
1088 | $ hg init debugoutput |
|
1088 | $ hg init debugoutput | |
1089 | $ cd debugoutput |
|
1089 | $ cd debugoutput | |
1090 |
|
1090 | |||
1091 | $ printf "foo\nbar\nbaz\n" > foo.changed |
|
1091 | $ printf "foo\nbar\nbaz\n" > foo.changed | |
1092 | $ hg commit -Aqm "foo" |
|
1092 | $ hg commit -Aqm "foo" | |
1093 | $ printf "Foo\nbar\nBaz\n" > foo.changed |
|
1093 | $ printf "Foo\nbar\nBaz\n" > foo.changed | |
1094 | $ hg --debug fix --working-dir |
|
1094 | $ hg --debug fix --working-dir | |
1095 | subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob) |
|
1095 | subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob) | |
1096 |
|
1096 | |||
1097 | $ cd .. |
|
1097 | $ cd .. | |
1098 |
|
1098 | |||
1099 | Fixing an obsolete revision can cause divergence, so we abort unless the user |
|
1099 | Fixing an obsolete revision can cause divergence, so we abort unless the user | |
1100 | configures to allow it. This is not yet smart enough to know whether there is a |
|
1100 | configures to allow it. This is not yet smart enough to know whether there is a | |
1101 | successor, but even then it is not likely intentional or idiomatic to fix an |
|
1101 | successor, but even then it is not likely intentional or idiomatic to fix an | |
1102 | obsolete revision. |
|
1102 | obsolete revision. | |
1103 |
|
1103 | |||
1104 | $ hg init abortobsoleterev |
|
1104 | $ hg init abortobsoleterev | |
1105 | $ cd abortobsoleterev |
|
1105 | $ cd abortobsoleterev | |
1106 |
|
1106 | |||
1107 | $ printf "foo\n" > foo.changed |
|
1107 | $ printf "foo\n" > foo.changed | |
1108 | $ hg commit -Aqm "foo" |
|
1108 | $ hg commit -Aqm "foo" | |
1109 | $ hg debugobsolete `hg parents --template '{node}'` |
|
1109 | $ hg debugobsolete `hg parents --template '{node}'` | |
1110 | 1 new obsolescence markers |
|
1110 | 1 new obsolescence markers | |
1111 | obsoleted 1 changesets |
|
1111 | obsoleted 1 changesets | |
1112 | $ hg --hidden fix -r 0 |
|
1112 | $ hg --hidden fix -r 0 | |
1113 | abort: fixing obsolete revision could cause divergence |
|
1113 | abort: fixing obsolete revision could cause divergence | |
1114 | [255] |
|
1114 | [255] | |
1115 |
|
1115 | |||
1116 | $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true |
|
1116 | $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true | |
1117 | $ hg cat -r tip foo.changed |
|
1117 | $ hg cat -r tip foo.changed | |
1118 | FOO |
|
1118 | FOO | |
1119 |
|
1119 | |||
1120 | $ cd .. |
|
1120 | $ cd .. | |
1121 |
|
1121 | |||
1122 | Test all of the available substitution values for fixer commands. |
|
1122 | Test all of the available substitution values for fixer commands. | |
1123 |
|
1123 | |||
1124 | $ hg init substitution |
|
1124 | $ hg init substitution | |
1125 | $ cd substitution |
|
1125 | $ cd substitution | |
1126 |
|
1126 | |||
1127 | $ mkdir foo |
|
1127 | $ mkdir foo | |
1128 | $ printf "hello\ngoodbye\n" > foo/bar |
|
1128 | $ printf "hello\ngoodbye\n" > foo/bar | |
1129 | $ hg add |
|
1129 | $ hg add | |
1130 | adding foo/bar |
|
1130 | adding foo/bar | |
1131 | $ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \ |
|
1131 | $ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \ | |
1132 | > --config "fix.fail:linerange='{first}' '{last}'" \ |
|
1132 | > --config "fix.fail:linerange='{first}' '{last}'" \ | |
1133 | > --config "fix.fail:pattern=foo/bar" \ |
|
1133 | > --config "fix.fail:pattern=foo/bar" \ | |
1134 | > fix --working-dir |
|
1134 | > fix --working-dir | |
1135 | $ cat foo/bar |
|
1135 | $ cat foo/bar | |
1136 | foo/bar |
|
1136 | foo/bar | |
1137 | bar |
|
1137 | bar | |
1138 | 1 |
|
1138 | 1 | |
1139 | 2 |
|
1139 | 2 | |
1140 |
|
1140 | |||
1141 | $ cd .. |
|
1141 | $ cd .. | |
1142 |
|
1142 | |||
1143 | The --base flag should allow picking the revisions to diff against for changed |
|
1143 | The --base flag should allow picking the revisions to diff against for changed | |
1144 | files and incremental line formatting. |
|
1144 | files and incremental line formatting. | |
1145 |
|
1145 | |||
1146 | $ hg init baseflag |
|
1146 | $ hg init baseflag | |
1147 | $ cd baseflag |
|
1147 | $ cd baseflag | |
1148 |
|
1148 | |||
1149 | $ printf "one\ntwo\n" > foo.changed |
|
1149 | $ printf "one\ntwo\n" > foo.changed | |
1150 | $ printf "bar\n" > bar.changed |
|
1150 | $ printf "bar\n" > bar.changed | |
1151 | $ hg commit -Aqm "first" |
|
1151 | $ hg commit -Aqm "first" | |
1152 | $ printf "one\nTwo\n" > foo.changed |
|
1152 | $ printf "one\nTwo\n" > foo.changed | |
1153 | $ hg commit -m "second" |
|
1153 | $ hg commit -m "second" | |
1154 | $ hg fix -w --base . |
|
1154 | $ hg fix -w --base . | |
1155 | $ hg status |
|
1155 | $ hg status | |
1156 | $ hg fix -w --base null |
|
1156 | $ hg fix -w --base null | |
1157 | $ cat foo.changed |
|
1157 | $ cat foo.changed | |
1158 | ONE |
|
1158 | ONE | |
1159 | TWO |
|
1159 | TWO | |
1160 | $ cat bar.changed |
|
1160 | $ cat bar.changed | |
1161 | BAR |
|
1161 | BAR | |
1162 |
|
1162 | |||
1163 | $ cd .. |
|
1163 | $ cd .. | |
1164 |
|
1164 | |||
1165 | If the user asks to fix the parent of another commit, they are asking to create |
|
1165 | If the user asks to fix the parent of another commit, they are asking to create | |
1166 | an orphan. We must respect experimental.evolution.allowunstable. |
|
1166 | an orphan. We must respect experimental.evolution.allowunstable. | |
1167 |
|
1167 | |||
1168 | $ hg init allowunstable |
|
1168 | $ hg init allowunstable | |
1169 | $ cd allowunstable |
|
1169 | $ cd allowunstable | |
1170 |
|
1170 | |||
1171 | $ printf "one\n" > foo.whole |
|
1171 | $ printf "one\n" > foo.whole | |
1172 | $ hg commit -Aqm "first" |
|
1172 | $ hg commit -Aqm "first" | |
1173 | $ printf "two\n" > foo.whole |
|
1173 | $ printf "two\n" > foo.whole | |
1174 | $ hg commit -m "second" |
|
1174 | $ hg commit -m "second" | |
1175 | $ hg --config experimental.evolution.allowunstable=False fix -r '.^' |
|
1175 | $ hg --config experimental.evolution.allowunstable=False fix -r '.^' | |
1176 | abort: cannot fix changeset with children |
|
1176 | abort: cannot fix changeset with children | |
1177 | [255] |
|
1177 | [255] | |
1178 | $ hg fix -r '.^' |
|
1178 | $ hg fix -r '.^' | |
1179 | 1 new orphan changesets |
|
1179 | 1 new orphan changesets | |
1180 | $ hg cat -r 2 foo.whole |
|
1180 | $ hg cat -r 2 foo.whole | |
1181 | ONE |
|
1181 | ONE | |
1182 |
|
1182 | |||
1183 | $ cd .. |
|
1183 | $ cd .. | |
1184 |
|
1184 | |||
1185 | The --base flag affects the set of files being fixed. So while the --whole flag |
|
1185 | The --base flag affects the set of files being fixed. So while the --whole flag | |
1186 | makes the base irrelevant for changed line ranges, it still changes the |
|
1186 | makes the base irrelevant for changed line ranges, it still changes the | |
1187 | meaning and effect of the command. In this example, no files or lines are fixed |
|
1187 | meaning and effect of the command. In this example, no files or lines are fixed | |
1188 | until we specify the base, but then we do fix unchanged lines. |
|
1188 | until we specify the base, but then we do fix unchanged lines. | |
1189 |
|
1189 | |||
1190 | $ hg init basewhole |
|
1190 | $ hg init basewhole | |
1191 | $ cd basewhole |
|
1191 | $ cd basewhole | |
1192 | $ printf "foo1\n" > foo.changed |
|
1192 | $ printf "foo1\n" > foo.changed | |
1193 | $ hg commit -Aqm "first" |
|
1193 | $ hg commit -Aqm "first" | |
1194 | $ printf "foo2\n" >> foo.changed |
|
1194 | $ printf "foo2\n" >> foo.changed | |
1195 | $ printf "bar\n" > bar.changed |
|
1195 | $ printf "bar\n" > bar.changed | |
1196 | $ hg commit -Aqm "second" |
|
1196 | $ hg commit -Aqm "second" | |
1197 |
|
1197 | |||
1198 | $ hg fix --working-dir --whole |
|
1198 | $ hg fix --working-dir --whole | |
1199 | $ cat *.changed |
|
1199 | $ cat *.changed | |
1200 | bar |
|
1200 | bar | |
1201 | foo1 |
|
1201 | foo1 | |
1202 | foo2 |
|
1202 | foo2 | |
1203 |
|
1203 | |||
1204 | $ hg fix --working-dir --base 0 --whole |
|
1204 | $ hg fix --working-dir --base 0 --whole | |
1205 | $ cat *.changed |
|
1205 | $ cat *.changed | |
1206 | BAR |
|
1206 | BAR | |
1207 | FOO1 |
|
1207 | FOO1 | |
1208 | FOO2 |
|
1208 | FOO2 | |
1209 |
|
1209 | |||
1210 | $ cd .. |
|
1210 | $ cd .. | |
1211 |
|
1211 | |||
1212 | The execution order of tools can be controlled. This example doesn't work if |
|
1212 | The execution order of tools can be controlled. This example doesn't work if | |
1213 | you sort after truncating, but the config defines the correct order while the |
|
1213 | you sort after truncating, but the config defines the correct order while the | |
1214 | definitions are out of order (which might imply the incorrect order given the |
|
1214 | definitions are out of order (which might imply the incorrect order given the | |
1215 | implementation of fix). The goal is to use multiple tools to select the lowest |
|
1215 | implementation of fix). The goal is to use multiple tools to select the lowest | |
1216 | 5 numbers in the file. |
|
1216 | 5 numbers in the file. | |
1217 |
|
1217 | |||
1218 | $ hg init priorityexample |
|
1218 | $ hg init priorityexample | |
1219 | $ cd priorityexample |
|
1219 | $ cd priorityexample | |
1220 |
|
1220 | |||
1221 | $ cat >> .hg/hgrc <<EOF |
|
1221 | $ cat >> .hg/hgrc <<EOF | |
1222 | > [fix] |
|
1222 | > [fix] | |
1223 | > head:command = head -n 5 |
|
1223 | > head:command = head -n 5 | |
1224 | > head:pattern = numbers.txt |
|
1224 | > head:pattern = numbers.txt | |
1225 | > head:priority = 1 |
|
1225 | > head:priority = 1 | |
1226 | > sort:command = sort -n |
|
1226 | > sort:command = sort -n | |
1227 | > sort:pattern = numbers.txt |
|
1227 | > sort:pattern = numbers.txt | |
1228 | > sort:priority = 2 |
|
1228 | > sort:priority = 2 | |
1229 | > EOF |
|
1229 | > EOF | |
1230 |
|
1230 | |||
1231 | $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt |
|
1231 | $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt | |
1232 | $ hg add -q |
|
1232 | $ hg add -q | |
1233 | $ hg fix -w |
|
1233 | $ hg fix -w | |
1234 | $ cat numbers.txt |
|
1234 | $ cat numbers.txt | |
1235 | 0 |
|
1235 | 0 | |
1236 | 1 |
|
1236 | 1 | |
1237 | 2 |
|
1237 | 2 | |
1238 | 3 |
|
1238 | 3 | |
1239 | 4 |
|
1239 | 4 | |
1240 |
|
1240 | |||
1241 | And of course we should be able to break this by reversing the execution order. |
|
1241 | And of course we should be able to break this by reversing the execution order. | |
1242 | Test negative priorities while we're at it. |
|
1242 | Test negative priorities while we're at it. | |
1243 |
|
1243 | |||
1244 | $ cat >> .hg/hgrc <<EOF |
|
1244 | $ cat >> .hg/hgrc <<EOF | |
1245 | > [fix] |
|
1245 | > [fix] | |
1246 | > head:priority = -1 |
|
1246 | > head:priority = -1 | |
1247 | > sort:priority = -2 |
|
1247 | > sort:priority = -2 | |
1248 | > EOF |
|
1248 | > EOF | |
1249 | $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt |
|
1249 | $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt | |
1250 | $ hg fix -w |
|
1250 | $ hg fix -w | |
1251 | $ cat numbers.txt |
|
1251 | $ cat numbers.txt | |
1252 | 2 |
|
1252 | 2 | |
1253 | 3 |
|
1253 | 3 | |
1254 | 6 |
|
1254 | 6 | |
1255 | 7 |
|
1255 | 7 | |
1256 | 8 |
|
1256 | 8 | |
1257 |
|
1257 | |||
1258 | $ cd .. |
|
1258 | $ cd .. | |
1259 |
|
1259 | |||
1260 | It's possible for repeated applications of a fixer tool to create cycles in the |
|
1260 | It's possible for repeated applications of a fixer tool to create cycles in the | |
1261 | generated content of a file. For example, two users with different versions of |
|
1261 | generated content of a file. For example, two users with different versions of | |
1262 | a code formatter might fight over the formatting when they run hg fix. In the |
|
1262 | a code formatter might fight over the formatting when they run hg fix. In the | |
1263 | absence of other changes, this means we could produce commits with the same |
|
1263 | absence of other changes, this means we could produce commits with the same | |
1264 | hash in subsequent runs of hg fix. This is a problem unless we support |
|
1264 | hash in subsequent runs of hg fix. This is a problem unless we support | |
1265 | obsolescence cycles well. We avoid this by adding an extra field to the |
|
1265 | obsolescence cycles well. We avoid this by adding an extra field to the | |
1266 | successor which forces it to have a new hash. That's why this test creates |
|
1266 | successor which forces it to have a new hash. That's why this test creates | |
1267 | three revisions instead of two. |
|
1267 | three revisions instead of two. | |
1268 |
|
1268 | |||
1269 | $ hg init cyclictool |
|
1269 | $ hg init cyclictool | |
1270 | $ cd cyclictool |
|
1270 | $ cd cyclictool | |
1271 |
|
1271 | |||
1272 | $ cat >> .hg/hgrc <<EOF |
|
1272 | $ cat >> .hg/hgrc <<EOF | |
1273 | > [fix] |
|
1273 | > [fix] | |
1274 | > swapletters:command = tr ab ba |
|
1274 | > swapletters:command = tr ab ba | |
1275 | > swapletters:pattern = foo |
|
1275 | > swapletters:pattern = foo | |
1276 | > EOF |
|
1276 | > EOF | |
1277 |
|
1277 | |||
1278 | $ echo ab > foo |
|
1278 | $ echo ab > foo | |
1279 | $ hg commit -Aqm foo |
|
1279 | $ hg commit -Aqm foo | |
1280 |
|
1280 | |||
1281 | $ hg fix -r 0 |
|
1281 | $ hg fix -r 0 | |
1282 | $ hg fix -r 1 |
|
1282 | $ hg fix -r 1 | |
1283 |
|
1283 | |||
1284 | $ hg cat -r 0 foo --hidden |
|
1284 | $ hg cat -r 0 foo --hidden | |
1285 | ab |
|
1285 | ab | |
1286 | $ hg cat -r 1 foo --hidden |
|
1286 | $ hg cat -r 1 foo --hidden | |
1287 | ba |
|
1287 | ba | |
1288 | $ hg cat -r 2 foo |
|
1288 | $ hg cat -r 2 foo | |
1289 | ab |
|
1289 | ab | |
1290 |
|
1290 | |||
1291 | $ cd .. |
|
1291 | $ cd .. | |
1292 |
|
1292 | |||
1293 | We run fixer tools in the repo root so they can look for config files or other |
|
1293 | We run fixer tools in the repo root so they can look for config files or other | |
1294 | important things in the working directory. This does NOT mean we are |
|
1294 | important things in the working directory. This does NOT mean we are | |
1295 | reconstructing a working copy of every revision being fixed; we're just giving |
|
1295 | reconstructing a working copy of every revision being fixed; we're just giving | |
1296 | the tool knowledge of the repo's location in case it can do something |
|
1296 | the tool knowledge of the repo's location in case it can do something | |
1297 | reasonable with that. |
|
1297 | reasonable with that. | |
1298 |
|
1298 | |||
1299 | $ hg init subprocesscwd |
|
1299 | $ hg init subprocesscwd | |
1300 | $ cd subprocesscwd |
|
1300 | $ cd subprocesscwd | |
1301 |
|
1301 | |||
1302 | $ cat >> .hg/hgrc <<EOF |
|
1302 | $ cat >> .hg/hgrc <<EOF | |
1303 | > [fix] |
|
1303 | > [fix] | |
1304 | > printcwd:command = "$PYTHON" -c "import os; print(os.getcwd())" |
|
1304 | > printcwd:command = "$PYTHON" -c "import os; print(os.getcwd())" | |
1305 | > printcwd:pattern = relpath:foo/bar |
|
1305 | > printcwd:pattern = relpath:foo/bar | |
1306 | > filesetpwd:command = "$PYTHON" -c "import os; print('fs: ' + os.getcwd())" |
|
1306 | > filesetpwd:command = "$PYTHON" -c "import os; print('fs: ' + os.getcwd())" | |
1307 | > filesetpwd:pattern = set:**quux |
|
1307 | > filesetpwd:pattern = set:**quux | |
1308 | > EOF |
|
1308 | > EOF | |
1309 |
|
1309 | |||
1310 | $ mkdir foo |
|
1310 | $ mkdir foo | |
1311 | $ printf "bar\n" > foo/bar |
|
1311 | $ printf "bar\n" > foo/bar | |
1312 | $ printf "quux\n" > quux |
|
1312 | $ printf "quux\n" > quux | |
1313 | $ hg commit -Aqm blah |
|
1313 | $ hg commit -Aqm blah | |
1314 |
|
1314 | |||
1315 | $ hg fix -w -r . foo/bar |
|
1315 | $ hg fix -w -r . foo/bar | |
1316 | $ hg cat -r tip foo/bar |
|
1316 | $ hg cat -r tip foo/bar | |
1317 | $TESTTMP/subprocesscwd |
|
1317 | $TESTTMP/subprocesscwd | |
1318 | $ cat foo/bar |
|
1318 | $ cat foo/bar | |
1319 | $TESTTMP/subprocesscwd |
|
1319 | $TESTTMP/subprocesscwd | |
1320 |
|
1320 | |||
1321 | $ cd foo |
|
1321 | $ cd foo | |
1322 |
|
1322 | |||
1323 | $ hg fix -w -r . bar |
|
1323 | $ hg fix -w -r . bar | |
1324 | $ hg cat -r tip bar ../quux |
|
1324 | $ hg cat -r tip bar ../quux | |
1325 | $TESTTMP/subprocesscwd |
|
1325 | $TESTTMP/subprocesscwd | |
1326 | quux |
|
1326 | quux | |
1327 | $ cat bar ../quux |
|
1327 | $ cat bar ../quux | |
1328 | $TESTTMP/subprocesscwd |
|
1328 | $TESTTMP/subprocesscwd | |
1329 | quux |
|
1329 | quux | |
1330 | $ echo modified > bar |
|
1330 | $ echo modified > bar | |
1331 | $ hg fix -w bar |
|
1331 | $ hg fix -w bar | |
1332 | $ cat bar |
|
1332 | $ cat bar | |
1333 | $TESTTMP/subprocesscwd |
|
1333 | $TESTTMP/subprocesscwd | |
1334 |
|
1334 | |||
1335 | Apparently fixing p1() and its descendants doesn't include wdir() unless |
|
1335 | Apparently fixing p1() and its descendants doesn't include wdir() unless | |
1336 | explicitly stated. |
|
1336 | explicitly stated. | |
1337 |
|
1337 | |||
1338 | $ hg fix -r '.::' |
|
1338 | $ hg fix -r '.::' | |
1339 | $ hg cat -r . ../quux |
|
1339 | $ hg cat -r . ../quux | |
1340 | quux |
|
1340 | quux | |
1341 | $ hg cat -r tip ../quux |
|
1341 | $ hg cat -r tip ../quux | |
1342 | fs: $TESTTMP/subprocesscwd |
|
1342 | fs: $TESTTMP/subprocesscwd | |
1343 | $ cat ../quux |
|
1343 | $ cat ../quux | |
1344 | quux |
|
1344 | quux | |
1345 |
|
1345 | |||
1346 | Clean files are not fixed unless explicitly named |
|
1346 | Clean files are not fixed unless explicitly named | |
1347 | $ echo 'dirty' > ../quux |
|
1347 | $ echo 'dirty' > ../quux | |
1348 |
|
1348 | |||
1349 | $ hg fix --working-dir |
|
1349 | $ hg fix --working-dir | |
1350 | $ cat ../quux |
|
1350 | $ cat ../quux | |
1351 | fs: $TESTTMP/subprocesscwd |
|
1351 | fs: $TESTTMP/subprocesscwd | |
1352 |
|
1352 | |||
1353 | $ cd ../.. |
|
1353 | $ cd ../.. | |
1354 |
|
1354 | |||
1355 | Tools configured without a pattern are ignored. It would be too dangerous to |
|
1355 | Tools configured without a pattern are ignored. It would be too dangerous to | |
1356 | run them on all files, because this might happen while testing a configuration |
|
1356 | run them on all files, because this might happen while testing a configuration | |
1357 | that also deletes all of the file content. There is no reasonable subset of the |
|
1357 | that also deletes all of the file content. There is no reasonable subset of the | |
1358 | files to use as a default. Users should be explicit about what files are |
|
1358 | files to use as a default. Users should be explicit about what files are | |
1359 | affected by a tool. This test also confirms that we don't crash when the |
|
1359 | affected by a tool. This test also confirms that we don't crash when the | |
1360 | pattern config is missing, and that we only warn about it once. |
|
1360 | pattern config is missing, and that we only warn about it once. | |
1361 |
|
1361 | |||
1362 | $ hg init nopatternconfigured |
|
1362 | $ hg init nopatternconfigured | |
1363 | $ cd nopatternconfigured |
|
1363 | $ cd nopatternconfigured | |
1364 |
|
1364 | |||
1365 | $ printf "foo" > foo |
|
1365 | $ printf "foo" > foo | |
1366 | $ printf "bar" > bar |
|
1366 | $ printf "bar" > bar | |
1367 | $ hg add -q |
|
1367 | $ hg add -q | |
1368 | $ hg fix --debug --working-dir --config "fix.nopattern:command=echo fixed" |
|
1368 | $ hg fix --debug --working-dir --config "fix.nopattern:command=echo fixed" | |
1369 | fixer tool has no pattern configuration: nopattern |
|
1369 | fixer tool has no pattern configuration: nopattern | |
1370 | $ cat foo bar |
|
1370 | $ cat foo bar | |
1371 | foobar (no-eol) |
|
1371 | foobar (no-eol) | |
1372 | $ hg fix --debug --working-dir --config "fix.nocommand:pattern=foo.bar" |
|
1372 | $ hg fix --debug --working-dir --config "fix.nocommand:pattern=foo.bar" | |
1373 | fixer tool has no command configuration: nocommand |
|
1373 | fixer tool has no command configuration: nocommand | |
1374 |
|
1374 | |||
1375 | $ cd .. |
|
1375 | $ cd .. | |
1376 |
|
1376 | |||
1377 | Tools can be disabled. Disabled tools do nothing but print a debug message. |
|
1377 | Tools can be disabled. Disabled tools do nothing but print a debug message. | |
1378 |
|
1378 | |||
1379 | $ hg init disabled |
|
1379 | $ hg init disabled | |
1380 | $ cd disabled |
|
1380 | $ cd disabled | |
1381 |
|
1381 | |||
1382 | $ printf "foo\n" > foo |
|
1382 | $ printf "foo\n" > foo | |
1383 | $ hg add -q |
|
1383 | $ hg add -q | |
1384 | $ hg fix --debug --working-dir --config "fix.disabled:command=echo fixed" \ |
|
1384 | $ hg fix --debug --working-dir --config "fix.disabled:command=echo fixed" \ | |
1385 | > --config "fix.disabled:pattern=foo" \ |
|
1385 | > --config "fix.disabled:pattern=foo" \ | |
1386 | > --config "fix.disabled:enabled=false" |
|
1386 | > --config "fix.disabled:enabled=false" | |
1387 | ignoring disabled fixer tool: disabled |
|
1387 | ignoring disabled fixer tool: disabled | |
1388 | $ cat foo |
|
1388 | $ cat foo | |
1389 | foo |
|
1389 | foo | |
1390 |
|
1390 | |||
1391 | $ cd .. |
|
1391 | $ cd .. | |
1392 |
|
1392 | |||
1393 | Test that we can configure a fixer to affect all files regardless of the cwd. |
|
1393 | Test that we can configure a fixer to affect all files regardless of the cwd. | |
1394 | The way we invoke matching must not prohibit this. |
|
1394 | The way we invoke matching must not prohibit this. | |
1395 |
|
1395 | |||
1396 | $ hg init affectallfiles |
|
1396 | $ hg init affectallfiles | |
1397 | $ cd affectallfiles |
|
1397 | $ cd affectallfiles | |
1398 |
|
1398 | |||
1399 | $ mkdir foo bar |
|
1399 | $ mkdir foo bar | |
1400 | $ printf "foo" > foo/file |
|
1400 | $ printf "foo" > foo/file | |
1401 | $ printf "bar" > bar/file |
|
1401 | $ printf "bar" > bar/file | |
1402 | $ printf "baz" > baz_file |
|
1402 | $ printf "baz" > baz_file | |
1403 | $ hg add -q |
|
1403 | $ hg add -q | |
1404 |
|
1404 | |||
1405 | $ cd bar |
|
1405 | $ cd bar | |
1406 | $ hg fix --working-dir --config "fix.cooltool:command=echo fixed" \ |
|
1406 | $ hg fix --working-dir --config "fix.cooltool:command=echo fixed" \ | |
1407 | > --config "fix.cooltool:pattern=glob:**" |
|
1407 | > --config "fix.cooltool:pattern=glob:**" | |
1408 | $ cd .. |
|
1408 | $ cd .. | |
1409 |
|
1409 | |||
1410 | $ cat foo/file |
|
1410 | $ cat foo/file | |
1411 | fixed |
|
1411 | fixed | |
1412 | $ cat bar/file |
|
1412 | $ cat bar/file | |
1413 | fixed |
|
1413 | fixed | |
1414 | $ cat baz_file |
|
1414 | $ cat baz_file | |
1415 | fixed |
|
1415 | fixed | |
1416 |
|
1416 | |||
1417 | $ cd .. |
|
1417 | $ cd .. | |
1418 |
|
1418 | |||
1419 | Tools should be able to run on unchanged files, even if they set :linerange. |
|
1419 | Tools should be able to run on unchanged files, even if they set :linerange. | |
1420 | This includes a corner case where deleted chunks of a file are not considered |
|
1420 | This includes a corner case where deleted chunks of a file are not considered | |
1421 | changes. |
|
1421 | changes. | |
1422 |
|
1422 | |||
1423 | $ hg init skipclean |
|
1423 | $ hg init skipclean | |
1424 | $ cd skipclean |
|
1424 | $ cd skipclean | |
1425 |
|
1425 | |||
1426 | $ printf "a\nb\nc\n" > foo |
|
1426 | $ printf "a\nb\nc\n" > foo | |
1427 | $ printf "a\nb\nc\n" > bar |
|
1427 | $ printf "a\nb\nc\n" > bar | |
1428 | $ printf "a\nb\nc\n" > baz |
|
1428 | $ printf "a\nb\nc\n" > baz | |
1429 | $ hg commit -Aqm "base" |
|
1429 | $ hg commit -Aqm "base" | |
1430 |
|
1430 | |||
1431 | $ printf "a\nc\n" > foo |
|
1431 | $ printf "a\nc\n" > foo | |
1432 | $ printf "a\nx\nc\n" > baz |
|
1432 | $ printf "a\nx\nc\n" > baz | |
1433 |
|
1433 | |||
1434 | $ cat >> print.py <<EOF |
|
1434 | $ cat >> print.py <<EOF | |
1435 | > import sys |
|
1435 | > import sys | |
1436 | > for a in sys.argv[1:]: |
|
1436 | > for a in sys.argv[1:]: | |
1437 | > print(a) |
|
1437 | > print(a) | |
1438 | > EOF |
|
1438 | > EOF | |
1439 |
|
1439 | |||
1440 | $ hg fix --working-dir foo bar baz \ |
|
1440 | $ hg fix --working-dir foo bar baz \ | |
1441 | > --config "fix.changedlines:command=\"$PYTHON\" print.py \"Line ranges:\"" \ |
|
1441 | > --config "fix.changedlines:command=\"$PYTHON\" print.py \"Line ranges:\"" \ | |
1442 | > --config 'fix.changedlines:linerange="{first} through {last}"' \ |
|
1442 | > --config 'fix.changedlines:linerange="{first} through {last}"' \ | |
1443 | > --config 'fix.changedlines:pattern=glob:**' \ |
|
1443 | > --config 'fix.changedlines:pattern=glob:**' \ | |
1444 | > --config 'fix.changedlines:skipclean=false' |
|
1444 | > --config 'fix.changedlines:skipclean=false' | |
1445 |
|
1445 | |||
1446 | $ cat foo |
|
1446 | $ cat foo | |
1447 | Line ranges: |
|
1447 | Line ranges: | |
1448 | $ cat bar |
|
1448 | $ cat bar | |
1449 | Line ranges: |
|
1449 | Line ranges: | |
1450 | $ cat baz |
|
1450 | $ cat baz | |
1451 | Line ranges: |
|
1451 | Line ranges: | |
1452 | 2 through 2 |
|
1452 | 2 through 2 | |
1453 |
|
1453 | |||
1454 | $ cd .. |
|
1454 | $ cd .. | |
1455 |
|
1455 | |||
1456 | Test various cases around merges. We were previously dropping files if they were |
|
1456 | Test various cases around merges. We were previously dropping files if they were | |
1457 | created on only the p2 side of the merge, so let's test permutations of: |
|
1457 | created on only the p2 side of the merge, so let's test permutations of: | |
1458 | * added, was fixed |
|
1458 | * added, was fixed | |
1459 | * added, considered for fixing but was already good |
|
1459 | * added, considered for fixing but was already good | |
1460 | * added, not considered for fixing |
|
1460 | * added, not considered for fixing | |
1461 | * modified, was fixed |
|
1461 | * modified, was fixed | |
1462 | * modified, considered for fixing but was already good |
|
1462 | * modified, considered for fixing but was already good | |
1463 | * modified, not considered for fixing |
|
1463 | * modified, not considered for fixing | |
1464 |
|
1464 | |||
1465 | Before the bug was fixed where we would drop files, this test demonstrated the |
|
1465 | Before the bug was fixed where we would drop files, this test demonstrated the | |
1466 | following issues: |
|
1466 | following issues: | |
1467 | * new_in_r1.ignored, new_in_r1_already_good.changed, and |
|
1467 | * new_in_r1.ignored, new_in_r1_already_good.changed, and | |
1468 | > mod_in_r1_already_good.changed were NOT in the manifest for the merge commit |
|
1468 | > mod_in_r1_already_good.changed were NOT in the manifest for the merge commit | |
1469 | * mod_in_r1.ignored had its contents from r0, NOT r1. |
|
1469 | * mod_in_r1.ignored had its contents from r0, NOT r1. | |
1470 |
|
1470 | |||
1471 | We're also setting a named branch for every commit to demonstrate that the |
|
1471 | We're also setting a named branch for every commit to demonstrate that the | |
1472 | branch is kept intact and there aren't issues updating to another branch in the |
|
1472 | branch is kept intact and there aren't issues updating to another branch in the | |
1473 | middle of fix. |
|
1473 | middle of fix. | |
1474 |
|
1474 | |||
1475 | $ hg init merge_keeps_files |
|
1475 | $ hg init merge_keeps_files | |
1476 | $ cd merge_keeps_files |
|
1476 | $ cd merge_keeps_files | |
1477 | $ for f in r0 mod_in_r1 mod_in_r2 mod_in_merge mod_in_child; do |
|
1477 | $ for f in r0 mod_in_r1 mod_in_r2 mod_in_merge mod_in_child; do | |
1478 | > for c in changed whole ignored; do |
|
1478 | > for c in changed whole ignored; do | |
1479 | > printf "hello\n" > $f.$c |
|
1479 | > printf "hello\n" > $f.$c | |
1480 | > done |
|
1480 | > done | |
1481 | > printf "HELLO\n" > "mod_in_${f}_already_good.changed" |
|
1481 | > printf "HELLO\n" > "mod_in_${f}_already_good.changed" | |
1482 | > done |
|
1482 | > done | |
1483 | $ hg branch -q r0 |
|
1483 | $ hg branch -q r0 | |
1484 | $ hg ci -Aqm 'r0' |
|
1484 | $ hg ci -Aqm 'r0' | |
1485 | $ hg phase -p |
|
1485 | $ hg phase -p | |
1486 | $ make_test_files() { |
|
1486 | $ make_test_files() { | |
1487 | > printf "world\n" >> "mod_in_$1.changed" |
|
1487 | > printf "world\n" >> "mod_in_$1.changed" | |
1488 | > printf "world\n" >> "mod_in_$1.whole" |
|
1488 | > printf "world\n" >> "mod_in_$1.whole" | |
1489 | > printf "world\n" >> "mod_in_$1.ignored" |
|
1489 | > printf "world\n" >> "mod_in_$1.ignored" | |
1490 | > printf "WORLD\n" >> "mod_in_$1_already_good.changed" |
|
1490 | > printf "WORLD\n" >> "mod_in_$1_already_good.changed" | |
1491 | > printf "new in $1\n" > "new_in_$1.changed" |
|
1491 | > printf "new in $1\n" > "new_in_$1.changed" | |
1492 | > printf "new in $1\n" > "new_in_$1.whole" |
|
1492 | > printf "new in $1\n" > "new_in_$1.whole" | |
1493 | > printf "new in $1\n" > "new_in_$1.ignored" |
|
1493 | > printf "new in $1\n" > "new_in_$1.ignored" | |
1494 | > printf "ALREADY GOOD, NEW IN THIS REV\n" > "new_in_$1_already_good.changed" |
|
1494 | > printf "ALREADY GOOD, NEW IN THIS REV\n" > "new_in_$1_already_good.changed" | |
1495 | > } |
|
1495 | > } | |
1496 | $ make_test_commit() { |
|
1496 | $ make_test_commit() { | |
1497 | > make_test_files "$1" |
|
1497 | > make_test_files "$1" | |
1498 | > hg branch -q "$1" |
|
1498 | > hg branch -q "$1" | |
1499 | > hg ci -Aqm "$2" |
|
1499 | > hg ci -Aqm "$2" | |
1500 | > } |
|
1500 | > } | |
1501 | $ make_test_commit r1 "merge me, pt1" |
|
1501 | $ make_test_commit r1 "merge me, pt1" | |
1502 | $ hg co -q ".^" |
|
1502 | $ hg co -q ".^" | |
1503 | $ make_test_commit r2 "merge me, pt2" |
|
1503 | $ make_test_commit r2 "merge me, pt2" | |
1504 | $ hg merge -qr 1 |
|
1504 | $ hg merge -qr 1 | |
1505 | $ make_test_commit merge "evil merge" |
|
1505 | $ make_test_commit merge "evil merge" | |
1506 | $ make_test_commit child "child of merge" |
|
1506 | $ make_test_commit child "child of merge" | |
1507 | $ make_test_files wdir |
|
1507 | $ make_test_files wdir | |
1508 | $ hg fix -r 'not public()' -w |
|
1508 | $ hg fix -r 'not public()' -w | |
1509 | $ hg log -G -T'{rev}:{shortest(node,8)}: branch:{branch} desc:{desc}' |
|
1509 | $ hg log -G -T'{rev}:{shortest(node,8)}: branch:{branch} desc:{desc}' | |
1510 | @ 8:c22ce900: branch:child desc:child of merge |
|
1510 | @ 8:c22ce900: branch:child desc:child of merge | |
1511 | | |
|
1511 | | | |
1512 | o 7:5a30615a: branch:merge desc:evil merge |
|
1512 | o 7:5a30615a: branch:merge desc:evil merge | |
1513 | |\ |
|
1513 | |\ | |
1514 | | o 6:4e5acdc4: branch:r2 desc:merge me, pt2 |
|
1514 | | o 6:4e5acdc4: branch:r2 desc:merge me, pt2 | |
1515 | | | |
|
1515 | | | | |
1516 | o | 5:eea01878: branch:r1 desc:merge me, pt1 |
|
1516 | o | 5:eea01878: branch:r1 desc:merge me, pt1 | |
1517 | |/ |
|
1517 | |/ | |
1518 | o 0:0c548d87: branch:r0 desc:r0 |
|
1518 | o 0:0c548d87: branch:r0 desc:r0 | |
1519 |
|
1519 | |||
1520 | $ hg files -r tip |
|
1520 | $ hg files -r tip | |
1521 | mod_in_child.changed |
|
1521 | mod_in_child.changed | |
1522 | mod_in_child.ignored |
|
1522 | mod_in_child.ignored | |
1523 | mod_in_child.whole |
|
1523 | mod_in_child.whole | |
1524 | mod_in_child_already_good.changed |
|
1524 | mod_in_child_already_good.changed | |
1525 | mod_in_merge.changed |
|
1525 | mod_in_merge.changed | |
1526 | mod_in_merge.ignored |
|
1526 | mod_in_merge.ignored | |
1527 | mod_in_merge.whole |
|
1527 | mod_in_merge.whole | |
1528 | mod_in_merge_already_good.changed |
|
1528 | mod_in_merge_already_good.changed | |
1529 | mod_in_mod_in_child_already_good.changed |
|
1529 | mod_in_mod_in_child_already_good.changed | |
1530 | mod_in_mod_in_merge_already_good.changed |
|
1530 | mod_in_mod_in_merge_already_good.changed | |
1531 | mod_in_mod_in_r1_already_good.changed |
|
1531 | mod_in_mod_in_r1_already_good.changed | |
1532 | mod_in_mod_in_r2_already_good.changed |
|
1532 | mod_in_mod_in_r2_already_good.changed | |
1533 | mod_in_r0_already_good.changed |
|
1533 | mod_in_r0_already_good.changed | |
1534 | mod_in_r1.changed |
|
1534 | mod_in_r1.changed | |
1535 | mod_in_r1.ignored |
|
1535 | mod_in_r1.ignored | |
1536 | mod_in_r1.whole |
|
1536 | mod_in_r1.whole | |
1537 | mod_in_r1_already_good.changed |
|
1537 | mod_in_r1_already_good.changed | |
1538 | mod_in_r2.changed |
|
1538 | mod_in_r2.changed | |
1539 | mod_in_r2.ignored |
|
1539 | mod_in_r2.ignored | |
1540 | mod_in_r2.whole |
|
1540 | mod_in_r2.whole | |
1541 | mod_in_r2_already_good.changed |
|
1541 | mod_in_r2_already_good.changed | |
1542 | new_in_child.changed |
|
1542 | new_in_child.changed | |
1543 | new_in_child.ignored |
|
1543 | new_in_child.ignored | |
1544 | new_in_child.whole |
|
1544 | new_in_child.whole | |
1545 | new_in_child_already_good.changed |
|
1545 | new_in_child_already_good.changed | |
1546 | new_in_merge.changed |
|
1546 | new_in_merge.changed | |
1547 | new_in_merge.ignored |
|
1547 | new_in_merge.ignored | |
1548 | new_in_merge.whole |
|
1548 | new_in_merge.whole | |
1549 | new_in_merge_already_good.changed |
|
1549 | new_in_merge_already_good.changed | |
1550 | new_in_r1.changed |
|
1550 | new_in_r1.changed | |
1551 | new_in_r1.ignored |
|
1551 | new_in_r1.ignored | |
1552 | new_in_r1.whole |
|
1552 | new_in_r1.whole | |
1553 | new_in_r1_already_good.changed |
|
1553 | new_in_r1_already_good.changed | |
1554 | new_in_r2.changed |
|
1554 | new_in_r2.changed | |
1555 | new_in_r2.ignored |
|
1555 | new_in_r2.ignored | |
1556 | new_in_r2.whole |
|
1556 | new_in_r2.whole | |
1557 | new_in_r2_already_good.changed |
|
1557 | new_in_r2_already_good.changed | |
1558 | r0.changed |
|
1558 | r0.changed | |
1559 | r0.ignored |
|
1559 | r0.ignored | |
1560 | r0.whole |
|
1560 | r0.whole | |
1561 | $ for f in "$(hg files -r tip)"; do hg cat -r tip $f -T'{path}:\n{data}\n'; done |
|
1561 | $ for f in "$(hg files -r tip)"; do hg cat -r tip $f -T'{path}:\n{data}\n'; done | |
1562 | mod_in_child.changed: |
|
1562 | mod_in_child.changed: | |
1563 | hello |
|
1563 | hello | |
1564 | WORLD |
|
1564 | WORLD | |
1565 |
|
1565 | |||
1566 | mod_in_child.ignored: |
|
1566 | mod_in_child.ignored: | |
1567 | hello |
|
1567 | hello | |
1568 | world |
|
1568 | world | |
1569 |
|
1569 | |||
1570 | mod_in_child.whole: |
|
1570 | mod_in_child.whole: | |
1571 | HELLO |
|
1571 | HELLO | |
1572 | WORLD |
|
1572 | WORLD | |
1573 |
|
1573 | |||
1574 | mod_in_child_already_good.changed: |
|
1574 | mod_in_child_already_good.changed: | |
1575 | WORLD |
|
1575 | WORLD | |
1576 |
|
1576 | |||
1577 | mod_in_merge.changed: |
|
1577 | mod_in_merge.changed: | |
1578 | hello |
|
1578 | hello | |
1579 | WORLD |
|
1579 | WORLD | |
1580 |
|
1580 | |||
1581 | mod_in_merge.ignored: |
|
1581 | mod_in_merge.ignored: | |
1582 | hello |
|
1582 | hello | |
1583 | world |
|
1583 | world | |
1584 |
|
1584 | |||
1585 | mod_in_merge.whole: |
|
1585 | mod_in_merge.whole: | |
1586 | HELLO |
|
1586 | HELLO | |
1587 | WORLD |
|
1587 | WORLD | |
1588 |
|
1588 | |||
1589 | mod_in_merge_already_good.changed: |
|
1589 | mod_in_merge_already_good.changed: | |
1590 | WORLD |
|
1590 | WORLD | |
1591 |
|
1591 | |||
1592 | mod_in_mod_in_child_already_good.changed: |
|
1592 | mod_in_mod_in_child_already_good.changed: | |
1593 | HELLO |
|
1593 | HELLO | |
1594 |
|
1594 | |||
1595 | mod_in_mod_in_merge_already_good.changed: |
|
1595 | mod_in_mod_in_merge_already_good.changed: | |
1596 | HELLO |
|
1596 | HELLO | |
1597 |
|
1597 | |||
1598 | mod_in_mod_in_r1_already_good.changed: |
|
1598 | mod_in_mod_in_r1_already_good.changed: | |
1599 | HELLO |
|
1599 | HELLO | |
1600 |
|
1600 | |||
1601 | mod_in_mod_in_r2_already_good.changed: |
|
1601 | mod_in_mod_in_r2_already_good.changed: | |
1602 | HELLO |
|
1602 | HELLO | |
1603 |
|
1603 | |||
1604 | mod_in_r0_already_good.changed: |
|
1604 | mod_in_r0_already_good.changed: | |
1605 | HELLO |
|
1605 | HELLO | |
1606 |
|
1606 | |||
1607 | mod_in_r1.changed: |
|
1607 | mod_in_r1.changed: | |
1608 | hello |
|
1608 | hello | |
1609 | WORLD |
|
1609 | WORLD | |
1610 |
|
1610 | |||
1611 | mod_in_r1.ignored: |
|
1611 | mod_in_r1.ignored: | |
1612 | hello |
|
1612 | hello | |
1613 | world |
|
1613 | world | |
1614 |
|
1614 | |||
1615 | mod_in_r1.whole: |
|
1615 | mod_in_r1.whole: | |
1616 | HELLO |
|
1616 | HELLO | |
1617 | WORLD |
|
1617 | WORLD | |
1618 |
|
1618 | |||
1619 | mod_in_r1_already_good.changed: |
|
1619 | mod_in_r1_already_good.changed: | |
1620 | WORLD |
|
1620 | WORLD | |
1621 |
|
1621 | |||
1622 | mod_in_r2.changed: |
|
1622 | mod_in_r2.changed: | |
1623 | hello |
|
1623 | hello | |
1624 | WORLD |
|
1624 | WORLD | |
1625 |
|
1625 | |||
1626 | mod_in_r2.ignored: |
|
1626 | mod_in_r2.ignored: | |
1627 | hello |
|
1627 | hello | |
1628 | world |
|
1628 | world | |
1629 |
|
1629 | |||
1630 | mod_in_r2.whole: |
|
1630 | mod_in_r2.whole: | |
1631 | HELLO |
|
1631 | HELLO | |
1632 | WORLD |
|
1632 | WORLD | |
1633 |
|
1633 | |||
1634 | mod_in_r2_already_good.changed: |
|
1634 | mod_in_r2_already_good.changed: | |
1635 | WORLD |
|
1635 | WORLD | |
1636 |
|
1636 | |||
1637 | new_in_child.changed: |
|
1637 | new_in_child.changed: | |
1638 | NEW IN CHILD |
|
1638 | NEW IN CHILD | |
1639 |
|
1639 | |||
1640 | new_in_child.ignored: |
|
1640 | new_in_child.ignored: | |
1641 | new in child |
|
1641 | new in child | |
1642 |
|
1642 | |||
1643 | new_in_child.whole: |
|
1643 | new_in_child.whole: | |
1644 | NEW IN CHILD |
|
1644 | NEW IN CHILD | |
1645 |
|
1645 | |||
1646 | new_in_child_already_good.changed: |
|
1646 | new_in_child_already_good.changed: | |
1647 | ALREADY GOOD, NEW IN THIS REV |
|
1647 | ALREADY GOOD, NEW IN THIS REV | |
1648 |
|
1648 | |||
1649 | new_in_merge.changed: |
|
1649 | new_in_merge.changed: | |
1650 | NEW IN MERGE |
|
1650 | NEW IN MERGE | |
1651 |
|
1651 | |||
1652 | new_in_merge.ignored: |
|
1652 | new_in_merge.ignored: | |
1653 | new in merge |
|
1653 | new in merge | |
1654 |
|
1654 | |||
1655 | new_in_merge.whole: |
|
1655 | new_in_merge.whole: | |
1656 | NEW IN MERGE |
|
1656 | NEW IN MERGE | |
1657 |
|
1657 | |||
1658 | new_in_merge_already_good.changed: |
|
1658 | new_in_merge_already_good.changed: | |
1659 | ALREADY GOOD, NEW IN THIS REV |
|
1659 | ALREADY GOOD, NEW IN THIS REV | |
1660 |
|
1660 | |||
1661 | new_in_r1.changed: |
|
1661 | new_in_r1.changed: | |
1662 | NEW IN R1 |
|
1662 | NEW IN R1 | |
1663 |
|
1663 | |||
1664 | new_in_r1.ignored: |
|
1664 | new_in_r1.ignored: | |
1665 | new in r1 |
|
1665 | new in r1 | |
1666 |
|
1666 | |||
1667 | new_in_r1.whole: |
|
1667 | new_in_r1.whole: | |
1668 | NEW IN R1 |
|
1668 | NEW IN R1 | |
1669 |
|
1669 | |||
1670 | new_in_r1_already_good.changed: |
|
1670 | new_in_r1_already_good.changed: | |
1671 | ALREADY GOOD, NEW IN THIS REV |
|
1671 | ALREADY GOOD, NEW IN THIS REV | |
1672 |
|
1672 | |||
1673 | new_in_r2.changed: |
|
1673 | new_in_r2.changed: | |
1674 | NEW IN R2 |
|
1674 | NEW IN R2 | |
1675 |
|
1675 | |||
1676 | new_in_r2.ignored: |
|
1676 | new_in_r2.ignored: | |
1677 | new in r2 |
|
1677 | new in r2 | |
1678 |
|
1678 | |||
1679 | new_in_r2.whole: |
|
1679 | new_in_r2.whole: | |
1680 | NEW IN R2 |
|
1680 | NEW IN R2 | |
1681 |
|
1681 | |||
1682 | new_in_r2_already_good.changed: |
|
1682 | new_in_r2_already_good.changed: | |
1683 | ALREADY GOOD, NEW IN THIS REV |
|
1683 | ALREADY GOOD, NEW IN THIS REV | |
1684 |
|
1684 | |||
1685 | r0.changed: |
|
1685 | r0.changed: | |
1686 | hello |
|
1686 | hello | |
1687 |
|
1687 | |||
1688 | r0.ignored: |
|
1688 | r0.ignored: | |
1689 | hello |
|
1689 | hello | |
1690 |
|
1690 | |||
1691 | r0.whole: |
|
1691 | r0.whole: | |
1692 | hello |
|
1692 | hello | |
1693 |
|
1693 |
@@ -1,605 +1,605 b'' | |||||
1 | #testcases abortcommand abortflag |
|
1 | #testcases abortcommand abortflag | |
2 |
|
2 | |||
3 | #if abortflag |
|
3 | #if abortflag | |
4 | $ cat >> $HGRCPATH <<EOF |
|
4 | $ cat >> $HGRCPATH <<EOF | |
5 | > [alias] |
|
5 | > [alias] | |
6 | > abort = histedit --abort |
|
6 | > abort = histedit --abort | |
7 | > EOF |
|
7 | > EOF | |
8 | #endif |
|
8 | #endif | |
9 |
|
9 | |||
10 | Test argument handling and various data parsing |
|
10 | Test argument handling and various data parsing | |
11 | ================================================== |
|
11 | ================================================== | |
12 |
|
12 | |||
13 |
|
13 | |||
14 | Enable extensions used by this test. |
|
14 | Enable extensions used by this test. | |
15 | $ cat >>$HGRCPATH <<EOF |
|
15 | $ cat >>$HGRCPATH <<EOF | |
16 | > [extensions] |
|
16 | > [extensions] | |
17 | > histedit= |
|
17 | > histedit= | |
18 | > EOF |
|
18 | > EOF | |
19 |
|
19 | |||
20 | Repo setup. |
|
20 | Repo setup. | |
21 | $ hg init foo |
|
21 | $ hg init foo | |
22 | $ cd foo |
|
22 | $ cd foo | |
23 | $ echo alpha >> alpha |
|
23 | $ echo alpha >> alpha | |
24 | $ hg addr |
|
24 | $ hg addr | |
25 | adding alpha |
|
25 | adding alpha | |
26 | $ hg ci -m one |
|
26 | $ hg ci -m one | |
27 | $ echo alpha >> alpha |
|
27 | $ echo alpha >> alpha | |
28 | $ hg ci -m two |
|
28 | $ hg ci -m two | |
29 | $ echo alpha >> alpha |
|
29 | $ echo alpha >> alpha | |
30 | $ hg ci -m three |
|
30 | $ hg ci -m three | |
31 | $ echo alpha >> alpha |
|
31 | $ echo alpha >> alpha | |
32 | $ hg ci -m four |
|
32 | $ hg ci -m four | |
33 | $ echo alpha >> alpha |
|
33 | $ echo alpha >> alpha | |
34 | $ hg ci -m five |
|
34 | $ hg ci -m five | |
35 |
|
35 | |||
36 | $ hg log --style compact --graph |
|
36 | $ hg log --style compact --graph | |
37 | @ 4[tip] 08d98a8350f3 1970-01-01 00:00 +0000 test |
|
37 | @ 4[tip] 08d98a8350f3 1970-01-01 00:00 +0000 test | |
38 | | five |
|
38 | | five | |
39 | | |
|
39 | | | |
40 | o 3 c8e68270e35a 1970-01-01 00:00 +0000 test |
|
40 | o 3 c8e68270e35a 1970-01-01 00:00 +0000 test | |
41 | | four |
|
41 | | four | |
42 | | |
|
42 | | | |
43 | o 2 eb57da33312f 1970-01-01 00:00 +0000 test |
|
43 | o 2 eb57da33312f 1970-01-01 00:00 +0000 test | |
44 | | three |
|
44 | | three | |
45 | | |
|
45 | | | |
46 | o 1 579e40513370 1970-01-01 00:00 +0000 test |
|
46 | o 1 579e40513370 1970-01-01 00:00 +0000 test | |
47 | | two |
|
47 | | two | |
48 | | |
|
48 | | | |
49 | o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test |
|
49 | o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test | |
50 | one |
|
50 | one | |
51 |
|
51 | |||
52 |
|
52 | |||
53 | histedit --continue/--abort with no existing state |
|
53 | histedit --continue/--abort with no existing state | |
54 | -------------------------------------------------- |
|
54 | -------------------------------------------------- | |
55 |
|
55 | |||
56 | $ hg histedit --continue |
|
56 | $ hg histedit --continue | |
57 | abort: no histedit in progress |
|
57 | abort: no histedit in progress | |
58 | [255] |
|
58 | [255] | |
59 | $ hg abort |
|
59 | $ hg abort | |
60 | abort: no histedit in progress (abortflag !) |
|
60 | abort: no histedit in progress (abortflag !) | |
61 | abort: no operation in progress (abortcommand !) |
|
61 | abort: no operation in progress (abortcommand !) | |
62 | [255] |
|
62 | [255] | |
63 |
|
63 | |||
64 | Run a dummy edit to make sure we get tip^^ correctly via revsingle. |
|
64 | Run a dummy edit to make sure we get tip^^ correctly via revsingle. | |
65 | -------------------------------------------------------------------- |
|
65 | -------------------------------------------------------------------- | |
66 |
|
66 | |||
67 | $ HGEDITOR=cat hg histedit "tip^^" |
|
67 | $ HGEDITOR=cat hg histedit "tip^^" | |
68 | pick eb57da33312f 2 three |
|
68 | pick eb57da33312f 2 three | |
69 | pick c8e68270e35a 3 four |
|
69 | pick c8e68270e35a 3 four | |
70 | pick 08d98a8350f3 4 five |
|
70 | pick 08d98a8350f3 4 five | |
71 |
|
71 | |||
72 | # Edit history between eb57da33312f and 08d98a8350f3 |
|
72 | # Edit history between eb57da33312f and 08d98a8350f3 | |
73 | # |
|
73 | # | |
74 | # Commits are listed from least to most recent |
|
74 | # Commits are listed from least to most recent | |
75 | # |
|
75 | # | |
76 | # You can reorder changesets by reordering the lines |
|
76 | # You can reorder changesets by reordering the lines | |
77 | # |
|
77 | # | |
78 | # Commands: |
|
78 | # Commands: | |
79 | # |
|
79 | # | |
80 | # e, edit = use commit, but stop for amending |
|
80 | # e, edit = use commit, but stop for amending | |
81 | # m, mess = edit commit message without changing commit content |
|
81 | # m, mess = edit commit message without changing commit content | |
82 | # p, pick = use commit |
|
82 | # p, pick = use commit | |
83 | # b, base = checkout changeset and apply further changesets from there |
|
83 | # b, base = checkout changeset and apply further changesets from there | |
84 | # d, drop = remove commit from history |
|
84 | # d, drop = remove commit from history | |
85 | # f, fold = use commit, but combine it with the one above |
|
85 | # f, fold = use commit, but combine it with the one above | |
86 | # r, roll = like fold, but discard this commit's description and date |
|
86 | # r, roll = like fold, but discard this commit's description and date | |
87 | # |
|
87 | # | |
88 |
|
88 | |||
89 | Run on a revision not ancestors of the current working directory. |
|
89 | Run on a revision not ancestors of the current working directory. | |
90 | -------------------------------------------------------------------- |
|
90 | -------------------------------------------------------------------- | |
91 |
|
91 | |||
92 | $ hg up 2 |
|
92 | $ hg up 2 | |
93 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
93 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
94 | $ hg histedit -r 4 |
|
94 | $ hg histedit -r 4 | |
95 | abort: 08d98a8350f3 is not an ancestor of working directory |
|
95 | abort: 08d98a8350f3 is not an ancestor of working directory | |
96 | [255] |
|
96 | [255] | |
97 | $ hg up --quiet |
|
97 | $ hg up --quiet | |
98 |
|
98 | |||
99 |
|
99 | |||
100 | Test that we pick the minimum of a revrange |
|
100 | Test that we pick the minimum of a revrange | |
101 | --------------------------------------- |
|
101 | --------------------------------------- | |
102 |
|
102 | |||
103 | $ HGEDITOR=cat hg histedit '2::' --commands - << EOF |
|
103 | $ HGEDITOR=cat hg histedit '2::' --commands - << EOF | |
104 | > pick eb57da33312f 2 three |
|
104 | > pick eb57da33312f 2 three | |
105 | > pick c8e68270e35a 3 four |
|
105 | > pick c8e68270e35a 3 four | |
106 | > pick 08d98a8350f3 4 five |
|
106 | > pick 08d98a8350f3 4 five | |
107 | > EOF |
|
107 | > EOF | |
108 | $ hg up --quiet |
|
108 | $ hg up --quiet | |
109 |
|
109 | |||
110 | $ HGEDITOR=cat hg histedit 'tip:2' --commands - << EOF |
|
110 | $ HGEDITOR=cat hg histedit 'tip:2' --commands - << EOF | |
111 | > pick eb57da33312f 2 three |
|
111 | > pick eb57da33312f 2 three | |
112 | > pick c8e68270e35a 3 four |
|
112 | > pick c8e68270e35a 3 four | |
113 | > pick 08d98a8350f3 4 five |
|
113 | > pick 08d98a8350f3 4 five | |
114 | > EOF |
|
114 | > EOF | |
115 | $ hg up --quiet |
|
115 | $ hg up --quiet | |
116 |
|
116 | |||
117 | Test config specified default |
|
117 | Test config specified default | |
118 | ----------------------------- |
|
118 | ----------------------------- | |
119 |
|
119 | |||
120 | $ HGEDITOR=cat hg histedit --config "histedit.defaultrev=only(.) - ::eb57da33312f" --commands - << EOF |
|
120 | $ HGEDITOR=cat hg histedit --config "histedit.defaultrev=only(.) - ::eb57da33312f" --commands - << EOF | |
121 | > pick c8e68270e35a 3 four |
|
121 | > pick c8e68270e35a 3 four | |
122 | > pick 08d98a8350f3 4 five |
|
122 | > pick 08d98a8350f3 4 five | |
123 | > EOF |
|
123 | > EOF | |
124 |
|
124 | |||
125 | Test invalid config default |
|
125 | Test invalid config default | |
126 | --------------------------- |
|
126 | --------------------------- | |
127 |
|
127 | |||
128 | $ hg histedit --config "histedit.defaultrev=" |
|
128 | $ hg histedit --config "histedit.defaultrev=" | |
129 | abort: config option histedit.defaultrev can't be empty |
|
129 | abort: config option histedit.defaultrev can't be empty | |
130 | [255] |
|
130 | [255] | |
131 |
|
131 | |||
132 | Run on a revision not descendants of the initial parent |
|
132 | Run on a revision not descendants of the initial parent | |
133 | -------------------------------------------------------------------- |
|
133 | -------------------------------------------------------------------- | |
134 |
|
134 | |||
135 | Test the message shown for inconsistent histedit state, which may be |
|
135 | Test the message shown for inconsistent histedit state, which may be | |
136 | created (and forgotten) by Mercurial earlier than 2.7. This emulates |
|
136 | created (and forgotten) by Mercurial earlier than 2.7. This emulates | |
137 | Mercurial earlier than 2.7 by renaming ".hg/histedit-state" |
|
137 | Mercurial earlier than 2.7 by renaming ".hg/histedit-state" | |
138 | temporarily. |
|
138 | temporarily. | |
139 |
|
139 | |||
140 | $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2:: |
|
140 | $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2:: | |
141 | @ 4 08d9 five |
|
141 | @ 4 08d9 five | |
142 | | |
|
142 | | | |
143 | o 3 c8e6 four |
|
143 | o 3 c8e6 four | |
144 | | |
|
144 | | | |
145 | o 2 eb57 three |
|
145 | o 2 eb57 three | |
146 | | |
|
146 | | | |
147 | ~ |
|
147 | ~ | |
148 | $ HGEDITOR=cat hg histedit -r 4 --commands - << EOF |
|
148 | $ HGEDITOR=cat hg histedit -r 4 --commands - << EOF | |
149 | > edit 08d98a8350f3 4 five |
|
149 | > edit 08d98a8350f3 4 five | |
150 | > EOF |
|
150 | > EOF | |
151 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
151 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
152 | Editing (08d98a8350f3), you may commit or record as needed now. |
|
152 | Editing (08d98a8350f3), you may commit or record as needed now. | |
153 | (hg histedit --continue to resume) |
|
153 | (hg histedit --continue to resume) | |
154 |
[ |
|
154 | [240] | |
155 |
|
155 | |||
156 | $ hg graft --continue |
|
156 | $ hg graft --continue | |
157 | abort: no graft in progress |
|
157 | abort: no graft in progress | |
158 | (continue: hg histedit --continue) |
|
158 | (continue: hg histedit --continue) | |
159 | [255] |
|
159 | [255] | |
160 |
|
160 | |||
161 | $ mv .hg/histedit-state .hg/histedit-state.back |
|
161 | $ mv .hg/histedit-state .hg/histedit-state.back | |
162 | $ hg update --quiet --clean 2 |
|
162 | $ hg update --quiet --clean 2 | |
163 | $ echo alpha >> alpha |
|
163 | $ echo alpha >> alpha | |
164 | $ mv .hg/histedit-state.back .hg/histedit-state |
|
164 | $ mv .hg/histedit-state.back .hg/histedit-state | |
165 |
|
165 | |||
166 | $ hg histedit --continue |
|
166 | $ hg histedit --continue | |
167 | saved backup bundle to $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-histedit.hg |
|
167 | saved backup bundle to $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-histedit.hg | |
168 | $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2:: |
|
168 | $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2:: | |
169 | @ 4 f5ed five |
|
169 | @ 4 f5ed five | |
170 | | |
|
170 | | | |
171 | | o 3 c8e6 four |
|
171 | | o 3 c8e6 four | |
172 | |/ |
|
172 | |/ | |
173 | o 2 eb57 three |
|
173 | o 2 eb57 three | |
174 | | |
|
174 | | | |
175 | ~ |
|
175 | ~ | |
176 |
|
176 | |||
177 | $ hg unbundle -q $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-histedit.hg |
|
177 | $ hg unbundle -q $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-histedit.hg | |
178 | $ hg strip -q -r f5ed --config extensions.strip= |
|
178 | $ hg strip -q -r f5ed --config extensions.strip= | |
179 | $ hg up -q 08d98a8350f3 |
|
179 | $ hg up -q 08d98a8350f3 | |
180 |
|
180 | |||
181 | Test that missing revisions are detected |
|
181 | Test that missing revisions are detected | |
182 | --------------------------------------- |
|
182 | --------------------------------------- | |
183 |
|
183 | |||
184 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF |
|
184 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF | |
185 | > pick eb57da33312f 2 three |
|
185 | > pick eb57da33312f 2 three | |
186 | > pick 08d98a8350f3 4 five |
|
186 | > pick 08d98a8350f3 4 five | |
187 | > EOF |
|
187 | > EOF | |
188 | hg: parse error: missing rules for changeset c8e68270e35a |
|
188 | hg: parse error: missing rules for changeset c8e68270e35a | |
189 | (use "drop c8e68270e35a" to discard, see also: 'hg help -e histedit.config') |
|
189 | (use "drop c8e68270e35a" to discard, see also: 'hg help -e histedit.config') | |
190 | [255] |
|
190 | [255] | |
191 |
|
191 | |||
192 | Test that extra revisions are detected |
|
192 | Test that extra revisions are detected | |
193 | --------------------------------------- |
|
193 | --------------------------------------- | |
194 |
|
194 | |||
195 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF |
|
195 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF | |
196 | > pick 6058cbb6cfd7 0 one |
|
196 | > pick 6058cbb6cfd7 0 one | |
197 | > pick c8e68270e35a 3 four |
|
197 | > pick c8e68270e35a 3 four | |
198 | > pick 08d98a8350f3 4 five |
|
198 | > pick 08d98a8350f3 4 five | |
199 | > EOF |
|
199 | > EOF | |
200 | hg: parse error: pick "6058cbb6cfd7" changeset was not a candidate |
|
200 | hg: parse error: pick "6058cbb6cfd7" changeset was not a candidate | |
201 | (only use listed changesets) |
|
201 | (only use listed changesets) | |
202 | [255] |
|
202 | [255] | |
203 |
|
203 | |||
204 | Test malformed line |
|
204 | Test malformed line | |
205 | --------------------------------------- |
|
205 | --------------------------------------- | |
206 |
|
206 | |||
207 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF |
|
207 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF | |
208 | > pickeb57da33312f2three |
|
208 | > pickeb57da33312f2three | |
209 | > pick c8e68270e35a 3 four |
|
209 | > pick c8e68270e35a 3 four | |
210 | > pick 08d98a8350f3 4 five |
|
210 | > pick 08d98a8350f3 4 five | |
211 | > EOF |
|
211 | > EOF | |
212 | hg: parse error: malformed line "pickeb57da33312f2three" |
|
212 | hg: parse error: malformed line "pickeb57da33312f2three" | |
213 | [255] |
|
213 | [255] | |
214 |
|
214 | |||
215 | Test unknown changeset |
|
215 | Test unknown changeset | |
216 | --------------------------------------- |
|
216 | --------------------------------------- | |
217 |
|
217 | |||
218 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF |
|
218 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF | |
219 | > pick 0123456789ab 2 three |
|
219 | > pick 0123456789ab 2 three | |
220 | > pick c8e68270e35a 3 four |
|
220 | > pick c8e68270e35a 3 four | |
221 | > pick 08d98a8350f3 4 five |
|
221 | > pick 08d98a8350f3 4 five | |
222 | > EOF |
|
222 | > EOF | |
223 | hg: parse error: unknown changeset 0123456789ab listed |
|
223 | hg: parse error: unknown changeset 0123456789ab listed | |
224 | [255] |
|
224 | [255] | |
225 |
|
225 | |||
226 | Test unknown command |
|
226 | Test unknown command | |
227 | --------------------------------------- |
|
227 | --------------------------------------- | |
228 |
|
228 | |||
229 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF |
|
229 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF | |
230 | > coin eb57da33312f 2 three |
|
230 | > coin eb57da33312f 2 three | |
231 | > pick c8e68270e35a 3 four |
|
231 | > pick c8e68270e35a 3 four | |
232 | > pick 08d98a8350f3 4 five |
|
232 | > pick 08d98a8350f3 4 five | |
233 | > EOF |
|
233 | > EOF | |
234 | hg: parse error: unknown action "coin" |
|
234 | hg: parse error: unknown action "coin" | |
235 | [255] |
|
235 | [255] | |
236 |
|
236 | |||
237 | Test duplicated changeset |
|
237 | Test duplicated changeset | |
238 | --------------------------------------- |
|
238 | --------------------------------------- | |
239 |
|
239 | |||
240 | So one is missing and one appear twice. |
|
240 | So one is missing and one appear twice. | |
241 |
|
241 | |||
242 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF |
|
242 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF | |
243 | > pick eb57da33312f 2 three |
|
243 | > pick eb57da33312f 2 three | |
244 | > pick eb57da33312f 2 three |
|
244 | > pick eb57da33312f 2 three | |
245 | > pick 08d98a8350f3 4 five |
|
245 | > pick 08d98a8350f3 4 five | |
246 | > EOF |
|
246 | > EOF | |
247 | hg: parse error: duplicated command for changeset eb57da33312f |
|
247 | hg: parse error: duplicated command for changeset eb57da33312f | |
248 | [255] |
|
248 | [255] | |
249 |
|
249 | |||
250 | Test bogus rev |
|
250 | Test bogus rev | |
251 | --------------------------------------- |
|
251 | --------------------------------------- | |
252 |
|
252 | |||
253 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF |
|
253 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF | |
254 | > pick eb57da33312f 2 three |
|
254 | > pick eb57da33312f 2 three | |
255 | > pick 0u98 |
|
255 | > pick 0u98 | |
256 | > pick 08d98a8350f3 4 five |
|
256 | > pick 08d98a8350f3 4 five | |
257 | > EOF |
|
257 | > EOF | |
258 | hg: parse error: invalid changeset 0u98 |
|
258 | hg: parse error: invalid changeset 0u98 | |
259 | [255] |
|
259 | [255] | |
260 |
|
260 | |||
261 | Test short version of command |
|
261 | Test short version of command | |
262 | --------------------------------------- |
|
262 | --------------------------------------- | |
263 |
|
263 | |||
264 | Note: we use varying amounts of white space between command name and changeset |
|
264 | Note: we use varying amounts of white space between command name and changeset | |
265 | short hash. This tests issue3893. |
|
265 | short hash. This tests issue3893. | |
266 |
|
266 | |||
267 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF |
|
267 | $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF | |
268 | > pick eb57da33312f 2 three |
|
268 | > pick eb57da33312f 2 three | |
269 | > p c8e68270e35a 3 four |
|
269 | > p c8e68270e35a 3 four | |
270 | > f 08d98a8350f3 4 five |
|
270 | > f 08d98a8350f3 4 five | |
271 | > EOF |
|
271 | > EOF | |
272 | four |
|
272 | four | |
273 | *** |
|
273 | *** | |
274 | five |
|
274 | five | |
275 |
|
275 | |||
276 |
|
276 | |||
277 |
|
277 | |||
278 | HG: Enter commit message. Lines beginning with 'HG:' are removed. |
|
278 | HG: Enter commit message. Lines beginning with 'HG:' are removed. | |
279 | HG: Leave message empty to abort commit. |
|
279 | HG: Leave message empty to abort commit. | |
280 | HG: -- |
|
280 | HG: -- | |
281 | HG: user: test |
|
281 | HG: user: test | |
282 | HG: branch 'default' |
|
282 | HG: branch 'default' | |
283 | HG: changed alpha |
|
283 | HG: changed alpha | |
284 | saved backup bundle to $TESTTMP/foo/.hg/strip-backup/c8e68270e35a-63d8b8d8-histedit.hg |
|
284 | saved backup bundle to $TESTTMP/foo/.hg/strip-backup/c8e68270e35a-63d8b8d8-histedit.hg | |
285 |
|
285 | |||
286 | $ hg update -q 2 |
|
286 | $ hg update -q 2 | |
287 | $ echo x > x |
|
287 | $ echo x > x | |
288 | $ hg add x |
|
288 | $ hg add x | |
289 | $ hg commit -m'x' x |
|
289 | $ hg commit -m'x' x | |
290 | created new head |
|
290 | created new head | |
291 | $ hg histedit -r 'heads(all())' |
|
291 | $ hg histedit -r 'heads(all())' | |
292 | abort: The specified revisions must have exactly one common root |
|
292 | abort: The specified revisions must have exactly one common root | |
293 | [255] |
|
293 | [255] | |
294 |
|
294 | |||
295 | Test that trimming description using multi-byte characters |
|
295 | Test that trimming description using multi-byte characters | |
296 | -------------------------------------------------------------------- |
|
296 | -------------------------------------------------------------------- | |
297 |
|
297 | |||
298 | $ "$PYTHON" <<EOF |
|
298 | $ "$PYTHON" <<EOF | |
299 | > fp = open('logfile', 'wb') |
|
299 | > fp = open('logfile', 'wb') | |
300 | > fp.write(b'12345678901234567890123456789012345678901234567890' + |
|
300 | > fp.write(b'12345678901234567890123456789012345678901234567890' + | |
301 | > b'12345') # there are 5 more columns for 80 columns |
|
301 | > b'12345') # there are 5 more columns for 80 columns | |
302 | > |
|
302 | > | |
303 | > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes |
|
303 | > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes | |
304 | > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8')) |
|
304 | > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8')) | |
305 | > |
|
305 | > | |
306 | > fp.close() |
|
306 | > fp.close() | |
307 | > EOF |
|
307 | > EOF | |
308 | $ echo xx >> x |
|
308 | $ echo xx >> x | |
309 | $ hg --encoding utf-8 commit --logfile logfile |
|
309 | $ hg --encoding utf-8 commit --logfile logfile | |
310 |
|
310 | |||
311 | $ HGEDITOR=cat hg --encoding utf-8 histedit tip |
|
311 | $ HGEDITOR=cat hg --encoding utf-8 histedit tip | |
312 | pick 3d3ea1f3a10b 5 1234567890123456789012345678901234567890123456789012345\xe3\x81\x82... (esc) |
|
312 | pick 3d3ea1f3a10b 5 1234567890123456789012345678901234567890123456789012345\xe3\x81\x82... (esc) | |
313 |
|
313 | |||
314 | # Edit history between 3d3ea1f3a10b and 3d3ea1f3a10b |
|
314 | # Edit history between 3d3ea1f3a10b and 3d3ea1f3a10b | |
315 | # |
|
315 | # | |
316 | # Commits are listed from least to most recent |
|
316 | # Commits are listed from least to most recent | |
317 | # |
|
317 | # | |
318 | # You can reorder changesets by reordering the lines |
|
318 | # You can reorder changesets by reordering the lines | |
319 | # |
|
319 | # | |
320 | # Commands: |
|
320 | # Commands: | |
321 | # |
|
321 | # | |
322 | # e, edit = use commit, but stop for amending |
|
322 | # e, edit = use commit, but stop for amending | |
323 | # m, mess = edit commit message without changing commit content |
|
323 | # m, mess = edit commit message without changing commit content | |
324 | # p, pick = use commit |
|
324 | # p, pick = use commit | |
325 | # b, base = checkout changeset and apply further changesets from there |
|
325 | # b, base = checkout changeset and apply further changesets from there | |
326 | # d, drop = remove commit from history |
|
326 | # d, drop = remove commit from history | |
327 | # f, fold = use commit, but combine it with the one above |
|
327 | # f, fold = use commit, but combine it with the one above | |
328 | # r, roll = like fold, but discard this commit's description and date |
|
328 | # r, roll = like fold, but discard this commit's description and date | |
329 | # |
|
329 | # | |
330 |
|
330 | |||
331 | Test --continue with --keep |
|
331 | Test --continue with --keep | |
332 |
|
332 | |||
333 | $ hg strip -q -r . --config extensions.strip= |
|
333 | $ hg strip -q -r . --config extensions.strip= | |
334 | $ hg histedit '.^' -q --keep --commands - << EOF |
|
334 | $ hg histedit '.^' -q --keep --commands - << EOF | |
335 | > edit eb57da33312f 2 three |
|
335 | > edit eb57da33312f 2 three | |
336 | > pick f3cfcca30c44 4 x |
|
336 | > pick f3cfcca30c44 4 x | |
337 | > EOF |
|
337 | > EOF | |
338 | Editing (eb57da33312f), you may commit or record as needed now. |
|
338 | Editing (eb57da33312f), you may commit or record as needed now. | |
339 | (hg histedit --continue to resume) |
|
339 | (hg histedit --continue to resume) | |
340 |
[ |
|
340 | [240] | |
341 | $ echo edit >> alpha |
|
341 | $ echo edit >> alpha | |
342 | $ hg histedit -q --continue |
|
342 | $ hg histedit -q --continue | |
343 | $ hg log -G -T '{rev}:{node|short} {desc}' |
|
343 | $ hg log -G -T '{rev}:{node|short} {desc}' | |
344 | @ 6:8fda0c726bf2 x |
|
344 | @ 6:8fda0c726bf2 x | |
345 | | |
|
345 | | | |
346 | o 5:63379946892c three |
|
346 | o 5:63379946892c three | |
347 | | |
|
347 | | | |
348 | | o 4:f3cfcca30c44 x |
|
348 | | o 4:f3cfcca30c44 x | |
349 | | | |
|
349 | | | | |
350 | | | o 3:2a30f3cfee78 four |
|
350 | | | o 3:2a30f3cfee78 four | |
351 | | |/ *** |
|
351 | | |/ *** | |
352 | | | five |
|
352 | | | five | |
353 | | o 2:eb57da33312f three |
|
353 | | o 2:eb57da33312f three | |
354 | |/ |
|
354 | |/ | |
355 | o 1:579e40513370 two |
|
355 | o 1:579e40513370 two | |
356 | | |
|
356 | | | |
357 | o 0:6058cbb6cfd7 one |
|
357 | o 0:6058cbb6cfd7 one | |
358 |
|
358 | |||
359 |
|
359 | |||
360 | Test that abort fails gracefully on exception |
|
360 | Test that abort fails gracefully on exception | |
361 | ---------------------------------------------- |
|
361 | ---------------------------------------------- | |
362 | $ hg histedit . -q --commands - << EOF |
|
362 | $ hg histedit . -q --commands - << EOF | |
363 | > edit 8fda0c726bf2 6 x |
|
363 | > edit 8fda0c726bf2 6 x | |
364 | > EOF |
|
364 | > EOF | |
365 | Editing (8fda0c726bf2), you may commit or record as needed now. |
|
365 | Editing (8fda0c726bf2), you may commit or record as needed now. | |
366 | (hg histedit --continue to resume) |
|
366 | (hg histedit --continue to resume) | |
367 |
[ |
|
367 | [240] | |
368 | Corrupt histedit state file |
|
368 | Corrupt histedit state file | |
369 | $ sed 's/8fda0c726bf2/123456789012/' .hg/histedit-state > ../corrupt-histedit |
|
369 | $ sed 's/8fda0c726bf2/123456789012/' .hg/histedit-state > ../corrupt-histedit | |
370 | $ mv ../corrupt-histedit .hg/histedit-state |
|
370 | $ mv ../corrupt-histedit .hg/histedit-state | |
371 | $ hg abort |
|
371 | $ hg abort | |
372 | warning: encountered an exception during histedit --abort; the repository may not have been completely cleaned up |
|
372 | warning: encountered an exception during histedit --abort; the repository may not have been completely cleaned up | |
373 | abort: $TESTTMP/foo/.hg/strip-backup/*-histedit.hg: $ENOENT$ (glob) (windows !) |
|
373 | abort: $TESTTMP/foo/.hg/strip-backup/*-histedit.hg: $ENOENT$ (glob) (windows !) | |
374 | abort: $ENOENT$: '$TESTTMP/foo/.hg/strip-backup/*-histedit.hg' (glob) (no-windows !) |
|
374 | abort: $ENOENT$: '$TESTTMP/foo/.hg/strip-backup/*-histedit.hg' (glob) (no-windows !) | |
375 | [255] |
|
375 | [255] | |
376 | Histedit state has been exited |
|
376 | Histedit state has been exited | |
377 | $ hg summary -q |
|
377 | $ hg summary -q | |
378 | parent: 5:63379946892c |
|
378 | parent: 5:63379946892c | |
379 | commit: 1 added, 1 unknown (new branch head) |
|
379 | commit: 1 added, 1 unknown (new branch head) | |
380 | update: 4 new changesets (update) |
|
380 | update: 4 new changesets (update) | |
381 |
|
381 | |||
382 | $ cd .. |
|
382 | $ cd .. | |
383 |
|
383 | |||
384 | Set up default base revision tests |
|
384 | Set up default base revision tests | |
385 |
|
385 | |||
386 | $ hg init defaultbase |
|
386 | $ hg init defaultbase | |
387 | $ cd defaultbase |
|
387 | $ cd defaultbase | |
388 | $ touch foo |
|
388 | $ touch foo | |
389 | $ hg -q commit -A -m root |
|
389 | $ hg -q commit -A -m root | |
390 | $ echo 1 > foo |
|
390 | $ echo 1 > foo | |
391 | $ hg commit -m 'public 1' |
|
391 | $ hg commit -m 'public 1' | |
392 | $ hg phase --force --public -r . |
|
392 | $ hg phase --force --public -r . | |
393 | $ echo 2 > foo |
|
393 | $ echo 2 > foo | |
394 | $ hg commit -m 'draft after public' |
|
394 | $ hg commit -m 'draft after public' | |
395 | $ hg -q up -r 1 |
|
395 | $ hg -q up -r 1 | |
396 | $ echo 3 > foo |
|
396 | $ echo 3 > foo | |
397 | $ hg commit -m 'head 1 public' |
|
397 | $ hg commit -m 'head 1 public' | |
398 | created new head |
|
398 | created new head | |
399 | $ hg phase --force --public -r . |
|
399 | $ hg phase --force --public -r . | |
400 | $ echo 4 > foo |
|
400 | $ echo 4 > foo | |
401 | $ hg commit -m 'head 1 draft 1' |
|
401 | $ hg commit -m 'head 1 draft 1' | |
402 | $ echo 5 > foo |
|
402 | $ echo 5 > foo | |
403 | $ hg commit -m 'head 1 draft 2' |
|
403 | $ hg commit -m 'head 1 draft 2' | |
404 | $ hg -q up -r 2 |
|
404 | $ hg -q up -r 2 | |
405 | $ echo 6 > foo |
|
405 | $ echo 6 > foo | |
406 | $ hg commit -m 'head 2 commit 1' |
|
406 | $ hg commit -m 'head 2 commit 1' | |
407 | $ echo 7 > foo |
|
407 | $ echo 7 > foo | |
408 | $ hg commit -m 'head 2 commit 2' |
|
408 | $ hg commit -m 'head 2 commit 2' | |
409 | $ hg -q up -r 2 |
|
409 | $ hg -q up -r 2 | |
410 | $ echo 8 > foo |
|
410 | $ echo 8 > foo | |
411 | $ hg commit -m 'head 3' |
|
411 | $ hg commit -m 'head 3' | |
412 | created new head |
|
412 | created new head | |
413 | $ hg -q up -r 2 |
|
413 | $ hg -q up -r 2 | |
414 | $ echo 9 > foo |
|
414 | $ echo 9 > foo | |
415 | $ hg commit -m 'head 4' |
|
415 | $ hg commit -m 'head 4' | |
416 | created new head |
|
416 | created new head | |
417 | $ hg merge --tool :local -r 8 |
|
417 | $ hg merge --tool :local -r 8 | |
418 | 0 files updated, 1 files merged, 0 files removed, 0 files unresolved |
|
418 | 0 files updated, 1 files merged, 0 files removed, 0 files unresolved | |
419 | (branch merge, don't forget to commit) |
|
419 | (branch merge, don't forget to commit) | |
420 | $ hg commit -m 'merge head 3 into head 4' |
|
420 | $ hg commit -m 'merge head 3 into head 4' | |
421 | $ echo 11 > foo |
|
421 | $ echo 11 > foo | |
422 | $ hg commit -m 'commit 1 after merge' |
|
422 | $ hg commit -m 'commit 1 after merge' | |
423 | $ echo 12 > foo |
|
423 | $ echo 12 > foo | |
424 | $ hg commit -m 'commit 2 after merge' |
|
424 | $ hg commit -m 'commit 2 after merge' | |
425 |
|
425 | |||
426 | $ hg log -G -T '{rev}:{node|short} {phase} {desc}\n' |
|
426 | $ hg log -G -T '{rev}:{node|short} {phase} {desc}\n' | |
427 | @ 12:8cde254db839 draft commit 2 after merge |
|
427 | @ 12:8cde254db839 draft commit 2 after merge | |
428 | | |
|
428 | | | |
429 | o 11:6f2f0241f119 draft commit 1 after merge |
|
429 | o 11:6f2f0241f119 draft commit 1 after merge | |
430 | | |
|
430 | | | |
431 | o 10:90506cc76b00 draft merge head 3 into head 4 |
|
431 | o 10:90506cc76b00 draft merge head 3 into head 4 | |
432 | |\ |
|
432 | |\ | |
433 | | o 9:f8607a373a97 draft head 4 |
|
433 | | o 9:f8607a373a97 draft head 4 | |
434 | | | |
|
434 | | | | |
435 | o | 8:0da92be05148 draft head 3 |
|
435 | o | 8:0da92be05148 draft head 3 | |
436 | |/ |
|
436 | |/ | |
437 | | o 7:4c35cdf97d5e draft head 2 commit 2 |
|
437 | | o 7:4c35cdf97d5e draft head 2 commit 2 | |
438 | | | |
|
438 | | | | |
439 | | o 6:931820154288 draft head 2 commit 1 |
|
439 | | o 6:931820154288 draft head 2 commit 1 | |
440 | |/ |
|
440 | |/ | |
441 | | o 5:8cdc02b9bc63 draft head 1 draft 2 |
|
441 | | o 5:8cdc02b9bc63 draft head 1 draft 2 | |
442 | | | |
|
442 | | | | |
443 | | o 4:463b8c0d2973 draft head 1 draft 1 |
|
443 | | o 4:463b8c0d2973 draft head 1 draft 1 | |
444 | | | |
|
444 | | | | |
445 | | o 3:23a0c4eefcbf public head 1 public |
|
445 | | o 3:23a0c4eefcbf public head 1 public | |
446 | | | |
|
446 | | | | |
447 | o | 2:4117331c3abb draft draft after public |
|
447 | o | 2:4117331c3abb draft draft after public | |
448 | |/ |
|
448 | |/ | |
449 | o 1:4426d359ea59 public public 1 |
|
449 | o 1:4426d359ea59 public public 1 | |
450 | | |
|
450 | | | |
451 | o 0:54136a8ddf32 public root |
|
451 | o 0:54136a8ddf32 public root | |
452 |
|
452 | |||
453 |
|
453 | |||
454 | Default base revision should stop at public changesets |
|
454 | Default base revision should stop at public changesets | |
455 |
|
455 | |||
456 | $ hg -q up 8cdc02b9bc63 |
|
456 | $ hg -q up 8cdc02b9bc63 | |
457 | $ hg histedit --commands - <<EOF |
|
457 | $ hg histedit --commands - <<EOF | |
458 | > pick 463b8c0d2973 |
|
458 | > pick 463b8c0d2973 | |
459 | > pick 8cdc02b9bc63 |
|
459 | > pick 8cdc02b9bc63 | |
460 | > EOF |
|
460 | > EOF | |
461 |
|
461 | |||
462 | Default base revision should stop at branchpoint |
|
462 | Default base revision should stop at branchpoint | |
463 |
|
463 | |||
464 | $ hg -q up 4c35cdf97d5e |
|
464 | $ hg -q up 4c35cdf97d5e | |
465 | $ hg histedit --commands - <<EOF |
|
465 | $ hg histedit --commands - <<EOF | |
466 | > pick 931820154288 |
|
466 | > pick 931820154288 | |
467 | > pick 4c35cdf97d5e |
|
467 | > pick 4c35cdf97d5e | |
468 | > EOF |
|
468 | > EOF | |
469 |
|
469 | |||
470 | Default base revision should stop at merge commit |
|
470 | Default base revision should stop at merge commit | |
471 |
|
471 | |||
472 | $ hg -q up 8cde254db839 |
|
472 | $ hg -q up 8cde254db839 | |
473 | $ hg histedit --commands - <<EOF |
|
473 | $ hg histedit --commands - <<EOF | |
474 | > pick 6f2f0241f119 |
|
474 | > pick 6f2f0241f119 | |
475 | > pick 8cde254db839 |
|
475 | > pick 8cde254db839 | |
476 | > EOF |
|
476 | > EOF | |
477 |
|
477 | |||
478 | commit --amend should abort if histedit is in progress |
|
478 | commit --amend should abort if histedit is in progress | |
479 | (issue4800) and markers are not being created. |
|
479 | (issue4800) and markers are not being created. | |
480 | Eventually, histedit could perhaps look at `source` extra, |
|
480 | Eventually, histedit could perhaps look at `source` extra, | |
481 | in which case this test should be revisited. |
|
481 | in which case this test should be revisited. | |
482 |
|
482 | |||
483 | $ hg -q up 8cde254db839 |
|
483 | $ hg -q up 8cde254db839 | |
484 | $ hg histedit 6f2f0241f119 --commands - <<EOF |
|
484 | $ hg histedit 6f2f0241f119 --commands - <<EOF | |
485 | > pick 8cde254db839 |
|
485 | > pick 8cde254db839 | |
486 | > edit 6f2f0241f119 |
|
486 | > edit 6f2f0241f119 | |
487 | > EOF |
|
487 | > EOF | |
488 | merging foo |
|
488 | merging foo | |
489 | warning: conflicts while merging foo! (edit, then use 'hg resolve --mark') |
|
489 | warning: conflicts while merging foo! (edit, then use 'hg resolve --mark') | |
490 | Fix up the change (pick 8cde254db839) |
|
490 | Fix up the change (pick 8cde254db839) | |
491 | (hg histedit --continue to resume) |
|
491 | (hg histedit --continue to resume) | |
492 |
[ |
|
492 | [240] | |
493 | $ hg resolve -m --all |
|
493 | $ hg resolve -m --all | |
494 | (no more unresolved files) |
|
494 | (no more unresolved files) | |
495 | continue: hg histedit --continue |
|
495 | continue: hg histedit --continue | |
496 | $ hg histedit --cont |
|
496 | $ hg histedit --cont | |
497 | merging foo |
|
497 | merging foo | |
498 | warning: conflicts while merging foo! (edit, then use 'hg resolve --mark') |
|
498 | warning: conflicts while merging foo! (edit, then use 'hg resolve --mark') | |
499 | Editing (6f2f0241f119), you may commit or record as needed now. |
|
499 | Editing (6f2f0241f119), you may commit or record as needed now. | |
500 | (hg histedit --continue to resume) |
|
500 | (hg histedit --continue to resume) | |
501 |
[ |
|
501 | [240] | |
502 | $ hg resolve -m --all |
|
502 | $ hg resolve -m --all | |
503 | (no more unresolved files) |
|
503 | (no more unresolved files) | |
504 | continue: hg histedit --continue |
|
504 | continue: hg histedit --continue | |
505 | $ hg commit --amend -m 'reject this fold' |
|
505 | $ hg commit --amend -m 'reject this fold' | |
506 | abort: histedit in progress |
|
506 | abort: histedit in progress | |
507 | (use 'hg histedit --continue' or 'hg histedit --abort') |
|
507 | (use 'hg histedit --continue' or 'hg histedit --abort') | |
508 | [255] |
|
508 | [255] | |
509 |
|
509 | |||
510 | With markers enabled, histedit does not get confused, and |
|
510 | With markers enabled, histedit does not get confused, and | |
511 | amend should not be blocked by the ongoing histedit. |
|
511 | amend should not be blocked by the ongoing histedit. | |
512 |
|
512 | |||
513 | $ cat >>$HGRCPATH <<EOF |
|
513 | $ cat >>$HGRCPATH <<EOF | |
514 | > [experimental] |
|
514 | > [experimental] | |
515 | > evolution.createmarkers=True |
|
515 | > evolution.createmarkers=True | |
516 | > evolution.allowunstable=True |
|
516 | > evolution.allowunstable=True | |
517 | > EOF |
|
517 | > EOF | |
518 | $ hg commit --amend -m 'allow this fold' |
|
518 | $ hg commit --amend -m 'allow this fold' | |
519 | $ hg histedit --continue |
|
519 | $ hg histedit --continue | |
520 |
|
520 | |||
521 | $ cd .. |
|
521 | $ cd .. | |
522 |
|
522 | |||
523 | Test autoverb feature |
|
523 | Test autoverb feature | |
524 |
|
524 | |||
525 | $ hg init autoverb |
|
525 | $ hg init autoverb | |
526 | $ cd autoverb |
|
526 | $ cd autoverb | |
527 | $ echo alpha >> alpha |
|
527 | $ echo alpha >> alpha | |
528 | $ hg ci -qAm one |
|
528 | $ hg ci -qAm one | |
529 | $ echo alpha >> alpha |
|
529 | $ echo alpha >> alpha | |
530 | $ hg ci -qm two |
|
530 | $ hg ci -qm two | |
531 | $ echo beta >> beta |
|
531 | $ echo beta >> beta | |
532 | $ hg ci -qAm "roll! one" |
|
532 | $ hg ci -qAm "roll! one" | |
533 |
|
533 | |||
534 | $ hg log --style compact --graph |
|
534 | $ hg log --style compact --graph | |
535 | @ 2[tip] 4f34d0f8b5fa 1970-01-01 00:00 +0000 test |
|
535 | @ 2[tip] 4f34d0f8b5fa 1970-01-01 00:00 +0000 test | |
536 | | roll! one |
|
536 | | roll! one | |
537 | | |
|
537 | | | |
538 | o 1 579e40513370 1970-01-01 00:00 +0000 test |
|
538 | o 1 579e40513370 1970-01-01 00:00 +0000 test | |
539 | | two |
|
539 | | two | |
540 | | |
|
540 | | | |
541 | o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test |
|
541 | o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test | |
542 | one |
|
542 | one | |
543 |
|
543 | |||
544 |
|
544 | |||
545 | Check that 'roll' is selected by default |
|
545 | Check that 'roll' is selected by default | |
546 |
|
546 | |||
547 | $ HGEDITOR=cat hg histedit 0 --config experimental.histedit.autoverb=True |
|
547 | $ HGEDITOR=cat hg histedit 0 --config experimental.histedit.autoverb=True | |
548 | pick 6058cbb6cfd7 0 one |
|
548 | pick 6058cbb6cfd7 0 one | |
549 | roll 4f34d0f8b5fa 2 roll! one |
|
549 | roll 4f34d0f8b5fa 2 roll! one | |
550 | pick 579e40513370 1 two |
|
550 | pick 579e40513370 1 two | |
551 |
|
551 | |||
552 | # Edit history between 6058cbb6cfd7 and 4f34d0f8b5fa |
|
552 | # Edit history between 6058cbb6cfd7 and 4f34d0f8b5fa | |
553 | # |
|
553 | # | |
554 | # Commits are listed from least to most recent |
|
554 | # Commits are listed from least to most recent | |
555 | # |
|
555 | # | |
556 | # You can reorder changesets by reordering the lines |
|
556 | # You can reorder changesets by reordering the lines | |
557 | # |
|
557 | # | |
558 | # Commands: |
|
558 | # Commands: | |
559 | # |
|
559 | # | |
560 | # e, edit = use commit, but stop for amending |
|
560 | # e, edit = use commit, but stop for amending | |
561 | # m, mess = edit commit message without changing commit content |
|
561 | # m, mess = edit commit message without changing commit content | |
562 | # p, pick = use commit |
|
562 | # p, pick = use commit | |
563 | # b, base = checkout changeset and apply further changesets from there |
|
563 | # b, base = checkout changeset and apply further changesets from there | |
564 | # d, drop = remove commit from history |
|
564 | # d, drop = remove commit from history | |
565 | # f, fold = use commit, but combine it with the one above |
|
565 | # f, fold = use commit, but combine it with the one above | |
566 | # r, roll = like fold, but discard this commit's description and date |
|
566 | # r, roll = like fold, but discard this commit's description and date | |
567 | # |
|
567 | # | |
568 |
|
568 | |||
569 | $ cd .. |
|
569 | $ cd .. | |
570 |
|
570 | |||
571 | Check that histedit's commands accept revsets |
|
571 | Check that histedit's commands accept revsets | |
572 | $ hg init bar |
|
572 | $ hg init bar | |
573 | $ cd bar |
|
573 | $ cd bar | |
574 | $ echo w >> a |
|
574 | $ echo w >> a | |
575 | $ hg ci -qAm "adds a" |
|
575 | $ hg ci -qAm "adds a" | |
576 | $ echo x >> b |
|
576 | $ echo x >> b | |
577 | $ hg ci -qAm "adds b" |
|
577 | $ hg ci -qAm "adds b" | |
578 | $ echo y >> c |
|
578 | $ echo y >> c | |
579 | $ hg ci -qAm "adds c" |
|
579 | $ hg ci -qAm "adds c" | |
580 | $ echo z >> d |
|
580 | $ echo z >> d | |
581 | $ hg ci -qAm "adds d" |
|
581 | $ hg ci -qAm "adds d" | |
582 | $ hg log -G -T '{rev} {desc}\n' |
|
582 | $ hg log -G -T '{rev} {desc}\n' | |
583 | @ 3 adds d |
|
583 | @ 3 adds d | |
584 | | |
|
584 | | | |
585 | o 2 adds c |
|
585 | o 2 adds c | |
586 | | |
|
586 | | | |
587 | o 1 adds b |
|
587 | o 1 adds b | |
588 | | |
|
588 | | | |
589 | o 0 adds a |
|
589 | o 0 adds a | |
590 |
|
590 | |||
591 | $ HGEDITOR=cat hg histedit "2" --commands - << EOF |
|
591 | $ HGEDITOR=cat hg histedit "2" --commands - << EOF | |
592 | > base -4 adds c |
|
592 | > base -4 adds c | |
593 | > pick 2 adds c |
|
593 | > pick 2 adds c | |
594 | > pick tip adds d |
|
594 | > pick tip adds d | |
595 | > EOF |
|
595 | > EOF | |
596 | $ hg log -G -T '{rev} {desc}\n' |
|
596 | $ hg log -G -T '{rev} {desc}\n' | |
597 | @ 5 adds d |
|
597 | @ 5 adds d | |
598 | | |
|
598 | | | |
599 | o 4 adds c |
|
599 | o 4 adds c | |
600 | | |
|
600 | | | |
601 | | o 1 adds b |
|
601 | | o 1 adds b | |
602 | |/ |
|
602 | |/ | |
603 | o 0 adds a |
|
603 | o 0 adds a | |
604 |
|
604 | |||
605 |
|
605 |
@@ -1,556 +1,556 b'' | |||||
1 | $ . "$TESTDIR/histedit-helpers.sh" |
|
1 | $ . "$TESTDIR/histedit-helpers.sh" | |
2 |
|
2 | |||
3 | $ cat >> $HGRCPATH <<EOF |
|
3 | $ cat >> $HGRCPATH <<EOF | |
4 | > [extensions] |
|
4 | > [extensions] | |
5 | > histedit= |
|
5 | > histedit= | |
6 | > strip= |
|
6 | > strip= | |
7 | > mockmakedate = $TESTDIR/mockmakedate.py |
|
7 | > mockmakedate = $TESTDIR/mockmakedate.py | |
8 | > EOF |
|
8 | > EOF | |
9 |
|
9 | |||
10 | $ initrepo () |
|
10 | $ initrepo () | |
11 | > { |
|
11 | > { | |
12 | > hg init r |
|
12 | > hg init r | |
13 | > cd r |
|
13 | > cd r | |
14 | > for x in a b c d e f g; do |
|
14 | > for x in a b c d e f g; do | |
15 | > echo $x > $x |
|
15 | > echo $x > $x | |
16 | > hg add $x |
|
16 | > hg add $x | |
17 | > hg ci -m $x |
|
17 | > hg ci -m $x | |
18 | > done |
|
18 | > done | |
19 | > } |
|
19 | > } | |
20 |
|
20 | |||
21 | $ initrepo |
|
21 | $ initrepo | |
22 |
|
22 | |||
23 | log before edit |
|
23 | log before edit | |
24 | $ hg log --graph |
|
24 | $ hg log --graph | |
25 | @ changeset: 6:3c6a8ed2ebe8 |
|
25 | @ changeset: 6:3c6a8ed2ebe8 | |
26 | | tag: tip |
|
26 | | tag: tip | |
27 | | user: test |
|
27 | | user: test | |
28 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
28 | | date: Thu Jan 01 00:00:00 1970 +0000 | |
29 | | summary: g |
|
29 | | summary: g | |
30 | | |
|
30 | | | |
31 | o changeset: 5:652413bf663e |
|
31 | o changeset: 5:652413bf663e | |
32 | | user: test |
|
32 | | user: test | |
33 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
33 | | date: Thu Jan 01 00:00:00 1970 +0000 | |
34 | | summary: f |
|
34 | | summary: f | |
35 | | |
|
35 | | | |
36 | o changeset: 4:e860deea161a |
|
36 | o changeset: 4:e860deea161a | |
37 | | user: test |
|
37 | | user: test | |
38 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
38 | | date: Thu Jan 01 00:00:00 1970 +0000 | |
39 | | summary: e |
|
39 | | summary: e | |
40 | | |
|
40 | | | |
41 | o changeset: 3:055a42cdd887 |
|
41 | o changeset: 3:055a42cdd887 | |
42 | | user: test |
|
42 | | user: test | |
43 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
43 | | date: Thu Jan 01 00:00:00 1970 +0000 | |
44 | | summary: d |
|
44 | | summary: d | |
45 | | |
|
45 | | | |
46 | o changeset: 2:177f92b77385 |
|
46 | o changeset: 2:177f92b77385 | |
47 | | user: test |
|
47 | | user: test | |
48 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
48 | | date: Thu Jan 01 00:00:00 1970 +0000 | |
49 | | summary: c |
|
49 | | summary: c | |
50 | | |
|
50 | | | |
51 | o changeset: 1:d2ae7f538514 |
|
51 | o changeset: 1:d2ae7f538514 | |
52 | | user: test |
|
52 | | user: test | |
53 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
53 | | date: Thu Jan 01 00:00:00 1970 +0000 | |
54 | | summary: b |
|
54 | | summary: b | |
55 | | |
|
55 | | | |
56 | o changeset: 0:cb9a9f314b8b |
|
56 | o changeset: 0:cb9a9f314b8b | |
57 | user: test |
|
57 | user: test | |
58 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
58 | date: Thu Jan 01 00:00:00 1970 +0000 | |
59 | summary: a |
|
59 | summary: a | |
60 |
|
60 | |||
61 | dirty a file |
|
61 | dirty a file | |
62 | $ echo a > g |
|
62 | $ echo a > g | |
63 | $ hg histedit 177f92b77385 --commands - 2>&1 << EOF |
|
63 | $ hg histedit 177f92b77385 --commands - 2>&1 << EOF | |
64 | > EOF |
|
64 | > EOF | |
65 | abort: uncommitted changes |
|
65 | abort: uncommitted changes | |
66 | [255] |
|
66 | [255] | |
67 | $ echo g > g |
|
67 | $ echo g > g | |
68 |
|
68 | |||
69 | edit the history |
|
69 | edit the history | |
70 | $ hg histedit 177f92b77385 --commands - 2>&1 << EOF| fixbundle |
|
70 | $ hg histedit 177f92b77385 --commands - 2>&1 << EOF| fixbundle | |
71 | > pick 177f92b77385 c |
|
71 | > pick 177f92b77385 c | |
72 | > pick 055a42cdd887 d |
|
72 | > pick 055a42cdd887 d | |
73 | > edit e860deea161a e |
|
73 | > edit e860deea161a e | |
74 | > pick 652413bf663e f |
|
74 | > pick 652413bf663e f | |
75 | > pick 3c6a8ed2ebe8 g |
|
75 | > pick 3c6a8ed2ebe8 g | |
76 | > EOF |
|
76 | > EOF | |
77 | 0 files updated, 0 files merged, 3 files removed, 0 files unresolved |
|
77 | 0 files updated, 0 files merged, 3 files removed, 0 files unresolved | |
78 | Editing (e860deea161a), you may commit or record as needed now. |
|
78 | Editing (e860deea161a), you may commit or record as needed now. | |
79 | (hg histedit --continue to resume) |
|
79 | (hg histedit --continue to resume) | |
80 |
|
80 | |||
81 | try to update and get an error |
|
81 | try to update and get an error | |
82 | $ hg update tip |
|
82 | $ hg update tip | |
83 | abort: histedit in progress |
|
83 | abort: histedit in progress | |
84 | (use 'hg histedit --continue' or 'hg histedit --abort') |
|
84 | (use 'hg histedit --continue' or 'hg histedit --abort') | |
85 | [255] |
|
85 | [255] | |
86 |
|
86 | |||
87 | edit the plan via the editor |
|
87 | edit the plan via the editor | |
88 | $ cat >> $TESTTMP/editplan.sh <<EOF |
|
88 | $ cat >> $TESTTMP/editplan.sh <<EOF | |
89 | > cat > \$1 <<EOF2 |
|
89 | > cat > \$1 <<EOF2 | |
90 | > drop e860deea161a e |
|
90 | > drop e860deea161a e | |
91 | > drop 652413bf663e f |
|
91 | > drop 652413bf663e f | |
92 | > drop 3c6a8ed2ebe8 g |
|
92 | > drop 3c6a8ed2ebe8 g | |
93 | > EOF2 |
|
93 | > EOF2 | |
94 | > EOF |
|
94 | > EOF | |
95 | $ HGEDITOR="sh $TESTTMP/editplan.sh" hg histedit --edit-plan |
|
95 | $ HGEDITOR="sh $TESTTMP/editplan.sh" hg histedit --edit-plan | |
96 | $ cat .hg/histedit-state |
|
96 | $ cat .hg/histedit-state | |
97 | v1 |
|
97 | v1 | |
98 | 055a42cdd88768532f9cf79daa407fc8d138de9b |
|
98 | 055a42cdd88768532f9cf79daa407fc8d138de9b | |
99 | 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799 |
|
99 | 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799 | |
100 | False |
|
100 | False | |
101 | 3 |
|
101 | 3 | |
102 | drop |
|
102 | drop | |
103 | e860deea161a2f77de56603b340ebbb4536308ae |
|
103 | e860deea161a2f77de56603b340ebbb4536308ae | |
104 | drop |
|
104 | drop | |
105 | 652413bf663ef2a641cab26574e46d5f5a64a55a |
|
105 | 652413bf663ef2a641cab26574e46d5f5a64a55a | |
106 | drop |
|
106 | drop | |
107 | 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799 |
|
107 | 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799 | |
108 | 0 |
|
108 | 0 | |
109 | strip-backup/177f92b77385-0ebe6a8f-histedit.hg |
|
109 | strip-backup/177f92b77385-0ebe6a8f-histedit.hg | |
110 |
|
110 | |||
111 | edit the plan via --commands |
|
111 | edit the plan via --commands | |
112 | $ hg histedit --edit-plan --commands - 2>&1 << EOF |
|
112 | $ hg histedit --edit-plan --commands - 2>&1 << EOF | |
113 | > edit e860deea161a e |
|
113 | > edit e860deea161a e | |
114 | > pick 652413bf663e f |
|
114 | > pick 652413bf663e f | |
115 | > drop 3c6a8ed2ebe8 g |
|
115 | > drop 3c6a8ed2ebe8 g | |
116 | > EOF |
|
116 | > EOF | |
117 | $ cat .hg/histedit-state |
|
117 | $ cat .hg/histedit-state | |
118 | v1 |
|
118 | v1 | |
119 | 055a42cdd88768532f9cf79daa407fc8d138de9b |
|
119 | 055a42cdd88768532f9cf79daa407fc8d138de9b | |
120 | 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799 |
|
120 | 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799 | |
121 | False |
|
121 | False | |
122 | 3 |
|
122 | 3 | |
123 | edit |
|
123 | edit | |
124 | e860deea161a2f77de56603b340ebbb4536308ae |
|
124 | e860deea161a2f77de56603b340ebbb4536308ae | |
125 | pick |
|
125 | pick | |
126 | 652413bf663ef2a641cab26574e46d5f5a64a55a |
|
126 | 652413bf663ef2a641cab26574e46d5f5a64a55a | |
127 | drop |
|
127 | drop | |
128 | 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799 |
|
128 | 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799 | |
129 | 0 |
|
129 | 0 | |
130 | strip-backup/177f92b77385-0ebe6a8f-histedit.hg |
|
130 | strip-backup/177f92b77385-0ebe6a8f-histedit.hg | |
131 |
|
131 | |||
132 | Go at a random point and try to continue |
|
132 | Go at a random point and try to continue | |
133 |
|
133 | |||
134 | $ hg id -n |
|
134 | $ hg id -n | |
135 | 3+ |
|
135 | 3+ | |
136 | $ hg up 0 |
|
136 | $ hg up 0 | |
137 | abort: histedit in progress |
|
137 | abort: histedit in progress | |
138 | (use 'hg histedit --continue' or 'hg histedit --abort') |
|
138 | (use 'hg histedit --continue' or 'hg histedit --abort') | |
139 | [255] |
|
139 | [255] | |
140 |
|
140 | |||
141 | Try to delete necessary commit |
|
141 | Try to delete necessary commit | |
142 | $ hg strip -r 652413b |
|
142 | $ hg strip -r 652413b | |
143 | abort: histedit in progress, can't strip 652413bf663e |
|
143 | abort: histedit in progress, can't strip 652413bf663e | |
144 | [255] |
|
144 | [255] | |
145 |
|
145 | |||
146 | commit, then edit the revision |
|
146 | commit, then edit the revision | |
147 | $ hg ci -m 'wat' |
|
147 | $ hg ci -m 'wat' | |
148 | created new head |
|
148 | created new head | |
149 | $ echo a > e |
|
149 | $ echo a > e | |
150 |
|
150 | |||
151 | qnew should fail while we're in the middle of the edit step |
|
151 | qnew should fail while we're in the middle of the edit step | |
152 |
|
152 | |||
153 | $ hg --config extensions.mq= qnew please-fail |
|
153 | $ hg --config extensions.mq= qnew please-fail | |
154 | abort: histedit in progress |
|
154 | abort: histedit in progress | |
155 | (use 'hg histedit --continue' or 'hg histedit --abort') |
|
155 | (use 'hg histedit --continue' or 'hg histedit --abort') | |
156 | [255] |
|
156 | [255] | |
157 | $ HGEDITOR='echo foobaz > ' hg histedit --continue 2>&1 | fixbundle |
|
157 | $ HGEDITOR='echo foobaz > ' hg histedit --continue 2>&1 | fixbundle | |
158 |
|
158 | |||
159 | $ hg log --graph |
|
159 | $ hg log --graph | |
160 | @ changeset: 6:b5f70786f9b0 |
|
160 | @ changeset: 6:b5f70786f9b0 | |
161 | | tag: tip |
|
161 | | tag: tip | |
162 | | user: test |
|
162 | | user: test | |
163 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
163 | | date: Thu Jan 01 00:00:00 1970 +0000 | |
164 | | summary: f |
|
164 | | summary: f | |
165 | | |
|
165 | | | |
166 | o changeset: 5:a5e1ba2f7afb |
|
166 | o changeset: 5:a5e1ba2f7afb | |
167 | | user: test |
|
167 | | user: test | |
168 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
168 | | date: Thu Jan 01 00:00:00 1970 +0000 | |
169 | | summary: foobaz |
|
169 | | summary: foobaz | |
170 | | |
|
170 | | | |
171 | o changeset: 4:1a60820cd1f6 |
|
171 | o changeset: 4:1a60820cd1f6 | |
172 | | user: test |
|
172 | | user: test | |
173 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
173 | | date: Thu Jan 01 00:00:00 1970 +0000 | |
174 | | summary: wat |
|
174 | | summary: wat | |
175 | | |
|
175 | | | |
176 | o changeset: 3:055a42cdd887 |
|
176 | o changeset: 3:055a42cdd887 | |
177 | | user: test |
|
177 | | user: test | |
178 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
178 | | date: Thu Jan 01 00:00:00 1970 +0000 | |
179 | | summary: d |
|
179 | | summary: d | |
180 | | |
|
180 | | | |
181 | o changeset: 2:177f92b77385 |
|
181 | o changeset: 2:177f92b77385 | |
182 | | user: test |
|
182 | | user: test | |
183 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
183 | | date: Thu Jan 01 00:00:00 1970 +0000 | |
184 | | summary: c |
|
184 | | summary: c | |
185 | | |
|
185 | | | |
186 | o changeset: 1:d2ae7f538514 |
|
186 | o changeset: 1:d2ae7f538514 | |
187 | | user: test |
|
187 | | user: test | |
188 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
188 | | date: Thu Jan 01 00:00:00 1970 +0000 | |
189 | | summary: b |
|
189 | | summary: b | |
190 | | |
|
190 | | | |
191 | o changeset: 0:cb9a9f314b8b |
|
191 | o changeset: 0:cb9a9f314b8b | |
192 | user: test |
|
192 | user: test | |
193 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
193 | date: Thu Jan 01 00:00:00 1970 +0000 | |
194 | summary: a |
|
194 | summary: a | |
195 |
|
195 | |||
196 |
|
196 | |||
197 | $ hg cat e |
|
197 | $ hg cat e | |
198 | a |
|
198 | a | |
199 |
|
199 | |||
200 | Stripping necessary commits should not break --abort |
|
200 | Stripping necessary commits should not break --abort | |
201 |
|
201 | |||
202 | $ hg histedit 1a60820cd1f6 --commands - 2>&1 << EOF| fixbundle |
|
202 | $ hg histedit 1a60820cd1f6 --commands - 2>&1 << EOF| fixbundle | |
203 | > edit 1a60820cd1f6 wat |
|
203 | > edit 1a60820cd1f6 wat | |
204 | > pick a5e1ba2f7afb foobaz |
|
204 | > pick a5e1ba2f7afb foobaz | |
205 | > pick b5f70786f9b0 g |
|
205 | > pick b5f70786f9b0 g | |
206 | > EOF |
|
206 | > EOF | |
207 | 0 files updated, 0 files merged, 2 files removed, 0 files unresolved |
|
207 | 0 files updated, 0 files merged, 2 files removed, 0 files unresolved | |
208 | Editing (1a60820cd1f6), you may commit or record as needed now. |
|
208 | Editing (1a60820cd1f6), you may commit or record as needed now. | |
209 | (hg histedit --continue to resume) |
|
209 | (hg histedit --continue to resume) | |
210 |
|
210 | |||
211 | $ mv .hg/histedit-state .hg/histedit-state.bak |
|
211 | $ mv .hg/histedit-state .hg/histedit-state.bak | |
212 | $ hg strip -q -r b5f70786f9b0 |
|
212 | $ hg strip -q -r b5f70786f9b0 | |
213 | $ mv .hg/histedit-state.bak .hg/histedit-state |
|
213 | $ mv .hg/histedit-state.bak .hg/histedit-state | |
214 | $ hg histedit --abort |
|
214 | $ hg histedit --abort | |
215 | adding changesets |
|
215 | adding changesets | |
216 | adding manifests |
|
216 | adding manifests | |
217 | adding file changes |
|
217 | adding file changes | |
218 | added 1 changesets with 1 changes to 3 files |
|
218 | added 1 changesets with 1 changes to 3 files | |
219 | 2 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
219 | 2 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
220 | $ hg log -r . |
|
220 | $ hg log -r . | |
221 | changeset: 6:b5f70786f9b0 |
|
221 | changeset: 6:b5f70786f9b0 | |
222 | tag: tip |
|
222 | tag: tip | |
223 | user: test |
|
223 | user: test | |
224 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
224 | date: Thu Jan 01 00:00:00 1970 +0000 | |
225 | summary: f |
|
225 | summary: f | |
226 |
|
226 | |||
227 |
|
227 | |||
228 | check histedit_source |
|
228 | check histedit_source | |
229 |
|
229 | |||
230 | $ hg log --debug --rev 5 |
|
230 | $ hg log --debug --rev 5 | |
231 | changeset: 5:a5e1ba2f7afb899ef1581cea528fd885d2fca70d |
|
231 | changeset: 5:a5e1ba2f7afb899ef1581cea528fd885d2fca70d | |
232 | phase: draft |
|
232 | phase: draft | |
233 | parent: 4:1a60820cd1f6004a362aa622ebc47d59bc48eb34 |
|
233 | parent: 4:1a60820cd1f6004a362aa622ebc47d59bc48eb34 | |
234 | parent: -1:0000000000000000000000000000000000000000 |
|
234 | parent: -1:0000000000000000000000000000000000000000 | |
235 | manifest: 5:5ad3be8791f39117565557781f5464363b918a45 |
|
235 | manifest: 5:5ad3be8791f39117565557781f5464363b918a45 | |
236 | user: test |
|
236 | user: test | |
237 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
237 | date: Thu Jan 01 00:00:00 1970 +0000 | |
238 | files: e |
|
238 | files: e | |
239 | extra: branch=default |
|
239 | extra: branch=default | |
240 | extra: histedit_source=e860deea161a2f77de56603b340ebbb4536308ae |
|
240 | extra: histedit_source=e860deea161a2f77de56603b340ebbb4536308ae | |
241 | description: |
|
241 | description: | |
242 | foobaz |
|
242 | foobaz | |
243 |
|
243 | |||
244 |
|
244 | |||
245 |
|
245 | |||
246 | $ hg histedit tip --commands - 2>&1 <<EOF| fixbundle |
|
246 | $ hg histedit tip --commands - 2>&1 <<EOF| fixbundle | |
247 | > edit b5f70786f9b0 f |
|
247 | > edit b5f70786f9b0 f | |
248 | > EOF |
|
248 | > EOF | |
249 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved |
|
249 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved | |
250 | Editing (b5f70786f9b0), you may commit or record as needed now. |
|
250 | Editing (b5f70786f9b0), you may commit or record as needed now. | |
251 | (hg histedit --continue to resume) |
|
251 | (hg histedit --continue to resume) | |
252 | $ hg status |
|
252 | $ hg status | |
253 | A f |
|
253 | A f | |
254 |
|
254 | |||
255 | $ hg summary |
|
255 | $ hg summary | |
256 | parent: 5:a5e1ba2f7afb |
|
256 | parent: 5:a5e1ba2f7afb | |
257 | foobaz |
|
257 | foobaz | |
258 | branch: default |
|
258 | branch: default | |
259 | commit: 1 added (new branch head) |
|
259 | commit: 1 added (new branch head) | |
260 | update: 1 new changesets (update) |
|
260 | update: 1 new changesets (update) | |
261 | phases: 7 draft |
|
261 | phases: 7 draft | |
262 | hist: 1 remaining (histedit --continue) |
|
262 | hist: 1 remaining (histedit --continue) | |
263 |
|
263 | |||
264 | (test also that editor is invoked if histedit is continued for |
|
264 | (test also that editor is invoked if histedit is continued for | |
265 | "edit" action) |
|
265 | "edit" action) | |
266 |
|
266 | |||
267 | $ HGEDITOR='cat' hg histedit --continue |
|
267 | $ HGEDITOR='cat' hg histedit --continue | |
268 | f |
|
268 | f | |
269 |
|
269 | |||
270 |
|
270 | |||
271 | HG: Enter commit message. Lines beginning with 'HG:' are removed. |
|
271 | HG: Enter commit message. Lines beginning with 'HG:' are removed. | |
272 | HG: Leave message empty to abort commit. |
|
272 | HG: Leave message empty to abort commit. | |
273 | HG: -- |
|
273 | HG: -- | |
274 | HG: user: test |
|
274 | HG: user: test | |
275 | HG: branch 'default' |
|
275 | HG: branch 'default' | |
276 | HG: added f |
|
276 | HG: added f | |
277 | saved backup bundle to $TESTTMP/r/.hg/strip-backup/b5f70786f9b0-c28d9c86-histedit.hg |
|
277 | saved backup bundle to $TESTTMP/r/.hg/strip-backup/b5f70786f9b0-c28d9c86-histedit.hg | |
278 |
|
278 | |||
279 | $ hg status |
|
279 | $ hg status | |
280 |
|
280 | |||
281 | log after edit |
|
281 | log after edit | |
282 | $ hg log --limit 1 |
|
282 | $ hg log --limit 1 | |
283 | changeset: 6:a107ee126658 |
|
283 | changeset: 6:a107ee126658 | |
284 | tag: tip |
|
284 | tag: tip | |
285 | user: test |
|
285 | user: test | |
286 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
286 | date: Thu Jan 01 00:00:00 1970 +0000 | |
287 | summary: f |
|
287 | summary: f | |
288 |
|
288 | |||
289 |
|
289 | |||
290 | say we'll change the message, but don't. |
|
290 | say we'll change the message, but don't. | |
291 | $ cat > ../edit.sh <<EOF |
|
291 | $ cat > ../edit.sh <<EOF | |
292 | > cat "\$1" | sed s/pick/mess/ > tmp |
|
292 | > cat "\$1" | sed s/pick/mess/ > tmp | |
293 | > mv tmp "\$1" |
|
293 | > mv tmp "\$1" | |
294 | > EOF |
|
294 | > EOF | |
295 | $ HGEDITOR="sh ../edit.sh" hg histedit tip 2>&1 | fixbundle |
|
295 | $ HGEDITOR="sh ../edit.sh" hg histedit tip 2>&1 | fixbundle | |
296 | $ hg status |
|
296 | $ hg status | |
297 | $ hg log --limit 1 |
|
297 | $ hg log --limit 1 | |
298 | changeset: 6:1fd3b2fe7754 |
|
298 | changeset: 6:1fd3b2fe7754 | |
299 | tag: tip |
|
299 | tag: tip | |
300 | user: test |
|
300 | user: test | |
301 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
301 | date: Thu Jan 01 00:00:00 1970 +0000 | |
302 | summary: f |
|
302 | summary: f | |
303 |
|
303 | |||
304 |
|
304 | |||
305 | modify the message |
|
305 | modify the message | |
306 |
|
306 | |||
307 | check saving last-message.txt, at first |
|
307 | check saving last-message.txt, at first | |
308 |
|
308 | |||
309 | $ cat > $TESTTMP/commitfailure.py <<EOF |
|
309 | $ cat > $TESTTMP/commitfailure.py <<EOF | |
310 | > from mercurial import error |
|
310 | > from mercurial import error | |
311 | > def reposetup(ui, repo): |
|
311 | > def reposetup(ui, repo): | |
312 | > class commitfailure(repo.__class__): |
|
312 | > class commitfailure(repo.__class__): | |
313 | > def commit(self, *args, **kwargs): |
|
313 | > def commit(self, *args, **kwargs): | |
314 | > raise error.Abort(b'emulating unexpected abort') |
|
314 | > raise error.Abort(b'emulating unexpected abort') | |
315 | > repo.__class__ = commitfailure |
|
315 | > repo.__class__ = commitfailure | |
316 | > EOF |
|
316 | > EOF | |
317 | $ cat >> .hg/hgrc <<EOF |
|
317 | $ cat >> .hg/hgrc <<EOF | |
318 | > [extensions] |
|
318 | > [extensions] | |
319 | > # this failure occurs before editor invocation |
|
319 | > # this failure occurs before editor invocation | |
320 | > commitfailure = $TESTTMP/commitfailure.py |
|
320 | > commitfailure = $TESTTMP/commitfailure.py | |
321 | > EOF |
|
321 | > EOF | |
322 |
|
322 | |||
323 | $ cat > $TESTTMP/editor.sh <<EOF |
|
323 | $ cat > $TESTTMP/editor.sh <<EOF | |
324 | > echo "==== before editing" |
|
324 | > echo "==== before editing" | |
325 | > cat \$1 |
|
325 | > cat \$1 | |
326 | > echo "====" |
|
326 | > echo "====" | |
327 | > echo "check saving last-message.txt" >> \$1 |
|
327 | > echo "check saving last-message.txt" >> \$1 | |
328 | > EOF |
|
328 | > EOF | |
329 |
|
329 | |||
330 | (test that editor is not invoked before transaction starting) |
|
330 | (test that editor is not invoked before transaction starting) | |
331 |
|
331 | |||
332 | $ rm -f .hg/last-message.txt |
|
332 | $ rm -f .hg/last-message.txt | |
333 | $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF | fixbundle |
|
333 | $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF | fixbundle | |
334 | > mess 1fd3b2fe7754 f |
|
334 | > mess 1fd3b2fe7754 f | |
335 | > EOF |
|
335 | > EOF | |
336 | abort: emulating unexpected abort |
|
336 | abort: emulating unexpected abort | |
337 | $ test -f .hg/last-message.txt |
|
337 | $ test -f .hg/last-message.txt | |
338 | [1] |
|
338 | [1] | |
339 |
|
339 | |||
340 | $ cat >> .hg/hgrc <<EOF |
|
340 | $ cat >> .hg/hgrc <<EOF | |
341 | > [extensions] |
|
341 | > [extensions] | |
342 | > commitfailure = ! |
|
342 | > commitfailure = ! | |
343 | > EOF |
|
343 | > EOF | |
344 | $ hg histedit --abort -q |
|
344 | $ hg histedit --abort -q | |
345 |
|
345 | |||
346 | (test that editor is invoked and commit message is saved into |
|
346 | (test that editor is invoked and commit message is saved into | |
347 | "last-message.txt") |
|
347 | "last-message.txt") | |
348 |
|
348 | |||
349 | $ cat >> .hg/hgrc <<EOF |
|
349 | $ cat >> .hg/hgrc <<EOF | |
350 | > [hooks] |
|
350 | > [hooks] | |
351 | > # this failure occurs after editor invocation |
|
351 | > # this failure occurs after editor invocation | |
352 | > pretxncommit.unexpectedabort = false |
|
352 | > pretxncommit.unexpectedabort = false | |
353 | > EOF |
|
353 | > EOF | |
354 |
|
354 | |||
355 | $ hg status --rev '1fd3b2fe7754^1' --rev 1fd3b2fe7754 |
|
355 | $ hg status --rev '1fd3b2fe7754^1' --rev 1fd3b2fe7754 | |
356 | A f |
|
356 | A f | |
357 |
|
357 | |||
358 | $ rm -f .hg/last-message.txt |
|
358 | $ rm -f .hg/last-message.txt | |
359 | $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF |
|
359 | $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF | |
360 | > mess 1fd3b2fe7754 f |
|
360 | > mess 1fd3b2fe7754 f | |
361 | > EOF |
|
361 | > EOF | |
362 | ==== before editing |
|
362 | ==== before editing | |
363 | f |
|
363 | f | |
364 |
|
364 | |||
365 |
|
365 | |||
366 | HG: Enter commit message. Lines beginning with 'HG:' are removed. |
|
366 | HG: Enter commit message. Lines beginning with 'HG:' are removed. | |
367 | HG: Leave message empty to abort commit. |
|
367 | HG: Leave message empty to abort commit. | |
368 | HG: -- |
|
368 | HG: -- | |
369 | HG: user: test |
|
369 | HG: user: test | |
370 | HG: branch 'default' |
|
370 | HG: branch 'default' | |
371 | HG: added f |
|
371 | HG: added f | |
372 | ==== |
|
372 | ==== | |
373 | transaction abort! |
|
373 | transaction abort! | |
374 | rollback completed |
|
374 | rollback completed | |
375 | note: commit message saved in .hg/last-message.txt |
|
375 | note: commit message saved in .hg/last-message.txt | |
376 | note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it |
|
376 | note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it | |
377 | abort: pretxncommit.unexpectedabort hook exited with status 1 |
|
377 | abort: pretxncommit.unexpectedabort hook exited with status 1 | |
378 | [255] |
|
378 | [255] | |
379 | $ cat .hg/last-message.txt |
|
379 | $ cat .hg/last-message.txt | |
380 | f |
|
380 | f | |
381 |
|
381 | |||
382 |
|
382 | |||
383 | check saving last-message.txt |
|
383 | check saving last-message.txt | |
384 |
|
384 | |||
385 | (test also that editor is invoked if histedit is continued for "message" |
|
385 | (test also that editor is invoked if histedit is continued for "message" | |
386 | action) |
|
386 | action) | |
387 |
|
387 | |||
388 | $ HGEDITOR=cat hg histedit --continue |
|
388 | $ HGEDITOR=cat hg histedit --continue | |
389 | f |
|
389 | f | |
390 |
|
390 | |||
391 |
|
391 | |||
392 | HG: Enter commit message. Lines beginning with 'HG:' are removed. |
|
392 | HG: Enter commit message. Lines beginning with 'HG:' are removed. | |
393 | HG: Leave message empty to abort commit. |
|
393 | HG: Leave message empty to abort commit. | |
394 | HG: -- |
|
394 | HG: -- | |
395 | HG: user: test |
|
395 | HG: user: test | |
396 | HG: branch 'default' |
|
396 | HG: branch 'default' | |
397 | HG: added f |
|
397 | HG: added f | |
398 | transaction abort! |
|
398 | transaction abort! | |
399 | rollback completed |
|
399 | rollback completed | |
400 | note: commit message saved in .hg/last-message.txt |
|
400 | note: commit message saved in .hg/last-message.txt | |
401 | note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it |
|
401 | note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it | |
402 | abort: pretxncommit.unexpectedabort hook exited with status 1 |
|
402 | abort: pretxncommit.unexpectedabort hook exited with status 1 | |
403 | [255] |
|
403 | [255] | |
404 |
|
404 | |||
405 | $ cat >> .hg/hgrc <<EOF |
|
405 | $ cat >> .hg/hgrc <<EOF | |
406 | > [hooks] |
|
406 | > [hooks] | |
407 | > pretxncommit.unexpectedabort = |
|
407 | > pretxncommit.unexpectedabort = | |
408 | > EOF |
|
408 | > EOF | |
409 | $ hg histedit --abort -q |
|
409 | $ hg histedit --abort -q | |
410 |
|
410 | |||
411 | then, check "modify the message" itself |
|
411 | then, check "modify the message" itself | |
412 |
|
412 | |||
413 | $ hg histedit tip --commands - 2>&1 << EOF | fixbundle |
|
413 | $ hg histedit tip --commands - 2>&1 << EOF | fixbundle | |
414 | > mess 1fd3b2fe7754 f |
|
414 | > mess 1fd3b2fe7754 f | |
415 | > EOF |
|
415 | > EOF | |
416 | $ hg status |
|
416 | $ hg status | |
417 | $ hg log --limit 1 |
|
417 | $ hg log --limit 1 | |
418 | changeset: 6:62feedb1200e |
|
418 | changeset: 6:62feedb1200e | |
419 | tag: tip |
|
419 | tag: tip | |
420 | user: test |
|
420 | user: test | |
421 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
421 | date: Thu Jan 01 00:00:00 1970 +0000 | |
422 | summary: f |
|
422 | summary: f | |
423 |
|
423 | |||
424 |
|
424 | |||
425 | rollback should not work after a histedit |
|
425 | rollback should not work after a histedit | |
426 | $ hg rollback |
|
426 | $ hg rollback | |
427 | no rollback information available |
|
427 | no rollback information available | |
428 | [1] |
|
428 | [1] | |
429 |
|
429 | |||
430 | $ cd .. |
|
430 | $ cd .. | |
431 | $ hg clone -qr0 r r0 |
|
431 | $ hg clone -qr0 r r0 | |
432 | $ cd r0 |
|
432 | $ cd r0 | |
433 | $ hg phase -fdr0 |
|
433 | $ hg phase -fdr0 | |
434 | $ hg histedit --commands - 0 2>&1 << EOF |
|
434 | $ hg histedit --commands - 0 2>&1 << EOF | |
435 | > edit cb9a9f314b8b a > $EDITED |
|
435 | > edit cb9a9f314b8b a > $EDITED | |
436 | > EOF |
|
436 | > EOF | |
437 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved |
|
437 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved | |
438 | Editing (cb9a9f314b8b), you may commit or record as needed now. |
|
438 | Editing (cb9a9f314b8b), you may commit or record as needed now. | |
439 | (hg histedit --continue to resume) |
|
439 | (hg histedit --continue to resume) | |
440 |
[ |
|
440 | [240] | |
441 | $ HGEDITOR=true hg histedit --continue |
|
441 | $ HGEDITOR=true hg histedit --continue | |
442 | saved backup bundle to $TESTTMP/r0/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-histedit.hg |
|
442 | saved backup bundle to $TESTTMP/r0/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-histedit.hg | |
443 |
|
443 | |||
444 | $ hg log -G |
|
444 | $ hg log -G | |
445 | @ changeset: 0:0efcea34f18a |
|
445 | @ changeset: 0:0efcea34f18a | |
446 | tag: tip |
|
446 | tag: tip | |
447 | user: test |
|
447 | user: test | |
448 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
448 | date: Thu Jan 01 00:00:00 1970 +0000 | |
449 | summary: a |
|
449 | summary: a | |
450 |
|
450 | |||
451 | $ echo foo >> b |
|
451 | $ echo foo >> b | |
452 | $ hg addr |
|
452 | $ hg addr | |
453 | adding b |
|
453 | adding b | |
454 | $ hg ci -m 'add b' |
|
454 | $ hg ci -m 'add b' | |
455 | $ echo foo >> a |
|
455 | $ echo foo >> a | |
456 | $ hg ci -m 'extend a' |
|
456 | $ hg ci -m 'extend a' | |
457 | $ hg phase --public 1 |
|
457 | $ hg phase --public 1 | |
458 | Attempting to fold a change into a public change should not work: |
|
458 | Attempting to fold a change into a public change should not work: | |
459 | $ cat > ../edit.sh <<EOF |
|
459 | $ cat > ../edit.sh <<EOF | |
460 | > cat "\$1" | sed s/pick/fold/ > tmp |
|
460 | > cat "\$1" | sed s/pick/fold/ > tmp | |
461 | > mv tmp "\$1" |
|
461 | > mv tmp "\$1" | |
462 | > EOF |
|
462 | > EOF | |
463 | $ HGEDITOR="sh ../edit.sh" hg histedit 2 |
|
463 | $ HGEDITOR="sh ../edit.sh" hg histedit 2 | |
464 | warning: histedit rules saved to: .hg/histedit-last-edit.txt |
|
464 | warning: histedit rules saved to: .hg/histedit-last-edit.txt | |
465 | hg: parse error: first changeset cannot use verb "fold" |
|
465 | hg: parse error: first changeset cannot use verb "fold" | |
466 | [255] |
|
466 | [255] | |
467 | $ cat .hg/histedit-last-edit.txt |
|
467 | $ cat .hg/histedit-last-edit.txt | |
468 | fold 0012be4a27ea 2 extend a |
|
468 | fold 0012be4a27ea 2 extend a | |
469 |
|
469 | |||
470 | # Edit history between 0012be4a27ea and 0012be4a27ea |
|
470 | # Edit history between 0012be4a27ea and 0012be4a27ea | |
471 | # |
|
471 | # | |
472 | # Commits are listed from least to most recent |
|
472 | # Commits are listed from least to most recent | |
473 | # |
|
473 | # | |
474 | # You can reorder changesets by reordering the lines |
|
474 | # You can reorder changesets by reordering the lines | |
475 | # |
|
475 | # | |
476 | # Commands: |
|
476 | # Commands: | |
477 | # |
|
477 | # | |
478 | # e, edit = use commit, but stop for amending |
|
478 | # e, edit = use commit, but stop for amending | |
479 | # m, mess = edit commit message without changing commit content |
|
479 | # m, mess = edit commit message without changing commit content | |
480 | # p, fold = use commit |
|
480 | # p, fold = use commit | |
481 | # b, base = checkout changeset and apply further changesets from there |
|
481 | # b, base = checkout changeset and apply further changesets from there | |
482 | # d, drop = remove commit from history |
|
482 | # d, drop = remove commit from history | |
483 | # f, fold = use commit, but combine it with the one above |
|
483 | # f, fold = use commit, but combine it with the one above | |
484 | # r, roll = like fold, but discard this commit's description and date |
|
484 | # r, roll = like fold, but discard this commit's description and date | |
485 | # |
|
485 | # | |
486 |
|
486 | |||
487 | $ cd .. |
|
487 | $ cd .. | |
488 |
|
488 | |||
489 | ============================================ |
|
489 | ============================================ | |
490 | Test update-timestamp config option in mess| |
|
490 | Test update-timestamp config option in mess| | |
491 | ============================================ |
|
491 | ============================================ | |
492 |
|
492 | |||
493 | $ addwithdate () |
|
493 | $ addwithdate () | |
494 | > { |
|
494 | > { | |
495 | > echo $1 > $1 |
|
495 | > echo $1 > $1 | |
496 | > hg add $1 |
|
496 | > hg add $1 | |
497 | > hg ci -m $1 -d "$2 0" |
|
497 | > hg ci -m $1 -d "$2 0" | |
498 | > } |
|
498 | > } | |
499 |
|
499 | |||
500 | $ initrepo () |
|
500 | $ initrepo () | |
501 | > { |
|
501 | > { | |
502 | > hg init r2 |
|
502 | > hg init r2 | |
503 | > cd r2 |
|
503 | > cd r2 | |
504 | > addwithdate a 1 |
|
504 | > addwithdate a 1 | |
505 | > addwithdate b 2 |
|
505 | > addwithdate b 2 | |
506 | > addwithdate c 3 |
|
506 | > addwithdate c 3 | |
507 | > addwithdate d 4 |
|
507 | > addwithdate d 4 | |
508 | > addwithdate e 5 |
|
508 | > addwithdate e 5 | |
509 | > addwithdate f 6 |
|
509 | > addwithdate f 6 | |
510 | > } |
|
510 | > } | |
511 |
|
511 | |||
512 | $ initrepo |
|
512 | $ initrepo | |
513 |
|
513 | |||
514 | log before edit |
|
514 | log before edit | |
515 |
|
515 | |||
516 | $ hg log --limit 1 |
|
516 | $ hg log --limit 1 | |
517 | changeset: 5:178e35e0ce73 |
|
517 | changeset: 5:178e35e0ce73 | |
518 | tag: tip |
|
518 | tag: tip | |
519 | user: test |
|
519 | user: test | |
520 | date: Thu Jan 01 00:00:06 1970 +0000 |
|
520 | date: Thu Jan 01 00:00:06 1970 +0000 | |
521 | summary: f |
|
521 | summary: f | |
522 |
|
522 | |||
523 | $ hg histedit tip --commands - 2>&1 --config rewrite.update-timestamp=True << EOF | fixbundle |
|
523 | $ hg histedit tip --commands - 2>&1 --config rewrite.update-timestamp=True << EOF | fixbundle | |
524 | > mess 178e35e0ce73 f |
|
524 | > mess 178e35e0ce73 f | |
525 | > EOF |
|
525 | > EOF | |
526 |
|
526 | |||
527 | log after edit |
|
527 | log after edit | |
528 |
|
528 | |||
529 | $ hg log --limit 1 |
|
529 | $ hg log --limit 1 | |
530 | changeset: 5:98bf456d476b |
|
530 | changeset: 5:98bf456d476b | |
531 | tag: tip |
|
531 | tag: tip | |
532 | user: test |
|
532 | user: test | |
533 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
533 | date: Thu Jan 01 00:00:00 1970 +0000 | |
534 | summary: f |
|
534 | summary: f | |
535 |
|
535 | |||
536 |
|
536 | |||
537 | $ cd .. |
|
537 | $ cd .. | |
538 |
|
538 | |||
539 | warn the user on editing tagged commits |
|
539 | warn the user on editing tagged commits | |
540 |
|
540 | |||
541 | $ hg init issue4017 |
|
541 | $ hg init issue4017 | |
542 | $ cd issue4017 |
|
542 | $ cd issue4017 | |
543 | $ echo > a |
|
543 | $ echo > a | |
544 | $ hg ci -Am 'add a' |
|
544 | $ hg ci -Am 'add a' | |
545 | adding a |
|
545 | adding a | |
546 | $ hg tag a |
|
546 | $ hg tag a | |
547 | $ hg tags |
|
547 | $ hg tags | |
548 | tip 1:bd7ee4f3939b |
|
548 | tip 1:bd7ee4f3939b | |
549 | a 0:a8a82d372bb3 |
|
549 | a 0:a8a82d372bb3 | |
550 | $ hg histedit |
|
550 | $ hg histedit | |
551 | warning: tags associated with the given changeset will be lost after histedit. |
|
551 | warning: tags associated with the given changeset will be lost after histedit. | |
552 | do you want to continue (yN)? n |
|
552 | do you want to continue (yN)? n | |
553 | abort: histedit cancelled |
|
553 | abort: histedit cancelled | |
554 |
|
554 | |||
555 | [255] |
|
555 | [255] | |
556 | $ cd .. |
|
556 | $ cd .. |
@@ -1,706 +1,706 b'' | |||||
1 | Test histedit extension: Fold commands |
|
1 | Test histedit extension: Fold commands | |
2 | ====================================== |
|
2 | ====================================== | |
3 |
|
3 | |||
4 | This test file is dedicated to testing the fold command in non conflicting |
|
4 | This test file is dedicated to testing the fold command in non conflicting | |
5 | case. |
|
5 | case. | |
6 |
|
6 | |||
7 | Initialization |
|
7 | Initialization | |
8 | --------------- |
|
8 | --------------- | |
9 |
|
9 | |||
10 |
|
10 | |||
11 | $ . "$TESTDIR/histedit-helpers.sh" |
|
11 | $ . "$TESTDIR/histedit-helpers.sh" | |
12 |
|
12 | |||
13 | $ cat >> $HGRCPATH <<EOF |
|
13 | $ cat >> $HGRCPATH <<EOF | |
14 | > [alias] |
|
14 | > [alias] | |
15 | > logt = log --template '{rev}:{node|short} {desc|firstline}\n' |
|
15 | > logt = log --template '{rev}:{node|short} {desc|firstline}\n' | |
16 | > [extensions] |
|
16 | > [extensions] | |
17 | > histedit= |
|
17 | > histedit= | |
18 | > mockmakedate = $TESTDIR/mockmakedate.py |
|
18 | > mockmakedate = $TESTDIR/mockmakedate.py | |
19 | > EOF |
|
19 | > EOF | |
20 |
|
20 | |||
21 |
|
21 | |||
22 | Simple folding |
|
22 | Simple folding | |
23 | -------------------- |
|
23 | -------------------- | |
24 | $ addwithdate () |
|
24 | $ addwithdate () | |
25 | > { |
|
25 | > { | |
26 | > echo $1 > $1 |
|
26 | > echo $1 > $1 | |
27 | > hg add $1 |
|
27 | > hg add $1 | |
28 | > hg ci -m $1 -d "$2 0" |
|
28 | > hg ci -m $1 -d "$2 0" | |
29 | > } |
|
29 | > } | |
30 |
|
30 | |||
31 | $ initrepo () |
|
31 | $ initrepo () | |
32 | > { |
|
32 | > { | |
33 | > hg init r |
|
33 | > hg init r | |
34 | > cd r |
|
34 | > cd r | |
35 | > addwithdate a 1 |
|
35 | > addwithdate a 1 | |
36 | > addwithdate b 2 |
|
36 | > addwithdate b 2 | |
37 | > addwithdate c 3 |
|
37 | > addwithdate c 3 | |
38 | > addwithdate d 4 |
|
38 | > addwithdate d 4 | |
39 | > addwithdate e 5 |
|
39 | > addwithdate e 5 | |
40 | > addwithdate f 6 |
|
40 | > addwithdate f 6 | |
41 | > } |
|
41 | > } | |
42 |
|
42 | |||
43 | $ initrepo |
|
43 | $ initrepo | |
44 |
|
44 | |||
45 | log before edit |
|
45 | log before edit | |
46 | $ hg logt --graph |
|
46 | $ hg logt --graph | |
47 | @ 5:178e35e0ce73 f |
|
47 | @ 5:178e35e0ce73 f | |
48 | | |
|
48 | | | |
49 | o 4:1ddb6c90f2ee e |
|
49 | o 4:1ddb6c90f2ee e | |
50 | | |
|
50 | | | |
51 | o 3:532247a8969b d |
|
51 | o 3:532247a8969b d | |
52 | | |
|
52 | | | |
53 | o 2:ff2c9fa2018b c |
|
53 | o 2:ff2c9fa2018b c | |
54 | | |
|
54 | | | |
55 | o 1:97d72e5f12c7 b |
|
55 | o 1:97d72e5f12c7 b | |
56 | | |
|
56 | | | |
57 | o 0:8580ff50825a a |
|
57 | o 0:8580ff50825a a | |
58 |
|
58 | |||
59 |
|
59 | |||
60 | $ hg histedit ff2c9fa2018b --commands - 2>&1 <<EOF | fixbundle |
|
60 | $ hg histedit ff2c9fa2018b --commands - 2>&1 <<EOF | fixbundle | |
61 | > pick 1ddb6c90f2ee e |
|
61 | > pick 1ddb6c90f2ee e | |
62 | > pick 178e35e0ce73 f |
|
62 | > pick 178e35e0ce73 f | |
63 | > fold ff2c9fa2018b c |
|
63 | > fold ff2c9fa2018b c | |
64 | > pick 532247a8969b d |
|
64 | > pick 532247a8969b d | |
65 | > EOF |
|
65 | > EOF | |
66 |
|
66 | |||
67 | log after edit |
|
67 | log after edit | |
68 | $ hg logt --graph |
|
68 | $ hg logt --graph | |
69 | @ 4:c4d7f3def76d d |
|
69 | @ 4:c4d7f3def76d d | |
70 | | |
|
70 | | | |
71 | o 3:575228819b7e f |
|
71 | o 3:575228819b7e f | |
72 | | |
|
72 | | | |
73 | o 2:505a591af19e e |
|
73 | o 2:505a591af19e e | |
74 | | |
|
74 | | | |
75 | o 1:97d72e5f12c7 b |
|
75 | o 1:97d72e5f12c7 b | |
76 | | |
|
76 | | | |
77 | o 0:8580ff50825a a |
|
77 | o 0:8580ff50825a a | |
78 |
|
78 | |||
79 |
|
79 | |||
80 | post-fold manifest |
|
80 | post-fold manifest | |
81 | $ hg manifest |
|
81 | $ hg manifest | |
82 | a |
|
82 | a | |
83 | b |
|
83 | b | |
84 | c |
|
84 | c | |
85 | d |
|
85 | d | |
86 | e |
|
86 | e | |
87 | f |
|
87 | f | |
88 |
|
88 | |||
89 |
|
89 | |||
90 | check histedit_source, including that it uses the later date, from the first changeset |
|
90 | check histedit_source, including that it uses the later date, from the first changeset | |
91 |
|
91 | |||
92 | $ hg log --debug --rev 3 |
|
92 | $ hg log --debug --rev 3 | |
93 | changeset: 3:575228819b7e6ed69e8c0a6a383ee59a80db7358 |
|
93 | changeset: 3:575228819b7e6ed69e8c0a6a383ee59a80db7358 | |
94 | phase: draft |
|
94 | phase: draft | |
95 | parent: 2:505a591af19eed18f560af827b9e03d2076773dc |
|
95 | parent: 2:505a591af19eed18f560af827b9e03d2076773dc | |
96 | parent: -1:0000000000000000000000000000000000000000 |
|
96 | parent: -1:0000000000000000000000000000000000000000 | |
97 | manifest: 3:81eede616954057198ead0b2c73b41d1f392829a |
|
97 | manifest: 3:81eede616954057198ead0b2c73b41d1f392829a | |
98 | user: test |
|
98 | user: test | |
99 | date: Thu Jan 01 00:00:06 1970 +0000 |
|
99 | date: Thu Jan 01 00:00:06 1970 +0000 | |
100 | files+: c f |
|
100 | files+: c f | |
101 | extra: branch=default |
|
101 | extra: branch=default | |
102 | extra: histedit_source=7cad1d7030207872dfd1c3a7cb430f24f2884086,ff2c9fa2018b15fa74b33363bda9527323e2a99f |
|
102 | extra: histedit_source=7cad1d7030207872dfd1c3a7cb430f24f2884086,ff2c9fa2018b15fa74b33363bda9527323e2a99f | |
103 | description: |
|
103 | description: | |
104 | f |
|
104 | f | |
105 | *** |
|
105 | *** | |
106 | c |
|
106 | c | |
107 |
|
107 | |||
108 |
|
108 | |||
109 |
|
109 | |||
110 | rollup will fold without preserving the folded commit's message or date |
|
110 | rollup will fold without preserving the folded commit's message or date | |
111 |
|
111 | |||
112 | $ OLDHGEDITOR=$HGEDITOR |
|
112 | $ OLDHGEDITOR=$HGEDITOR | |
113 | $ HGEDITOR=false |
|
113 | $ HGEDITOR=false | |
114 | $ hg histedit 97d72e5f12c7 --commands - 2>&1 <<EOF | fixbundle |
|
114 | $ hg histedit 97d72e5f12c7 --commands - 2>&1 <<EOF | fixbundle | |
115 | > pick 97d72e5f12c7 b |
|
115 | > pick 97d72e5f12c7 b | |
116 | > roll 505a591af19e e |
|
116 | > roll 505a591af19e e | |
117 | > pick 575228819b7e f |
|
117 | > pick 575228819b7e f | |
118 | > pick c4d7f3def76d d |
|
118 | > pick c4d7f3def76d d | |
119 | > EOF |
|
119 | > EOF | |
120 |
|
120 | |||
121 | $ HGEDITOR=$OLDHGEDITOR |
|
121 | $ HGEDITOR=$OLDHGEDITOR | |
122 |
|
122 | |||
123 | log after edit |
|
123 | log after edit | |
124 | $ hg logt --graph |
|
124 | $ hg logt --graph | |
125 | @ 3:bab801520cec d |
|
125 | @ 3:bab801520cec d | |
126 | | |
|
126 | | | |
127 | o 2:58c8f2bfc151 f |
|
127 | o 2:58c8f2bfc151 f | |
128 | | |
|
128 | | | |
129 | o 1:5d939c56c72e b |
|
129 | o 1:5d939c56c72e b | |
130 | | |
|
130 | | | |
131 | o 0:8580ff50825a a |
|
131 | o 0:8580ff50825a a | |
132 |
|
132 | |||
133 |
|
133 | |||
134 | description is taken from rollup target commit |
|
134 | description is taken from rollup target commit | |
135 |
|
135 | |||
136 | $ hg log --debug --rev 1 |
|
136 | $ hg log --debug --rev 1 | |
137 | changeset: 1:5d939c56c72e77e29f5167696218e2131a40f5cf |
|
137 | changeset: 1:5d939c56c72e77e29f5167696218e2131a40f5cf | |
138 | phase: draft |
|
138 | phase: draft | |
139 | parent: 0:8580ff50825a50c8f716709acdf8de0deddcd6ab |
|
139 | parent: 0:8580ff50825a50c8f716709acdf8de0deddcd6ab | |
140 | parent: -1:0000000000000000000000000000000000000000 |
|
140 | parent: -1:0000000000000000000000000000000000000000 | |
141 | manifest: 1:b5e112a3a8354e269b1524729f0918662d847c38 |
|
141 | manifest: 1:b5e112a3a8354e269b1524729f0918662d847c38 | |
142 | user: test |
|
142 | user: test | |
143 | date: Thu Jan 01 00:00:02 1970 +0000 |
|
143 | date: Thu Jan 01 00:00:02 1970 +0000 | |
144 | files+: b e |
|
144 | files+: b e | |
145 | extra: branch=default |
|
145 | extra: branch=default | |
146 | extra: histedit_source=97d72e5f12c7e84f85064aa72e5a297142c36ed9,505a591af19eed18f560af827b9e03d2076773dc |
|
146 | extra: histedit_source=97d72e5f12c7e84f85064aa72e5a297142c36ed9,505a591af19eed18f560af827b9e03d2076773dc | |
147 | description: |
|
147 | description: | |
148 | b |
|
148 | b | |
149 |
|
149 | |||
150 |
|
150 | |||
151 |
|
151 | |||
152 | check saving last-message.txt |
|
152 | check saving last-message.txt | |
153 |
|
153 | |||
154 | $ cat > $TESTTMP/abortfolding.py <<EOF |
|
154 | $ cat > $TESTTMP/abortfolding.py <<EOF | |
155 | > from mercurial import util |
|
155 | > from mercurial import util | |
156 | > def abortfolding(ui, repo, hooktype, **kwargs): |
|
156 | > def abortfolding(ui, repo, hooktype, **kwargs): | |
157 | > ctx = repo[kwargs.get('node')] |
|
157 | > ctx = repo[kwargs.get('node')] | |
158 | > if set(ctx.files()) == {b'c', b'd', b'f'}: |
|
158 | > if set(ctx.files()) == {b'c', b'd', b'f'}: | |
159 | > return True # abort folding commit only |
|
159 | > return True # abort folding commit only | |
160 | > ui.warn(b'allow non-folding commit\\n') |
|
160 | > ui.warn(b'allow non-folding commit\\n') | |
161 | > EOF |
|
161 | > EOF | |
162 | $ cat > .hg/hgrc <<EOF |
|
162 | $ cat > .hg/hgrc <<EOF | |
163 | > [hooks] |
|
163 | > [hooks] | |
164 | > pretxncommit.abortfolding = python:$TESTTMP/abortfolding.py:abortfolding |
|
164 | > pretxncommit.abortfolding = python:$TESTTMP/abortfolding.py:abortfolding | |
165 | > EOF |
|
165 | > EOF | |
166 |
|
166 | |||
167 | $ cat > $TESTTMP/editor.sh << EOF |
|
167 | $ cat > $TESTTMP/editor.sh << EOF | |
168 | > echo "==== before editing" |
|
168 | > echo "==== before editing" | |
169 | > cat \$1 |
|
169 | > cat \$1 | |
170 | > echo "====" |
|
170 | > echo "====" | |
171 | > echo "check saving last-message.txt" >> \$1 |
|
171 | > echo "check saving last-message.txt" >> \$1 | |
172 | > EOF |
|
172 | > EOF | |
173 |
|
173 | |||
174 | $ rm -f .hg/last-message.txt |
|
174 | $ rm -f .hg/last-message.txt | |
175 | $ hg status --rev '58c8f2bfc151^1::bab801520cec' |
|
175 | $ hg status --rev '58c8f2bfc151^1::bab801520cec' | |
176 | A c |
|
176 | A c | |
177 | A d |
|
177 | A d | |
178 | A f |
|
178 | A f | |
179 | $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit 58c8f2bfc151 --commands - 2>&1 <<EOF |
|
179 | $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit 58c8f2bfc151 --commands - 2>&1 <<EOF | |
180 | > pick 58c8f2bfc151 f |
|
180 | > pick 58c8f2bfc151 f | |
181 | > fold bab801520cec d |
|
181 | > fold bab801520cec d | |
182 | > EOF |
|
182 | > EOF | |
183 | allow non-folding commit |
|
183 | allow non-folding commit | |
184 | ==== before editing |
|
184 | ==== before editing | |
185 | f |
|
185 | f | |
186 | *** |
|
186 | *** | |
187 | c |
|
187 | c | |
188 | *** |
|
188 | *** | |
189 | d |
|
189 | d | |
190 |
|
190 | |||
191 |
|
191 | |||
192 |
|
192 | |||
193 | HG: Enter commit message. Lines beginning with 'HG:' are removed. |
|
193 | HG: Enter commit message. Lines beginning with 'HG:' are removed. | |
194 | HG: Leave message empty to abort commit. |
|
194 | HG: Leave message empty to abort commit. | |
195 | HG: -- |
|
195 | HG: -- | |
196 | HG: user: test |
|
196 | HG: user: test | |
197 | HG: branch 'default' |
|
197 | HG: branch 'default' | |
198 | HG: added c |
|
198 | HG: added c | |
199 | HG: added d |
|
199 | HG: added d | |
200 | HG: added f |
|
200 | HG: added f | |
201 | ==== |
|
201 | ==== | |
202 | transaction abort! |
|
202 | transaction abort! | |
203 | rollback completed |
|
203 | rollback completed | |
204 | abort: pretxncommit.abortfolding hook failed |
|
204 | abort: pretxncommit.abortfolding hook failed | |
205 | [255] |
|
205 | [255] | |
206 |
|
206 | |||
207 | $ cat .hg/last-message.txt |
|
207 | $ cat .hg/last-message.txt | |
208 | f |
|
208 | f | |
209 | *** |
|
209 | *** | |
210 | c |
|
210 | c | |
211 | *** |
|
211 | *** | |
212 | d |
|
212 | d | |
213 |
|
213 | |||
214 |
|
214 | |||
215 |
|
215 | |||
216 | check saving last-message.txt |
|
216 | check saving last-message.txt | |
217 |
|
217 | |||
218 | $ cd .. |
|
218 | $ cd .. | |
219 | $ rm -r r |
|
219 | $ rm -r r | |
220 |
|
220 | |||
221 | folding preserves initial author but uses later date |
|
221 | folding preserves initial author but uses later date | |
222 | ---------------------------------------------------- |
|
222 | ---------------------------------------------------- | |
223 |
|
223 | |||
224 | $ initrepo |
|
224 | $ initrepo | |
225 |
|
225 | |||
226 | $ hg ci -d '7 0' --user "someone else" --amend --quiet |
|
226 | $ hg ci -d '7 0' --user "someone else" --amend --quiet | |
227 |
|
227 | |||
228 | tip before edit |
|
228 | tip before edit | |
229 | $ hg log --rev . |
|
229 | $ hg log --rev . | |
230 | changeset: 5:10c36dd37515 |
|
230 | changeset: 5:10c36dd37515 | |
231 | tag: tip |
|
231 | tag: tip | |
232 | user: someone else |
|
232 | user: someone else | |
233 | date: Thu Jan 01 00:00:07 1970 +0000 |
|
233 | date: Thu Jan 01 00:00:07 1970 +0000 | |
234 | summary: f |
|
234 | summary: f | |
235 |
|
235 | |||
236 |
|
236 | |||
237 | $ hg --config progress.debug=1 --debug \ |
|
237 | $ hg --config progress.debug=1 --debug \ | |
238 | > histedit 1ddb6c90f2ee --commands - 2>&1 <<EOF | \ |
|
238 | > histedit 1ddb6c90f2ee --commands - 2>&1 <<EOF | \ | |
239 | > egrep 'editing|unresolved' |
|
239 | > egrep 'editing|unresolved' | |
240 | > pick 1ddb6c90f2ee e |
|
240 | > pick 1ddb6c90f2ee e | |
241 | > fold 10c36dd37515 f |
|
241 | > fold 10c36dd37515 f | |
242 | > EOF |
|
242 | > EOF | |
243 | editing: pick 1ddb6c90f2ee 4 e 1/2 changes (50.00%) |
|
243 | editing: pick 1ddb6c90f2ee 4 e 1/2 changes (50.00%) | |
244 | editing: fold 10c36dd37515 5 f 2/2 changes (100.00%) |
|
244 | editing: fold 10c36dd37515 5 f 2/2 changes (100.00%) | |
245 |
|
245 | |||
246 | tip after edit, which should use the later date, from the second changeset |
|
246 | tip after edit, which should use the later date, from the second changeset | |
247 | $ hg log --rev . |
|
247 | $ hg log --rev . | |
248 | changeset: 4:e4f3ec5d0b40 |
|
248 | changeset: 4:e4f3ec5d0b40 | |
249 | tag: tip |
|
249 | tag: tip | |
250 | user: test |
|
250 | user: test | |
251 | date: Thu Jan 01 00:00:07 1970 +0000 |
|
251 | date: Thu Jan 01 00:00:07 1970 +0000 | |
252 | summary: e |
|
252 | summary: e | |
253 |
|
253 | |||
254 |
|
254 | |||
255 | $ cd .. |
|
255 | $ cd .. | |
256 | $ rm -r r |
|
256 | $ rm -r r | |
257 |
|
257 | |||
258 | folding and creating no new change doesn't break: |
|
258 | folding and creating no new change doesn't break: | |
259 | ------------------------------------------------- |
|
259 | ------------------------------------------------- | |
260 |
|
260 | |||
261 | folded content is dropped during a merge. The folded commit should properly disappear. |
|
261 | folded content is dropped during a merge. The folded commit should properly disappear. | |
262 |
|
262 | |||
263 | $ mkdir fold-to-empty-test |
|
263 | $ mkdir fold-to-empty-test | |
264 | $ cd fold-to-empty-test |
|
264 | $ cd fold-to-empty-test | |
265 | $ hg init |
|
265 | $ hg init | |
266 | $ printf "1\n2\n3\n" > file |
|
266 | $ printf "1\n2\n3\n" > file | |
267 | $ hg add file |
|
267 | $ hg add file | |
268 | $ hg commit -m '1+2+3' |
|
268 | $ hg commit -m '1+2+3' | |
269 | $ echo 4 >> file |
|
269 | $ echo 4 >> file | |
270 | $ hg commit -m '+4' |
|
270 | $ hg commit -m '+4' | |
271 | $ echo 5 >> file |
|
271 | $ echo 5 >> file | |
272 | $ hg commit -m '+5' |
|
272 | $ hg commit -m '+5' | |
273 | $ echo 6 >> file |
|
273 | $ echo 6 >> file | |
274 | $ hg commit -m '+6' |
|
274 | $ hg commit -m '+6' | |
275 | $ hg logt --graph |
|
275 | $ hg logt --graph | |
276 | @ 3:251d831eeec5 +6 |
|
276 | @ 3:251d831eeec5 +6 | |
277 | | |
|
277 | | | |
278 | o 2:888f9082bf99 +5 |
|
278 | o 2:888f9082bf99 +5 | |
279 | | |
|
279 | | | |
280 | o 1:617f94f13c0f +4 |
|
280 | o 1:617f94f13c0f +4 | |
281 | | |
|
281 | | | |
282 | o 0:0189ba417d34 1+2+3 |
|
282 | o 0:0189ba417d34 1+2+3 | |
283 |
|
283 | |||
284 |
|
284 | |||
285 | $ hg histedit 1 --commands - << EOF |
|
285 | $ hg histedit 1 --commands - << EOF | |
286 | > pick 617f94f13c0f 1 +4 |
|
286 | > pick 617f94f13c0f 1 +4 | |
287 | > drop 888f9082bf99 2 +5 |
|
287 | > drop 888f9082bf99 2 +5 | |
288 | > fold 251d831eeec5 3 +6 |
|
288 | > fold 251d831eeec5 3 +6 | |
289 | > EOF |
|
289 | > EOF | |
290 | merging file |
|
290 | merging file | |
291 | warning: conflicts while merging file! (edit, then use 'hg resolve --mark') |
|
291 | warning: conflicts while merging file! (edit, then use 'hg resolve --mark') | |
292 | Fix up the change (fold 251d831eeec5) |
|
292 | Fix up the change (fold 251d831eeec5) | |
293 | (hg histedit --continue to resume) |
|
293 | (hg histedit --continue to resume) | |
294 |
[ |
|
294 | [240] | |
295 | There were conflicts, we keep P1 content. This |
|
295 | There were conflicts, we keep P1 content. This | |
296 | should effectively drop the changes from +6. |
|
296 | should effectively drop the changes from +6. | |
297 |
|
297 | |||
298 | $ hg status -v |
|
298 | $ hg status -v | |
299 | M file |
|
299 | M file | |
300 | ? file.orig |
|
300 | ? file.orig | |
301 | # The repository is in an unfinished *histedit* state. |
|
301 | # The repository is in an unfinished *histedit* state. | |
302 |
|
302 | |||
303 | # Unresolved merge conflicts: |
|
303 | # Unresolved merge conflicts: | |
304 | # |
|
304 | # | |
305 | # file |
|
305 | # file | |
306 | # |
|
306 | # | |
307 | # To mark files as resolved: hg resolve --mark FILE |
|
307 | # To mark files as resolved: hg resolve --mark FILE | |
308 |
|
308 | |||
309 | # To continue: hg histedit --continue |
|
309 | # To continue: hg histedit --continue | |
310 | # To abort: hg histedit --abort |
|
310 | # To abort: hg histedit --abort | |
311 |
|
311 | |||
312 | $ hg resolve -l |
|
312 | $ hg resolve -l | |
313 | U file |
|
313 | U file | |
314 | $ hg revert -r 'p1()' file |
|
314 | $ hg revert -r 'p1()' file | |
315 | $ hg resolve --mark file |
|
315 | $ hg resolve --mark file | |
316 | (no more unresolved files) |
|
316 | (no more unresolved files) | |
317 | continue: hg histedit --continue |
|
317 | continue: hg histedit --continue | |
318 | $ hg histedit --continue |
|
318 | $ hg histedit --continue | |
319 | 251d831eeec5: empty changeset |
|
319 | 251d831eeec5: empty changeset | |
320 | saved backup bundle to $TESTTMP/fold-to-empty-test/.hg/strip-backup/888f9082bf99-daa0b8b3-histedit.hg |
|
320 | saved backup bundle to $TESTTMP/fold-to-empty-test/.hg/strip-backup/888f9082bf99-daa0b8b3-histedit.hg | |
321 | $ hg logt --graph |
|
321 | $ hg logt --graph | |
322 | @ 1:617f94f13c0f +4 |
|
322 | @ 1:617f94f13c0f +4 | |
323 | | |
|
323 | | | |
324 | o 0:0189ba417d34 1+2+3 |
|
324 | o 0:0189ba417d34 1+2+3 | |
325 |
|
325 | |||
326 |
|
326 | |||
327 | $ cd .. |
|
327 | $ cd .. | |
328 |
|
328 | |||
329 |
|
329 | |||
330 | Test fold through dropped |
|
330 | Test fold through dropped | |
331 | ------------------------- |
|
331 | ------------------------- | |
332 |
|
332 | |||
333 |
|
333 | |||
334 | Test corner case where folded revision is separated from its parent by a |
|
334 | Test corner case where folded revision is separated from its parent by a | |
335 | dropped revision. |
|
335 | dropped revision. | |
336 |
|
336 | |||
337 |
|
337 | |||
338 | $ hg init fold-with-dropped |
|
338 | $ hg init fold-with-dropped | |
339 | $ cd fold-with-dropped |
|
339 | $ cd fold-with-dropped | |
340 | $ printf "1\n2\n3\n" > file |
|
340 | $ printf "1\n2\n3\n" > file | |
341 | $ hg commit -Am '1+2+3' |
|
341 | $ hg commit -Am '1+2+3' | |
342 | adding file |
|
342 | adding file | |
343 | $ echo 4 >> file |
|
343 | $ echo 4 >> file | |
344 | $ hg commit -m '+4' |
|
344 | $ hg commit -m '+4' | |
345 | $ echo 5 >> file |
|
345 | $ echo 5 >> file | |
346 | $ hg commit -m '+5' |
|
346 | $ hg commit -m '+5' | |
347 | $ echo 6 >> file |
|
347 | $ echo 6 >> file | |
348 | $ hg commit -m '+6' |
|
348 | $ hg commit -m '+6' | |
349 | $ hg logt -G |
|
349 | $ hg logt -G | |
350 | @ 3:251d831eeec5 +6 |
|
350 | @ 3:251d831eeec5 +6 | |
351 | | |
|
351 | | | |
352 | o 2:888f9082bf99 +5 |
|
352 | o 2:888f9082bf99 +5 | |
353 | | |
|
353 | | | |
354 | o 1:617f94f13c0f +4 |
|
354 | o 1:617f94f13c0f +4 | |
355 | | |
|
355 | | | |
356 | o 0:0189ba417d34 1+2+3 |
|
356 | o 0:0189ba417d34 1+2+3 | |
357 |
|
357 | |||
358 | $ hg histedit 1 --commands - << EOF |
|
358 | $ hg histedit 1 --commands - << EOF | |
359 | > pick 617f94f13c0f 1 +4 |
|
359 | > pick 617f94f13c0f 1 +4 | |
360 | > drop 888f9082bf99 2 +5 |
|
360 | > drop 888f9082bf99 2 +5 | |
361 | > fold 251d831eeec5 3 +6 |
|
361 | > fold 251d831eeec5 3 +6 | |
362 | > EOF |
|
362 | > EOF | |
363 | merging file |
|
363 | merging file | |
364 | warning: conflicts while merging file! (edit, then use 'hg resolve --mark') |
|
364 | warning: conflicts while merging file! (edit, then use 'hg resolve --mark') | |
365 | Fix up the change (fold 251d831eeec5) |
|
365 | Fix up the change (fold 251d831eeec5) | |
366 | (hg histedit --continue to resume) |
|
366 | (hg histedit --continue to resume) | |
367 |
[ |
|
367 | [240] | |
368 | $ cat > file << EOF |
|
368 | $ cat > file << EOF | |
369 | > 1 |
|
369 | > 1 | |
370 | > 2 |
|
370 | > 2 | |
371 | > 3 |
|
371 | > 3 | |
372 | > 4 |
|
372 | > 4 | |
373 | > 5 |
|
373 | > 5 | |
374 | > EOF |
|
374 | > EOF | |
375 | $ hg resolve --mark file |
|
375 | $ hg resolve --mark file | |
376 | (no more unresolved files) |
|
376 | (no more unresolved files) | |
377 | continue: hg histedit --continue |
|
377 | continue: hg histedit --continue | |
378 | $ hg commit -m '+5.2' |
|
378 | $ hg commit -m '+5.2' | |
379 | created new head |
|
379 | created new head | |
380 | $ echo 6 >> file |
|
380 | $ echo 6 >> file | |
381 | $ HGEDITOR=cat hg histedit --continue |
|
381 | $ HGEDITOR=cat hg histedit --continue | |
382 | +4 |
|
382 | +4 | |
383 | *** |
|
383 | *** | |
384 | +5.2 |
|
384 | +5.2 | |
385 | *** |
|
385 | *** | |
386 | +6 |
|
386 | +6 | |
387 |
|
387 | |||
388 |
|
388 | |||
389 |
|
389 | |||
390 | HG: Enter commit message. Lines beginning with 'HG:' are removed. |
|
390 | HG: Enter commit message. Lines beginning with 'HG:' are removed. | |
391 | HG: Leave message empty to abort commit. |
|
391 | HG: Leave message empty to abort commit. | |
392 | HG: -- |
|
392 | HG: -- | |
393 | HG: user: test |
|
393 | HG: user: test | |
394 | HG: branch 'default' |
|
394 | HG: branch 'default' | |
395 | HG: changed file |
|
395 | HG: changed file | |
396 | saved backup bundle to $TESTTMP/fold-with-dropped/.hg/strip-backup/617f94f13c0f-3d69522c-histedit.hg |
|
396 | saved backup bundle to $TESTTMP/fold-with-dropped/.hg/strip-backup/617f94f13c0f-3d69522c-histedit.hg | |
397 | $ hg logt -G |
|
397 | $ hg logt -G | |
398 | @ 1:10c647b2cdd5 +4 |
|
398 | @ 1:10c647b2cdd5 +4 | |
399 | | |
|
399 | | | |
400 | o 0:0189ba417d34 1+2+3 |
|
400 | o 0:0189ba417d34 1+2+3 | |
401 |
|
401 | |||
402 | $ hg export tip |
|
402 | $ hg export tip | |
403 | # HG changeset patch |
|
403 | # HG changeset patch | |
404 | # User test |
|
404 | # User test | |
405 | # Date 0 0 |
|
405 | # Date 0 0 | |
406 | # Thu Jan 01 00:00:00 1970 +0000 |
|
406 | # Thu Jan 01 00:00:00 1970 +0000 | |
407 | # Node ID 10c647b2cdd54db0603ecb99b2ff5ce66d5a5323 |
|
407 | # Node ID 10c647b2cdd54db0603ecb99b2ff5ce66d5a5323 | |
408 | # Parent 0189ba417d34df9dda55f88b637dcae9917b5964 |
|
408 | # Parent 0189ba417d34df9dda55f88b637dcae9917b5964 | |
409 | +4 |
|
409 | +4 | |
410 | *** |
|
410 | *** | |
411 | +5.2 |
|
411 | +5.2 | |
412 | *** |
|
412 | *** | |
413 | +6 |
|
413 | +6 | |
414 |
|
414 | |||
415 | diff -r 0189ba417d34 -r 10c647b2cdd5 file |
|
415 | diff -r 0189ba417d34 -r 10c647b2cdd5 file | |
416 | --- a/file Thu Jan 01 00:00:00 1970 +0000 |
|
416 | --- a/file Thu Jan 01 00:00:00 1970 +0000 | |
417 | +++ b/file Thu Jan 01 00:00:00 1970 +0000 |
|
417 | +++ b/file Thu Jan 01 00:00:00 1970 +0000 | |
418 | @@ -1,3 +1,6 @@ |
|
418 | @@ -1,3 +1,6 @@ | |
419 | 1 |
|
419 | 1 | |
420 | 2 |
|
420 | 2 | |
421 | 3 |
|
421 | 3 | |
422 | +4 |
|
422 | +4 | |
423 | +5 |
|
423 | +5 | |
424 | +6 |
|
424 | +6 | |
425 | $ cd .. |
|
425 | $ cd .. | |
426 |
|
426 | |||
427 |
|
427 | |||
428 | Folding with initial rename (issue3729) |
|
428 | Folding with initial rename (issue3729) | |
429 | --------------------------------------- |
|
429 | --------------------------------------- | |
430 |
|
430 | |||
431 | $ hg init fold-rename |
|
431 | $ hg init fold-rename | |
432 | $ cd fold-rename |
|
432 | $ cd fold-rename | |
433 | $ echo a > a.txt |
|
433 | $ echo a > a.txt | |
434 | $ hg add a.txt |
|
434 | $ hg add a.txt | |
435 | $ hg commit -m a |
|
435 | $ hg commit -m a | |
436 | $ hg rename a.txt b.txt |
|
436 | $ hg rename a.txt b.txt | |
437 | $ hg commit -m rename |
|
437 | $ hg commit -m rename | |
438 | $ echo b >> b.txt |
|
438 | $ echo b >> b.txt | |
439 | $ hg commit -m b |
|
439 | $ hg commit -m b | |
440 |
|
440 | |||
441 | $ hg logt --follow b.txt |
|
441 | $ hg logt --follow b.txt | |
442 | 2:e0371e0426bc b |
|
442 | 2:e0371e0426bc b | |
443 | 1:1c4f440a8085 rename |
|
443 | 1:1c4f440a8085 rename | |
444 | 0:6c795aa153cb a |
|
444 | 0:6c795aa153cb a | |
445 |
|
445 | |||
446 | $ hg histedit 1c4f440a8085 --commands - 2>&1 << EOF | fixbundle |
|
446 | $ hg histedit 1c4f440a8085 --commands - 2>&1 << EOF | fixbundle | |
447 | > pick 1c4f440a8085 rename |
|
447 | > pick 1c4f440a8085 rename | |
448 | > fold e0371e0426bc b |
|
448 | > fold e0371e0426bc b | |
449 | > EOF |
|
449 | > EOF | |
450 |
|
450 | |||
451 | $ hg logt --follow b.txt |
|
451 | $ hg logt --follow b.txt | |
452 | 1:cf858d235c76 rename |
|
452 | 1:cf858d235c76 rename | |
453 | 0:6c795aa153cb a |
|
453 | 0:6c795aa153cb a | |
454 |
|
454 | |||
455 | $ cd .. |
|
455 | $ cd .. | |
456 |
|
456 | |||
457 | Folding with swapping |
|
457 | Folding with swapping | |
458 | --------------------- |
|
458 | --------------------- | |
459 |
|
459 | |||
460 | This is an excuse to test hook with histedit temporary commit (issue4422) |
|
460 | This is an excuse to test hook with histedit temporary commit (issue4422) | |
461 |
|
461 | |||
462 |
|
462 | |||
463 | $ hg init issue4422 |
|
463 | $ hg init issue4422 | |
464 | $ cd issue4422 |
|
464 | $ cd issue4422 | |
465 | $ echo a > a.txt |
|
465 | $ echo a > a.txt | |
466 | $ hg add a.txt |
|
466 | $ hg add a.txt | |
467 | $ hg commit -m a |
|
467 | $ hg commit -m a | |
468 | $ echo b > b.txt |
|
468 | $ echo b > b.txt | |
469 | $ hg add b.txt |
|
469 | $ hg add b.txt | |
470 | $ hg commit -m b |
|
470 | $ hg commit -m b | |
471 | $ echo c > c.txt |
|
471 | $ echo c > c.txt | |
472 | $ hg add c.txt |
|
472 | $ hg add c.txt | |
473 | $ hg commit -m c |
|
473 | $ hg commit -m c | |
474 |
|
474 | |||
475 | $ hg logt |
|
475 | $ hg logt | |
476 | 2:a1a953ffb4b0 c |
|
476 | 2:a1a953ffb4b0 c | |
477 | 1:199b6bb90248 b |
|
477 | 1:199b6bb90248 b | |
478 | 0:6c795aa153cb a |
|
478 | 0:6c795aa153cb a | |
479 |
|
479 | |||
480 | $ hg histedit 6c795aa153cb --config hooks.commit='echo commit $HG_NODE' --config hooks.tonative.commit=True \ |
|
480 | $ hg histedit 6c795aa153cb --config hooks.commit='echo commit $HG_NODE' --config hooks.tonative.commit=True \ | |
481 | > --commands - 2>&1 << EOF | fixbundle |
|
481 | > --commands - 2>&1 << EOF | fixbundle | |
482 | > pick 199b6bb90248 b |
|
482 | > pick 199b6bb90248 b | |
483 | > fold a1a953ffb4b0 c |
|
483 | > fold a1a953ffb4b0 c | |
484 | > pick 6c795aa153cb a |
|
484 | > pick 6c795aa153cb a | |
485 | > EOF |
|
485 | > EOF | |
486 | commit 9599899f62c05f4377548c32bf1c9f1a39634b0c |
|
486 | commit 9599899f62c05f4377548c32bf1c9f1a39634b0c | |
487 |
|
487 | |||
488 | $ hg logt |
|
488 | $ hg logt | |
489 | 1:9599899f62c0 a |
|
489 | 1:9599899f62c0 a | |
490 | 0:79b99e9c8e49 b |
|
490 | 0:79b99e9c8e49 b | |
491 |
|
491 | |||
492 | Test unix -> windows style variable substitution in external hooks. |
|
492 | Test unix -> windows style variable substitution in external hooks. | |
493 |
|
493 | |||
494 | $ cat > $TESTTMP/tmp.hgrc <<'EOF' |
|
494 | $ cat > $TESTTMP/tmp.hgrc <<'EOF' | |
495 | > [hooks] |
|
495 | > [hooks] | |
496 | > pre-add = echo no variables |
|
496 | > pre-add = echo no variables | |
497 | > post-add = echo ran $HG_ARGS, literal \$non-var, 'also $non-var', $HG_RESULT |
|
497 | > post-add = echo ran $HG_ARGS, literal \$non-var, 'also $non-var', $HG_RESULT | |
498 | > tonative.post-add = True |
|
498 | > tonative.post-add = True | |
499 | > EOF |
|
499 | > EOF | |
500 |
|
500 | |||
501 | $ echo "foo" > amended.txt |
|
501 | $ echo "foo" > amended.txt | |
502 | $ HGRCPATH=$TESTTMP/tmp.hgrc hg add -v amended.txt |
|
502 | $ HGRCPATH=$TESTTMP/tmp.hgrc hg add -v amended.txt | |
503 | running hook pre-add: echo no variables |
|
503 | running hook pre-add: echo no variables | |
504 | no variables |
|
504 | no variables | |
505 | adding amended.txt |
|
505 | adding amended.txt | |
506 | converting hook "post-add" to native (windows !) |
|
506 | converting hook "post-add" to native (windows !) | |
507 | running hook post-add: echo ran %HG_ARGS%, literal $non-var, "also $non-var", %HG_RESULT% (windows !) |
|
507 | running hook post-add: echo ran %HG_ARGS%, literal $non-var, "also $non-var", %HG_RESULT% (windows !) | |
508 | running hook post-add: echo ran $HG_ARGS, literal \$non-var, 'also $non-var', $HG_RESULT (no-windows !) |
|
508 | running hook post-add: echo ran $HG_ARGS, literal \$non-var, 'also $non-var', $HG_RESULT (no-windows !) | |
509 | ran add -v amended.txt, literal $non-var, "also $non-var", 0 (windows !) |
|
509 | ran add -v amended.txt, literal $non-var, "also $non-var", 0 (windows !) | |
510 | ran add -v amended.txt, literal $non-var, also $non-var, 0 (no-windows !) |
|
510 | ran add -v amended.txt, literal $non-var, also $non-var, 0 (no-windows !) | |
511 | $ hg ci -q --config extensions.largefiles= --amend -I amended.txt |
|
511 | $ hg ci -q --config extensions.largefiles= --amend -I amended.txt | |
512 | The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !) |
|
512 | The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !) | |
513 |
|
513 | |||
514 | Test that folding multiple changes in a row doesn't show multiple |
|
514 | Test that folding multiple changes in a row doesn't show multiple | |
515 | editors. |
|
515 | editors. | |
516 |
|
516 | |||
517 | $ echo foo >> foo |
|
517 | $ echo foo >> foo | |
518 | $ hg add foo |
|
518 | $ hg add foo | |
519 | $ hg ci -m foo1 |
|
519 | $ hg ci -m foo1 | |
520 | $ echo foo >> foo |
|
520 | $ echo foo >> foo | |
521 | $ hg ci -m foo2 |
|
521 | $ hg ci -m foo2 | |
522 | $ echo foo >> foo |
|
522 | $ echo foo >> foo | |
523 | $ hg ci -m foo3 |
|
523 | $ hg ci -m foo3 | |
524 | $ hg logt |
|
524 | $ hg logt | |
525 | 4:21679ff7675c foo3 |
|
525 | 4:21679ff7675c foo3 | |
526 | 3:b7389cc4d66e foo2 |
|
526 | 3:b7389cc4d66e foo2 | |
527 | 2:0e01aeef5fa8 foo1 |
|
527 | 2:0e01aeef5fa8 foo1 | |
528 | 1:578c7455730c a |
|
528 | 1:578c7455730c a | |
529 | 0:79b99e9c8e49 b |
|
529 | 0:79b99e9c8e49 b | |
530 | $ cat > "$TESTTMP/editor.sh" <<EOF |
|
530 | $ cat > "$TESTTMP/editor.sh" <<EOF | |
531 | > echo ran editor >> "$TESTTMP/editorlog.txt" |
|
531 | > echo ran editor >> "$TESTTMP/editorlog.txt" | |
532 | > cat \$1 >> "$TESTTMP/editorlog.txt" |
|
532 | > cat \$1 >> "$TESTTMP/editorlog.txt" | |
533 | > echo END >> "$TESTTMP/editorlog.txt" |
|
533 | > echo END >> "$TESTTMP/editorlog.txt" | |
534 | > echo merged foos > \$1 |
|
534 | > echo merged foos > \$1 | |
535 | > EOF |
|
535 | > EOF | |
536 | $ HGEDITOR="sh \"$TESTTMP/editor.sh\"" hg histedit 1 --commands - 2>&1 <<EOF | fixbundle |
|
536 | $ HGEDITOR="sh \"$TESTTMP/editor.sh\"" hg histedit 1 --commands - 2>&1 <<EOF | fixbundle | |
537 | > pick 578c7455730c 1 a |
|
537 | > pick 578c7455730c 1 a | |
538 | > pick 0e01aeef5fa8 2 foo1 |
|
538 | > pick 0e01aeef5fa8 2 foo1 | |
539 | > fold b7389cc4d66e 3 foo2 |
|
539 | > fold b7389cc4d66e 3 foo2 | |
540 | > fold 21679ff7675c 4 foo3 |
|
540 | > fold 21679ff7675c 4 foo3 | |
541 | > EOF |
|
541 | > EOF | |
542 | merging foo |
|
542 | merging foo | |
543 | $ hg logt |
|
543 | $ hg logt | |
544 | 2:e8bedbda72c1 merged foos |
|
544 | 2:e8bedbda72c1 merged foos | |
545 | 1:578c7455730c a |
|
545 | 1:578c7455730c a | |
546 | 0:79b99e9c8e49 b |
|
546 | 0:79b99e9c8e49 b | |
547 | Editor should have run only once |
|
547 | Editor should have run only once | |
548 | $ cat $TESTTMP/editorlog.txt |
|
548 | $ cat $TESTTMP/editorlog.txt | |
549 | ran editor |
|
549 | ran editor | |
550 | foo1 |
|
550 | foo1 | |
551 | *** |
|
551 | *** | |
552 | foo2 |
|
552 | foo2 | |
553 | *** |
|
553 | *** | |
554 | foo3 |
|
554 | foo3 | |
555 |
|
555 | |||
556 |
|
556 | |||
557 |
|
557 | |||
558 | HG: Enter commit message. Lines beginning with 'HG:' are removed. |
|
558 | HG: Enter commit message. Lines beginning with 'HG:' are removed. | |
559 | HG: Leave message empty to abort commit. |
|
559 | HG: Leave message empty to abort commit. | |
560 | HG: -- |
|
560 | HG: -- | |
561 | HG: user: test |
|
561 | HG: user: test | |
562 | HG: branch 'default' |
|
562 | HG: branch 'default' | |
563 | HG: added foo |
|
563 | HG: added foo | |
564 | END |
|
564 | END | |
565 |
|
565 | |||
566 | $ cd .. |
|
566 | $ cd .. | |
567 |
|
567 | |||
568 | Test rolling into a commit with multiple children (issue5498) |
|
568 | Test rolling into a commit with multiple children (issue5498) | |
569 |
|
569 | |||
570 | $ hg init roll |
|
570 | $ hg init roll | |
571 | $ cd roll |
|
571 | $ cd roll | |
572 | $ echo a > a |
|
572 | $ echo a > a | |
573 | $ hg commit -qAm aa |
|
573 | $ hg commit -qAm aa | |
574 | $ echo b > b |
|
574 | $ echo b > b | |
575 | $ hg commit -qAm bb |
|
575 | $ hg commit -qAm bb | |
576 | $ hg up -q ".^" |
|
576 | $ hg up -q ".^" | |
577 | $ echo c > c |
|
577 | $ echo c > c | |
578 | $ hg commit -qAm cc |
|
578 | $ hg commit -qAm cc | |
579 | $ hg log -G -T '{node|short} {desc}' |
|
579 | $ hg log -G -T '{node|short} {desc}' | |
580 | @ 5db65b93a12b cc |
|
580 | @ 5db65b93a12b cc | |
581 | | |
|
581 | | | |
582 | | o 301d76bdc3ae bb |
|
582 | | o 301d76bdc3ae bb | |
583 | |/ |
|
583 | |/ | |
584 | o 8f0162e483d0 aa |
|
584 | o 8f0162e483d0 aa | |
585 |
|
585 | |||
586 |
|
586 | |||
587 | $ hg histedit . --commands - << EOF |
|
587 | $ hg histedit . --commands - << EOF | |
588 | > r 5db65b93a12b |
|
588 | > r 5db65b93a12b | |
589 | > EOF |
|
589 | > EOF | |
590 | hg: parse error: first changeset cannot use verb "roll" |
|
590 | hg: parse error: first changeset cannot use verb "roll" | |
591 | [255] |
|
591 | [255] | |
592 | $ hg log -G -T '{node|short} {desc}' |
|
592 | $ hg log -G -T '{node|short} {desc}' | |
593 | @ 5db65b93a12b cc |
|
593 | @ 5db65b93a12b cc | |
594 | | |
|
594 | | | |
595 | | o 301d76bdc3ae bb |
|
595 | | o 301d76bdc3ae bb | |
596 | |/ |
|
596 | |/ | |
597 | o 8f0162e483d0 aa |
|
597 | o 8f0162e483d0 aa | |
598 |
|
598 | |||
599 |
|
599 | |||
600 | $ cd .. |
|
600 | $ cd .. | |
601 |
|
601 | |||
602 | ==================================== |
|
602 | ==================================== | |
603 | Test update-timestamp config option| |
|
603 | Test update-timestamp config option| | |
604 | ==================================== |
|
604 | ==================================== | |
605 |
|
605 | |||
606 | $ addwithdate () |
|
606 | $ addwithdate () | |
607 | > { |
|
607 | > { | |
608 | > echo $1 > $1 |
|
608 | > echo $1 > $1 | |
609 | > hg add $1 |
|
609 | > hg add $1 | |
610 | > hg ci -m $1 -d "$2 0" |
|
610 | > hg ci -m $1 -d "$2 0" | |
611 | > } |
|
611 | > } | |
612 |
|
612 | |||
613 | $ initrepo () |
|
613 | $ initrepo () | |
614 | > { |
|
614 | > { | |
615 | > hg init r |
|
615 | > hg init r | |
616 | > cd r |
|
616 | > cd r | |
617 | > addwithdate a 1 |
|
617 | > addwithdate a 1 | |
618 | > addwithdate b 2 |
|
618 | > addwithdate b 2 | |
619 | > addwithdate c 3 |
|
619 | > addwithdate c 3 | |
620 | > addwithdate d 4 |
|
620 | > addwithdate d 4 | |
621 | > addwithdate e 5 |
|
621 | > addwithdate e 5 | |
622 | > addwithdate f 6 |
|
622 | > addwithdate f 6 | |
623 | > } |
|
623 | > } | |
624 |
|
624 | |||
625 | $ initrepo |
|
625 | $ initrepo | |
626 |
|
626 | |||
627 | log before edit |
|
627 | log before edit | |
628 |
|
628 | |||
629 | $ hg log |
|
629 | $ hg log | |
630 | changeset: 5:178e35e0ce73 |
|
630 | changeset: 5:178e35e0ce73 | |
631 | tag: tip |
|
631 | tag: tip | |
632 | user: test |
|
632 | user: test | |
633 | date: Thu Jan 01 00:00:06 1970 +0000 |
|
633 | date: Thu Jan 01 00:00:06 1970 +0000 | |
634 | summary: f |
|
634 | summary: f | |
635 |
|
635 | |||
636 | changeset: 4:1ddb6c90f2ee |
|
636 | changeset: 4:1ddb6c90f2ee | |
637 | user: test |
|
637 | user: test | |
638 | date: Thu Jan 01 00:00:05 1970 +0000 |
|
638 | date: Thu Jan 01 00:00:05 1970 +0000 | |
639 | summary: e |
|
639 | summary: e | |
640 |
|
640 | |||
641 | changeset: 3:532247a8969b |
|
641 | changeset: 3:532247a8969b | |
642 | user: test |
|
642 | user: test | |
643 | date: Thu Jan 01 00:00:04 1970 +0000 |
|
643 | date: Thu Jan 01 00:00:04 1970 +0000 | |
644 | summary: d |
|
644 | summary: d | |
645 |
|
645 | |||
646 | changeset: 2:ff2c9fa2018b |
|
646 | changeset: 2:ff2c9fa2018b | |
647 | user: test |
|
647 | user: test | |
648 | date: Thu Jan 01 00:00:03 1970 +0000 |
|
648 | date: Thu Jan 01 00:00:03 1970 +0000 | |
649 | summary: c |
|
649 | summary: c | |
650 |
|
650 | |||
651 | changeset: 1:97d72e5f12c7 |
|
651 | changeset: 1:97d72e5f12c7 | |
652 | user: test |
|
652 | user: test | |
653 | date: Thu Jan 01 00:00:02 1970 +0000 |
|
653 | date: Thu Jan 01 00:00:02 1970 +0000 | |
654 | summary: b |
|
654 | summary: b | |
655 |
|
655 | |||
656 | changeset: 0:8580ff50825a |
|
656 | changeset: 0:8580ff50825a | |
657 | user: test |
|
657 | user: test | |
658 | date: Thu Jan 01 00:00:01 1970 +0000 |
|
658 | date: Thu Jan 01 00:00:01 1970 +0000 | |
659 | summary: a |
|
659 | summary: a | |
660 |
|
660 | |||
661 |
|
661 | |||
662 | $ hg histedit 1ddb6c90f2ee --commands - 2>&1 --config rewrite.update-timestamp=True <<EOF | fixbundle |
|
662 | $ hg histedit 1ddb6c90f2ee --commands - 2>&1 --config rewrite.update-timestamp=True <<EOF | fixbundle | |
663 | > pick 178e35e0ce73 f |
|
663 | > pick 178e35e0ce73 f | |
664 | > fold 1ddb6c90f2ee e |
|
664 | > fold 1ddb6c90f2ee e | |
665 | > EOF |
|
665 | > EOF | |
666 |
|
666 | |||
667 | log after edit |
|
667 | log after edit | |
668 | observe time from f is updated |
|
668 | observe time from f is updated | |
669 |
|
669 | |||
670 | $ hg log |
|
670 | $ hg log | |
671 | changeset: 4:f7909b1863a2 |
|
671 | changeset: 4:f7909b1863a2 | |
672 | tag: tip |
|
672 | tag: tip | |
673 | user: test |
|
673 | user: test | |
674 | date: Thu Jan 01 00:00:01 1970 +0000 |
|
674 | date: Thu Jan 01 00:00:01 1970 +0000 | |
675 | summary: f |
|
675 | summary: f | |
676 |
|
676 | |||
677 | changeset: 3:532247a8969b |
|
677 | changeset: 3:532247a8969b | |
678 | user: test |
|
678 | user: test | |
679 | date: Thu Jan 01 00:00:04 1970 +0000 |
|
679 | date: Thu Jan 01 00:00:04 1970 +0000 | |
680 | summary: d |
|
680 | summary: d | |
681 |
|
681 | |||
682 | changeset: 2:ff2c9fa2018b |
|
682 | changeset: 2:ff2c9fa2018b | |
683 | user: test |
|
683 | user: test | |
684 | date: Thu Jan 01 00:00:03 1970 +0000 |
|
684 | date: Thu Jan 01 00:00:03 1970 +0000 | |
685 | summary: c |
|
685 | summary: c | |
686 |
|
686 | |||
687 | changeset: 1:97d72e5f12c7 |
|
687 | changeset: 1:97d72e5f12c7 | |
688 | user: test |
|
688 | user: test | |
689 | date: Thu Jan 01 00:00:02 1970 +0000 |
|
689 | date: Thu Jan 01 00:00:02 1970 +0000 | |
690 | summary: b |
|
690 | summary: b | |
691 |
|
691 | |||
692 | changeset: 0:8580ff50825a |
|
692 | changeset: 0:8580ff50825a | |
693 | user: test |
|
693 | user: test | |
694 | date: Thu Jan 01 00:00:01 1970 +0000 |
|
694 | date: Thu Jan 01 00:00:01 1970 +0000 | |
695 | summary: a |
|
695 | summary: a | |
696 |
|
696 | |||
697 | post-fold manifest |
|
697 | post-fold manifest | |
698 | $ hg manifest |
|
698 | $ hg manifest | |
699 | a |
|
699 | a | |
700 | b |
|
700 | b | |
701 | c |
|
701 | c | |
702 | d |
|
702 | d | |
703 | e |
|
703 | e | |
704 | f |
|
704 | f | |
705 |
|
705 | |||
706 | $ cd .. |
|
706 | $ cd .. |
@@ -1,80 +1,80 b'' | |||||
1 | #testcases abortcommand abortflag |
|
1 | #testcases abortcommand abortflag | |
2 |
|
2 | |||
3 | #if abortflag |
|
3 | #if abortflag | |
4 | $ cat >> $HGRCPATH <<EOF |
|
4 | $ cat >> $HGRCPATH <<EOF | |
5 | > [alias] |
|
5 | > [alias] | |
6 | > abort = histedit --abort |
|
6 | > abort = histedit --abort | |
7 | > EOF |
|
7 | > EOF | |
8 | #endif |
|
8 | #endif | |
9 |
|
9 | |||
10 | $ . "$TESTDIR/histedit-helpers.sh" |
|
10 | $ . "$TESTDIR/histedit-helpers.sh" | |
11 |
|
11 | |||
12 | Enable extension used by this test |
|
12 | Enable extension used by this test | |
13 | $ cat >>$HGRCPATH <<EOF |
|
13 | $ cat >>$HGRCPATH <<EOF | |
14 | > [extensions] |
|
14 | > [extensions] | |
15 | > histedit= |
|
15 | > histedit= | |
16 | > EOF |
|
16 | > EOF | |
17 |
|
17 | |||
18 | ================================= |
|
18 | ================================= | |
19 | Test backup-bundle config option| |
|
19 | Test backup-bundle config option| | |
20 | ================================= |
|
20 | ================================= | |
21 | Repo setup: |
|
21 | Repo setup: | |
22 | $ hg init foo |
|
22 | $ hg init foo | |
23 | $ cd foo |
|
23 | $ cd foo | |
24 | $ echo first>file |
|
24 | $ echo first>file | |
25 | $ hg ci -qAm one |
|
25 | $ hg ci -qAm one | |
26 | $ echo second>>file |
|
26 | $ echo second>>file | |
27 | $ hg ci -m two |
|
27 | $ hg ci -m two | |
28 | $ echo third>>file |
|
28 | $ echo third>>file | |
29 | $ hg ci -m three |
|
29 | $ hg ci -m three | |
30 | $ echo forth>>file |
|
30 | $ echo forth>>file | |
31 | $ hg ci -m four |
|
31 | $ hg ci -m four | |
32 | $ hg log -G --style compact |
|
32 | $ hg log -G --style compact | |
33 | @ 3[tip] 7d5187087c79 1970-01-01 00:00 +0000 test |
|
33 | @ 3[tip] 7d5187087c79 1970-01-01 00:00 +0000 test | |
34 | | four |
|
34 | | four | |
35 | | |
|
35 | | | |
36 | o 2 80d23dfa866d 1970-01-01 00:00 +0000 test |
|
36 | o 2 80d23dfa866d 1970-01-01 00:00 +0000 test | |
37 | | three |
|
37 | | three | |
38 | | |
|
38 | | | |
39 | o 1 6153eb23e623 1970-01-01 00:00 +0000 test |
|
39 | o 1 6153eb23e623 1970-01-01 00:00 +0000 test | |
40 | | two |
|
40 | | two | |
41 | | |
|
41 | | | |
42 | o 0 36b4bdd91f5b 1970-01-01 00:00 +0000 test |
|
42 | o 0 36b4bdd91f5b 1970-01-01 00:00 +0000 test | |
43 | one |
|
43 | one | |
44 |
|
44 | |||
45 | Test when `backup-bundle` config option is enabled: |
|
45 | Test when `backup-bundle` config option is enabled: | |
46 | $ hg histedit -r '36b4bdd91f5b' --commands - << EOF |
|
46 | $ hg histedit -r '36b4bdd91f5b' --commands - << EOF | |
47 | > pick 36b4bdd91f5b 0 one |
|
47 | > pick 36b4bdd91f5b 0 one | |
48 | > pick 6153eb23e623 1 two |
|
48 | > pick 6153eb23e623 1 two | |
49 | > roll 80d23dfa866d 2 three |
|
49 | > roll 80d23dfa866d 2 three | |
50 | > edit 7d5187087c79 3 four |
|
50 | > edit 7d5187087c79 3 four | |
51 | > EOF |
|
51 | > EOF | |
52 | merging file |
|
52 | merging file | |
53 | Editing (7d5187087c79), you may commit or record as needed now. |
|
53 | Editing (7d5187087c79), you may commit or record as needed now. | |
54 | (hg histedit --continue to resume) |
|
54 | (hg histedit --continue to resume) | |
55 |
[ |
|
55 | [240] | |
56 | $ hg abort |
|
56 | $ hg abort | |
57 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
57 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
58 | saved backup bundle to $TESTTMP/foo/.hg/strip-backup/1d8f701c7b35-cf7be322-backup.hg |
|
58 | saved backup bundle to $TESTTMP/foo/.hg/strip-backup/1d8f701c7b35-cf7be322-backup.hg | |
59 | saved backup bundle to $TESTTMP/foo/.hg/strip-backup/5c0056670bce-b54b65d0-backup.hg |
|
59 | saved backup bundle to $TESTTMP/foo/.hg/strip-backup/5c0056670bce-b54b65d0-backup.hg | |
60 |
|
60 | |||
61 | Test when `backup-bundle` config option is not enabled |
|
61 | Test when `backup-bundle` config option is not enabled | |
62 | Enable config option: |
|
62 | Enable config option: | |
63 | $ cat >>$HGRCPATH <<EOF |
|
63 | $ cat >>$HGRCPATH <<EOF | |
64 | > [rewrite] |
|
64 | > [rewrite] | |
65 | > backup-bundle = False |
|
65 | > backup-bundle = False | |
66 | > EOF |
|
66 | > EOF | |
67 |
|
67 | |||
68 | $ hg histedit -r '36b4bdd91f5b' --commands - << EOF |
|
68 | $ hg histedit -r '36b4bdd91f5b' --commands - << EOF | |
69 | > pick 36b4bdd91f5b 0 one |
|
69 | > pick 36b4bdd91f5b 0 one | |
70 | > pick 6153eb23e623 1 two |
|
70 | > pick 6153eb23e623 1 two | |
71 | > roll 80d23dfa866d 2 three |
|
71 | > roll 80d23dfa866d 2 three | |
72 | > edit 7d5187087c79 3 four |
|
72 | > edit 7d5187087c79 3 four | |
73 | > EOF |
|
73 | > EOF | |
74 | merging file |
|
74 | merging file | |
75 | Editing (7d5187087c79), you may commit or record as needed now. |
|
75 | Editing (7d5187087c79), you may commit or record as needed now. | |
76 | (hg histedit --continue to resume) |
|
76 | (hg histedit --continue to resume) | |
77 |
[ |
|
77 | [240] | |
78 |
|
78 | |||
79 | $ hg abort |
|
79 | $ hg abort | |
80 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
80 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
General Comments 0
You need to be logged in to leave comments.
Login now