##// END OF EJS Templates
errors: add config that lets user get more detailed exit codes...
Martin von Zweigbergk -
r46430:21733e8c default
parent child Browse files
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 [1]
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 [1]
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 [1]
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 [1]
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 [1]
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 [1]
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 [1]
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 [1]
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 [1]
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 [1]
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 [1]
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 [1]
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 [1]
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 [1]
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 [1]
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 [1]
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 [1]
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