##// END OF EJS Templates
http: drop custom http client logic...
Augie Fackler -
r36444:23d12524 default
parent child Browse files
Show More
@@ -1,1308 +1,1302 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 def loadconfigtable(ui, extname, configtable):
18 def loadconfigtable(ui, extname, configtable):
19 """update config item known to the ui with the extension ones"""
19 """update config item known to the ui with the extension ones"""
20 for section, items in sorted(configtable.items()):
20 for section, items in sorted(configtable.items()):
21 knownitems = ui._knownconfig.setdefault(section, itemregister())
21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 knownkeys = set(knownitems)
22 knownkeys = set(knownitems)
23 newkeys = set(items)
23 newkeys = set(items)
24 for key in sorted(knownkeys & newkeys):
24 for key in sorted(knownkeys & newkeys):
25 msg = "extension '%s' overwrite config item '%s.%s'"
25 msg = "extension '%s' overwrite config item '%s.%s'"
26 msg %= (extname, section, key)
26 msg %= (extname, section, key)
27 ui.develwarn(msg, config='warn-config')
27 ui.develwarn(msg, config='warn-config')
28
28
29 knownitems.update(items)
29 knownitems.update(items)
30
30
31 class configitem(object):
31 class configitem(object):
32 """represent a known config item
32 """represent a known config item
33
33
34 :section: the official config section where to find this item,
34 :section: the official config section where to find this item,
35 :name: the official name within the section,
35 :name: the official name within the section,
36 :default: default value for this item,
36 :default: default value for this item,
37 :alias: optional list of tuples as alternatives,
37 :alias: optional list of tuples as alternatives,
38 :generic: this is a generic definition, match name using regular expression.
38 :generic: this is a generic definition, match name using regular expression.
39 """
39 """
40
40
41 def __init__(self, section, name, default=None, alias=(),
41 def __init__(self, section, name, default=None, alias=(),
42 generic=False, priority=0):
42 generic=False, priority=0):
43 self.section = section
43 self.section = section
44 self.name = name
44 self.name = name
45 self.default = default
45 self.default = default
46 self.alias = list(alias)
46 self.alias = list(alias)
47 self.generic = generic
47 self.generic = generic
48 self.priority = priority
48 self.priority = priority
49 self._re = None
49 self._re = None
50 if generic:
50 if generic:
51 self._re = re.compile(self.name)
51 self._re = re.compile(self.name)
52
52
53 class itemregister(dict):
53 class itemregister(dict):
54 """A specialized dictionary that can handle wild-card selection"""
54 """A specialized dictionary that can handle wild-card selection"""
55
55
56 def __init__(self):
56 def __init__(self):
57 super(itemregister, self).__init__()
57 super(itemregister, self).__init__()
58 self._generics = set()
58 self._generics = set()
59
59
60 def update(self, other):
60 def update(self, other):
61 super(itemregister, self).update(other)
61 super(itemregister, self).update(other)
62 self._generics.update(other._generics)
62 self._generics.update(other._generics)
63
63
64 def __setitem__(self, key, item):
64 def __setitem__(self, key, item):
65 super(itemregister, self).__setitem__(key, item)
65 super(itemregister, self).__setitem__(key, item)
66 if item.generic:
66 if item.generic:
67 self._generics.add(item)
67 self._generics.add(item)
68
68
69 def get(self, key):
69 def get(self, key):
70 baseitem = super(itemregister, self).get(key)
70 baseitem = super(itemregister, self).get(key)
71 if baseitem is not None and not baseitem.generic:
71 if baseitem is not None and not baseitem.generic:
72 return baseitem
72 return baseitem
73
73
74 # search for a matching generic item
74 # search for a matching generic item
75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
76 for item in generics:
76 for item in generics:
77 # we use 'match' instead of 'search' to make the matching simpler
77 # we use 'match' instead of 'search' to make the matching simpler
78 # for people unfamiliar with regular expression. Having the match
78 # for people unfamiliar with regular expression. Having the match
79 # rooted to the start of the string will produce less surprising
79 # rooted to the start of the string will produce less surprising
80 # result for user writing simple regex for sub-attribute.
80 # result for user writing simple regex for sub-attribute.
81 #
81 #
82 # For example using "color\..*" match produces an unsurprising
82 # For example using "color\..*" match produces an unsurprising
83 # result, while using search could suddenly match apparently
83 # result, while using search could suddenly match apparently
84 # unrelated configuration that happens to contains "color."
84 # unrelated configuration that happens to contains "color."
85 # anywhere. This is a tradeoff where we favor requiring ".*" on
85 # anywhere. This is a tradeoff where we favor requiring ".*" on
86 # some match to avoid the need to prefix most pattern with "^".
86 # some match to avoid the need to prefix most pattern with "^".
87 # The "^" seems more error prone.
87 # The "^" seems more error prone.
88 if item._re.match(key):
88 if item._re.match(key):
89 return item
89 return item
90
90
91 return None
91 return None
92
92
93 coreitems = {}
93 coreitems = {}
94
94
95 def _register(configtable, *args, **kwargs):
95 def _register(configtable, *args, **kwargs):
96 item = configitem(*args, **kwargs)
96 item = configitem(*args, **kwargs)
97 section = configtable.setdefault(item.section, itemregister())
97 section = configtable.setdefault(item.section, itemregister())
98 if item.name in section:
98 if item.name in section:
99 msg = "duplicated config item registration for '%s.%s'"
99 msg = "duplicated config item registration for '%s.%s'"
100 raise error.ProgrammingError(msg % (item.section, item.name))
100 raise error.ProgrammingError(msg % (item.section, item.name))
101 section[item.name] = item
101 section[item.name] = item
102
102
103 # special value for case where the default is derived from other values
103 # special value for case where the default is derived from other values
104 dynamicdefault = object()
104 dynamicdefault = object()
105
105
106 # Registering actual config items
106 # Registering actual config items
107
107
108 def getitemregister(configtable):
108 def getitemregister(configtable):
109 f = functools.partial(_register, configtable)
109 f = functools.partial(_register, configtable)
110 # export pseudo enum as configitem.*
110 # export pseudo enum as configitem.*
111 f.dynamicdefault = dynamicdefault
111 f.dynamicdefault = dynamicdefault
112 return f
112 return f
113
113
114 coreconfigitem = getitemregister(coreitems)
114 coreconfigitem = getitemregister(coreitems)
115
115
116 coreconfigitem('alias', '.*',
116 coreconfigitem('alias', '.*',
117 default=None,
117 default=None,
118 generic=True,
118 generic=True,
119 )
119 )
120 coreconfigitem('annotate', 'nodates',
120 coreconfigitem('annotate', 'nodates',
121 default=False,
121 default=False,
122 )
122 )
123 coreconfigitem('annotate', 'showfunc',
123 coreconfigitem('annotate', 'showfunc',
124 default=False,
124 default=False,
125 )
125 )
126 coreconfigitem('annotate', 'unified',
126 coreconfigitem('annotate', 'unified',
127 default=None,
127 default=None,
128 )
128 )
129 coreconfigitem('annotate', 'git',
129 coreconfigitem('annotate', 'git',
130 default=False,
130 default=False,
131 )
131 )
132 coreconfigitem('annotate', 'ignorews',
132 coreconfigitem('annotate', 'ignorews',
133 default=False,
133 default=False,
134 )
134 )
135 coreconfigitem('annotate', 'ignorewsamount',
135 coreconfigitem('annotate', 'ignorewsamount',
136 default=False,
136 default=False,
137 )
137 )
138 coreconfigitem('annotate', 'ignoreblanklines',
138 coreconfigitem('annotate', 'ignoreblanklines',
139 default=False,
139 default=False,
140 )
140 )
141 coreconfigitem('annotate', 'ignorewseol',
141 coreconfigitem('annotate', 'ignorewseol',
142 default=False,
142 default=False,
143 )
143 )
144 coreconfigitem('annotate', 'nobinary',
144 coreconfigitem('annotate', 'nobinary',
145 default=False,
145 default=False,
146 )
146 )
147 coreconfigitem('annotate', 'noprefix',
147 coreconfigitem('annotate', 'noprefix',
148 default=False,
148 default=False,
149 )
149 )
150 coreconfigitem('auth', 'cookiefile',
150 coreconfigitem('auth', 'cookiefile',
151 default=None,
151 default=None,
152 )
152 )
153 # bookmarks.pushing: internal hack for discovery
153 # bookmarks.pushing: internal hack for discovery
154 coreconfigitem('bookmarks', 'pushing',
154 coreconfigitem('bookmarks', 'pushing',
155 default=list,
155 default=list,
156 )
156 )
157 # bundle.mainreporoot: internal hack for bundlerepo
157 # bundle.mainreporoot: internal hack for bundlerepo
158 coreconfigitem('bundle', 'mainreporoot',
158 coreconfigitem('bundle', 'mainreporoot',
159 default='',
159 default='',
160 )
160 )
161 # bundle.reorder: experimental config
161 # bundle.reorder: experimental config
162 coreconfigitem('bundle', 'reorder',
162 coreconfigitem('bundle', 'reorder',
163 default='auto',
163 default='auto',
164 )
164 )
165 coreconfigitem('censor', 'policy',
165 coreconfigitem('censor', 'policy',
166 default='abort',
166 default='abort',
167 )
167 )
168 coreconfigitem('chgserver', 'idletimeout',
168 coreconfigitem('chgserver', 'idletimeout',
169 default=3600,
169 default=3600,
170 )
170 )
171 coreconfigitem('chgserver', 'skiphash',
171 coreconfigitem('chgserver', 'skiphash',
172 default=False,
172 default=False,
173 )
173 )
174 coreconfigitem('cmdserver', 'log',
174 coreconfigitem('cmdserver', 'log',
175 default=None,
175 default=None,
176 )
176 )
177 coreconfigitem('color', '.*',
177 coreconfigitem('color', '.*',
178 default=None,
178 default=None,
179 generic=True,
179 generic=True,
180 )
180 )
181 coreconfigitem('color', 'mode',
181 coreconfigitem('color', 'mode',
182 default='auto',
182 default='auto',
183 )
183 )
184 coreconfigitem('color', 'pagermode',
184 coreconfigitem('color', 'pagermode',
185 default=dynamicdefault,
185 default=dynamicdefault,
186 )
186 )
187 coreconfigitem('commands', 'show.aliasprefix',
187 coreconfigitem('commands', 'show.aliasprefix',
188 default=list,
188 default=list,
189 )
189 )
190 coreconfigitem('commands', 'status.relative',
190 coreconfigitem('commands', 'status.relative',
191 default=False,
191 default=False,
192 )
192 )
193 coreconfigitem('commands', 'status.skipstates',
193 coreconfigitem('commands', 'status.skipstates',
194 default=[],
194 default=[],
195 )
195 )
196 coreconfigitem('commands', 'status.verbose',
196 coreconfigitem('commands', 'status.verbose',
197 default=False,
197 default=False,
198 )
198 )
199 coreconfigitem('commands', 'update.check',
199 coreconfigitem('commands', 'update.check',
200 default=None,
200 default=None,
201 # Deprecated, remove after 4.4 release
201 # Deprecated, remove after 4.4 release
202 alias=[('experimental', 'updatecheck')]
202 alias=[('experimental', 'updatecheck')]
203 )
203 )
204 coreconfigitem('commands', 'update.requiredest',
204 coreconfigitem('commands', 'update.requiredest',
205 default=False,
205 default=False,
206 )
206 )
207 coreconfigitem('committemplate', '.*',
207 coreconfigitem('committemplate', '.*',
208 default=None,
208 default=None,
209 generic=True,
209 generic=True,
210 )
210 )
211 coreconfigitem('convert', 'cvsps.cache',
211 coreconfigitem('convert', 'cvsps.cache',
212 default=True,
212 default=True,
213 )
213 )
214 coreconfigitem('convert', 'cvsps.fuzz',
214 coreconfigitem('convert', 'cvsps.fuzz',
215 default=60,
215 default=60,
216 )
216 )
217 coreconfigitem('convert', 'cvsps.logencoding',
217 coreconfigitem('convert', 'cvsps.logencoding',
218 default=None,
218 default=None,
219 )
219 )
220 coreconfigitem('convert', 'cvsps.mergefrom',
220 coreconfigitem('convert', 'cvsps.mergefrom',
221 default=None,
221 default=None,
222 )
222 )
223 coreconfigitem('convert', 'cvsps.mergeto',
223 coreconfigitem('convert', 'cvsps.mergeto',
224 default=None,
224 default=None,
225 )
225 )
226 coreconfigitem('convert', 'git.committeractions',
226 coreconfigitem('convert', 'git.committeractions',
227 default=lambda: ['messagedifferent'],
227 default=lambda: ['messagedifferent'],
228 )
228 )
229 coreconfigitem('convert', 'git.extrakeys',
229 coreconfigitem('convert', 'git.extrakeys',
230 default=list,
230 default=list,
231 )
231 )
232 coreconfigitem('convert', 'git.findcopiesharder',
232 coreconfigitem('convert', 'git.findcopiesharder',
233 default=False,
233 default=False,
234 )
234 )
235 coreconfigitem('convert', 'git.remoteprefix',
235 coreconfigitem('convert', 'git.remoteprefix',
236 default='remote',
236 default='remote',
237 )
237 )
238 coreconfigitem('convert', 'git.renamelimit',
238 coreconfigitem('convert', 'git.renamelimit',
239 default=400,
239 default=400,
240 )
240 )
241 coreconfigitem('convert', 'git.saverev',
241 coreconfigitem('convert', 'git.saverev',
242 default=True,
242 default=True,
243 )
243 )
244 coreconfigitem('convert', 'git.similarity',
244 coreconfigitem('convert', 'git.similarity',
245 default=50,
245 default=50,
246 )
246 )
247 coreconfigitem('convert', 'git.skipsubmodules',
247 coreconfigitem('convert', 'git.skipsubmodules',
248 default=False,
248 default=False,
249 )
249 )
250 coreconfigitem('convert', 'hg.clonebranches',
250 coreconfigitem('convert', 'hg.clonebranches',
251 default=False,
251 default=False,
252 )
252 )
253 coreconfigitem('convert', 'hg.ignoreerrors',
253 coreconfigitem('convert', 'hg.ignoreerrors',
254 default=False,
254 default=False,
255 )
255 )
256 coreconfigitem('convert', 'hg.revs',
256 coreconfigitem('convert', 'hg.revs',
257 default=None,
257 default=None,
258 )
258 )
259 coreconfigitem('convert', 'hg.saverev',
259 coreconfigitem('convert', 'hg.saverev',
260 default=False,
260 default=False,
261 )
261 )
262 coreconfigitem('convert', 'hg.sourcename',
262 coreconfigitem('convert', 'hg.sourcename',
263 default=None,
263 default=None,
264 )
264 )
265 coreconfigitem('convert', 'hg.startrev',
265 coreconfigitem('convert', 'hg.startrev',
266 default=None,
266 default=None,
267 )
267 )
268 coreconfigitem('convert', 'hg.tagsbranch',
268 coreconfigitem('convert', 'hg.tagsbranch',
269 default='default',
269 default='default',
270 )
270 )
271 coreconfigitem('convert', 'hg.usebranchnames',
271 coreconfigitem('convert', 'hg.usebranchnames',
272 default=True,
272 default=True,
273 )
273 )
274 coreconfigitem('convert', 'ignoreancestorcheck',
274 coreconfigitem('convert', 'ignoreancestorcheck',
275 default=False,
275 default=False,
276 )
276 )
277 coreconfigitem('convert', 'localtimezone',
277 coreconfigitem('convert', 'localtimezone',
278 default=False,
278 default=False,
279 )
279 )
280 coreconfigitem('convert', 'p4.encoding',
280 coreconfigitem('convert', 'p4.encoding',
281 default=dynamicdefault,
281 default=dynamicdefault,
282 )
282 )
283 coreconfigitem('convert', 'p4.startrev',
283 coreconfigitem('convert', 'p4.startrev',
284 default=0,
284 default=0,
285 )
285 )
286 coreconfigitem('convert', 'skiptags',
286 coreconfigitem('convert', 'skiptags',
287 default=False,
287 default=False,
288 )
288 )
289 coreconfigitem('convert', 'svn.debugsvnlog',
289 coreconfigitem('convert', 'svn.debugsvnlog',
290 default=True,
290 default=True,
291 )
291 )
292 coreconfigitem('convert', 'svn.trunk',
292 coreconfigitem('convert', 'svn.trunk',
293 default=None,
293 default=None,
294 )
294 )
295 coreconfigitem('convert', 'svn.tags',
295 coreconfigitem('convert', 'svn.tags',
296 default=None,
296 default=None,
297 )
297 )
298 coreconfigitem('convert', 'svn.branches',
298 coreconfigitem('convert', 'svn.branches',
299 default=None,
299 default=None,
300 )
300 )
301 coreconfigitem('convert', 'svn.startrev',
301 coreconfigitem('convert', 'svn.startrev',
302 default=0,
302 default=0,
303 )
303 )
304 coreconfigitem('debug', 'dirstate.delaywrite',
304 coreconfigitem('debug', 'dirstate.delaywrite',
305 default=0,
305 default=0,
306 )
306 )
307 coreconfigitem('defaults', '.*',
307 coreconfigitem('defaults', '.*',
308 default=None,
308 default=None,
309 generic=True,
309 generic=True,
310 )
310 )
311 coreconfigitem('devel', 'all-warnings',
311 coreconfigitem('devel', 'all-warnings',
312 default=False,
312 default=False,
313 )
313 )
314 coreconfigitem('devel', 'bundle2.debug',
314 coreconfigitem('devel', 'bundle2.debug',
315 default=False,
315 default=False,
316 )
316 )
317 coreconfigitem('devel', 'cache-vfs',
317 coreconfigitem('devel', 'cache-vfs',
318 default=None,
318 default=None,
319 )
319 )
320 coreconfigitem('devel', 'check-locks',
320 coreconfigitem('devel', 'check-locks',
321 default=False,
321 default=False,
322 )
322 )
323 coreconfigitem('devel', 'check-relroot',
323 coreconfigitem('devel', 'check-relroot',
324 default=False,
324 default=False,
325 )
325 )
326 coreconfigitem('devel', 'default-date',
326 coreconfigitem('devel', 'default-date',
327 default=None,
327 default=None,
328 )
328 )
329 coreconfigitem('devel', 'deprec-warn',
329 coreconfigitem('devel', 'deprec-warn',
330 default=False,
330 default=False,
331 )
331 )
332 coreconfigitem('devel', 'disableloaddefaultcerts',
332 coreconfigitem('devel', 'disableloaddefaultcerts',
333 default=False,
333 default=False,
334 )
334 )
335 coreconfigitem('devel', 'warn-empty-changegroup',
335 coreconfigitem('devel', 'warn-empty-changegroup',
336 default=False,
336 default=False,
337 )
337 )
338 coreconfigitem('devel', 'legacy.exchange',
338 coreconfigitem('devel', 'legacy.exchange',
339 default=list,
339 default=list,
340 )
340 )
341 coreconfigitem('devel', 'servercafile',
341 coreconfigitem('devel', 'servercafile',
342 default='',
342 default='',
343 )
343 )
344 coreconfigitem('devel', 'serverexactprotocol',
344 coreconfigitem('devel', 'serverexactprotocol',
345 default='',
345 default='',
346 )
346 )
347 coreconfigitem('devel', 'serverrequirecert',
347 coreconfigitem('devel', 'serverrequirecert',
348 default=False,
348 default=False,
349 )
349 )
350 coreconfigitem('devel', 'strip-obsmarkers',
350 coreconfigitem('devel', 'strip-obsmarkers',
351 default=True,
351 default=True,
352 )
352 )
353 coreconfigitem('devel', 'warn-config',
353 coreconfigitem('devel', 'warn-config',
354 default=None,
354 default=None,
355 )
355 )
356 coreconfigitem('devel', 'warn-config-default',
356 coreconfigitem('devel', 'warn-config-default',
357 default=None,
357 default=None,
358 )
358 )
359 coreconfigitem('devel', 'user.obsmarker',
359 coreconfigitem('devel', 'user.obsmarker',
360 default=None,
360 default=None,
361 )
361 )
362 coreconfigitem('devel', 'warn-config-unknown',
362 coreconfigitem('devel', 'warn-config-unknown',
363 default=None,
363 default=None,
364 )
364 )
365 coreconfigitem('devel', 'debug.peer-request',
365 coreconfigitem('devel', 'debug.peer-request',
366 default=False,
366 default=False,
367 )
367 )
368 coreconfigitem('diff', 'nodates',
368 coreconfigitem('diff', 'nodates',
369 default=False,
369 default=False,
370 )
370 )
371 coreconfigitem('diff', 'showfunc',
371 coreconfigitem('diff', 'showfunc',
372 default=False,
372 default=False,
373 )
373 )
374 coreconfigitem('diff', 'unified',
374 coreconfigitem('diff', 'unified',
375 default=None,
375 default=None,
376 )
376 )
377 coreconfigitem('diff', 'git',
377 coreconfigitem('diff', 'git',
378 default=False,
378 default=False,
379 )
379 )
380 coreconfigitem('diff', 'ignorews',
380 coreconfigitem('diff', 'ignorews',
381 default=False,
381 default=False,
382 )
382 )
383 coreconfigitem('diff', 'ignorewsamount',
383 coreconfigitem('diff', 'ignorewsamount',
384 default=False,
384 default=False,
385 )
385 )
386 coreconfigitem('diff', 'ignoreblanklines',
386 coreconfigitem('diff', 'ignoreblanklines',
387 default=False,
387 default=False,
388 )
388 )
389 coreconfigitem('diff', 'ignorewseol',
389 coreconfigitem('diff', 'ignorewseol',
390 default=False,
390 default=False,
391 )
391 )
392 coreconfigitem('diff', 'nobinary',
392 coreconfigitem('diff', 'nobinary',
393 default=False,
393 default=False,
394 )
394 )
395 coreconfigitem('diff', 'noprefix',
395 coreconfigitem('diff', 'noprefix',
396 default=False,
396 default=False,
397 )
397 )
398 coreconfigitem('email', 'bcc',
398 coreconfigitem('email', 'bcc',
399 default=None,
399 default=None,
400 )
400 )
401 coreconfigitem('email', 'cc',
401 coreconfigitem('email', 'cc',
402 default=None,
402 default=None,
403 )
403 )
404 coreconfigitem('email', 'charsets',
404 coreconfigitem('email', 'charsets',
405 default=list,
405 default=list,
406 )
406 )
407 coreconfigitem('email', 'from',
407 coreconfigitem('email', 'from',
408 default=None,
408 default=None,
409 )
409 )
410 coreconfigitem('email', 'method',
410 coreconfigitem('email', 'method',
411 default='smtp',
411 default='smtp',
412 )
412 )
413 coreconfigitem('email', 'reply-to',
413 coreconfigitem('email', 'reply-to',
414 default=None,
414 default=None,
415 )
415 )
416 coreconfigitem('email', 'to',
416 coreconfigitem('email', 'to',
417 default=None,
417 default=None,
418 )
418 )
419 coreconfigitem('experimental', 'archivemetatemplate',
419 coreconfigitem('experimental', 'archivemetatemplate',
420 default=dynamicdefault,
420 default=dynamicdefault,
421 )
421 )
422 coreconfigitem('experimental', 'bundle-phases',
422 coreconfigitem('experimental', 'bundle-phases',
423 default=False,
423 default=False,
424 )
424 )
425 coreconfigitem('experimental', 'bundle2-advertise',
425 coreconfigitem('experimental', 'bundle2-advertise',
426 default=True,
426 default=True,
427 )
427 )
428 coreconfigitem('experimental', 'bundle2-output-capture',
428 coreconfigitem('experimental', 'bundle2-output-capture',
429 default=False,
429 default=False,
430 )
430 )
431 coreconfigitem('experimental', 'bundle2.pushback',
431 coreconfigitem('experimental', 'bundle2.pushback',
432 default=False,
432 default=False,
433 )
433 )
434 coreconfigitem('experimental', 'bundle2.stream',
434 coreconfigitem('experimental', 'bundle2.stream',
435 default=False,
435 default=False,
436 )
436 )
437 coreconfigitem('experimental', 'bundle2lazylocking',
437 coreconfigitem('experimental', 'bundle2lazylocking',
438 default=False,
438 default=False,
439 )
439 )
440 coreconfigitem('experimental', 'bundlecomplevel',
440 coreconfigitem('experimental', 'bundlecomplevel',
441 default=None,
441 default=None,
442 )
442 )
443 coreconfigitem('experimental', 'changegroup3',
443 coreconfigitem('experimental', 'changegroup3',
444 default=False,
444 default=False,
445 )
445 )
446 coreconfigitem('experimental', 'clientcompressionengines',
446 coreconfigitem('experimental', 'clientcompressionengines',
447 default=list,
447 default=list,
448 )
448 )
449 coreconfigitem('experimental', 'copytrace',
449 coreconfigitem('experimental', 'copytrace',
450 default='on',
450 default='on',
451 )
451 )
452 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
452 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
453 default=100,
453 default=100,
454 )
454 )
455 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
455 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
456 default=100,
456 default=100,
457 )
457 )
458 coreconfigitem('experimental', 'crecordtest',
458 coreconfigitem('experimental', 'crecordtest',
459 default=None,
459 default=None,
460 )
460 )
461 coreconfigitem('experimental', 'directaccess',
461 coreconfigitem('experimental', 'directaccess',
462 default=False,
462 default=False,
463 )
463 )
464 coreconfigitem('experimental', 'directaccess.revnums',
464 coreconfigitem('experimental', 'directaccess.revnums',
465 default=False,
465 default=False,
466 )
466 )
467 coreconfigitem('experimental', 'editortmpinhg',
467 coreconfigitem('experimental', 'editortmpinhg',
468 default=False,
468 default=False,
469 )
469 )
470 coreconfigitem('experimental', 'evolution',
470 coreconfigitem('experimental', 'evolution',
471 default=list,
471 default=list,
472 )
472 )
473 coreconfigitem('experimental', 'evolution.allowdivergence',
473 coreconfigitem('experimental', 'evolution.allowdivergence',
474 default=False,
474 default=False,
475 alias=[('experimental', 'allowdivergence')]
475 alias=[('experimental', 'allowdivergence')]
476 )
476 )
477 coreconfigitem('experimental', 'evolution.allowunstable',
477 coreconfigitem('experimental', 'evolution.allowunstable',
478 default=None,
478 default=None,
479 )
479 )
480 coreconfigitem('experimental', 'evolution.createmarkers',
480 coreconfigitem('experimental', 'evolution.createmarkers',
481 default=None,
481 default=None,
482 )
482 )
483 coreconfigitem('experimental', 'evolution.effect-flags',
483 coreconfigitem('experimental', 'evolution.effect-flags',
484 default=True,
484 default=True,
485 alias=[('experimental', 'effect-flags')]
485 alias=[('experimental', 'effect-flags')]
486 )
486 )
487 coreconfigitem('experimental', 'evolution.exchange',
487 coreconfigitem('experimental', 'evolution.exchange',
488 default=None,
488 default=None,
489 )
489 )
490 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
490 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
491 default=False,
491 default=False,
492 )
492 )
493 coreconfigitem('experimental', 'evolution.report-instabilities',
493 coreconfigitem('experimental', 'evolution.report-instabilities',
494 default=True,
494 default=True,
495 )
495 )
496 coreconfigitem('experimental', 'evolution.track-operation',
496 coreconfigitem('experimental', 'evolution.track-operation',
497 default=True,
497 default=True,
498 )
498 )
499 coreconfigitem('experimental', 'worddiff',
499 coreconfigitem('experimental', 'worddiff',
500 default=False,
500 default=False,
501 )
501 )
502 coreconfigitem('experimental', 'maxdeltachainspan',
502 coreconfigitem('experimental', 'maxdeltachainspan',
503 default=-1,
503 default=-1,
504 )
504 )
505 coreconfigitem('experimental', 'mmapindexthreshold',
505 coreconfigitem('experimental', 'mmapindexthreshold',
506 default=None,
506 default=None,
507 )
507 )
508 coreconfigitem('experimental', 'nonnormalparanoidcheck',
508 coreconfigitem('experimental', 'nonnormalparanoidcheck',
509 default=False,
509 default=False,
510 )
510 )
511 coreconfigitem('experimental', 'exportableenviron',
511 coreconfigitem('experimental', 'exportableenviron',
512 default=list,
512 default=list,
513 )
513 )
514 coreconfigitem('experimental', 'extendedheader.index',
514 coreconfigitem('experimental', 'extendedheader.index',
515 default=None,
515 default=None,
516 )
516 )
517 coreconfigitem('experimental', 'extendedheader.similarity',
517 coreconfigitem('experimental', 'extendedheader.similarity',
518 default=False,
518 default=False,
519 )
519 )
520 coreconfigitem('experimental', 'format.compression',
520 coreconfigitem('experimental', 'format.compression',
521 default='zlib',
521 default='zlib',
522 )
522 )
523 coreconfigitem('experimental', 'graphshorten',
523 coreconfigitem('experimental', 'graphshorten',
524 default=False,
524 default=False,
525 )
525 )
526 coreconfigitem('experimental', 'graphstyle.parent',
526 coreconfigitem('experimental', 'graphstyle.parent',
527 default=dynamicdefault,
527 default=dynamicdefault,
528 )
528 )
529 coreconfigitem('experimental', 'graphstyle.missing',
529 coreconfigitem('experimental', 'graphstyle.missing',
530 default=dynamicdefault,
530 default=dynamicdefault,
531 )
531 )
532 coreconfigitem('experimental', 'graphstyle.grandparent',
532 coreconfigitem('experimental', 'graphstyle.grandparent',
533 default=dynamicdefault,
533 default=dynamicdefault,
534 )
534 )
535 coreconfigitem('experimental', 'hook-track-tags',
535 coreconfigitem('experimental', 'hook-track-tags',
536 default=False,
536 default=False,
537 )
537 )
538 coreconfigitem('experimental', 'httppostargs',
538 coreconfigitem('experimental', 'httppostargs',
539 default=False,
539 default=False,
540 )
540 )
541 coreconfigitem('experimental', 'mergedriver',
541 coreconfigitem('experimental', 'mergedriver',
542 default=None,
542 default=None,
543 )
543 )
544 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
544 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
545 default=False,
545 default=False,
546 )
546 )
547 coreconfigitem('experimental', 'remotenames',
547 coreconfigitem('experimental', 'remotenames',
548 default=False,
548 default=False,
549 )
549 )
550 coreconfigitem('experimental', 'revlogv2',
550 coreconfigitem('experimental', 'revlogv2',
551 default=None,
551 default=None,
552 )
552 )
553 coreconfigitem('experimental', 'single-head-per-branch',
553 coreconfigitem('experimental', 'single-head-per-branch',
554 default=False,
554 default=False,
555 )
555 )
556 coreconfigitem('experimental', 'sshserver.support-v2',
556 coreconfigitem('experimental', 'sshserver.support-v2',
557 default=False,
557 default=False,
558 )
558 )
559 coreconfigitem('experimental', 'spacemovesdown',
559 coreconfigitem('experimental', 'spacemovesdown',
560 default=False,
560 default=False,
561 )
561 )
562 coreconfigitem('experimental', 'sparse-read',
562 coreconfigitem('experimental', 'sparse-read',
563 default=False,
563 default=False,
564 )
564 )
565 coreconfigitem('experimental', 'sparse-read.density-threshold',
565 coreconfigitem('experimental', 'sparse-read.density-threshold',
566 default=0.25,
566 default=0.25,
567 )
567 )
568 coreconfigitem('experimental', 'sparse-read.min-gap-size',
568 coreconfigitem('experimental', 'sparse-read.min-gap-size',
569 default='256K',
569 default='256K',
570 )
570 )
571 coreconfigitem('experimental', 'treemanifest',
571 coreconfigitem('experimental', 'treemanifest',
572 default=False,
572 default=False,
573 )
573 )
574 coreconfigitem('experimental', 'update.atomic-file',
574 coreconfigitem('experimental', 'update.atomic-file',
575 default=False,
575 default=False,
576 )
576 )
577 coreconfigitem('experimental', 'sshpeer.advertise-v2',
577 coreconfigitem('experimental', 'sshpeer.advertise-v2',
578 default=False,
578 default=False,
579 )
579 )
580 coreconfigitem('extensions', '.*',
580 coreconfigitem('extensions', '.*',
581 default=None,
581 default=None,
582 generic=True,
582 generic=True,
583 )
583 )
584 coreconfigitem('extdata', '.*',
584 coreconfigitem('extdata', '.*',
585 default=None,
585 default=None,
586 generic=True,
586 generic=True,
587 )
587 )
588 coreconfigitem('format', 'aggressivemergedeltas',
588 coreconfigitem('format', 'aggressivemergedeltas',
589 default=False,
589 default=False,
590 )
590 )
591 coreconfigitem('format', 'chunkcachesize',
591 coreconfigitem('format', 'chunkcachesize',
592 default=None,
592 default=None,
593 )
593 )
594 coreconfigitem('format', 'dotencode',
594 coreconfigitem('format', 'dotencode',
595 default=True,
595 default=True,
596 )
596 )
597 coreconfigitem('format', 'generaldelta',
597 coreconfigitem('format', 'generaldelta',
598 default=False,
598 default=False,
599 )
599 )
600 coreconfigitem('format', 'manifestcachesize',
600 coreconfigitem('format', 'manifestcachesize',
601 default=None,
601 default=None,
602 )
602 )
603 coreconfigitem('format', 'maxchainlen',
603 coreconfigitem('format', 'maxchainlen',
604 default=None,
604 default=None,
605 )
605 )
606 coreconfigitem('format', 'obsstore-version',
606 coreconfigitem('format', 'obsstore-version',
607 default=None,
607 default=None,
608 )
608 )
609 coreconfigitem('format', 'usefncache',
609 coreconfigitem('format', 'usefncache',
610 default=True,
610 default=True,
611 )
611 )
612 coreconfigitem('format', 'usegeneraldelta',
612 coreconfigitem('format', 'usegeneraldelta',
613 default=True,
613 default=True,
614 )
614 )
615 coreconfigitem('format', 'usestore',
615 coreconfigitem('format', 'usestore',
616 default=True,
616 default=True,
617 )
617 )
618 coreconfigitem('fsmonitor', 'warn_when_unused',
618 coreconfigitem('fsmonitor', 'warn_when_unused',
619 default=True,
619 default=True,
620 )
620 )
621 coreconfigitem('fsmonitor', 'warn_update_file_count',
621 coreconfigitem('fsmonitor', 'warn_update_file_count',
622 default=50000,
622 default=50000,
623 )
623 )
624 coreconfigitem('hooks', '.*',
624 coreconfigitem('hooks', '.*',
625 default=dynamicdefault,
625 default=dynamicdefault,
626 generic=True,
626 generic=True,
627 )
627 )
628 coreconfigitem('hgweb-paths', '.*',
628 coreconfigitem('hgweb-paths', '.*',
629 default=list,
629 default=list,
630 generic=True,
630 generic=True,
631 )
631 )
632 coreconfigitem('hostfingerprints', '.*',
632 coreconfigitem('hostfingerprints', '.*',
633 default=list,
633 default=list,
634 generic=True,
634 generic=True,
635 )
635 )
636 coreconfigitem('hostsecurity', 'ciphers',
636 coreconfigitem('hostsecurity', 'ciphers',
637 default=None,
637 default=None,
638 )
638 )
639 coreconfigitem('hostsecurity', 'disabletls10warning',
639 coreconfigitem('hostsecurity', 'disabletls10warning',
640 default=False,
640 default=False,
641 )
641 )
642 coreconfigitem('hostsecurity', 'minimumprotocol',
642 coreconfigitem('hostsecurity', 'minimumprotocol',
643 default=dynamicdefault,
643 default=dynamicdefault,
644 )
644 )
645 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
645 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
646 default=dynamicdefault,
646 default=dynamicdefault,
647 generic=True,
647 generic=True,
648 )
648 )
649 coreconfigitem('hostsecurity', '.*:ciphers$',
649 coreconfigitem('hostsecurity', '.*:ciphers$',
650 default=dynamicdefault,
650 default=dynamicdefault,
651 generic=True,
651 generic=True,
652 )
652 )
653 coreconfigitem('hostsecurity', '.*:fingerprints$',
653 coreconfigitem('hostsecurity', '.*:fingerprints$',
654 default=list,
654 default=list,
655 generic=True,
655 generic=True,
656 )
656 )
657 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
657 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
658 default=None,
658 default=None,
659 generic=True,
659 generic=True,
660 )
660 )
661
661
662 coreconfigitem('http_proxy', 'always',
662 coreconfigitem('http_proxy', 'always',
663 default=False,
663 default=False,
664 )
664 )
665 coreconfigitem('http_proxy', 'host',
665 coreconfigitem('http_proxy', 'host',
666 default=None,
666 default=None,
667 )
667 )
668 coreconfigitem('http_proxy', 'no',
668 coreconfigitem('http_proxy', 'no',
669 default=list,
669 default=list,
670 )
670 )
671 coreconfigitem('http_proxy', 'passwd',
671 coreconfigitem('http_proxy', 'passwd',
672 default=None,
672 default=None,
673 )
673 )
674 coreconfigitem('http_proxy', 'user',
674 coreconfigitem('http_proxy', 'user',
675 default=None,
675 default=None,
676 )
676 )
677 coreconfigitem('logtoprocess', 'commandexception',
677 coreconfigitem('logtoprocess', 'commandexception',
678 default=None,
678 default=None,
679 )
679 )
680 coreconfigitem('logtoprocess', 'commandfinish',
680 coreconfigitem('logtoprocess', 'commandfinish',
681 default=None,
681 default=None,
682 )
682 )
683 coreconfigitem('logtoprocess', 'command',
683 coreconfigitem('logtoprocess', 'command',
684 default=None,
684 default=None,
685 )
685 )
686 coreconfigitem('logtoprocess', 'develwarn',
686 coreconfigitem('logtoprocess', 'develwarn',
687 default=None,
687 default=None,
688 )
688 )
689 coreconfigitem('logtoprocess', 'uiblocked',
689 coreconfigitem('logtoprocess', 'uiblocked',
690 default=None,
690 default=None,
691 )
691 )
692 coreconfigitem('merge', 'checkunknown',
692 coreconfigitem('merge', 'checkunknown',
693 default='abort',
693 default='abort',
694 )
694 )
695 coreconfigitem('merge', 'checkignored',
695 coreconfigitem('merge', 'checkignored',
696 default='abort',
696 default='abort',
697 )
697 )
698 coreconfigitem('experimental', 'merge.checkpathconflicts',
698 coreconfigitem('experimental', 'merge.checkpathconflicts',
699 default=False,
699 default=False,
700 )
700 )
701 coreconfigitem('merge', 'followcopies',
701 coreconfigitem('merge', 'followcopies',
702 default=True,
702 default=True,
703 )
703 )
704 coreconfigitem('merge', 'on-failure',
704 coreconfigitem('merge', 'on-failure',
705 default='continue',
705 default='continue',
706 )
706 )
707 coreconfigitem('merge', 'preferancestor',
707 coreconfigitem('merge', 'preferancestor',
708 default=lambda: ['*'],
708 default=lambda: ['*'],
709 )
709 )
710 coreconfigitem('merge-tools', '.*',
710 coreconfigitem('merge-tools', '.*',
711 default=None,
711 default=None,
712 generic=True,
712 generic=True,
713 )
713 )
714 coreconfigitem('merge-tools', br'.*\.args$',
714 coreconfigitem('merge-tools', br'.*\.args$',
715 default="$local $base $other",
715 default="$local $base $other",
716 generic=True,
716 generic=True,
717 priority=-1,
717 priority=-1,
718 )
718 )
719 coreconfigitem('merge-tools', br'.*\.binary$',
719 coreconfigitem('merge-tools', br'.*\.binary$',
720 default=False,
720 default=False,
721 generic=True,
721 generic=True,
722 priority=-1,
722 priority=-1,
723 )
723 )
724 coreconfigitem('merge-tools', br'.*\.check$',
724 coreconfigitem('merge-tools', br'.*\.check$',
725 default=list,
725 default=list,
726 generic=True,
726 generic=True,
727 priority=-1,
727 priority=-1,
728 )
728 )
729 coreconfigitem('merge-tools', br'.*\.checkchanged$',
729 coreconfigitem('merge-tools', br'.*\.checkchanged$',
730 default=False,
730 default=False,
731 generic=True,
731 generic=True,
732 priority=-1,
732 priority=-1,
733 )
733 )
734 coreconfigitem('merge-tools', br'.*\.executable$',
734 coreconfigitem('merge-tools', br'.*\.executable$',
735 default=dynamicdefault,
735 default=dynamicdefault,
736 generic=True,
736 generic=True,
737 priority=-1,
737 priority=-1,
738 )
738 )
739 coreconfigitem('merge-tools', br'.*\.fixeol$',
739 coreconfigitem('merge-tools', br'.*\.fixeol$',
740 default=False,
740 default=False,
741 generic=True,
741 generic=True,
742 priority=-1,
742 priority=-1,
743 )
743 )
744 coreconfigitem('merge-tools', br'.*\.gui$',
744 coreconfigitem('merge-tools', br'.*\.gui$',
745 default=False,
745 default=False,
746 generic=True,
746 generic=True,
747 priority=-1,
747 priority=-1,
748 )
748 )
749 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
749 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
750 default='basic',
750 default='basic',
751 generic=True,
751 generic=True,
752 priority=-1,
752 priority=-1,
753 )
753 )
754 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
754 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
755 default=dynamicdefault, # take from ui.mergemarkertemplate
755 default=dynamicdefault, # take from ui.mergemarkertemplate
756 generic=True,
756 generic=True,
757 priority=-1,
757 priority=-1,
758 )
758 )
759 coreconfigitem('merge-tools', br'.*\.priority$',
759 coreconfigitem('merge-tools', br'.*\.priority$',
760 default=0,
760 default=0,
761 generic=True,
761 generic=True,
762 priority=-1,
762 priority=-1,
763 )
763 )
764 coreconfigitem('merge-tools', br'.*\.premerge$',
764 coreconfigitem('merge-tools', br'.*\.premerge$',
765 default=dynamicdefault,
765 default=dynamicdefault,
766 generic=True,
766 generic=True,
767 priority=-1,
767 priority=-1,
768 )
768 )
769 coreconfigitem('merge-tools', br'.*\.symlink$',
769 coreconfigitem('merge-tools', br'.*\.symlink$',
770 default=False,
770 default=False,
771 generic=True,
771 generic=True,
772 priority=-1,
772 priority=-1,
773 )
773 )
774 coreconfigitem('pager', 'attend-.*',
774 coreconfigitem('pager', 'attend-.*',
775 default=dynamicdefault,
775 default=dynamicdefault,
776 generic=True,
776 generic=True,
777 )
777 )
778 coreconfigitem('pager', 'ignore',
778 coreconfigitem('pager', 'ignore',
779 default=list,
779 default=list,
780 )
780 )
781 coreconfigitem('pager', 'pager',
781 coreconfigitem('pager', 'pager',
782 default=dynamicdefault,
782 default=dynamicdefault,
783 )
783 )
784 coreconfigitem('patch', 'eol',
784 coreconfigitem('patch', 'eol',
785 default='strict',
785 default='strict',
786 )
786 )
787 coreconfigitem('patch', 'fuzz',
787 coreconfigitem('patch', 'fuzz',
788 default=2,
788 default=2,
789 )
789 )
790 coreconfigitem('paths', 'default',
790 coreconfigitem('paths', 'default',
791 default=None,
791 default=None,
792 )
792 )
793 coreconfigitem('paths', 'default-push',
793 coreconfigitem('paths', 'default-push',
794 default=None,
794 default=None,
795 )
795 )
796 coreconfigitem('paths', '.*',
796 coreconfigitem('paths', '.*',
797 default=None,
797 default=None,
798 generic=True,
798 generic=True,
799 )
799 )
800 coreconfigitem('phases', 'checksubrepos',
800 coreconfigitem('phases', 'checksubrepos',
801 default='follow',
801 default='follow',
802 )
802 )
803 coreconfigitem('phases', 'new-commit',
803 coreconfigitem('phases', 'new-commit',
804 default='draft',
804 default='draft',
805 )
805 )
806 coreconfigitem('phases', 'publish',
806 coreconfigitem('phases', 'publish',
807 default=True,
807 default=True,
808 )
808 )
809 coreconfigitem('profiling', 'enabled',
809 coreconfigitem('profiling', 'enabled',
810 default=False,
810 default=False,
811 )
811 )
812 coreconfigitem('profiling', 'format',
812 coreconfigitem('profiling', 'format',
813 default='text',
813 default='text',
814 )
814 )
815 coreconfigitem('profiling', 'freq',
815 coreconfigitem('profiling', 'freq',
816 default=1000,
816 default=1000,
817 )
817 )
818 coreconfigitem('profiling', 'limit',
818 coreconfigitem('profiling', 'limit',
819 default=30,
819 default=30,
820 )
820 )
821 coreconfigitem('profiling', 'nested',
821 coreconfigitem('profiling', 'nested',
822 default=0,
822 default=0,
823 )
823 )
824 coreconfigitem('profiling', 'output',
824 coreconfigitem('profiling', 'output',
825 default=None,
825 default=None,
826 )
826 )
827 coreconfigitem('profiling', 'showmax',
827 coreconfigitem('profiling', 'showmax',
828 default=0.999,
828 default=0.999,
829 )
829 )
830 coreconfigitem('profiling', 'showmin',
830 coreconfigitem('profiling', 'showmin',
831 default=dynamicdefault,
831 default=dynamicdefault,
832 )
832 )
833 coreconfigitem('profiling', 'sort',
833 coreconfigitem('profiling', 'sort',
834 default='inlinetime',
834 default='inlinetime',
835 )
835 )
836 coreconfigitem('profiling', 'statformat',
836 coreconfigitem('profiling', 'statformat',
837 default='hotpath',
837 default='hotpath',
838 )
838 )
839 coreconfigitem('profiling', 'type',
839 coreconfigitem('profiling', 'type',
840 default='stat',
840 default='stat',
841 )
841 )
842 coreconfigitem('progress', 'assume-tty',
842 coreconfigitem('progress', 'assume-tty',
843 default=False,
843 default=False,
844 )
844 )
845 coreconfigitem('progress', 'changedelay',
845 coreconfigitem('progress', 'changedelay',
846 default=1,
846 default=1,
847 )
847 )
848 coreconfigitem('progress', 'clear-complete',
848 coreconfigitem('progress', 'clear-complete',
849 default=True,
849 default=True,
850 )
850 )
851 coreconfigitem('progress', 'debug',
851 coreconfigitem('progress', 'debug',
852 default=False,
852 default=False,
853 )
853 )
854 coreconfigitem('progress', 'delay',
854 coreconfigitem('progress', 'delay',
855 default=3,
855 default=3,
856 )
856 )
857 coreconfigitem('progress', 'disable',
857 coreconfigitem('progress', 'disable',
858 default=False,
858 default=False,
859 )
859 )
860 coreconfigitem('progress', 'estimateinterval',
860 coreconfigitem('progress', 'estimateinterval',
861 default=60.0,
861 default=60.0,
862 )
862 )
863 coreconfigitem('progress', 'format',
863 coreconfigitem('progress', 'format',
864 default=lambda: ['topic', 'bar', 'number', 'estimate'],
864 default=lambda: ['topic', 'bar', 'number', 'estimate'],
865 )
865 )
866 coreconfigitem('progress', 'refresh',
866 coreconfigitem('progress', 'refresh',
867 default=0.1,
867 default=0.1,
868 )
868 )
869 coreconfigitem('progress', 'width',
869 coreconfigitem('progress', 'width',
870 default=dynamicdefault,
870 default=dynamicdefault,
871 )
871 )
872 coreconfigitem('push', 'pushvars.server',
872 coreconfigitem('push', 'pushvars.server',
873 default=False,
873 default=False,
874 )
874 )
875 coreconfigitem('server', 'bookmarks-pushkey-compat',
875 coreconfigitem('server', 'bookmarks-pushkey-compat',
876 default=True,
876 default=True,
877 )
877 )
878 coreconfigitem('server', 'bundle1',
878 coreconfigitem('server', 'bundle1',
879 default=True,
879 default=True,
880 )
880 )
881 coreconfigitem('server', 'bundle1gd',
881 coreconfigitem('server', 'bundle1gd',
882 default=None,
882 default=None,
883 )
883 )
884 coreconfigitem('server', 'bundle1.pull',
884 coreconfigitem('server', 'bundle1.pull',
885 default=None,
885 default=None,
886 )
886 )
887 coreconfigitem('server', 'bundle1gd.pull',
887 coreconfigitem('server', 'bundle1gd.pull',
888 default=None,
888 default=None,
889 )
889 )
890 coreconfigitem('server', 'bundle1.push',
890 coreconfigitem('server', 'bundle1.push',
891 default=None,
891 default=None,
892 )
892 )
893 coreconfigitem('server', 'bundle1gd.push',
893 coreconfigitem('server', 'bundle1gd.push',
894 default=None,
894 default=None,
895 )
895 )
896 coreconfigitem('server', 'compressionengines',
896 coreconfigitem('server', 'compressionengines',
897 default=list,
897 default=list,
898 )
898 )
899 coreconfigitem('server', 'concurrent-push-mode',
899 coreconfigitem('server', 'concurrent-push-mode',
900 default='strict',
900 default='strict',
901 )
901 )
902 coreconfigitem('server', 'disablefullbundle',
902 coreconfigitem('server', 'disablefullbundle',
903 default=False,
903 default=False,
904 )
904 )
905 coreconfigitem('server', 'maxhttpheaderlen',
905 coreconfigitem('server', 'maxhttpheaderlen',
906 default=1024,
906 default=1024,
907 )
907 )
908 coreconfigitem('server', 'preferuncompressed',
908 coreconfigitem('server', 'preferuncompressed',
909 default=False,
909 default=False,
910 )
910 )
911 coreconfigitem('server', 'uncompressed',
911 coreconfigitem('server', 'uncompressed',
912 default=True,
912 default=True,
913 )
913 )
914 coreconfigitem('server', 'uncompressedallowsecret',
914 coreconfigitem('server', 'uncompressedallowsecret',
915 default=False,
915 default=False,
916 )
916 )
917 coreconfigitem('server', 'validate',
917 coreconfigitem('server', 'validate',
918 default=False,
918 default=False,
919 )
919 )
920 coreconfigitem('server', 'zliblevel',
920 coreconfigitem('server', 'zliblevel',
921 default=-1,
921 default=-1,
922 )
922 )
923 coreconfigitem('share', 'pool',
923 coreconfigitem('share', 'pool',
924 default=None,
924 default=None,
925 )
925 )
926 coreconfigitem('share', 'poolnaming',
926 coreconfigitem('share', 'poolnaming',
927 default='identity',
927 default='identity',
928 )
928 )
929 coreconfigitem('smtp', 'host',
929 coreconfigitem('smtp', 'host',
930 default=None,
930 default=None,
931 )
931 )
932 coreconfigitem('smtp', 'local_hostname',
932 coreconfigitem('smtp', 'local_hostname',
933 default=None,
933 default=None,
934 )
934 )
935 coreconfigitem('smtp', 'password',
935 coreconfigitem('smtp', 'password',
936 default=None,
936 default=None,
937 )
937 )
938 coreconfigitem('smtp', 'port',
938 coreconfigitem('smtp', 'port',
939 default=dynamicdefault,
939 default=dynamicdefault,
940 )
940 )
941 coreconfigitem('smtp', 'tls',
941 coreconfigitem('smtp', 'tls',
942 default='none',
942 default='none',
943 )
943 )
944 coreconfigitem('smtp', 'username',
944 coreconfigitem('smtp', 'username',
945 default=None,
945 default=None,
946 )
946 )
947 coreconfigitem('sparse', 'missingwarning',
947 coreconfigitem('sparse', 'missingwarning',
948 default=True,
948 default=True,
949 )
949 )
950 coreconfigitem('subrepos', 'allowed',
950 coreconfigitem('subrepos', 'allowed',
951 default=dynamicdefault, # to make backporting simpler
951 default=dynamicdefault, # to make backporting simpler
952 )
952 )
953 coreconfigitem('subrepos', 'hg:allowed',
953 coreconfigitem('subrepos', 'hg:allowed',
954 default=dynamicdefault,
954 default=dynamicdefault,
955 )
955 )
956 coreconfigitem('subrepos', 'git:allowed',
956 coreconfigitem('subrepos', 'git:allowed',
957 default=dynamicdefault,
957 default=dynamicdefault,
958 )
958 )
959 coreconfigitem('subrepos', 'svn:allowed',
959 coreconfigitem('subrepos', 'svn:allowed',
960 default=dynamicdefault,
960 default=dynamicdefault,
961 )
961 )
962 coreconfigitem('templates', '.*',
962 coreconfigitem('templates', '.*',
963 default=None,
963 default=None,
964 generic=True,
964 generic=True,
965 )
965 )
966 coreconfigitem('trusted', 'groups',
966 coreconfigitem('trusted', 'groups',
967 default=list,
967 default=list,
968 )
968 )
969 coreconfigitem('trusted', 'users',
969 coreconfigitem('trusted', 'users',
970 default=list,
970 default=list,
971 )
971 )
972 coreconfigitem('ui', '_usedassubrepo',
972 coreconfigitem('ui', '_usedassubrepo',
973 default=False,
973 default=False,
974 )
974 )
975 coreconfigitem('ui', 'allowemptycommit',
975 coreconfigitem('ui', 'allowemptycommit',
976 default=False,
976 default=False,
977 )
977 )
978 coreconfigitem('ui', 'archivemeta',
978 coreconfigitem('ui', 'archivemeta',
979 default=True,
979 default=True,
980 )
980 )
981 coreconfigitem('ui', 'askusername',
981 coreconfigitem('ui', 'askusername',
982 default=False,
982 default=False,
983 )
983 )
984 coreconfigitem('ui', 'clonebundlefallback',
984 coreconfigitem('ui', 'clonebundlefallback',
985 default=False,
985 default=False,
986 )
986 )
987 coreconfigitem('ui', 'clonebundleprefers',
987 coreconfigitem('ui', 'clonebundleprefers',
988 default=list,
988 default=list,
989 )
989 )
990 coreconfigitem('ui', 'clonebundles',
990 coreconfigitem('ui', 'clonebundles',
991 default=True,
991 default=True,
992 )
992 )
993 coreconfigitem('ui', 'color',
993 coreconfigitem('ui', 'color',
994 default='auto',
994 default='auto',
995 )
995 )
996 coreconfigitem('ui', 'commitsubrepos',
996 coreconfigitem('ui', 'commitsubrepos',
997 default=False,
997 default=False,
998 )
998 )
999 coreconfigitem('ui', 'debug',
999 coreconfigitem('ui', 'debug',
1000 default=False,
1000 default=False,
1001 )
1001 )
1002 coreconfigitem('ui', 'debugger',
1002 coreconfigitem('ui', 'debugger',
1003 default=None,
1003 default=None,
1004 )
1004 )
1005 coreconfigitem('ui', 'editor',
1005 coreconfigitem('ui', 'editor',
1006 default=dynamicdefault,
1006 default=dynamicdefault,
1007 )
1007 )
1008 coreconfigitem('ui', 'fallbackencoding',
1008 coreconfigitem('ui', 'fallbackencoding',
1009 default=None,
1009 default=None,
1010 )
1010 )
1011 coreconfigitem('ui', 'forcecwd',
1011 coreconfigitem('ui', 'forcecwd',
1012 default=None,
1012 default=None,
1013 )
1013 )
1014 coreconfigitem('ui', 'forcemerge',
1014 coreconfigitem('ui', 'forcemerge',
1015 default=None,
1015 default=None,
1016 )
1016 )
1017 coreconfigitem('ui', 'formatdebug',
1017 coreconfigitem('ui', 'formatdebug',
1018 default=False,
1018 default=False,
1019 )
1019 )
1020 coreconfigitem('ui', 'formatjson',
1020 coreconfigitem('ui', 'formatjson',
1021 default=False,
1021 default=False,
1022 )
1022 )
1023 coreconfigitem('ui', 'formatted',
1023 coreconfigitem('ui', 'formatted',
1024 default=None,
1024 default=None,
1025 )
1025 )
1026 coreconfigitem('ui', 'graphnodetemplate',
1026 coreconfigitem('ui', 'graphnodetemplate',
1027 default=None,
1027 default=None,
1028 )
1028 )
1029 coreconfigitem('ui', 'http2debuglevel',
1030 default=None,
1031 )
1032 coreconfigitem('ui', 'interactive',
1029 coreconfigitem('ui', 'interactive',
1033 default=None,
1030 default=None,
1034 )
1031 )
1035 coreconfigitem('ui', 'interface',
1032 coreconfigitem('ui', 'interface',
1036 default=None,
1033 default=None,
1037 )
1034 )
1038 coreconfigitem('ui', 'interface.chunkselector',
1035 coreconfigitem('ui', 'interface.chunkselector',
1039 default=None,
1036 default=None,
1040 )
1037 )
1041 coreconfigitem('ui', 'logblockedtimes',
1038 coreconfigitem('ui', 'logblockedtimes',
1042 default=False,
1039 default=False,
1043 )
1040 )
1044 coreconfigitem('ui', 'logtemplate',
1041 coreconfigitem('ui', 'logtemplate',
1045 default=None,
1042 default=None,
1046 )
1043 )
1047 coreconfigitem('ui', 'merge',
1044 coreconfigitem('ui', 'merge',
1048 default=None,
1045 default=None,
1049 )
1046 )
1050 coreconfigitem('ui', 'mergemarkers',
1047 coreconfigitem('ui', 'mergemarkers',
1051 default='basic',
1048 default='basic',
1052 )
1049 )
1053 coreconfigitem('ui', 'mergemarkertemplate',
1050 coreconfigitem('ui', 'mergemarkertemplate',
1054 default=('{node|short} '
1051 default=('{node|short} '
1055 '{ifeq(tags, "tip", "", '
1052 '{ifeq(tags, "tip", "", '
1056 'ifeq(tags, "", "", "{tags} "))}'
1053 'ifeq(tags, "", "", "{tags} "))}'
1057 '{if(bookmarks, "{bookmarks} ")}'
1054 '{if(bookmarks, "{bookmarks} ")}'
1058 '{ifeq(branch, "default", "", "{branch} ")}'
1055 '{ifeq(branch, "default", "", "{branch} ")}'
1059 '- {author|user}: {desc|firstline}')
1056 '- {author|user}: {desc|firstline}')
1060 )
1057 )
1061 coreconfigitem('ui', 'nontty',
1058 coreconfigitem('ui', 'nontty',
1062 default=False,
1059 default=False,
1063 )
1060 )
1064 coreconfigitem('ui', 'origbackuppath',
1061 coreconfigitem('ui', 'origbackuppath',
1065 default=None,
1062 default=None,
1066 )
1063 )
1067 coreconfigitem('ui', 'paginate',
1064 coreconfigitem('ui', 'paginate',
1068 default=True,
1065 default=True,
1069 )
1066 )
1070 coreconfigitem('ui', 'patch',
1067 coreconfigitem('ui', 'patch',
1071 default=None,
1068 default=None,
1072 )
1069 )
1073 coreconfigitem('ui', 'portablefilenames',
1070 coreconfigitem('ui', 'portablefilenames',
1074 default='warn',
1071 default='warn',
1075 )
1072 )
1076 coreconfigitem('ui', 'promptecho',
1073 coreconfigitem('ui', 'promptecho',
1077 default=False,
1074 default=False,
1078 )
1075 )
1079 coreconfigitem('ui', 'quiet',
1076 coreconfigitem('ui', 'quiet',
1080 default=False,
1077 default=False,
1081 )
1078 )
1082 coreconfigitem('ui', 'quietbookmarkmove',
1079 coreconfigitem('ui', 'quietbookmarkmove',
1083 default=False,
1080 default=False,
1084 )
1081 )
1085 coreconfigitem('ui', 'remotecmd',
1082 coreconfigitem('ui', 'remotecmd',
1086 default='hg',
1083 default='hg',
1087 )
1084 )
1088 coreconfigitem('ui', 'report_untrusted',
1085 coreconfigitem('ui', 'report_untrusted',
1089 default=True,
1086 default=True,
1090 )
1087 )
1091 coreconfigitem('ui', 'rollback',
1088 coreconfigitem('ui', 'rollback',
1092 default=True,
1089 default=True,
1093 )
1090 )
1094 coreconfigitem('ui', 'slash',
1091 coreconfigitem('ui', 'slash',
1095 default=False,
1092 default=False,
1096 )
1093 )
1097 coreconfigitem('ui', 'ssh',
1094 coreconfigitem('ui', 'ssh',
1098 default='ssh',
1095 default='ssh',
1099 )
1096 )
1100 coreconfigitem('ui', 'ssherrorhint',
1097 coreconfigitem('ui', 'ssherrorhint',
1101 default=None,
1098 default=None,
1102 )
1099 )
1103 coreconfigitem('ui', 'statuscopies',
1100 coreconfigitem('ui', 'statuscopies',
1104 default=False,
1101 default=False,
1105 )
1102 )
1106 coreconfigitem('ui', 'strict',
1103 coreconfigitem('ui', 'strict',
1107 default=False,
1104 default=False,
1108 )
1105 )
1109 coreconfigitem('ui', 'style',
1106 coreconfigitem('ui', 'style',
1110 default='',
1107 default='',
1111 )
1108 )
1112 coreconfigitem('ui', 'supportcontact',
1109 coreconfigitem('ui', 'supportcontact',
1113 default=None,
1110 default=None,
1114 )
1111 )
1115 coreconfigitem('ui', 'textwidth',
1112 coreconfigitem('ui', 'textwidth',
1116 default=78,
1113 default=78,
1117 )
1114 )
1118 coreconfigitem('ui', 'timeout',
1115 coreconfigitem('ui', 'timeout',
1119 default='600',
1116 default='600',
1120 )
1117 )
1121 coreconfigitem('ui', 'timeout.warn',
1118 coreconfigitem('ui', 'timeout.warn',
1122 default=0,
1119 default=0,
1123 )
1120 )
1124 coreconfigitem('ui', 'traceback',
1121 coreconfigitem('ui', 'traceback',
1125 default=False,
1122 default=False,
1126 )
1123 )
1127 coreconfigitem('ui', 'tweakdefaults',
1124 coreconfigitem('ui', 'tweakdefaults',
1128 default=False,
1125 default=False,
1129 )
1126 )
1130 coreconfigitem('ui', 'usehttp2',
1131 default=False,
1132 )
1133 coreconfigitem('ui', 'username',
1127 coreconfigitem('ui', 'username',
1134 alias=[('ui', 'user')]
1128 alias=[('ui', 'user')]
1135 )
1129 )
1136 coreconfigitem('ui', 'verbose',
1130 coreconfigitem('ui', 'verbose',
1137 default=False,
1131 default=False,
1138 )
1132 )
1139 coreconfigitem('verify', 'skipflags',
1133 coreconfigitem('verify', 'skipflags',
1140 default=None,
1134 default=None,
1141 )
1135 )
1142 coreconfigitem('web', 'allowbz2',
1136 coreconfigitem('web', 'allowbz2',
1143 default=False,
1137 default=False,
1144 )
1138 )
1145 coreconfigitem('web', 'allowgz',
1139 coreconfigitem('web', 'allowgz',
1146 default=False,
1140 default=False,
1147 )
1141 )
1148 coreconfigitem('web', 'allow-pull',
1142 coreconfigitem('web', 'allow-pull',
1149 alias=[('web', 'allowpull')],
1143 alias=[('web', 'allowpull')],
1150 default=True,
1144 default=True,
1151 )
1145 )
1152 coreconfigitem('web', 'allow-push',
1146 coreconfigitem('web', 'allow-push',
1153 alias=[('web', 'allow_push')],
1147 alias=[('web', 'allow_push')],
1154 default=list,
1148 default=list,
1155 )
1149 )
1156 coreconfigitem('web', 'allowzip',
1150 coreconfigitem('web', 'allowzip',
1157 default=False,
1151 default=False,
1158 )
1152 )
1159 coreconfigitem('web', 'archivesubrepos',
1153 coreconfigitem('web', 'archivesubrepos',
1160 default=False,
1154 default=False,
1161 )
1155 )
1162 coreconfigitem('web', 'cache',
1156 coreconfigitem('web', 'cache',
1163 default=True,
1157 default=True,
1164 )
1158 )
1165 coreconfigitem('web', 'contact',
1159 coreconfigitem('web', 'contact',
1166 default=None,
1160 default=None,
1167 )
1161 )
1168 coreconfigitem('web', 'deny_push',
1162 coreconfigitem('web', 'deny_push',
1169 default=list,
1163 default=list,
1170 )
1164 )
1171 coreconfigitem('web', 'guessmime',
1165 coreconfigitem('web', 'guessmime',
1172 default=False,
1166 default=False,
1173 )
1167 )
1174 coreconfigitem('web', 'hidden',
1168 coreconfigitem('web', 'hidden',
1175 default=False,
1169 default=False,
1176 )
1170 )
1177 coreconfigitem('web', 'labels',
1171 coreconfigitem('web', 'labels',
1178 default=list,
1172 default=list,
1179 )
1173 )
1180 coreconfigitem('web', 'logoimg',
1174 coreconfigitem('web', 'logoimg',
1181 default='hglogo.png',
1175 default='hglogo.png',
1182 )
1176 )
1183 coreconfigitem('web', 'logourl',
1177 coreconfigitem('web', 'logourl',
1184 default='https://mercurial-scm.org/',
1178 default='https://mercurial-scm.org/',
1185 )
1179 )
1186 coreconfigitem('web', 'accesslog',
1180 coreconfigitem('web', 'accesslog',
1187 default='-',
1181 default='-',
1188 )
1182 )
1189 coreconfigitem('web', 'address',
1183 coreconfigitem('web', 'address',
1190 default='',
1184 default='',
1191 )
1185 )
1192 coreconfigitem('web', 'allow_archive',
1186 coreconfigitem('web', 'allow_archive',
1193 default=list,
1187 default=list,
1194 )
1188 )
1195 coreconfigitem('web', 'allow_read',
1189 coreconfigitem('web', 'allow_read',
1196 default=list,
1190 default=list,
1197 )
1191 )
1198 coreconfigitem('web', 'baseurl',
1192 coreconfigitem('web', 'baseurl',
1199 default=None,
1193 default=None,
1200 )
1194 )
1201 coreconfigitem('web', 'cacerts',
1195 coreconfigitem('web', 'cacerts',
1202 default=None,
1196 default=None,
1203 )
1197 )
1204 coreconfigitem('web', 'certificate',
1198 coreconfigitem('web', 'certificate',
1205 default=None,
1199 default=None,
1206 )
1200 )
1207 coreconfigitem('web', 'collapse',
1201 coreconfigitem('web', 'collapse',
1208 default=False,
1202 default=False,
1209 )
1203 )
1210 coreconfigitem('web', 'csp',
1204 coreconfigitem('web', 'csp',
1211 default=None,
1205 default=None,
1212 )
1206 )
1213 coreconfigitem('web', 'deny_read',
1207 coreconfigitem('web', 'deny_read',
1214 default=list,
1208 default=list,
1215 )
1209 )
1216 coreconfigitem('web', 'descend',
1210 coreconfigitem('web', 'descend',
1217 default=True,
1211 default=True,
1218 )
1212 )
1219 coreconfigitem('web', 'description',
1213 coreconfigitem('web', 'description',
1220 default="",
1214 default="",
1221 )
1215 )
1222 coreconfigitem('web', 'encoding',
1216 coreconfigitem('web', 'encoding',
1223 default=lambda: encoding.encoding,
1217 default=lambda: encoding.encoding,
1224 )
1218 )
1225 coreconfigitem('web', 'errorlog',
1219 coreconfigitem('web', 'errorlog',
1226 default='-',
1220 default='-',
1227 )
1221 )
1228 coreconfigitem('web', 'ipv6',
1222 coreconfigitem('web', 'ipv6',
1229 default=False,
1223 default=False,
1230 )
1224 )
1231 coreconfigitem('web', 'maxchanges',
1225 coreconfigitem('web', 'maxchanges',
1232 default=10,
1226 default=10,
1233 )
1227 )
1234 coreconfigitem('web', 'maxfiles',
1228 coreconfigitem('web', 'maxfiles',
1235 default=10,
1229 default=10,
1236 )
1230 )
1237 coreconfigitem('web', 'maxshortchanges',
1231 coreconfigitem('web', 'maxshortchanges',
1238 default=60,
1232 default=60,
1239 )
1233 )
1240 coreconfigitem('web', 'motd',
1234 coreconfigitem('web', 'motd',
1241 default='',
1235 default='',
1242 )
1236 )
1243 coreconfigitem('web', 'name',
1237 coreconfigitem('web', 'name',
1244 default=dynamicdefault,
1238 default=dynamicdefault,
1245 )
1239 )
1246 coreconfigitem('web', 'port',
1240 coreconfigitem('web', 'port',
1247 default=8000,
1241 default=8000,
1248 )
1242 )
1249 coreconfigitem('web', 'prefix',
1243 coreconfigitem('web', 'prefix',
1250 default='',
1244 default='',
1251 )
1245 )
1252 coreconfigitem('web', 'push_ssl',
1246 coreconfigitem('web', 'push_ssl',
1253 default=True,
1247 default=True,
1254 )
1248 )
1255 coreconfigitem('web', 'refreshinterval',
1249 coreconfigitem('web', 'refreshinterval',
1256 default=20,
1250 default=20,
1257 )
1251 )
1258 coreconfigitem('web', 'staticurl',
1252 coreconfigitem('web', 'staticurl',
1259 default=None,
1253 default=None,
1260 )
1254 )
1261 coreconfigitem('web', 'stripes',
1255 coreconfigitem('web', 'stripes',
1262 default=1,
1256 default=1,
1263 )
1257 )
1264 coreconfigitem('web', 'style',
1258 coreconfigitem('web', 'style',
1265 default='paper',
1259 default='paper',
1266 )
1260 )
1267 coreconfigitem('web', 'templates',
1261 coreconfigitem('web', 'templates',
1268 default=None,
1262 default=None,
1269 )
1263 )
1270 coreconfigitem('web', 'view',
1264 coreconfigitem('web', 'view',
1271 default='served',
1265 default='served',
1272 )
1266 )
1273 coreconfigitem('worker', 'backgroundclose',
1267 coreconfigitem('worker', 'backgroundclose',
1274 default=dynamicdefault,
1268 default=dynamicdefault,
1275 )
1269 )
1276 # Windows defaults to a limit of 512 open files. A buffer of 128
1270 # Windows defaults to a limit of 512 open files. A buffer of 128
1277 # should give us enough headway.
1271 # should give us enough headway.
1278 coreconfigitem('worker', 'backgroundclosemaxqueue',
1272 coreconfigitem('worker', 'backgroundclosemaxqueue',
1279 default=384,
1273 default=384,
1280 )
1274 )
1281 coreconfigitem('worker', 'backgroundcloseminfilecount',
1275 coreconfigitem('worker', 'backgroundcloseminfilecount',
1282 default=2048,
1276 default=2048,
1283 )
1277 )
1284 coreconfigitem('worker', 'backgroundclosethreadcount',
1278 coreconfigitem('worker', 'backgroundclosethreadcount',
1285 default=4,
1279 default=4,
1286 )
1280 )
1287 coreconfigitem('worker', 'enabled',
1281 coreconfigitem('worker', 'enabled',
1288 default=True,
1282 default=True,
1289 )
1283 )
1290 coreconfigitem('worker', 'numcpus',
1284 coreconfigitem('worker', 'numcpus',
1291 default=None,
1285 default=None,
1292 )
1286 )
1293
1287
1294 # Rebase related configuration moved to core because other extension are doing
1288 # Rebase related configuration moved to core because other extension are doing
1295 # strange things. For example, shelve import the extensions to reuse some bit
1289 # strange things. For example, shelve import the extensions to reuse some bit
1296 # without formally loading it.
1290 # without formally loading it.
1297 coreconfigitem('commands', 'rebase.requiredest',
1291 coreconfigitem('commands', 'rebase.requiredest',
1298 default=False,
1292 default=False,
1299 )
1293 )
1300 coreconfigitem('experimental', 'rebaseskipobsolete',
1294 coreconfigitem('experimental', 'rebaseskipobsolete',
1301 default=True,
1295 default=True,
1302 )
1296 )
1303 coreconfigitem('rebase', 'singletransaction',
1297 coreconfigitem('rebase', 'singletransaction',
1304 default=False,
1298 default=False,
1305 )
1299 )
1306 coreconfigitem('rebase', 'experimental.inmemory',
1300 coreconfigitem('rebase', 'experimental.inmemory',
1307 default=False,
1301 default=False,
1308 )
1302 )
@@ -1,299 +1,107 b''
1 # httpconnection.py - urllib2 handler for new http support
1 # httpconnection.py - urllib2 handler for new http support
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 # Copyright 2011 Google, Inc.
6 # Copyright 2011 Google, Inc.
7 #
7 #
8 # This software may be used and distributed according to the terms of the
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2 or any later version.
9 # GNU General Public License version 2 or any later version.
10
10
11 from __future__ import absolute_import
11 from __future__ import absolute_import
12
12
13 import logging
14 import os
13 import os
15 import socket
16
14
17 from .i18n import _
15 from .i18n import _
18 from . import (
16 from . import (
19 httpclient,
20 sslutil,
21 urllibcompat,
22 util,
17 util,
23 )
18 )
24
19
25 urlerr = util.urlerr
20 urlerr = util.urlerr
26 urlreq = util.urlreq
21 urlreq = util.urlreq
27
22
28 # moved here from url.py to avoid a cycle
23 # moved here from url.py to avoid a cycle
29 class httpsendfile(object):
24 class httpsendfile(object):
30 """This is a wrapper around the objects returned by python's "open".
25 """This is a wrapper around the objects returned by python's "open".
31
26
32 Its purpose is to send file-like objects via HTTP.
27 Its purpose is to send file-like objects via HTTP.
33 It do however not define a __len__ attribute because the length
28 It do however not define a __len__ attribute because the length
34 might be more than Py_ssize_t can handle.
29 might be more than Py_ssize_t can handle.
35 """
30 """
36
31
37 def __init__(self, ui, *args, **kwargs):
32 def __init__(self, ui, *args, **kwargs):
38 self.ui = ui
33 self.ui = ui
39 self._data = open(*args, **kwargs)
34 self._data = open(*args, **kwargs)
40 self.seek = self._data.seek
35 self.seek = self._data.seek
41 self.close = self._data.close
36 self.close = self._data.close
42 self.write = self._data.write
37 self.write = self._data.write
43 self.length = os.fstat(self._data.fileno()).st_size
38 self.length = os.fstat(self._data.fileno()).st_size
44 self._pos = 0
39 self._pos = 0
45 self._total = self.length // 1024 * 2
40 self._total = self.length // 1024 * 2
46
41
47 def read(self, *args, **kwargs):
42 def read(self, *args, **kwargs):
48 ret = self._data.read(*args, **kwargs)
43 ret = self._data.read(*args, **kwargs)
49 if not ret:
44 if not ret:
50 self.ui.progress(_('sending'), None)
45 self.ui.progress(_('sending'), None)
51 return ret
46 return ret
52 self._pos += len(ret)
47 self._pos += len(ret)
53 # We pass double the max for total because we currently have
48 # We pass double the max for total because we currently have
54 # to send the bundle twice in the case of a server that
49 # to send the bundle twice in the case of a server that
55 # requires authentication. Since we can't know until we try
50 # requires authentication. Since we can't know until we try
56 # once whether authentication will be required, just lie to
51 # once whether authentication will be required, just lie to
57 # the user and maybe the push succeeds suddenly at 50%.
52 # the user and maybe the push succeeds suddenly at 50%.
58 self.ui.progress(_('sending'), self._pos // 1024,
53 self.ui.progress(_('sending'), self._pos // 1024,
59 unit=_('kb'), total=self._total)
54 unit=_('kb'), total=self._total)
60 return ret
55 return ret
61
56
62 def __enter__(self):
57 def __enter__(self):
63 return self
58 return self
64
59
65 def __exit__(self, exc_type, exc_val, exc_tb):
60 def __exit__(self, exc_type, exc_val, exc_tb):
66 self.close()
61 self.close()
67
62
68 # moved here from url.py to avoid a cycle
63 # moved here from url.py to avoid a cycle
69 def readauthforuri(ui, uri, user):
64 def readauthforuri(ui, uri, user):
70 # Read configuration
65 # Read configuration
71 groups = {}
66 groups = {}
72 for key, val in ui.configitems('auth'):
67 for key, val in ui.configitems('auth'):
73 if key in ('cookiefile',):
68 if key in ('cookiefile',):
74 continue
69 continue
75
70
76 if '.' not in key:
71 if '.' not in key:
77 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
72 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
78 continue
73 continue
79 group, setting = key.rsplit('.', 1)
74 group, setting = key.rsplit('.', 1)
80 gdict = groups.setdefault(group, {})
75 gdict = groups.setdefault(group, {})
81 if setting in ('username', 'cert', 'key'):
76 if setting in ('username', 'cert', 'key'):
82 val = util.expandpath(val)
77 val = util.expandpath(val)
83 gdict[setting] = val
78 gdict[setting] = val
84
79
85 # Find the best match
80 # Find the best match
86 scheme, hostpath = uri.split('://', 1)
81 scheme, hostpath = uri.split('://', 1)
87 bestuser = None
82 bestuser = None
88 bestlen = 0
83 bestlen = 0
89 bestauth = None
84 bestauth = None
90 for group, auth in groups.iteritems():
85 for group, auth in groups.iteritems():
91 if user and user != auth.get('username', user):
86 if user and user != auth.get('username', user):
92 # If a username was set in the URI, the entry username
87 # If a username was set in the URI, the entry username
93 # must either match it or be unset
88 # must either match it or be unset
94 continue
89 continue
95 prefix = auth.get('prefix')
90 prefix = auth.get('prefix')
96 if not prefix:
91 if not prefix:
97 continue
92 continue
98 p = prefix.split('://', 1)
93 p = prefix.split('://', 1)
99 if len(p) > 1:
94 if len(p) > 1:
100 schemes, prefix = [p[0]], p[1]
95 schemes, prefix = [p[0]], p[1]
101 else:
96 else:
102 schemes = (auth.get('schemes') or 'https').split()
97 schemes = (auth.get('schemes') or 'https').split()
103 if (prefix == '*' or hostpath.startswith(prefix)) and \
98 if (prefix == '*' or hostpath.startswith(prefix)) and \
104 (len(prefix) > bestlen or (len(prefix) == bestlen and \
99 (len(prefix) > bestlen or (len(prefix) == bestlen and \
105 not bestuser and 'username' in auth)) \
100 not bestuser and 'username' in auth)) \
106 and scheme in schemes:
101 and scheme in schemes:
107 bestlen = len(prefix)
102 bestlen = len(prefix)
108 bestauth = group, auth
103 bestauth = group, auth
109 bestuser = auth.get('username')
104 bestuser = auth.get('username')
110 if user and not bestuser:
105 if user and not bestuser:
111 auth['username'] = user
106 auth['username'] = user
112 return bestauth
107 return bestauth
113
114 # Mercurial (at least until we can remove the old codepath) requires
115 # that the http response object be sufficiently file-like, so we
116 # provide a close() method here.
117 class HTTPResponse(httpclient.HTTPResponse):
118 def close(self):
119 pass
120
121 class HTTPConnection(httpclient.HTTPConnection):
122 response_class = HTTPResponse
123 def request(self, method, uri, body=None, headers=None):
124 if headers is None:
125 headers = {}
126 if isinstance(body, httpsendfile):
127 body.seek(0)
128 httpclient.HTTPConnection.request(self, method, uri, body=body,
129 headers=headers)
130
131
132 _configuredlogging = False
133 LOGFMT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'
134 # Subclass BOTH of these because otherwise urllib2 "helpfully"
135 # reinserts them since it notices we don't include any subclasses of
136 # them.
137 class http2handler(urlreq.httphandler, urlreq.httpshandler):
138 def __init__(self, ui, pwmgr):
139 global _configuredlogging
140 urlreq.abstracthttphandler.__init__(self)
141 self.ui = ui
142 self.pwmgr = pwmgr
143 self._connections = {}
144 # developer config: ui.http2debuglevel
145 loglevel = ui.config('ui', 'http2debuglevel')
146 if loglevel and not _configuredlogging:
147 _configuredlogging = True
148 logger = logging.getLogger('mercurial.httpclient')
149 logger.setLevel(getattr(logging, loglevel.upper()))
150 handler = logging.StreamHandler()
151 handler.setFormatter(logging.Formatter(LOGFMT))
152 logger.addHandler(handler)
153
154 def close_all(self):
155 """Close and remove all connection objects being kept for reuse."""
156 for openconns in self._connections.values():
157 for conn in openconns:
158 conn.close()
159 self._connections = {}
160
161 # shamelessly borrowed from urllib2.AbstractHTTPHandler
162 def do_open(self, http_class, req, use_ssl):
163 """Return an addinfourl object for the request, using http_class.
164
165 http_class must implement the HTTPConnection API from httplib.
166 The addinfourl return value is a file-like object. It also
167 has methods and attributes including:
168 - info(): return a mimetools.Message object for the headers
169 - geturl(): return the original request URL
170 - code: HTTP status code
171 """
172 # If using a proxy, the host returned by get_host() is
173 # actually the proxy. On Python 2.6.1, the real destination
174 # hostname is encoded in the URI in the urllib2 request
175 # object. On Python 2.6.5, it's stored in the _tunnel_host
176 # attribute which has no accessor.
177 tunhost = getattr(req, '_tunnel_host', None)
178 host = urllibcompat.gethost(req)
179 if tunhost:
180 proxyhost = host
181 host = tunhost
182 elif req.has_proxy():
183 proxyhost = urllibcompat.gethost(req)
184 host = urllibcompat.getselector(
185 req).split('://', 1)[1].split('/', 1)[0]
186 else:
187 proxyhost = None
188
189 if proxyhost:
190 if ':' in proxyhost:
191 # Note: this means we'll explode if we try and use an
192 # IPv6 http proxy. This isn't a regression, so we
193 # won't worry about it for now.
194 proxyhost, proxyport = proxyhost.rsplit(':', 1)
195 else:
196 proxyport = 3128 # squid default
197 proxy = (proxyhost, proxyport)
198 else:
199 proxy = None
200
201 if not host:
202 raise urlerr.urlerror('no host given')
203
204 connkey = use_ssl, host, proxy
205 allconns = self._connections.get(connkey, [])
206 conns = [c for c in allconns if not c.busy()]
207 if conns:
208 h = conns[0]
209 else:
210 if allconns:
211 self.ui.debug('all connections for %s busy, making a new '
212 'one\n' % host)
213 timeout = None
214 if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
215 timeout = req.timeout
216 h = http_class(host, timeout=timeout, proxy_hostport=proxy)
217 self._connections.setdefault(connkey, []).append(h)
218
219 headers = dict(req.headers)
220 headers.update(req.unredirected_hdrs)
221 headers = dict(
222 (name.title(), val) for name, val in headers.items())
223 try:
224 path = urllibcompat.getselector(req)
225 if '://' in path:
226 path = path.split('://', 1)[1].split('/', 1)[1]
227 if path[0] != '/':
228 path = '/' + path
229 h.request(req.get_method(), path, req.data, headers)
230 r = h.getresponse()
231 except socket.error as err: # XXX what error?
232 raise urlerr.urlerror(err)
233
234 # Pick apart the HTTPResponse object to get the addinfourl
235 # object initialized properly.
236 r.recv = r.read
237
238 resp = urlreq.addinfourl(r, r.headers, urllibcompat.getfullurl(req))
239 resp.code = r.status
240 resp.msg = r.reason
241 return resp
242
243 # httplib always uses the given host/port as the socket connect
244 # target, and then allows full URIs in the request path, which it
245 # then observes and treats as a signal to do proxying instead.
246 def http_open(self, req):
247 if urllibcompat.getfullurl(req).startswith('https'):
248 return self.https_open(req)
249 def makehttpcon(*args, **kwargs):
250 k2 = dict(kwargs)
251 k2[r'use_ssl'] = False
252 return HTTPConnection(*args, **k2)
253 return self.do_open(makehttpcon, req, False)
254
255 def https_open(self, req):
256 # urllibcompat.getfullurl(req) does not contain credentials and we may
257 # need them to match the certificates.
258 url = urllibcompat.getfullurl(req)
259 user, password = self.pwmgr.find_stored_password(url)
260 res = readauthforuri(self.ui, url, user)
261 if res:
262 group, auth = res
263 self.auth = auth
264 self.ui.debug("using auth.%s.* for authentication\n" % group)
265 else:
266 self.auth = None
267 return self.do_open(self._makesslconnection, req, True)
268
269 def _makesslconnection(self, host, port=443, *args, **kwargs):
270 keyfile = None
271 certfile = None
272
273 if args: # key_file
274 keyfile = args.pop(0)
275 if args: # cert_file
276 certfile = args.pop(0)
277
278 # if the user has specified different key/cert files in
279 # hgrc, we prefer these
280 if self.auth and 'key' in self.auth and 'cert' in self.auth:
281 keyfile = self.auth['key']
282 certfile = self.auth['cert']
283
284 # let host port take precedence
285 if ':' in host and '[' not in host or ']:' in host:
286 host, port = host.rsplit(':', 1)
287 port = int(port)
288 if '[' in host:
289 host = host[1:-1]
290
291 kwargs[r'keyfile'] = keyfile
292 kwargs[r'certfile'] = certfile
293
294 con = HTTPConnection(host, port, use_ssl=True,
295 ssl_wrap_socket=sslutil.wrapsocket,
296 ssl_validator=sslutil.validatesocket,
297 ui=self.ui,
298 **kwargs)
299 return con
@@ -1,501 +1,498 b''
1 # httppeer.py - HTTP repository proxy classes for mercurial
1 # httppeer.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import io
12 import io
13 import os
13 import os
14 import socket
14 import socket
15 import struct
15 import struct
16 import tempfile
16 import tempfile
17
17
18 from .i18n import _
18 from .i18n import _
19 from . import (
19 from . import (
20 bundle2,
20 bundle2,
21 error,
21 error,
22 httpconnection,
22 httpconnection,
23 pycompat,
23 pycompat,
24 statichttprepo,
24 statichttprepo,
25 url,
25 url,
26 util,
26 util,
27 wireproto,
27 wireproto,
28 )
28 )
29
29
30 httplib = util.httplib
30 httplib = util.httplib
31 urlerr = util.urlerr
31 urlerr = util.urlerr
32 urlreq = util.urlreq
32 urlreq = util.urlreq
33
33
34 def encodevalueinheaders(value, header, limit):
34 def encodevalueinheaders(value, header, limit):
35 """Encode a string value into multiple HTTP headers.
35 """Encode a string value into multiple HTTP headers.
36
36
37 ``value`` will be encoded into 1 or more HTTP headers with the names
37 ``value`` will be encoded into 1 or more HTTP headers with the names
38 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
38 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
39 name + value will be at most ``limit`` bytes long.
39 name + value will be at most ``limit`` bytes long.
40
40
41 Returns an iterable of 2-tuples consisting of header names and
41 Returns an iterable of 2-tuples consisting of header names and
42 values as native strings.
42 values as native strings.
43 """
43 """
44 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
44 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
45 # not bytes. This function always takes bytes in as arguments.
45 # not bytes. This function always takes bytes in as arguments.
46 fmt = pycompat.strurl(header) + r'-%s'
46 fmt = pycompat.strurl(header) + r'-%s'
47 # Note: it is *NOT* a bug that the last bit here is a bytestring
47 # Note: it is *NOT* a bug that the last bit here is a bytestring
48 # and not a unicode: we're just getting the encoded length anyway,
48 # and not a unicode: we're just getting the encoded length anyway,
49 # and using an r-string to make it portable between Python 2 and 3
49 # and using an r-string to make it portable between Python 2 and 3
50 # doesn't work because then the \r is a literal backslash-r
50 # doesn't work because then the \r is a literal backslash-r
51 # instead of a carriage return.
51 # instead of a carriage return.
52 valuelen = limit - len(fmt % r'000') - len(': \r\n')
52 valuelen = limit - len(fmt % r'000') - len(': \r\n')
53 result = []
53 result = []
54
54
55 n = 0
55 n = 0
56 for i in xrange(0, len(value), valuelen):
56 for i in xrange(0, len(value), valuelen):
57 n += 1
57 n += 1
58 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
58 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
59
59
60 return result
60 return result
61
61
62 def _wraphttpresponse(resp):
62 def _wraphttpresponse(resp):
63 """Wrap an HTTPResponse with common error handlers.
63 """Wrap an HTTPResponse with common error handlers.
64
64
65 This ensures that any I/O from any consumer raises the appropriate
65 This ensures that any I/O from any consumer raises the appropriate
66 error and messaging.
66 error and messaging.
67 """
67 """
68 origread = resp.read
68 origread = resp.read
69
69
70 class readerproxy(resp.__class__):
70 class readerproxy(resp.__class__):
71 def read(self, size=None):
71 def read(self, size=None):
72 try:
72 try:
73 return origread(size)
73 return origread(size)
74 except httplib.IncompleteRead as e:
74 except httplib.IncompleteRead as e:
75 # e.expected is an integer if length known or None otherwise.
75 # e.expected is an integer if length known or None otherwise.
76 if e.expected:
76 if e.expected:
77 msg = _('HTTP request error (incomplete response; '
77 msg = _('HTTP request error (incomplete response; '
78 'expected %d bytes got %d)') % (e.expected,
78 'expected %d bytes got %d)') % (e.expected,
79 len(e.partial))
79 len(e.partial))
80 else:
80 else:
81 msg = _('HTTP request error (incomplete response)')
81 msg = _('HTTP request error (incomplete response)')
82
82
83 raise error.PeerTransportError(
83 raise error.PeerTransportError(
84 msg,
84 msg,
85 hint=_('this may be an intermittent network failure; '
85 hint=_('this may be an intermittent network failure; '
86 'if the error persists, consider contacting the '
86 'if the error persists, consider contacting the '
87 'network or server operator'))
87 'network or server operator'))
88 except httplib.HTTPException as e:
88 except httplib.HTTPException as e:
89 raise error.PeerTransportError(
89 raise error.PeerTransportError(
90 _('HTTP request error (%s)') % e,
90 _('HTTP request error (%s)') % e,
91 hint=_('this may be an intermittent network failure; '
91 hint=_('this may be an intermittent network failure; '
92 'if the error persists, consider contacting the '
92 'if the error persists, consider contacting the '
93 'network or server operator'))
93 'network or server operator'))
94
94
95 resp.__class__ = readerproxy
95 resp.__class__ = readerproxy
96
96
97 class _multifile(object):
97 class _multifile(object):
98 def __init__(self, *fileobjs):
98 def __init__(self, *fileobjs):
99 for f in fileobjs:
99 for f in fileobjs:
100 if not util.safehasattr(f, 'length'):
100 if not util.safehasattr(f, 'length'):
101 raise ValueError(
101 raise ValueError(
102 '_multifile only supports file objects that '
102 '_multifile only supports file objects that '
103 'have a length but this one does not:', type(f), f)
103 'have a length but this one does not:', type(f), f)
104 self._fileobjs = fileobjs
104 self._fileobjs = fileobjs
105 self._index = 0
105 self._index = 0
106
106
107 @property
107 @property
108 def length(self):
108 def length(self):
109 return sum(f.length for f in self._fileobjs)
109 return sum(f.length for f in self._fileobjs)
110
110
111 def read(self, amt=None):
111 def read(self, amt=None):
112 if amt <= 0:
112 if amt <= 0:
113 return ''.join(f.read() for f in self._fileobjs)
113 return ''.join(f.read() for f in self._fileobjs)
114 parts = []
114 parts = []
115 while amt and self._index < len(self._fileobjs):
115 while amt and self._index < len(self._fileobjs):
116 parts.append(self._fileobjs[self._index].read(amt))
116 parts.append(self._fileobjs[self._index].read(amt))
117 got = len(parts[-1])
117 got = len(parts[-1])
118 if got < amt:
118 if got < amt:
119 self._index += 1
119 self._index += 1
120 amt -= got
120 amt -= got
121 return ''.join(parts)
121 return ''.join(parts)
122
122
123 def seek(self, offset, whence=os.SEEK_SET):
123 def seek(self, offset, whence=os.SEEK_SET):
124 if whence != os.SEEK_SET:
124 if whence != os.SEEK_SET:
125 raise NotImplementedError(
125 raise NotImplementedError(
126 '_multifile does not support anything other'
126 '_multifile does not support anything other'
127 ' than os.SEEK_SET for whence on seek()')
127 ' than os.SEEK_SET for whence on seek()')
128 if offset != 0:
128 if offset != 0:
129 raise NotImplementedError(
129 raise NotImplementedError(
130 '_multifile only supports seeking to start, but that '
130 '_multifile only supports seeking to start, but that '
131 'could be fixed if you need it')
131 'could be fixed if you need it')
132 for f in self._fileobjs:
132 for f in self._fileobjs:
133 f.seek(0)
133 f.seek(0)
134 self._index = 0
134 self._index = 0
135
135
136 class httppeer(wireproto.wirepeer):
136 class httppeer(wireproto.wirepeer):
137 def __init__(self, ui, path):
137 def __init__(self, ui, path):
138 self._path = path
138 self._path = path
139 self._caps = None
139 self._caps = None
140 self._urlopener = None
140 self._urlopener = None
141 self._requestbuilder = None
141 self._requestbuilder = None
142 u = util.url(path)
142 u = util.url(path)
143 if u.query or u.fragment:
143 if u.query or u.fragment:
144 raise error.Abort(_('unsupported URL component: "%s"') %
144 raise error.Abort(_('unsupported URL component: "%s"') %
145 (u.query or u.fragment))
145 (u.query or u.fragment))
146
146
147 # urllib cannot handle URLs with embedded user or passwd
147 # urllib cannot handle URLs with embedded user or passwd
148 self._url, authinfo = u.authinfo()
148 self._url, authinfo = u.authinfo()
149
149
150 self._ui = ui
150 self._ui = ui
151 ui.debug('using %s\n' % self._url)
151 ui.debug('using %s\n' % self._url)
152
152
153 self._urlopener = url.opener(ui, authinfo)
153 self._urlopener = url.opener(ui, authinfo)
154 self._requestbuilder = urlreq.request
154 self._requestbuilder = urlreq.request
155
155
156 def __del__(self):
156 def __del__(self):
157 urlopener = getattr(self, '_urlopener', None)
157 urlopener = getattr(self, '_urlopener', None)
158 if urlopener:
158 if urlopener:
159 for h in urlopener.handlers:
159 for h in urlopener.handlers:
160 h.close()
160 h.close()
161 getattr(h, "close_all", lambda: None)()
161 getattr(h, "close_all", lambda: None)()
162
162
163 def _openurl(self, req):
163 def _openurl(self, req):
164 if (self._ui.debugflag
164 if (self._ui.debugflag
165 and self._ui.configbool('devel', 'debug.peer-request')):
165 and self._ui.configbool('devel', 'debug.peer-request')):
166 dbg = self._ui.debug
166 dbg = self._ui.debug
167 line = 'devel-peer-request: %s\n'
167 line = 'devel-peer-request: %s\n'
168 dbg(line % '%s %s' % (req.get_method(), req.get_full_url()))
168 dbg(line % '%s %s' % (req.get_method(), req.get_full_url()))
169 hgargssize = None
169 hgargssize = None
170
170
171 for header, value in sorted(req.header_items()):
171 for header, value in sorted(req.header_items()):
172 if header.startswith('X-hgarg-'):
172 if header.startswith('X-hgarg-'):
173 if hgargssize is None:
173 if hgargssize is None:
174 hgargssize = 0
174 hgargssize = 0
175 hgargssize += len(value)
175 hgargssize += len(value)
176 else:
176 else:
177 dbg(line % ' %s %s' % (header, value))
177 dbg(line % ' %s %s' % (header, value))
178
178
179 if hgargssize is not None:
179 if hgargssize is not None:
180 dbg(line % ' %d bytes of commands arguments in headers'
180 dbg(line % ' %d bytes of commands arguments in headers'
181 % hgargssize)
181 % hgargssize)
182
182
183 if req.has_data():
183 if req.has_data():
184 data = req.get_data()
184 data = req.get_data()
185 length = getattr(data, 'length', None)
185 length = getattr(data, 'length', None)
186 if length is None:
186 if length is None:
187 length = len(data)
187 length = len(data)
188 dbg(line % ' %d bytes of data' % length)
188 dbg(line % ' %d bytes of data' % length)
189
189
190 start = util.timer()
190 start = util.timer()
191
191
192 ret = self._urlopener.open(req)
192 ret = self._urlopener.open(req)
193 if self._ui.configbool('devel', 'debug.peer-request'):
193 if self._ui.configbool('devel', 'debug.peer-request'):
194 dbg(line % ' finished in %.4f seconds (%s)'
194 dbg(line % ' finished in %.4f seconds (%s)'
195 % (util.timer() - start, ret.code))
195 % (util.timer() - start, ret.code))
196 return ret
196 return ret
197
197
198 # Begin of _basepeer interface.
198 # Begin of _basepeer interface.
199
199
200 @util.propertycache
200 @util.propertycache
201 def ui(self):
201 def ui(self):
202 return self._ui
202 return self._ui
203
203
204 def url(self):
204 def url(self):
205 return self._path
205 return self._path
206
206
207 def local(self):
207 def local(self):
208 return None
208 return None
209
209
210 def peer(self):
210 def peer(self):
211 return self
211 return self
212
212
213 def canpush(self):
213 def canpush(self):
214 return True
214 return True
215
215
216 def close(self):
216 def close(self):
217 pass
217 pass
218
218
219 # End of _basepeer interface.
219 # End of _basepeer interface.
220
220
221 # Begin of _basewirepeer interface.
221 # Begin of _basewirepeer interface.
222
222
223 def capabilities(self):
223 def capabilities(self):
224 # self._fetchcaps() should have been called as part of peer
224 # self._fetchcaps() should have been called as part of peer
225 # handshake. So self._caps should always be set.
225 # handshake. So self._caps should always be set.
226 assert self._caps is not None
226 assert self._caps is not None
227 return self._caps
227 return self._caps
228
228
229 # End of _basewirepeer interface.
229 # End of _basewirepeer interface.
230
230
231 # look up capabilities only when needed
231 # look up capabilities only when needed
232
232
233 def _fetchcaps(self):
233 def _fetchcaps(self):
234 self._caps = set(self._call('capabilities').split())
234 self._caps = set(self._call('capabilities').split())
235
235
236 def _callstream(self, cmd, _compressible=False, **args):
236 def _callstream(self, cmd, _compressible=False, **args):
237 args = pycompat.byteskwargs(args)
237 args = pycompat.byteskwargs(args)
238 if cmd == 'pushkey':
238 if cmd == 'pushkey':
239 args['data'] = ''
239 args['data'] = ''
240 data = args.pop('data', None)
240 data = args.pop('data', None)
241 headers = args.pop('headers', {})
241 headers = args.pop('headers', {})
242
242
243 self.ui.debug("sending %s command\n" % cmd)
243 self.ui.debug("sending %s command\n" % cmd)
244 q = [('cmd', cmd)]
244 q = [('cmd', cmd)]
245 headersize = 0
245 headersize = 0
246 varyheaders = []
246 varyheaders = []
247 # Important: don't use self.capable() here or else you end up
247 # Important: don't use self.capable() here or else you end up
248 # with infinite recursion when trying to look up capabilities
248 # with infinite recursion when trying to look up capabilities
249 # for the first time.
249 # for the first time.
250 postargsok = self._caps is not None and 'httppostargs' in self._caps
250 postargsok = self._caps is not None and 'httppostargs' in self._caps
251
251
252 # Send arguments via POST.
252 # Send arguments via POST.
253 if postargsok and args:
253 if postargsok and args:
254 strargs = urlreq.urlencode(sorted(args.items()))
254 strargs = urlreq.urlencode(sorted(args.items()))
255 if not data:
255 if not data:
256 data = strargs
256 data = strargs
257 else:
257 else:
258 if isinstance(data, bytes):
258 if isinstance(data, bytes):
259 i = io.BytesIO(data)
259 i = io.BytesIO(data)
260 i.length = len(data)
260 i.length = len(data)
261 data = i
261 data = i
262 argsio = io.BytesIO(strargs)
262 argsio = io.BytesIO(strargs)
263 argsio.length = len(strargs)
263 argsio.length = len(strargs)
264 data = _multifile(argsio, data)
264 data = _multifile(argsio, data)
265 headers[r'X-HgArgs-Post'] = len(strargs)
265 headers[r'X-HgArgs-Post'] = len(strargs)
266 elif args:
266 elif args:
267 # Calling self.capable() can infinite loop if we are calling
267 # Calling self.capable() can infinite loop if we are calling
268 # "capabilities". But that command should never accept wire
268 # "capabilities". But that command should never accept wire
269 # protocol arguments. So this should never happen.
269 # protocol arguments. So this should never happen.
270 assert cmd != 'capabilities'
270 assert cmd != 'capabilities'
271 httpheader = self.capable('httpheader')
271 httpheader = self.capable('httpheader')
272 if httpheader:
272 if httpheader:
273 headersize = int(httpheader.split(',', 1)[0])
273 headersize = int(httpheader.split(',', 1)[0])
274
274
275 # Send arguments via HTTP headers.
275 # Send arguments via HTTP headers.
276 if headersize > 0:
276 if headersize > 0:
277 # The headers can typically carry more data than the URL.
277 # The headers can typically carry more data than the URL.
278 encargs = urlreq.urlencode(sorted(args.items()))
278 encargs = urlreq.urlencode(sorted(args.items()))
279 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
279 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
280 headersize):
280 headersize):
281 headers[header] = value
281 headers[header] = value
282 varyheaders.append(header)
282 varyheaders.append(header)
283 # Send arguments via query string (Mercurial <1.9).
283 # Send arguments via query string (Mercurial <1.9).
284 else:
284 else:
285 q += sorted(args.items())
285 q += sorted(args.items())
286
286
287 qs = '?%s' % urlreq.urlencode(q)
287 qs = '?%s' % urlreq.urlencode(q)
288 cu = "%s%s" % (self._url, qs)
288 cu = "%s%s" % (self._url, qs)
289 size = 0
289 size = 0
290 if util.safehasattr(data, 'length'):
290 if util.safehasattr(data, 'length'):
291 size = data.length
291 size = data.length
292 elif data is not None:
292 elif data is not None:
293 size = len(data)
293 size = len(data)
294 if size and self.ui.configbool('ui', 'usehttp2'):
295 headers[r'Expect'] = r'100-Continue'
296 headers[r'X-HgHttp2'] = r'1'
297 if data is not None and r'Content-Type' not in headers:
294 if data is not None and r'Content-Type' not in headers:
298 headers[r'Content-Type'] = r'application/mercurial-0.1'
295 headers[r'Content-Type'] = r'application/mercurial-0.1'
299
296
300 # Tell the server we accept application/mercurial-0.2 and multiple
297 # Tell the server we accept application/mercurial-0.2 and multiple
301 # compression formats if the server is capable of emitting those
298 # compression formats if the server is capable of emitting those
302 # payloads.
299 # payloads.
303 protoparams = []
300 protoparams = []
304
301
305 mediatypes = set()
302 mediatypes = set()
306 if self._caps is not None:
303 if self._caps is not None:
307 mt = self.capable('httpmediatype')
304 mt = self.capable('httpmediatype')
308 if mt:
305 if mt:
309 protoparams.append('0.1')
306 protoparams.append('0.1')
310 mediatypes = set(mt.split(','))
307 mediatypes = set(mt.split(','))
311
308
312 if '0.2tx' in mediatypes:
309 if '0.2tx' in mediatypes:
313 protoparams.append('0.2')
310 protoparams.append('0.2')
314
311
315 if '0.2tx' in mediatypes and self.capable('compression'):
312 if '0.2tx' in mediatypes and self.capable('compression'):
316 # We /could/ compare supported compression formats and prune
313 # We /could/ compare supported compression formats and prune
317 # non-mutually supported or error if nothing is mutually supported.
314 # non-mutually supported or error if nothing is mutually supported.
318 # For now, send the full list to the server and have it error.
315 # For now, send the full list to the server and have it error.
319 comps = [e.wireprotosupport().name for e in
316 comps = [e.wireprotosupport().name for e in
320 util.compengines.supportedwireengines(util.CLIENTROLE)]
317 util.compengines.supportedwireengines(util.CLIENTROLE)]
321 protoparams.append('comp=%s' % ','.join(comps))
318 protoparams.append('comp=%s' % ','.join(comps))
322
319
323 if protoparams:
320 if protoparams:
324 protoheaders = encodevalueinheaders(' '.join(protoparams),
321 protoheaders = encodevalueinheaders(' '.join(protoparams),
325 'X-HgProto',
322 'X-HgProto',
326 headersize or 1024)
323 headersize or 1024)
327 for header, value in protoheaders:
324 for header, value in protoheaders:
328 headers[header] = value
325 headers[header] = value
329 varyheaders.append(header)
326 varyheaders.append(header)
330
327
331 if varyheaders:
328 if varyheaders:
332 headers[r'Vary'] = r','.join(varyheaders)
329 headers[r'Vary'] = r','.join(varyheaders)
333
330
334 req = self._requestbuilder(pycompat.strurl(cu), data, headers)
331 req = self._requestbuilder(pycompat.strurl(cu), data, headers)
335
332
336 if data is not None:
333 if data is not None:
337 self.ui.debug("sending %d bytes\n" % size)
334 self.ui.debug("sending %d bytes\n" % size)
338 req.add_unredirected_header(r'Content-Length', r'%d' % size)
335 req.add_unredirected_header(r'Content-Length', r'%d' % size)
339 try:
336 try:
340 resp = self._openurl(req)
337 resp = self._openurl(req)
341 except urlerr.httperror as inst:
338 except urlerr.httperror as inst:
342 if inst.code == 401:
339 if inst.code == 401:
343 raise error.Abort(_('authorization failed'))
340 raise error.Abort(_('authorization failed'))
344 raise
341 raise
345 except httplib.HTTPException as inst:
342 except httplib.HTTPException as inst:
346 self.ui.debug('http error while sending %s command\n' % cmd)
343 self.ui.debug('http error while sending %s command\n' % cmd)
347 self.ui.traceback()
344 self.ui.traceback()
348 raise IOError(None, inst)
345 raise IOError(None, inst)
349
346
350 # Insert error handlers for common I/O failures.
347 # Insert error handlers for common I/O failures.
351 _wraphttpresponse(resp)
348 _wraphttpresponse(resp)
352
349
353 # record the url we got redirected to
350 # record the url we got redirected to
354 resp_url = pycompat.bytesurl(resp.geturl())
351 resp_url = pycompat.bytesurl(resp.geturl())
355 if resp_url.endswith(qs):
352 if resp_url.endswith(qs):
356 resp_url = resp_url[:-len(qs)]
353 resp_url = resp_url[:-len(qs)]
357 if self._url.rstrip('/') != resp_url.rstrip('/'):
354 if self._url.rstrip('/') != resp_url.rstrip('/'):
358 if not self.ui.quiet:
355 if not self.ui.quiet:
359 self.ui.warn(_('real URL is %s\n') % resp_url)
356 self.ui.warn(_('real URL is %s\n') % resp_url)
360 self._url = resp_url
357 self._url = resp_url
361 try:
358 try:
362 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
359 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
363 except AttributeError:
360 except AttributeError:
364 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
361 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
365
362
366 safeurl = util.hidepassword(self._url)
363 safeurl = util.hidepassword(self._url)
367 if proto.startswith('application/hg-error'):
364 if proto.startswith('application/hg-error'):
368 raise error.OutOfBandError(resp.read())
365 raise error.OutOfBandError(resp.read())
369 # accept old "text/plain" and "application/hg-changegroup" for now
366 # accept old "text/plain" and "application/hg-changegroup" for now
370 if not (proto.startswith('application/mercurial-') or
367 if not (proto.startswith('application/mercurial-') or
371 (proto.startswith('text/plain')
368 (proto.startswith('text/plain')
372 and not resp.headers.get('content-length')) or
369 and not resp.headers.get('content-length')) or
373 proto.startswith('application/hg-changegroup')):
370 proto.startswith('application/hg-changegroup')):
374 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
371 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
375 raise error.RepoError(
372 raise error.RepoError(
376 _("'%s' does not appear to be an hg repository:\n"
373 _("'%s' does not appear to be an hg repository:\n"
377 "---%%<--- (%s)\n%s\n---%%<---\n")
374 "---%%<--- (%s)\n%s\n---%%<---\n")
378 % (safeurl, proto or 'no content-type', resp.read(1024)))
375 % (safeurl, proto or 'no content-type', resp.read(1024)))
379
376
380 if proto.startswith('application/mercurial-'):
377 if proto.startswith('application/mercurial-'):
381 try:
378 try:
382 version = proto.split('-', 1)[1]
379 version = proto.split('-', 1)[1]
383 version_info = tuple([int(n) for n in version.split('.')])
380 version_info = tuple([int(n) for n in version.split('.')])
384 except ValueError:
381 except ValueError:
385 raise error.RepoError(_("'%s' sent a broken Content-Type "
382 raise error.RepoError(_("'%s' sent a broken Content-Type "
386 "header (%s)") % (safeurl, proto))
383 "header (%s)") % (safeurl, proto))
387
384
388 # TODO consider switching to a decompression reader that uses
385 # TODO consider switching to a decompression reader that uses
389 # generators.
386 # generators.
390 if version_info == (0, 1):
387 if version_info == (0, 1):
391 if _compressible:
388 if _compressible:
392 return util.compengines['zlib'].decompressorreader(resp)
389 return util.compengines['zlib'].decompressorreader(resp)
393 return resp
390 return resp
394 elif version_info == (0, 2):
391 elif version_info == (0, 2):
395 # application/mercurial-0.2 always identifies the compression
392 # application/mercurial-0.2 always identifies the compression
396 # engine in the payload header.
393 # engine in the payload header.
397 elen = struct.unpack('B', resp.read(1))[0]
394 elen = struct.unpack('B', resp.read(1))[0]
398 ename = resp.read(elen)
395 ename = resp.read(elen)
399 engine = util.compengines.forwiretype(ename)
396 engine = util.compengines.forwiretype(ename)
400 return engine.decompressorreader(resp)
397 return engine.decompressorreader(resp)
401 else:
398 else:
402 raise error.RepoError(_("'%s' uses newer protocol %s") %
399 raise error.RepoError(_("'%s' uses newer protocol %s") %
403 (safeurl, version))
400 (safeurl, version))
404
401
405 if _compressible:
402 if _compressible:
406 return util.compengines['zlib'].decompressorreader(resp)
403 return util.compengines['zlib'].decompressorreader(resp)
407
404
408 return resp
405 return resp
409
406
410 def _call(self, cmd, **args):
407 def _call(self, cmd, **args):
411 fp = self._callstream(cmd, **args)
408 fp = self._callstream(cmd, **args)
412 try:
409 try:
413 return fp.read()
410 return fp.read()
414 finally:
411 finally:
415 # if using keepalive, allow connection to be reused
412 # if using keepalive, allow connection to be reused
416 fp.close()
413 fp.close()
417
414
418 def _callpush(self, cmd, cg, **args):
415 def _callpush(self, cmd, cg, **args):
419 # have to stream bundle to a temp file because we do not have
416 # have to stream bundle to a temp file because we do not have
420 # http 1.1 chunked transfer.
417 # http 1.1 chunked transfer.
421
418
422 types = self.capable('unbundle')
419 types = self.capable('unbundle')
423 try:
420 try:
424 types = types.split(',')
421 types = types.split(',')
425 except AttributeError:
422 except AttributeError:
426 # servers older than d1b16a746db6 will send 'unbundle' as a
423 # servers older than d1b16a746db6 will send 'unbundle' as a
427 # boolean capability. They only support headerless/uncompressed
424 # boolean capability. They only support headerless/uncompressed
428 # bundles.
425 # bundles.
429 types = [""]
426 types = [""]
430 for x in types:
427 for x in types:
431 if x in bundle2.bundletypes:
428 if x in bundle2.bundletypes:
432 type = x
429 type = x
433 break
430 break
434
431
435 tempname = bundle2.writebundle(self.ui, cg, None, type)
432 tempname = bundle2.writebundle(self.ui, cg, None, type)
436 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
433 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
437 headers = {r'Content-Type': r'application/mercurial-0.1'}
434 headers = {r'Content-Type': r'application/mercurial-0.1'}
438
435
439 try:
436 try:
440 r = self._call(cmd, data=fp, headers=headers, **args)
437 r = self._call(cmd, data=fp, headers=headers, **args)
441 vals = r.split('\n', 1)
438 vals = r.split('\n', 1)
442 if len(vals) < 2:
439 if len(vals) < 2:
443 raise error.ResponseError(_("unexpected response:"), r)
440 raise error.ResponseError(_("unexpected response:"), r)
444 return vals
441 return vals
445 except socket.error as err:
442 except socket.error as err:
446 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
443 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
447 raise error.Abort(_('push failed: %s') % err.args[1])
444 raise error.Abort(_('push failed: %s') % err.args[1])
448 raise error.Abort(err.args[1])
445 raise error.Abort(err.args[1])
449 finally:
446 finally:
450 fp.close()
447 fp.close()
451 os.unlink(tempname)
448 os.unlink(tempname)
452
449
453 def _calltwowaystream(self, cmd, fp, **args):
450 def _calltwowaystream(self, cmd, fp, **args):
454 fh = None
451 fh = None
455 fp_ = None
452 fp_ = None
456 filename = None
453 filename = None
457 try:
454 try:
458 # dump bundle to disk
455 # dump bundle to disk
459 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
456 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
460 fh = os.fdopen(fd, pycompat.sysstr("wb"))
457 fh = os.fdopen(fd, pycompat.sysstr("wb"))
461 d = fp.read(4096)
458 d = fp.read(4096)
462 while d:
459 while d:
463 fh.write(d)
460 fh.write(d)
464 d = fp.read(4096)
461 d = fp.read(4096)
465 fh.close()
462 fh.close()
466 # start http push
463 # start http push
467 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
464 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
468 headers = {r'Content-Type': r'application/mercurial-0.1'}
465 headers = {r'Content-Type': r'application/mercurial-0.1'}
469 return self._callstream(cmd, data=fp_, headers=headers, **args)
466 return self._callstream(cmd, data=fp_, headers=headers, **args)
470 finally:
467 finally:
471 if fp_ is not None:
468 if fp_ is not None:
472 fp_.close()
469 fp_.close()
473 if fh is not None:
470 if fh is not None:
474 fh.close()
471 fh.close()
475 os.unlink(filename)
472 os.unlink(filename)
476
473
477 def _callcompressable(self, cmd, **args):
474 def _callcompressable(self, cmd, **args):
478 return self._callstream(cmd, _compressible=True, **args)
475 return self._callstream(cmd, _compressible=True, **args)
479
476
480 def _abort(self, exception):
477 def _abort(self, exception):
481 raise exception
478 raise exception
482
479
483 def instance(ui, path, create):
480 def instance(ui, path, create):
484 if create:
481 if create:
485 raise error.Abort(_('cannot create new http repository'))
482 raise error.Abort(_('cannot create new http repository'))
486 try:
483 try:
487 if path.startswith('https:') and not url.has_https:
484 if path.startswith('https:') and not url.has_https:
488 raise error.Abort(_('Python support for SSL and HTTPS '
485 raise error.Abort(_('Python support for SSL and HTTPS '
489 'is not installed'))
486 'is not installed'))
490
487
491 inst = httppeer(ui, path)
488 inst = httppeer(ui, path)
492 inst._fetchcaps()
489 inst._fetchcaps()
493
490
494 return inst
491 return inst
495 except error.RepoError as httpexception:
492 except error.RepoError as httpexception:
496 try:
493 try:
497 r = statichttprepo.instance(ui, "static-" + path, create)
494 r = statichttprepo.instance(ui, "static-" + path, create)
498 ui.note(_('(falling back to static-http)\n'))
495 ui.note(_('(falling back to static-http)\n'))
499 return r
496 return r
500 except error.RepoError:
497 except error.RepoError:
501 raise httpexception # use the original http RepoError instead
498 raise httpexception # use the original http RepoError instead
@@ -1,539 +1,531 b''
1 # url.py - HTTP handling for mercurial
1 # url.py - HTTP handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import base64
12 import base64
13 import os
13 import os
14 import socket
14 import socket
15
15
16 from .i18n import _
16 from .i18n import _
17 from . import (
17 from . import (
18 encoding,
18 encoding,
19 error,
19 error,
20 httpconnection as httpconnectionmod,
20 httpconnection as httpconnectionmod,
21 keepalive,
21 keepalive,
22 pycompat,
22 pycompat,
23 sslutil,
23 sslutil,
24 urllibcompat,
24 urllibcompat,
25 util,
25 util,
26 )
26 )
27
27
28 httplib = util.httplib
28 httplib = util.httplib
29 stringio = util.stringio
29 stringio = util.stringio
30 urlerr = util.urlerr
30 urlerr = util.urlerr
31 urlreq = util.urlreq
31 urlreq = util.urlreq
32
32
33 def escape(s, quote=None):
33 def escape(s, quote=None):
34 '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
34 '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
35 If the optional flag quote is true, the quotation mark character (")
35 If the optional flag quote is true, the quotation mark character (")
36 is also translated.
36 is also translated.
37
37
38 This is the same as cgi.escape in Python, but always operates on
38 This is the same as cgi.escape in Python, but always operates on
39 bytes, whereas cgi.escape in Python 3 only works on unicodes.
39 bytes, whereas cgi.escape in Python 3 only works on unicodes.
40 '''
40 '''
41 s = s.replace(b"&", b"&amp;")
41 s = s.replace(b"&", b"&amp;")
42 s = s.replace(b"<", b"&lt;")
42 s = s.replace(b"<", b"&lt;")
43 s = s.replace(b">", b"&gt;")
43 s = s.replace(b">", b"&gt;")
44 if quote:
44 if quote:
45 s = s.replace(b'"', b"&quot;")
45 s = s.replace(b'"', b"&quot;")
46 return s
46 return s
47
47
48 class passwordmgr(object):
48 class passwordmgr(object):
49 def __init__(self, ui, passwddb):
49 def __init__(self, ui, passwddb):
50 self.ui = ui
50 self.ui = ui
51 self.passwddb = passwddb
51 self.passwddb = passwddb
52
52
53 def add_password(self, realm, uri, user, passwd):
53 def add_password(self, realm, uri, user, passwd):
54 return self.passwddb.add_password(realm, uri, user, passwd)
54 return self.passwddb.add_password(realm, uri, user, passwd)
55
55
56 def find_user_password(self, realm, authuri):
56 def find_user_password(self, realm, authuri):
57 authinfo = self.passwddb.find_user_password(realm, authuri)
57 authinfo = self.passwddb.find_user_password(realm, authuri)
58 user, passwd = authinfo
58 user, passwd = authinfo
59 if user and passwd:
59 if user and passwd:
60 self._writedebug(user, passwd)
60 self._writedebug(user, passwd)
61 return (user, passwd)
61 return (user, passwd)
62
62
63 if not user or not passwd:
63 if not user or not passwd:
64 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
64 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
65 if res:
65 if res:
66 group, auth = res
66 group, auth = res
67 user, passwd = auth.get('username'), auth.get('password')
67 user, passwd = auth.get('username'), auth.get('password')
68 self.ui.debug("using auth.%s.* for authentication\n" % group)
68 self.ui.debug("using auth.%s.* for authentication\n" % group)
69 if not user or not passwd:
69 if not user or not passwd:
70 u = util.url(authuri)
70 u = util.url(authuri)
71 u.query = None
71 u.query = None
72 if not self.ui.interactive():
72 if not self.ui.interactive():
73 raise error.Abort(_('http authorization required for %s') %
73 raise error.Abort(_('http authorization required for %s') %
74 util.hidepassword(bytes(u)))
74 util.hidepassword(bytes(u)))
75
75
76 self.ui.write(_("http authorization required for %s\n") %
76 self.ui.write(_("http authorization required for %s\n") %
77 util.hidepassword(bytes(u)))
77 util.hidepassword(bytes(u)))
78 self.ui.write(_("realm: %s\n") % realm)
78 self.ui.write(_("realm: %s\n") % realm)
79 if user:
79 if user:
80 self.ui.write(_("user: %s\n") % user)
80 self.ui.write(_("user: %s\n") % user)
81 else:
81 else:
82 user = self.ui.prompt(_("user:"), default=None)
82 user = self.ui.prompt(_("user:"), default=None)
83
83
84 if not passwd:
84 if not passwd:
85 passwd = self.ui.getpass()
85 passwd = self.ui.getpass()
86
86
87 self.passwddb.add_password(realm, authuri, user, passwd)
87 self.passwddb.add_password(realm, authuri, user, passwd)
88 self._writedebug(user, passwd)
88 self._writedebug(user, passwd)
89 return (user, passwd)
89 return (user, passwd)
90
90
91 def _writedebug(self, user, passwd):
91 def _writedebug(self, user, passwd):
92 msg = _('http auth: user %s, password %s\n')
92 msg = _('http auth: user %s, password %s\n')
93 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
93 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
94
94
95 def find_stored_password(self, authuri):
95 def find_stored_password(self, authuri):
96 return self.passwddb.find_user_password(None, authuri)
96 return self.passwddb.find_user_password(None, authuri)
97
97
98 class proxyhandler(urlreq.proxyhandler):
98 class proxyhandler(urlreq.proxyhandler):
99 def __init__(self, ui):
99 def __init__(self, ui):
100 proxyurl = (ui.config("http_proxy", "host") or
100 proxyurl = (ui.config("http_proxy", "host") or
101 encoding.environ.get('http_proxy'))
101 encoding.environ.get('http_proxy'))
102 # XXX proxyauthinfo = None
102 # XXX proxyauthinfo = None
103
103
104 if proxyurl:
104 if proxyurl:
105 # proxy can be proper url or host[:port]
105 # proxy can be proper url or host[:port]
106 if not (proxyurl.startswith('http:') or
106 if not (proxyurl.startswith('http:') or
107 proxyurl.startswith('https:')):
107 proxyurl.startswith('https:')):
108 proxyurl = 'http://' + proxyurl + '/'
108 proxyurl = 'http://' + proxyurl + '/'
109 proxy = util.url(proxyurl)
109 proxy = util.url(proxyurl)
110 if not proxy.user:
110 if not proxy.user:
111 proxy.user = ui.config("http_proxy", "user")
111 proxy.user = ui.config("http_proxy", "user")
112 proxy.passwd = ui.config("http_proxy", "passwd")
112 proxy.passwd = ui.config("http_proxy", "passwd")
113
113
114 # see if we should use a proxy for this url
114 # see if we should use a proxy for this url
115 no_list = ["localhost", "127.0.0.1"]
115 no_list = ["localhost", "127.0.0.1"]
116 no_list.extend([p.lower() for
116 no_list.extend([p.lower() for
117 p in ui.configlist("http_proxy", "no")])
117 p in ui.configlist("http_proxy", "no")])
118 no_list.extend([p.strip().lower() for
118 no_list.extend([p.strip().lower() for
119 p in encoding.environ.get("no_proxy", '').split(',')
119 p in encoding.environ.get("no_proxy", '').split(',')
120 if p.strip()])
120 if p.strip()])
121 # "http_proxy.always" config is for running tests on localhost
121 # "http_proxy.always" config is for running tests on localhost
122 if ui.configbool("http_proxy", "always"):
122 if ui.configbool("http_proxy", "always"):
123 self.no_list = []
123 self.no_list = []
124 else:
124 else:
125 self.no_list = no_list
125 self.no_list = no_list
126
126
127 proxyurl = bytes(proxy)
127 proxyurl = bytes(proxy)
128 proxies = {'http': proxyurl, 'https': proxyurl}
128 proxies = {'http': proxyurl, 'https': proxyurl}
129 ui.debug('proxying through %s\n' % util.hidepassword(proxyurl))
129 ui.debug('proxying through %s\n' % util.hidepassword(proxyurl))
130 else:
130 else:
131 proxies = {}
131 proxies = {}
132
132
133 urlreq.proxyhandler.__init__(self, proxies)
133 urlreq.proxyhandler.__init__(self, proxies)
134 self.ui = ui
134 self.ui = ui
135
135
136 def proxy_open(self, req, proxy, type_):
136 def proxy_open(self, req, proxy, type_):
137 host = urllibcompat.gethost(req).split(':')[0]
137 host = urllibcompat.gethost(req).split(':')[0]
138 for e in self.no_list:
138 for e in self.no_list:
139 if host == e:
139 if host == e:
140 return None
140 return None
141 if e.startswith('*.') and host.endswith(e[2:]):
141 if e.startswith('*.') and host.endswith(e[2:]):
142 return None
142 return None
143 if e.startswith('.') and host.endswith(e[1:]):
143 if e.startswith('.') and host.endswith(e[1:]):
144 return None
144 return None
145
145
146 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
146 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
147
147
148 def _gen_sendfile(orgsend):
148 def _gen_sendfile(orgsend):
149 def _sendfile(self, data):
149 def _sendfile(self, data):
150 # send a file
150 # send a file
151 if isinstance(data, httpconnectionmod.httpsendfile):
151 if isinstance(data, httpconnectionmod.httpsendfile):
152 # if auth required, some data sent twice, so rewind here
152 # if auth required, some data sent twice, so rewind here
153 data.seek(0)
153 data.seek(0)
154 for chunk in util.filechunkiter(data):
154 for chunk in util.filechunkiter(data):
155 orgsend(self, chunk)
155 orgsend(self, chunk)
156 else:
156 else:
157 orgsend(self, data)
157 orgsend(self, data)
158 return _sendfile
158 return _sendfile
159
159
160 has_https = util.safehasattr(urlreq, 'httpshandler')
160 has_https = util.safehasattr(urlreq, 'httpshandler')
161
161
162 class httpconnection(keepalive.HTTPConnection):
162 class httpconnection(keepalive.HTTPConnection):
163 # must be able to send big bundle as stream.
163 # must be able to send big bundle as stream.
164 send = _gen_sendfile(keepalive.HTTPConnection.send)
164 send = _gen_sendfile(keepalive.HTTPConnection.send)
165
165
166 def getresponse(self):
166 def getresponse(self):
167 proxyres = getattr(self, 'proxyres', None)
167 proxyres = getattr(self, 'proxyres', None)
168 if proxyres:
168 if proxyres:
169 if proxyres.will_close:
169 if proxyres.will_close:
170 self.close()
170 self.close()
171 self.proxyres = None
171 self.proxyres = None
172 return proxyres
172 return proxyres
173 return keepalive.HTTPConnection.getresponse(self)
173 return keepalive.HTTPConnection.getresponse(self)
174
174
175 # general transaction handler to support different ways to handle
175 # general transaction handler to support different ways to handle
176 # HTTPS proxying before and after Python 2.6.3.
176 # HTTPS proxying before and after Python 2.6.3.
177 def _generic_start_transaction(handler, h, req):
177 def _generic_start_transaction(handler, h, req):
178 tunnel_host = getattr(req, '_tunnel_host', None)
178 tunnel_host = getattr(req, '_tunnel_host', None)
179 if tunnel_host:
179 if tunnel_host:
180 if tunnel_host[:7] not in ['http://', 'https:/']:
180 if tunnel_host[:7] not in ['http://', 'https:/']:
181 tunnel_host = 'https://' + tunnel_host
181 tunnel_host = 'https://' + tunnel_host
182 new_tunnel = True
182 new_tunnel = True
183 else:
183 else:
184 tunnel_host = urllibcompat.getselector(req)
184 tunnel_host = urllibcompat.getselector(req)
185 new_tunnel = False
185 new_tunnel = False
186
186
187 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
187 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
188 u = util.url(tunnel_host)
188 u = util.url(tunnel_host)
189 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
189 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
190 h.realhostport = ':'.join([u.host, (u.port or '443')])
190 h.realhostport = ':'.join([u.host, (u.port or '443')])
191 h.headers = req.headers.copy()
191 h.headers = req.headers.copy()
192 h.headers.update(handler.parent.addheaders)
192 h.headers.update(handler.parent.addheaders)
193 return
193 return
194
194
195 h.realhostport = None
195 h.realhostport = None
196 h.headers = None
196 h.headers = None
197
197
198 def _generic_proxytunnel(self):
198 def _generic_proxytunnel(self):
199 proxyheaders = dict(
199 proxyheaders = dict(
200 [(x, self.headers[x]) for x in self.headers
200 [(x, self.headers[x]) for x in self.headers
201 if x.lower().startswith('proxy-')])
201 if x.lower().startswith('proxy-')])
202 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
202 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
203 for header in proxyheaders.iteritems():
203 for header in proxyheaders.iteritems():
204 self.send('%s: %s\r\n' % header)
204 self.send('%s: %s\r\n' % header)
205 self.send('\r\n')
205 self.send('\r\n')
206
206
207 # majority of the following code is duplicated from
207 # majority of the following code is duplicated from
208 # httplib.HTTPConnection as there are no adequate places to
208 # httplib.HTTPConnection as there are no adequate places to
209 # override functions to provide the needed functionality
209 # override functions to provide the needed functionality
210 res = self.response_class(self.sock,
210 res = self.response_class(self.sock,
211 strict=self.strict,
211 strict=self.strict,
212 method=self._method)
212 method=self._method)
213
213
214 while True:
214 while True:
215 version, status, reason = res._read_status()
215 version, status, reason = res._read_status()
216 if status != httplib.CONTINUE:
216 if status != httplib.CONTINUE:
217 break
217 break
218 # skip lines that are all whitespace
218 # skip lines that are all whitespace
219 list(iter(lambda: res.fp.readline().strip(), ''))
219 list(iter(lambda: res.fp.readline().strip(), ''))
220 res.status = status
220 res.status = status
221 res.reason = reason.strip()
221 res.reason = reason.strip()
222
222
223 if res.status == 200:
223 if res.status == 200:
224 # skip lines until we find a blank line
224 # skip lines until we find a blank line
225 list(iter(res.fp.readline, '\r\n'))
225 list(iter(res.fp.readline, '\r\n'))
226 return True
226 return True
227
227
228 if version == 'HTTP/1.0':
228 if version == 'HTTP/1.0':
229 res.version = 10
229 res.version = 10
230 elif version.startswith('HTTP/1.'):
230 elif version.startswith('HTTP/1.'):
231 res.version = 11
231 res.version = 11
232 elif version == 'HTTP/0.9':
232 elif version == 'HTTP/0.9':
233 res.version = 9
233 res.version = 9
234 else:
234 else:
235 raise httplib.UnknownProtocol(version)
235 raise httplib.UnknownProtocol(version)
236
236
237 if res.version == 9:
237 if res.version == 9:
238 res.length = None
238 res.length = None
239 res.chunked = 0
239 res.chunked = 0
240 res.will_close = 1
240 res.will_close = 1
241 res.msg = httplib.HTTPMessage(stringio())
241 res.msg = httplib.HTTPMessage(stringio())
242 return False
242 return False
243
243
244 res.msg = httplib.HTTPMessage(res.fp)
244 res.msg = httplib.HTTPMessage(res.fp)
245 res.msg.fp = None
245 res.msg.fp = None
246
246
247 # are we using the chunked-style of transfer encoding?
247 # are we using the chunked-style of transfer encoding?
248 trenc = res.msg.getheader('transfer-encoding')
248 trenc = res.msg.getheader('transfer-encoding')
249 if trenc and trenc.lower() == "chunked":
249 if trenc and trenc.lower() == "chunked":
250 res.chunked = 1
250 res.chunked = 1
251 res.chunk_left = None
251 res.chunk_left = None
252 else:
252 else:
253 res.chunked = 0
253 res.chunked = 0
254
254
255 # will the connection close at the end of the response?
255 # will the connection close at the end of the response?
256 res.will_close = res._check_close()
256 res.will_close = res._check_close()
257
257
258 # do we have a Content-Length?
258 # do we have a Content-Length?
259 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
259 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
260 # transfer-encoding is "chunked"
260 # transfer-encoding is "chunked"
261 length = res.msg.getheader('content-length')
261 length = res.msg.getheader('content-length')
262 if length and not res.chunked:
262 if length and not res.chunked:
263 try:
263 try:
264 res.length = int(length)
264 res.length = int(length)
265 except ValueError:
265 except ValueError:
266 res.length = None
266 res.length = None
267 else:
267 else:
268 if res.length < 0: # ignore nonsensical negative lengths
268 if res.length < 0: # ignore nonsensical negative lengths
269 res.length = None
269 res.length = None
270 else:
270 else:
271 res.length = None
271 res.length = None
272
272
273 # does the body have a fixed length? (of zero)
273 # does the body have a fixed length? (of zero)
274 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
274 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
275 100 <= status < 200 or # 1xx codes
275 100 <= status < 200 or # 1xx codes
276 res._method == 'HEAD'):
276 res._method == 'HEAD'):
277 res.length = 0
277 res.length = 0
278
278
279 # if the connection remains open, and we aren't using chunked, and
279 # if the connection remains open, and we aren't using chunked, and
280 # a content-length was not provided, then assume that the connection
280 # a content-length was not provided, then assume that the connection
281 # WILL close.
281 # WILL close.
282 if (not res.will_close and
282 if (not res.will_close and
283 not res.chunked and
283 not res.chunked and
284 res.length is None):
284 res.length is None):
285 res.will_close = 1
285 res.will_close = 1
286
286
287 self.proxyres = res
287 self.proxyres = res
288
288
289 return False
289 return False
290
290
291 class httphandler(keepalive.HTTPHandler):
291 class httphandler(keepalive.HTTPHandler):
292 def http_open(self, req):
292 def http_open(self, req):
293 return self.do_open(httpconnection, req)
293 return self.do_open(httpconnection, req)
294
294
295 def _start_transaction(self, h, req):
295 def _start_transaction(self, h, req):
296 _generic_start_transaction(self, h, req)
296 _generic_start_transaction(self, h, req)
297 return keepalive.HTTPHandler._start_transaction(self, h, req)
297 return keepalive.HTTPHandler._start_transaction(self, h, req)
298
298
299 if has_https:
299 if has_https:
300 class httpsconnection(httplib.HTTPConnection):
300 class httpsconnection(httplib.HTTPConnection):
301 response_class = keepalive.HTTPResponse
301 response_class = keepalive.HTTPResponse
302 default_port = httplib.HTTPS_PORT
302 default_port = httplib.HTTPS_PORT
303 # must be able to send big bundle as stream.
303 # must be able to send big bundle as stream.
304 send = _gen_sendfile(keepalive.safesend)
304 send = _gen_sendfile(keepalive.safesend)
305 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
305 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
306
306
307 def __init__(self, host, port=None, key_file=None, cert_file=None,
307 def __init__(self, host, port=None, key_file=None, cert_file=None,
308 *args, **kwargs):
308 *args, **kwargs):
309 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
309 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
310 self.key_file = key_file
310 self.key_file = key_file
311 self.cert_file = cert_file
311 self.cert_file = cert_file
312
312
313 def connect(self):
313 def connect(self):
314 self.sock = socket.create_connection((self.host, self.port))
314 self.sock = socket.create_connection((self.host, self.port))
315
315
316 host = self.host
316 host = self.host
317 if self.realhostport: # use CONNECT proxy
317 if self.realhostport: # use CONNECT proxy
318 _generic_proxytunnel(self)
318 _generic_proxytunnel(self)
319 host = self.realhostport.rsplit(':', 1)[0]
319 host = self.realhostport.rsplit(':', 1)[0]
320 self.sock = sslutil.wrapsocket(
320 self.sock = sslutil.wrapsocket(
321 self.sock, self.key_file, self.cert_file, ui=self.ui,
321 self.sock, self.key_file, self.cert_file, ui=self.ui,
322 serverhostname=host)
322 serverhostname=host)
323 sslutil.validatesocket(self.sock)
323 sslutil.validatesocket(self.sock)
324
324
325 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
325 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
326 def __init__(self, ui):
326 def __init__(self, ui):
327 keepalive.KeepAliveHandler.__init__(self)
327 keepalive.KeepAliveHandler.__init__(self)
328 urlreq.httpshandler.__init__(self)
328 urlreq.httpshandler.__init__(self)
329 self.ui = ui
329 self.ui = ui
330 self.pwmgr = passwordmgr(self.ui,
330 self.pwmgr = passwordmgr(self.ui,
331 self.ui.httppasswordmgrdb)
331 self.ui.httppasswordmgrdb)
332
332
333 def _start_transaction(self, h, req):
333 def _start_transaction(self, h, req):
334 _generic_start_transaction(self, h, req)
334 _generic_start_transaction(self, h, req)
335 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
335 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
336
336
337 def https_open(self, req):
337 def https_open(self, req):
338 # urllibcompat.getfullurl() does not contain credentials
338 # urllibcompat.getfullurl() does not contain credentials
339 # and we may need them to match the certificates.
339 # and we may need them to match the certificates.
340 url = urllibcompat.getfullurl(req)
340 url = urllibcompat.getfullurl(req)
341 user, password = self.pwmgr.find_stored_password(url)
341 user, password = self.pwmgr.find_stored_password(url)
342 res = httpconnectionmod.readauthforuri(self.ui, url, user)
342 res = httpconnectionmod.readauthforuri(self.ui, url, user)
343 if res:
343 if res:
344 group, auth = res
344 group, auth = res
345 self.auth = auth
345 self.auth = auth
346 self.ui.debug("using auth.%s.* for authentication\n" % group)
346 self.ui.debug("using auth.%s.* for authentication\n" % group)
347 else:
347 else:
348 self.auth = None
348 self.auth = None
349 return self.do_open(self._makeconnection, req)
349 return self.do_open(self._makeconnection, req)
350
350
351 def _makeconnection(self, host, port=None, *args, **kwargs):
351 def _makeconnection(self, host, port=None, *args, **kwargs):
352 keyfile = None
352 keyfile = None
353 certfile = None
353 certfile = None
354
354
355 if len(args) >= 1: # key_file
355 if len(args) >= 1: # key_file
356 keyfile = args[0]
356 keyfile = args[0]
357 if len(args) >= 2: # cert_file
357 if len(args) >= 2: # cert_file
358 certfile = args[1]
358 certfile = args[1]
359 args = args[2:]
359 args = args[2:]
360
360
361 # if the user has specified different key/cert files in
361 # if the user has specified different key/cert files in
362 # hgrc, we prefer these
362 # hgrc, we prefer these
363 if self.auth and 'key' in self.auth and 'cert' in self.auth:
363 if self.auth and 'key' in self.auth and 'cert' in self.auth:
364 keyfile = self.auth['key']
364 keyfile = self.auth['key']
365 certfile = self.auth['cert']
365 certfile = self.auth['cert']
366
366
367 conn = httpsconnection(host, port, keyfile, certfile, *args,
367 conn = httpsconnection(host, port, keyfile, certfile, *args,
368 **kwargs)
368 **kwargs)
369 conn.ui = self.ui
369 conn.ui = self.ui
370 return conn
370 return conn
371
371
372 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
372 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
373 def __init__(self, *args, **kwargs):
373 def __init__(self, *args, **kwargs):
374 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
374 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
375 self.retried_req = None
375 self.retried_req = None
376
376
377 def reset_retry_count(self):
377 def reset_retry_count(self):
378 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
378 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
379 # forever. We disable reset_retry_count completely and reset in
379 # forever. We disable reset_retry_count completely and reset in
380 # http_error_auth_reqed instead.
380 # http_error_auth_reqed instead.
381 pass
381 pass
382
382
383 def http_error_auth_reqed(self, auth_header, host, req, headers):
383 def http_error_auth_reqed(self, auth_header, host, req, headers):
384 # Reset the retry counter once for each request.
384 # Reset the retry counter once for each request.
385 if req is not self.retried_req:
385 if req is not self.retried_req:
386 self.retried_req = req
386 self.retried_req = req
387 self.retried = 0
387 self.retried = 0
388 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
388 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
389 self, auth_header, host, req, headers)
389 self, auth_header, host, req, headers)
390
390
391 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
391 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
392 def __init__(self, *args, **kwargs):
392 def __init__(self, *args, **kwargs):
393 self.auth = None
393 self.auth = None
394 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
394 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
395 self.retried_req = None
395 self.retried_req = None
396
396
397 def http_request(self, request):
397 def http_request(self, request):
398 if self.auth:
398 if self.auth:
399 request.add_unredirected_header(self.auth_header, self.auth)
399 request.add_unredirected_header(self.auth_header, self.auth)
400
400
401 return request
401 return request
402
402
403 def https_request(self, request):
403 def https_request(self, request):
404 if self.auth:
404 if self.auth:
405 request.add_unredirected_header(self.auth_header, self.auth)
405 request.add_unredirected_header(self.auth_header, self.auth)
406
406
407 return request
407 return request
408
408
409 def reset_retry_count(self):
409 def reset_retry_count(self):
410 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
410 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
411 # forever. We disable reset_retry_count completely and reset in
411 # forever. We disable reset_retry_count completely and reset in
412 # http_error_auth_reqed instead.
412 # http_error_auth_reqed instead.
413 pass
413 pass
414
414
415 def http_error_auth_reqed(self, auth_header, host, req, headers):
415 def http_error_auth_reqed(self, auth_header, host, req, headers):
416 # Reset the retry counter once for each request.
416 # Reset the retry counter once for each request.
417 if req is not self.retried_req:
417 if req is not self.retried_req:
418 self.retried_req = req
418 self.retried_req = req
419 self.retried = 0
419 self.retried = 0
420 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
420 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
421 self, auth_header, host, req, headers)
421 self, auth_header, host, req, headers)
422
422
423 def retry_http_basic_auth(self, host, req, realm):
423 def retry_http_basic_auth(self, host, req, realm):
424 user, pw = self.passwd.find_user_password(
424 user, pw = self.passwd.find_user_password(
425 realm, urllibcompat.getfullurl(req))
425 realm, urllibcompat.getfullurl(req))
426 if pw is not None:
426 if pw is not None:
427 raw = "%s:%s" % (user, pw)
427 raw = "%s:%s" % (user, pw)
428 auth = 'Basic %s' % base64.b64encode(raw).strip()
428 auth = 'Basic %s' % base64.b64encode(raw).strip()
429 if req.get_header(self.auth_header, None) == auth:
429 if req.get_header(self.auth_header, None) == auth:
430 return None
430 return None
431 self.auth = auth
431 self.auth = auth
432 req.add_unredirected_header(self.auth_header, auth)
432 req.add_unredirected_header(self.auth_header, auth)
433 return self.parent.open(req)
433 return self.parent.open(req)
434 else:
434 else:
435 return None
435 return None
436
436
437 class cookiehandler(urlreq.basehandler):
437 class cookiehandler(urlreq.basehandler):
438 def __init__(self, ui):
438 def __init__(self, ui):
439 self.cookiejar = None
439 self.cookiejar = None
440
440
441 cookiefile = ui.config('auth', 'cookiefile')
441 cookiefile = ui.config('auth', 'cookiefile')
442 if not cookiefile:
442 if not cookiefile:
443 return
443 return
444
444
445 cookiefile = util.expandpath(cookiefile)
445 cookiefile = util.expandpath(cookiefile)
446 try:
446 try:
447 cookiejar = util.cookielib.MozillaCookieJar(cookiefile)
447 cookiejar = util.cookielib.MozillaCookieJar(cookiefile)
448 cookiejar.load()
448 cookiejar.load()
449 self.cookiejar = cookiejar
449 self.cookiejar = cookiejar
450 except util.cookielib.LoadError as e:
450 except util.cookielib.LoadError as e:
451 ui.warn(_('(error loading cookie file %s: %s; continuing without '
451 ui.warn(_('(error loading cookie file %s: %s; continuing without '
452 'cookies)\n') % (cookiefile, util.forcebytestr(e)))
452 'cookies)\n') % (cookiefile, util.forcebytestr(e)))
453
453
454 def http_request(self, request):
454 def http_request(self, request):
455 if self.cookiejar:
455 if self.cookiejar:
456 self.cookiejar.add_cookie_header(request)
456 self.cookiejar.add_cookie_header(request)
457
457
458 return request
458 return request
459
459
460 def https_request(self, request):
460 def https_request(self, request):
461 if self.cookiejar:
461 if self.cookiejar:
462 self.cookiejar.add_cookie_header(request)
462 self.cookiejar.add_cookie_header(request)
463
463
464 return request
464 return request
465
465
466 handlerfuncs = []
466 handlerfuncs = []
467
467
468 def opener(ui, authinfo=None, useragent=None):
468 def opener(ui, authinfo=None, useragent=None):
469 '''
469 '''
470 construct an opener suitable for urllib2
470 construct an opener suitable for urllib2
471 authinfo will be added to the password manager
471 authinfo will be added to the password manager
472 '''
472 '''
473 # experimental config: ui.usehttp2
473 handlers = [httphandler()]
474 if ui.configbool('ui', 'usehttp2'):
474 if has_https:
475 handlers = [
475 handlers.append(httpshandler(ui))
476 httpconnectionmod.http2handler(
477 ui,
478 passwordmgr(ui, ui.httppasswordmgrdb))
479 ]
480 else:
481 handlers = [httphandler()]
482 if has_https:
483 handlers.append(httpshandler(ui))
484
476
485 handlers.append(proxyhandler(ui))
477 handlers.append(proxyhandler(ui))
486
478
487 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
479 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
488 if authinfo is not None:
480 if authinfo is not None:
489 realm, uris, user, passwd = authinfo
481 realm, uris, user, passwd = authinfo
490 saveduser, savedpass = passmgr.find_stored_password(uris[0])
482 saveduser, savedpass = passmgr.find_stored_password(uris[0])
491 if user != saveduser or passwd:
483 if user != saveduser or passwd:
492 passmgr.add_password(realm, uris, user, passwd)
484 passmgr.add_password(realm, uris, user, passwd)
493 ui.debug('http auth: user %s, password %s\n' %
485 ui.debug('http auth: user %s, password %s\n' %
494 (user, passwd and '*' * len(passwd) or 'not set'))
486 (user, passwd and '*' * len(passwd) or 'not set'))
495
487
496 handlers.extend((httpbasicauthhandler(passmgr),
488 handlers.extend((httpbasicauthhandler(passmgr),
497 httpdigestauthhandler(passmgr)))
489 httpdigestauthhandler(passmgr)))
498 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
490 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
499 handlers.append(cookiehandler(ui))
491 handlers.append(cookiehandler(ui))
500 opener = urlreq.buildopener(*handlers)
492 opener = urlreq.buildopener(*handlers)
501
493
502 # The user agent should should *NOT* be used by servers for e.g.
494 # The user agent should should *NOT* be used by servers for e.g.
503 # protocol detection or feature negotiation: there are other
495 # protocol detection or feature negotiation: there are other
504 # facilities for that.
496 # facilities for that.
505 #
497 #
506 # "mercurial/proto-1.0" was the original user agent string and
498 # "mercurial/proto-1.0" was the original user agent string and
507 # exists for backwards compatibility reasons.
499 # exists for backwards compatibility reasons.
508 #
500 #
509 # The "(Mercurial %s)" string contains the distribution
501 # The "(Mercurial %s)" string contains the distribution
510 # name and version. Other client implementations should choose their
502 # name and version. Other client implementations should choose their
511 # own distribution name. Since servers should not be using the user
503 # own distribution name. Since servers should not be using the user
512 # agent string for anything, clients should be able to define whatever
504 # agent string for anything, clients should be able to define whatever
513 # user agent they deem appropriate.
505 # user agent they deem appropriate.
514 #
506 #
515 # The custom user agent is for lfs, because unfortunately some servers
507 # The custom user agent is for lfs, because unfortunately some servers
516 # do look at this value.
508 # do look at this value.
517 if not useragent:
509 if not useragent:
518 agent = 'mercurial/proto-1.0 (Mercurial %s)' % util.version()
510 agent = 'mercurial/proto-1.0 (Mercurial %s)' % util.version()
519 opener.addheaders = [(r'User-agent', pycompat.sysstr(agent))]
511 opener.addheaders = [(r'User-agent', pycompat.sysstr(agent))]
520 else:
512 else:
521 opener.addheaders = [(r'User-agent', pycompat.sysstr(useragent))]
513 opener.addheaders = [(r'User-agent', pycompat.sysstr(useragent))]
522
514
523 # This header should only be needed by wire protocol requests. But it has
515 # This header should only be needed by wire protocol requests. But it has
524 # been sent on all requests since forever. We keep sending it for backwards
516 # been sent on all requests since forever. We keep sending it for backwards
525 # compatibility reasons. Modern versions of the wire protocol use
517 # compatibility reasons. Modern versions of the wire protocol use
526 # X-HgProto-<N> for advertising client support.
518 # X-HgProto-<N> for advertising client support.
527 opener.addheaders.append((r'Accept', r'application/mercurial-0.1'))
519 opener.addheaders.append((r'Accept', r'application/mercurial-0.1'))
528 return opener
520 return opener
529
521
530 def open(ui, url_, data=None):
522 def open(ui, url_, data=None):
531 u = util.url(url_)
523 u = util.url(url_)
532 if u.scheme:
524 if u.scheme:
533 u.scheme = u.scheme.lower()
525 u.scheme = u.scheme.lower()
534 url_, authinfo = u.authinfo()
526 url_, authinfo = u.authinfo()
535 else:
527 else:
536 path = util.normpath(os.path.abspath(url_))
528 path = util.normpath(os.path.abspath(url_))
537 url_ = 'file://' + urlreq.pathname2url(path)
529 url_ = 'file://' + urlreq.pathname2url(path)
538 authinfo = None
530 authinfo = None
539 return opener(ui, authinfo).open(url_, data)
531 return opener(ui, authinfo).open(url_, data)
@@ -1,1055 +1,1054 b''
1 #
1 #
2 # This is the mercurial setup script.
2 # This is the mercurial setup script.
3 #
3 #
4 # 'python setup.py install', or
4 # 'python setup.py install', or
5 # 'python setup.py --help' for more options
5 # 'python setup.py --help' for more options
6
6
7 import os
7 import os
8
8
9 supportedpy = '~= 2.7'
9 supportedpy = '~= 2.7'
10 if os.environ.get('HGALLOWPYTHON3', ''):
10 if os.environ.get('HGALLOWPYTHON3', ''):
11 # Mercurial will never work on Python 3 before 3.5 due to a lack
11 # Mercurial will never work on Python 3 before 3.5 due to a lack
12 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
12 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
13 # due to a bug in % formatting in bytestrings.
13 # due to a bug in % formatting in bytestrings.
14 #
14 #
15 # TODO: when we actually work on Python 3, use this string as the
15 # TODO: when we actually work on Python 3, use this string as the
16 # actual supportedpy string.
16 # actual supportedpy string.
17 supportedpy = ','.join([
17 supportedpy = ','.join([
18 '>=2.7',
18 '>=2.7',
19 '!=3.0.*',
19 '!=3.0.*',
20 '!=3.1.*',
20 '!=3.1.*',
21 '!=3.2.*',
21 '!=3.2.*',
22 '!=3.3.*',
22 '!=3.3.*',
23 '!=3.4.*',
23 '!=3.4.*',
24 '!=3.6.0',
24 '!=3.6.0',
25 '!=3.6.1',
25 '!=3.6.1',
26 ])
26 ])
27
27
28 import sys, platform
28 import sys, platform
29 if sys.version_info[0] >= 3:
29 if sys.version_info[0] >= 3:
30 printf = eval('print')
30 printf = eval('print')
31 libdir_escape = 'unicode_escape'
31 libdir_escape = 'unicode_escape'
32 def sysstr(s):
32 def sysstr(s):
33 return s.decode('latin-1')
33 return s.decode('latin-1')
34 else:
34 else:
35 libdir_escape = 'string_escape'
35 libdir_escape = 'string_escape'
36 def printf(*args, **kwargs):
36 def printf(*args, **kwargs):
37 f = kwargs.get('file', sys.stdout)
37 f = kwargs.get('file', sys.stdout)
38 end = kwargs.get('end', '\n')
38 end = kwargs.get('end', '\n')
39 f.write(b' '.join(args) + end)
39 f.write(b' '.join(args) + end)
40 def sysstr(s):
40 def sysstr(s):
41 return s
41 return s
42
42
43 # Attempt to guide users to a modern pip - this means that 2.6 users
43 # Attempt to guide users to a modern pip - this means that 2.6 users
44 # should have a chance of getting a 4.2 release, and when we ratchet
44 # should have a chance of getting a 4.2 release, and when we ratchet
45 # the version requirement forward again hopefully everyone will get
45 # the version requirement forward again hopefully everyone will get
46 # something that works for them.
46 # something that works for them.
47 if sys.version_info < (2, 7, 0, 'final'):
47 if sys.version_info < (2, 7, 0, 'final'):
48 pip_message = ('This may be due to an out of date pip. '
48 pip_message = ('This may be due to an out of date pip. '
49 'Make sure you have pip >= 9.0.1.')
49 'Make sure you have pip >= 9.0.1.')
50 try:
50 try:
51 import pip
51 import pip
52 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
52 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
53 if pip_version < (9, 0, 1) :
53 if pip_version < (9, 0, 1) :
54 pip_message = (
54 pip_message = (
55 'Your pip version is out of date, please install '
55 'Your pip version is out of date, please install '
56 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
56 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
57 else:
57 else:
58 # pip is new enough - it must be something else
58 # pip is new enough - it must be something else
59 pip_message = ''
59 pip_message = ''
60 except Exception:
60 except Exception:
61 pass
61 pass
62 error = """
62 error = """
63 Mercurial does not support Python older than 2.7.
63 Mercurial does not support Python older than 2.7.
64 Python {py} detected.
64 Python {py} detected.
65 {pip}
65 {pip}
66 """.format(py=sys.version_info, pip=pip_message)
66 """.format(py=sys.version_info, pip=pip_message)
67 printf(error, file=sys.stderr)
67 printf(error, file=sys.stderr)
68 sys.exit(1)
68 sys.exit(1)
69
69
70 # We don't yet officially support Python 3. But we want to allow developers to
70 # We don't yet officially support Python 3. But we want to allow developers to
71 # hack on. Detect and disallow running on Python 3 by default. But provide a
71 # hack on. Detect and disallow running on Python 3 by default. But provide a
72 # backdoor to enable working on Python 3.
72 # backdoor to enable working on Python 3.
73 if sys.version_info[0] != 2:
73 if sys.version_info[0] != 2:
74 badpython = True
74 badpython = True
75
75
76 # Allow Python 3 from source checkouts.
76 # Allow Python 3 from source checkouts.
77 if os.path.isdir('.hg'):
77 if os.path.isdir('.hg'):
78 badpython = False
78 badpython = False
79
79
80 if badpython:
80 if badpython:
81 error = """
81 error = """
82 Mercurial only supports Python 2.7.
82 Mercurial only supports Python 2.7.
83 Python {py} detected.
83 Python {py} detected.
84 Please re-run with Python 2.7.
84 Please re-run with Python 2.7.
85 """.format(py=sys.version_info)
85 """.format(py=sys.version_info)
86
86
87 printf(error, file=sys.stderr)
87 printf(error, file=sys.stderr)
88 sys.exit(1)
88 sys.exit(1)
89
89
90 # Solaris Python packaging brain damage
90 # Solaris Python packaging brain damage
91 try:
91 try:
92 import hashlib
92 import hashlib
93 sha = hashlib.sha1()
93 sha = hashlib.sha1()
94 except ImportError:
94 except ImportError:
95 try:
95 try:
96 import sha
96 import sha
97 sha.sha # silence unused import warning
97 sha.sha # silence unused import warning
98 except ImportError:
98 except ImportError:
99 raise SystemExit(
99 raise SystemExit(
100 "Couldn't import standard hashlib (incomplete Python install).")
100 "Couldn't import standard hashlib (incomplete Python install).")
101
101
102 try:
102 try:
103 import zlib
103 import zlib
104 zlib.compressobj # silence unused import warning
104 zlib.compressobj # silence unused import warning
105 except ImportError:
105 except ImportError:
106 raise SystemExit(
106 raise SystemExit(
107 "Couldn't import standard zlib (incomplete Python install).")
107 "Couldn't import standard zlib (incomplete Python install).")
108
108
109 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
109 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
110 isironpython = False
110 isironpython = False
111 try:
111 try:
112 isironpython = (platform.python_implementation()
112 isironpython = (platform.python_implementation()
113 .lower().find("ironpython") != -1)
113 .lower().find("ironpython") != -1)
114 except AttributeError:
114 except AttributeError:
115 pass
115 pass
116
116
117 if isironpython:
117 if isironpython:
118 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
118 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
119 else:
119 else:
120 try:
120 try:
121 import bz2
121 import bz2
122 bz2.BZ2Compressor # silence unused import warning
122 bz2.BZ2Compressor # silence unused import warning
123 except ImportError:
123 except ImportError:
124 raise SystemExit(
124 raise SystemExit(
125 "Couldn't import standard bz2 (incomplete Python install).")
125 "Couldn't import standard bz2 (incomplete Python install).")
126
126
127 ispypy = "PyPy" in sys.version
127 ispypy = "PyPy" in sys.version
128
128
129 import ctypes
129 import ctypes
130 import stat, subprocess, time
130 import stat, subprocess, time
131 import re
131 import re
132 import shutil
132 import shutil
133 import tempfile
133 import tempfile
134 from distutils import log
134 from distutils import log
135 # We have issues with setuptools on some platforms and builders. Until
135 # We have issues with setuptools on some platforms and builders. Until
136 # those are resolved, setuptools is opt-in except for platforms where
136 # those are resolved, setuptools is opt-in except for platforms where
137 # we don't have issues.
137 # we don't have issues.
138 issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
138 issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
139 if issetuptools:
139 if issetuptools:
140 from setuptools import setup
140 from setuptools import setup
141 else:
141 else:
142 from distutils.core import setup
142 from distutils.core import setup
143 from distutils.ccompiler import new_compiler
143 from distutils.ccompiler import new_compiler
144 from distutils.core import Command, Extension
144 from distutils.core import Command, Extension
145 from distutils.dist import Distribution
145 from distutils.dist import Distribution
146 from distutils.command.build import build
146 from distutils.command.build import build
147 from distutils.command.build_ext import build_ext
147 from distutils.command.build_ext import build_ext
148 from distutils.command.build_py import build_py
148 from distutils.command.build_py import build_py
149 from distutils.command.build_scripts import build_scripts
149 from distutils.command.build_scripts import build_scripts
150 from distutils.command.install import install
150 from distutils.command.install import install
151 from distutils.command.install_lib import install_lib
151 from distutils.command.install_lib import install_lib
152 from distutils.command.install_scripts import install_scripts
152 from distutils.command.install_scripts import install_scripts
153 from distutils.spawn import spawn, find_executable
153 from distutils.spawn import spawn, find_executable
154 from distutils import file_util
154 from distutils import file_util
155 from distutils.errors import (
155 from distutils.errors import (
156 CCompilerError,
156 CCompilerError,
157 DistutilsError,
157 DistutilsError,
158 DistutilsExecError,
158 DistutilsExecError,
159 )
159 )
160 from distutils.sysconfig import get_python_inc, get_config_var
160 from distutils.sysconfig import get_python_inc, get_config_var
161 from distutils.version import StrictVersion
161 from distutils.version import StrictVersion
162
162
163 def write_if_changed(path, content):
163 def write_if_changed(path, content):
164 """Write content to a file iff the content hasn't changed."""
164 """Write content to a file iff the content hasn't changed."""
165 if os.path.exists(path):
165 if os.path.exists(path):
166 with open(path, 'rb') as fh:
166 with open(path, 'rb') as fh:
167 current = fh.read()
167 current = fh.read()
168 else:
168 else:
169 current = b''
169 current = b''
170
170
171 if current != content:
171 if current != content:
172 with open(path, 'wb') as fh:
172 with open(path, 'wb') as fh:
173 fh.write(content)
173 fh.write(content)
174
174
175 scripts = ['hg']
175 scripts = ['hg']
176 if os.name == 'nt':
176 if os.name == 'nt':
177 # We remove hg.bat if we are able to build hg.exe.
177 # We remove hg.bat if we are able to build hg.exe.
178 scripts.append('contrib/win32/hg.bat')
178 scripts.append('contrib/win32/hg.bat')
179
179
180 def cancompile(cc, code):
180 def cancompile(cc, code):
181 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
181 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
182 devnull = oldstderr = None
182 devnull = oldstderr = None
183 try:
183 try:
184 fname = os.path.join(tmpdir, 'testcomp.c')
184 fname = os.path.join(tmpdir, 'testcomp.c')
185 f = open(fname, 'w')
185 f = open(fname, 'w')
186 f.write(code)
186 f.write(code)
187 f.close()
187 f.close()
188 # Redirect stderr to /dev/null to hide any error messages
188 # Redirect stderr to /dev/null to hide any error messages
189 # from the compiler.
189 # from the compiler.
190 # This will have to be changed if we ever have to check
190 # This will have to be changed if we ever have to check
191 # for a function on Windows.
191 # for a function on Windows.
192 devnull = open('/dev/null', 'w')
192 devnull = open('/dev/null', 'w')
193 oldstderr = os.dup(sys.stderr.fileno())
193 oldstderr = os.dup(sys.stderr.fileno())
194 os.dup2(devnull.fileno(), sys.stderr.fileno())
194 os.dup2(devnull.fileno(), sys.stderr.fileno())
195 objects = cc.compile([fname], output_dir=tmpdir)
195 objects = cc.compile([fname], output_dir=tmpdir)
196 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
196 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
197 return True
197 return True
198 except Exception:
198 except Exception:
199 return False
199 return False
200 finally:
200 finally:
201 if oldstderr is not None:
201 if oldstderr is not None:
202 os.dup2(oldstderr, sys.stderr.fileno())
202 os.dup2(oldstderr, sys.stderr.fileno())
203 if devnull is not None:
203 if devnull is not None:
204 devnull.close()
204 devnull.close()
205 shutil.rmtree(tmpdir)
205 shutil.rmtree(tmpdir)
206
206
207 # simplified version of distutils.ccompiler.CCompiler.has_function
207 # simplified version of distutils.ccompiler.CCompiler.has_function
208 # that actually removes its temporary files.
208 # that actually removes its temporary files.
209 def hasfunction(cc, funcname):
209 def hasfunction(cc, funcname):
210 code = 'int main(void) { %s(); }\n' % funcname
210 code = 'int main(void) { %s(); }\n' % funcname
211 return cancompile(cc, code)
211 return cancompile(cc, code)
212
212
213 def hasheader(cc, headername):
213 def hasheader(cc, headername):
214 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
214 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
215 return cancompile(cc, code)
215 return cancompile(cc, code)
216
216
217 # py2exe needs to be installed to work
217 # py2exe needs to be installed to work
218 try:
218 try:
219 import py2exe
219 import py2exe
220 py2exe.Distribution # silence unused import warning
220 py2exe.Distribution # silence unused import warning
221 py2exeloaded = True
221 py2exeloaded = True
222 # import py2exe's patched Distribution class
222 # import py2exe's patched Distribution class
223 from distutils.core import Distribution
223 from distutils.core import Distribution
224 except ImportError:
224 except ImportError:
225 py2exeloaded = False
225 py2exeloaded = False
226
226
227 def runcmd(cmd, env):
227 def runcmd(cmd, env):
228 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
228 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
229 stderr=subprocess.PIPE, env=env)
229 stderr=subprocess.PIPE, env=env)
230 out, err = p.communicate()
230 out, err = p.communicate()
231 return p.returncode, out, err
231 return p.returncode, out, err
232
232
233 class hgcommand(object):
233 class hgcommand(object):
234 def __init__(self, cmd, env):
234 def __init__(self, cmd, env):
235 self.cmd = cmd
235 self.cmd = cmd
236 self.env = env
236 self.env = env
237
237
238 def run(self, args):
238 def run(self, args):
239 cmd = self.cmd + args
239 cmd = self.cmd + args
240 returncode, out, err = runcmd(cmd, self.env)
240 returncode, out, err = runcmd(cmd, self.env)
241 err = filterhgerr(err)
241 err = filterhgerr(err)
242 if err or returncode != 0:
242 if err or returncode != 0:
243 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
243 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
244 printf(err, file=sys.stderr)
244 printf(err, file=sys.stderr)
245 return ''
245 return ''
246 return out
246 return out
247
247
248 def filterhgerr(err):
248 def filterhgerr(err):
249 # If root is executing setup.py, but the repository is owned by
249 # If root is executing setup.py, but the repository is owned by
250 # another user (as in "sudo python setup.py install") we will get
250 # another user (as in "sudo python setup.py install") we will get
251 # trust warnings since the .hg/hgrc file is untrusted. That is
251 # trust warnings since the .hg/hgrc file is untrusted. That is
252 # fine, we don't want to load it anyway. Python may warn about
252 # fine, we don't want to load it anyway. Python may warn about
253 # a missing __init__.py in mercurial/locale, we also ignore that.
253 # a missing __init__.py in mercurial/locale, we also ignore that.
254 err = [e for e in err.splitlines()
254 err = [e for e in err.splitlines()
255 if (not e.startswith(b'not trusting file')
255 if (not e.startswith(b'not trusting file')
256 and not e.startswith(b'warning: Not importing')
256 and not e.startswith(b'warning: Not importing')
257 and not e.startswith(b'obsolete feature not enabled')
257 and not e.startswith(b'obsolete feature not enabled')
258 and not e.startswith(b'devel-warn:'))]
258 and not e.startswith(b'devel-warn:'))]
259 return b'\n'.join(b' ' + e for e in err)
259 return b'\n'.join(b' ' + e for e in err)
260
260
261 def findhg():
261 def findhg():
262 """Try to figure out how we should invoke hg for examining the local
262 """Try to figure out how we should invoke hg for examining the local
263 repository contents.
263 repository contents.
264
264
265 Returns an hgcommand object."""
265 Returns an hgcommand object."""
266 # By default, prefer the "hg" command in the user's path. This was
266 # By default, prefer the "hg" command in the user's path. This was
267 # presumably the hg command that the user used to create this repository.
267 # presumably the hg command that the user used to create this repository.
268 #
268 #
269 # This repository may require extensions or other settings that would not
269 # This repository may require extensions or other settings that would not
270 # be enabled by running the hg script directly from this local repository.
270 # be enabled by running the hg script directly from this local repository.
271 hgenv = os.environ.copy()
271 hgenv = os.environ.copy()
272 # Use HGPLAIN to disable hgrc settings that would change output formatting,
272 # Use HGPLAIN to disable hgrc settings that would change output formatting,
273 # and disable localization for the same reasons.
273 # and disable localization for the same reasons.
274 hgenv['HGPLAIN'] = '1'
274 hgenv['HGPLAIN'] = '1'
275 hgenv['LANGUAGE'] = 'C'
275 hgenv['LANGUAGE'] = 'C'
276 hgcmd = ['hg']
276 hgcmd = ['hg']
277 # Run a simple "hg log" command just to see if using hg from the user's
277 # Run a simple "hg log" command just to see if using hg from the user's
278 # path works and can successfully interact with this repository.
278 # path works and can successfully interact with this repository.
279 check_cmd = ['log', '-r.', '-Ttest']
279 check_cmd = ['log', '-r.', '-Ttest']
280 try:
280 try:
281 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
281 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
282 except EnvironmentError:
282 except EnvironmentError:
283 retcode = -1
283 retcode = -1
284 if retcode == 0 and not filterhgerr(err):
284 if retcode == 0 and not filterhgerr(err):
285 return hgcommand(hgcmd, hgenv)
285 return hgcommand(hgcmd, hgenv)
286
286
287 # Fall back to trying the local hg installation.
287 # Fall back to trying the local hg installation.
288 hgenv = localhgenv()
288 hgenv = localhgenv()
289 hgcmd = [sys.executable, 'hg']
289 hgcmd = [sys.executable, 'hg']
290 try:
290 try:
291 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
291 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
292 except EnvironmentError:
292 except EnvironmentError:
293 retcode = -1
293 retcode = -1
294 if retcode == 0 and not filterhgerr(err):
294 if retcode == 0 and not filterhgerr(err):
295 return hgcommand(hgcmd, hgenv)
295 return hgcommand(hgcmd, hgenv)
296
296
297 raise SystemExit('Unable to find a working hg binary to extract the '
297 raise SystemExit('Unable to find a working hg binary to extract the '
298 'version from the repository tags')
298 'version from the repository tags')
299
299
300 def localhgenv():
300 def localhgenv():
301 """Get an environment dictionary to use for invoking or importing
301 """Get an environment dictionary to use for invoking or importing
302 mercurial from the local repository."""
302 mercurial from the local repository."""
303 # Execute hg out of this directory with a custom environment which takes
303 # Execute hg out of this directory with a custom environment which takes
304 # care to not use any hgrc files and do no localization.
304 # care to not use any hgrc files and do no localization.
305 env = {'HGMODULEPOLICY': 'py',
305 env = {'HGMODULEPOLICY': 'py',
306 'HGRCPATH': '',
306 'HGRCPATH': '',
307 'LANGUAGE': 'C',
307 'LANGUAGE': 'C',
308 'PATH': ''} # make pypi modules that use os.environ['PATH'] happy
308 'PATH': ''} # make pypi modules that use os.environ['PATH'] happy
309 if 'LD_LIBRARY_PATH' in os.environ:
309 if 'LD_LIBRARY_PATH' in os.environ:
310 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
310 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
311 if 'SystemRoot' in os.environ:
311 if 'SystemRoot' in os.environ:
312 # SystemRoot is required by Windows to load various DLLs. See:
312 # SystemRoot is required by Windows to load various DLLs. See:
313 # https://bugs.python.org/issue13524#msg148850
313 # https://bugs.python.org/issue13524#msg148850
314 env['SystemRoot'] = os.environ['SystemRoot']
314 env['SystemRoot'] = os.environ['SystemRoot']
315 return env
315 return env
316
316
317 version = ''
317 version = ''
318
318
319 if os.path.isdir('.hg'):
319 if os.path.isdir('.hg'):
320 hg = findhg()
320 hg = findhg()
321 cmd = ['log', '-r', '.', '--template', '{tags}\n']
321 cmd = ['log', '-r', '.', '--template', '{tags}\n']
322 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
322 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
323 hgid = sysstr(hg.run(['id', '-i'])).strip()
323 hgid = sysstr(hg.run(['id', '-i'])).strip()
324 if not hgid:
324 if not hgid:
325 # Bail out if hg is having problems interacting with this repository,
325 # Bail out if hg is having problems interacting with this repository,
326 # rather than falling through and producing a bogus version number.
326 # rather than falling through and producing a bogus version number.
327 # Continuing with an invalid version number will break extensions
327 # Continuing with an invalid version number will break extensions
328 # that define minimumhgversion.
328 # that define minimumhgversion.
329 raise SystemExit('Unable to determine hg version from local repository')
329 raise SystemExit('Unable to determine hg version from local repository')
330 if numerictags: # tag(s) found
330 if numerictags: # tag(s) found
331 version = numerictags[-1]
331 version = numerictags[-1]
332 if hgid.endswith('+'): # propagate the dirty status to the tag
332 if hgid.endswith('+'): # propagate the dirty status to the tag
333 version += '+'
333 version += '+'
334 else: # no tag found
334 else: # no tag found
335 ltagcmd = ['parents', '--template', '{latesttag}']
335 ltagcmd = ['parents', '--template', '{latesttag}']
336 ltag = sysstr(hg.run(ltagcmd))
336 ltag = sysstr(hg.run(ltagcmd))
337 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
337 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
338 changessince = len(hg.run(changessincecmd).splitlines())
338 changessince = len(hg.run(changessincecmd).splitlines())
339 version = '%s+%s-%s' % (ltag, changessince, hgid)
339 version = '%s+%s-%s' % (ltag, changessince, hgid)
340 if version.endswith('+'):
340 if version.endswith('+'):
341 version += time.strftime('%Y%m%d')
341 version += time.strftime('%Y%m%d')
342 elif os.path.exists('.hg_archival.txt'):
342 elif os.path.exists('.hg_archival.txt'):
343 kw = dict([[t.strip() for t in l.split(':', 1)]
343 kw = dict([[t.strip() for t in l.split(':', 1)]
344 for l in open('.hg_archival.txt')])
344 for l in open('.hg_archival.txt')])
345 if 'tag' in kw:
345 if 'tag' in kw:
346 version = kw['tag']
346 version = kw['tag']
347 elif 'latesttag' in kw:
347 elif 'latesttag' in kw:
348 if 'changessincelatesttag' in kw:
348 if 'changessincelatesttag' in kw:
349 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
349 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
350 else:
350 else:
351 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
351 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
352 else:
352 else:
353 version = kw.get('node', '')[:12]
353 version = kw.get('node', '')[:12]
354
354
355 if version:
355 if version:
356 versionb = version
356 versionb = version
357 if not isinstance(versionb, bytes):
357 if not isinstance(versionb, bytes):
358 versionb = versionb.encode('ascii')
358 versionb = versionb.encode('ascii')
359
359
360 write_if_changed('mercurial/__version__.py', b''.join([
360 write_if_changed('mercurial/__version__.py', b''.join([
361 b'# this file is autogenerated by setup.py\n'
361 b'# this file is autogenerated by setup.py\n'
362 b'version = "%s"\n' % versionb,
362 b'version = "%s"\n' % versionb,
363 ]))
363 ]))
364
364
365 try:
365 try:
366 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
366 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
367 os.environ['HGMODULEPOLICY'] = 'py'
367 os.environ['HGMODULEPOLICY'] = 'py'
368 from mercurial import __version__
368 from mercurial import __version__
369 version = __version__.version
369 version = __version__.version
370 except ImportError:
370 except ImportError:
371 version = 'unknown'
371 version = 'unknown'
372 finally:
372 finally:
373 if oldpolicy is None:
373 if oldpolicy is None:
374 del os.environ['HGMODULEPOLICY']
374 del os.environ['HGMODULEPOLICY']
375 else:
375 else:
376 os.environ['HGMODULEPOLICY'] = oldpolicy
376 os.environ['HGMODULEPOLICY'] = oldpolicy
377
377
378 class hgbuild(build):
378 class hgbuild(build):
379 # Insert hgbuildmo first so that files in mercurial/locale/ are found
379 # Insert hgbuildmo first so that files in mercurial/locale/ are found
380 # when build_py is run next.
380 # when build_py is run next.
381 sub_commands = [('build_mo', None)] + build.sub_commands
381 sub_commands = [('build_mo', None)] + build.sub_commands
382
382
383 class hgbuildmo(build):
383 class hgbuildmo(build):
384
384
385 description = "build translations (.mo files)"
385 description = "build translations (.mo files)"
386
386
387 def run(self):
387 def run(self):
388 if not find_executable('msgfmt'):
388 if not find_executable('msgfmt'):
389 self.warn("could not find msgfmt executable, no translations "
389 self.warn("could not find msgfmt executable, no translations "
390 "will be built")
390 "will be built")
391 return
391 return
392
392
393 podir = 'i18n'
393 podir = 'i18n'
394 if not os.path.isdir(podir):
394 if not os.path.isdir(podir):
395 self.warn("could not find %s/ directory" % podir)
395 self.warn("could not find %s/ directory" % podir)
396 return
396 return
397
397
398 join = os.path.join
398 join = os.path.join
399 for po in os.listdir(podir):
399 for po in os.listdir(podir):
400 if not po.endswith('.po'):
400 if not po.endswith('.po'):
401 continue
401 continue
402 pofile = join(podir, po)
402 pofile = join(podir, po)
403 modir = join('locale', po[:-3], 'LC_MESSAGES')
403 modir = join('locale', po[:-3], 'LC_MESSAGES')
404 mofile = join(modir, 'hg.mo')
404 mofile = join(modir, 'hg.mo')
405 mobuildfile = join('mercurial', mofile)
405 mobuildfile = join('mercurial', mofile)
406 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
406 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
407 if sys.platform != 'sunos5':
407 if sys.platform != 'sunos5':
408 # msgfmt on Solaris does not know about -c
408 # msgfmt on Solaris does not know about -c
409 cmd.append('-c')
409 cmd.append('-c')
410 self.mkpath(join('mercurial', modir))
410 self.mkpath(join('mercurial', modir))
411 self.make_file([pofile], mobuildfile, spawn, (cmd,))
411 self.make_file([pofile], mobuildfile, spawn, (cmd,))
412
412
413
413
414 class hgdist(Distribution):
414 class hgdist(Distribution):
415 pure = False
415 pure = False
416 cffi = ispypy
416 cffi = ispypy
417
417
418 global_options = Distribution.global_options + \
418 global_options = Distribution.global_options + \
419 [('pure', None, "use pure (slow) Python "
419 [('pure', None, "use pure (slow) Python "
420 "code instead of C extensions"),
420 "code instead of C extensions"),
421 ]
421 ]
422
422
423 def has_ext_modules(self):
423 def has_ext_modules(self):
424 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
424 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
425 # too late for some cases
425 # too late for some cases
426 return not self.pure and Distribution.has_ext_modules(self)
426 return not self.pure and Distribution.has_ext_modules(self)
427
427
428 # This is ugly as a one-liner. So use a variable.
428 # This is ugly as a one-liner. So use a variable.
429 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
429 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
430 buildextnegops['no-zstd'] = 'zstd'
430 buildextnegops['no-zstd'] = 'zstd'
431
431
432 class hgbuildext(build_ext):
432 class hgbuildext(build_ext):
433 user_options = build_ext.user_options + [
433 user_options = build_ext.user_options + [
434 ('zstd', None, 'compile zstd bindings [default]'),
434 ('zstd', None, 'compile zstd bindings [default]'),
435 ('no-zstd', None, 'do not compile zstd bindings'),
435 ('no-zstd', None, 'do not compile zstd bindings'),
436 ]
436 ]
437
437
438 boolean_options = build_ext.boolean_options + ['zstd']
438 boolean_options = build_ext.boolean_options + ['zstd']
439 negative_opt = buildextnegops
439 negative_opt = buildextnegops
440
440
441 def initialize_options(self):
441 def initialize_options(self):
442 self.zstd = True
442 self.zstd = True
443 return build_ext.initialize_options(self)
443 return build_ext.initialize_options(self)
444
444
445 def build_extensions(self):
445 def build_extensions(self):
446 # Filter out zstd if disabled via argument.
446 # Filter out zstd if disabled via argument.
447 if not self.zstd:
447 if not self.zstd:
448 self.extensions = [e for e in self.extensions
448 self.extensions = [e for e in self.extensions
449 if e.name != 'mercurial.zstd']
449 if e.name != 'mercurial.zstd']
450
450
451 return build_ext.build_extensions(self)
451 return build_ext.build_extensions(self)
452
452
453 def build_extension(self, ext):
453 def build_extension(self, ext):
454 try:
454 try:
455 build_ext.build_extension(self, ext)
455 build_ext.build_extension(self, ext)
456 except CCompilerError:
456 except CCompilerError:
457 if not getattr(ext, 'optional', False):
457 if not getattr(ext, 'optional', False):
458 raise
458 raise
459 log.warn("Failed to build optional extension '%s' (skipping)",
459 log.warn("Failed to build optional extension '%s' (skipping)",
460 ext.name)
460 ext.name)
461
461
462 class hgbuildscripts(build_scripts):
462 class hgbuildscripts(build_scripts):
463 def run(self):
463 def run(self):
464 if os.name != 'nt' or self.distribution.pure:
464 if os.name != 'nt' or self.distribution.pure:
465 return build_scripts.run(self)
465 return build_scripts.run(self)
466
466
467 exebuilt = False
467 exebuilt = False
468 try:
468 try:
469 self.run_command('build_hgexe')
469 self.run_command('build_hgexe')
470 exebuilt = True
470 exebuilt = True
471 except (DistutilsError, CCompilerError):
471 except (DistutilsError, CCompilerError):
472 log.warn('failed to build optional hg.exe')
472 log.warn('failed to build optional hg.exe')
473
473
474 if exebuilt:
474 if exebuilt:
475 # Copying hg.exe to the scripts build directory ensures it is
475 # Copying hg.exe to the scripts build directory ensures it is
476 # installed by the install_scripts command.
476 # installed by the install_scripts command.
477 hgexecommand = self.get_finalized_command('build_hgexe')
477 hgexecommand = self.get_finalized_command('build_hgexe')
478 dest = os.path.join(self.build_dir, 'hg.exe')
478 dest = os.path.join(self.build_dir, 'hg.exe')
479 self.mkpath(self.build_dir)
479 self.mkpath(self.build_dir)
480 self.copy_file(hgexecommand.hgexepath, dest)
480 self.copy_file(hgexecommand.hgexepath, dest)
481
481
482 # Remove hg.bat because it is redundant with hg.exe.
482 # Remove hg.bat because it is redundant with hg.exe.
483 self.scripts.remove('contrib/win32/hg.bat')
483 self.scripts.remove('contrib/win32/hg.bat')
484
484
485 return build_scripts.run(self)
485 return build_scripts.run(self)
486
486
487 class hgbuildpy(build_py):
487 class hgbuildpy(build_py):
488 def finalize_options(self):
488 def finalize_options(self):
489 build_py.finalize_options(self)
489 build_py.finalize_options(self)
490
490
491 if self.distribution.pure:
491 if self.distribution.pure:
492 self.distribution.ext_modules = []
492 self.distribution.ext_modules = []
493 elif self.distribution.cffi:
493 elif self.distribution.cffi:
494 from mercurial.cffi import (
494 from mercurial.cffi import (
495 bdiffbuild,
495 bdiffbuild,
496 mpatchbuild,
496 mpatchbuild,
497 )
497 )
498 exts = [mpatchbuild.ffi.distutils_extension(),
498 exts = [mpatchbuild.ffi.distutils_extension(),
499 bdiffbuild.ffi.distutils_extension()]
499 bdiffbuild.ffi.distutils_extension()]
500 # cffi modules go here
500 # cffi modules go here
501 if sys.platform == 'darwin':
501 if sys.platform == 'darwin':
502 from mercurial.cffi import osutilbuild
502 from mercurial.cffi import osutilbuild
503 exts.append(osutilbuild.ffi.distutils_extension())
503 exts.append(osutilbuild.ffi.distutils_extension())
504 self.distribution.ext_modules = exts
504 self.distribution.ext_modules = exts
505 else:
505 else:
506 h = os.path.join(get_python_inc(), 'Python.h')
506 h = os.path.join(get_python_inc(), 'Python.h')
507 if not os.path.exists(h):
507 if not os.path.exists(h):
508 raise SystemExit('Python headers are required to build '
508 raise SystemExit('Python headers are required to build '
509 'Mercurial but weren\'t found in %s' % h)
509 'Mercurial but weren\'t found in %s' % h)
510
510
511 def run(self):
511 def run(self):
512 basepath = os.path.join(self.build_lib, 'mercurial')
512 basepath = os.path.join(self.build_lib, 'mercurial')
513 self.mkpath(basepath)
513 self.mkpath(basepath)
514
514
515 if self.distribution.pure:
515 if self.distribution.pure:
516 modulepolicy = 'py'
516 modulepolicy = 'py'
517 elif self.build_lib == '.':
517 elif self.build_lib == '.':
518 # in-place build should run without rebuilding C extensions
518 # in-place build should run without rebuilding C extensions
519 modulepolicy = 'allow'
519 modulepolicy = 'allow'
520 else:
520 else:
521 modulepolicy = 'c'
521 modulepolicy = 'c'
522
522
523 content = b''.join([
523 content = b''.join([
524 b'# this file is autogenerated by setup.py\n',
524 b'# this file is autogenerated by setup.py\n',
525 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
525 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
526 ])
526 ])
527 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'),
527 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'),
528 content)
528 content)
529
529
530 build_py.run(self)
530 build_py.run(self)
531
531
532 class buildhgextindex(Command):
532 class buildhgextindex(Command):
533 description = 'generate prebuilt index of hgext (for frozen package)'
533 description = 'generate prebuilt index of hgext (for frozen package)'
534 user_options = []
534 user_options = []
535 _indexfilename = 'hgext/__index__.py'
535 _indexfilename = 'hgext/__index__.py'
536
536
537 def initialize_options(self):
537 def initialize_options(self):
538 pass
538 pass
539
539
540 def finalize_options(self):
540 def finalize_options(self):
541 pass
541 pass
542
542
543 def run(self):
543 def run(self):
544 if os.path.exists(self._indexfilename):
544 if os.path.exists(self._indexfilename):
545 with open(self._indexfilename, 'w') as f:
545 with open(self._indexfilename, 'w') as f:
546 f.write('# empty\n')
546 f.write('# empty\n')
547
547
548 # here no extension enabled, disabled() lists up everything
548 # here no extension enabled, disabled() lists up everything
549 code = ('import pprint; from mercurial import extensions; '
549 code = ('import pprint; from mercurial import extensions; '
550 'pprint.pprint(extensions.disabled())')
550 'pprint.pprint(extensions.disabled())')
551 returncode, out, err = runcmd([sys.executable, '-c', code],
551 returncode, out, err = runcmd([sys.executable, '-c', code],
552 localhgenv())
552 localhgenv())
553 if err or returncode != 0:
553 if err or returncode != 0:
554 raise DistutilsExecError(err)
554 raise DistutilsExecError(err)
555
555
556 with open(self._indexfilename, 'w') as f:
556 with open(self._indexfilename, 'w') as f:
557 f.write('# this file is autogenerated by setup.py\n')
557 f.write('# this file is autogenerated by setup.py\n')
558 f.write('docs = ')
558 f.write('docs = ')
559 f.write(out)
559 f.write(out)
560
560
561 class buildhgexe(build_ext):
561 class buildhgexe(build_ext):
562 description = 'compile hg.exe from mercurial/exewrapper.c'
562 description = 'compile hg.exe from mercurial/exewrapper.c'
563 user_options = build_ext.user_options + [
563 user_options = build_ext.user_options + [
564 ('long-paths-support', None, 'enable support for long paths on '
564 ('long-paths-support', None, 'enable support for long paths on '
565 'Windows (off by default and '
565 'Windows (off by default and '
566 'experimental)'),
566 'experimental)'),
567 ]
567 ]
568
568
569 LONG_PATHS_MANIFEST = """
569 LONG_PATHS_MANIFEST = """
570 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
570 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
571 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
571 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
572 <application>
572 <application>
573 <windowsSettings
573 <windowsSettings
574 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
574 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
575 <ws2:longPathAware>true</ws2:longPathAware>
575 <ws2:longPathAware>true</ws2:longPathAware>
576 </windowsSettings>
576 </windowsSettings>
577 </application>
577 </application>
578 </assembly>"""
578 </assembly>"""
579
579
580 def initialize_options(self):
580 def initialize_options(self):
581 build_ext.initialize_options(self)
581 build_ext.initialize_options(self)
582 self.long_paths_support = False
582 self.long_paths_support = False
583
583
584 def build_extensions(self):
584 def build_extensions(self):
585 if os.name != 'nt':
585 if os.name != 'nt':
586 return
586 return
587 if isinstance(self.compiler, HackedMingw32CCompiler):
587 if isinstance(self.compiler, HackedMingw32CCompiler):
588 self.compiler.compiler_so = self.compiler.compiler # no -mdll
588 self.compiler.compiler_so = self.compiler.compiler # no -mdll
589 self.compiler.dll_libraries = [] # no -lmsrvc90
589 self.compiler.dll_libraries = [] # no -lmsrvc90
590
590
591 # Different Python installs can have different Python library
591 # Different Python installs can have different Python library
592 # names. e.g. the official CPython distribution uses pythonXY.dll
592 # names. e.g. the official CPython distribution uses pythonXY.dll
593 # and MinGW uses libpythonX.Y.dll.
593 # and MinGW uses libpythonX.Y.dll.
594 _kernel32 = ctypes.windll.kernel32
594 _kernel32 = ctypes.windll.kernel32
595 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
595 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
596 ctypes.c_void_p,
596 ctypes.c_void_p,
597 ctypes.c_ulong]
597 ctypes.c_ulong]
598 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
598 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
599 size = 1000
599 size = 1000
600 buf = ctypes.create_string_buffer(size + 1)
600 buf = ctypes.create_string_buffer(size + 1)
601 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
601 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
602 size)
602 size)
603
603
604 if filelen > 0 and filelen != size:
604 if filelen > 0 and filelen != size:
605 dllbasename = os.path.basename(buf.value)
605 dllbasename = os.path.basename(buf.value)
606 if not dllbasename.lower().endswith('.dll'):
606 if not dllbasename.lower().endswith('.dll'):
607 raise SystemExit('Python DLL does not end with .dll: %s' %
607 raise SystemExit('Python DLL does not end with .dll: %s' %
608 dllbasename)
608 dllbasename)
609 pythonlib = dllbasename[:-4]
609 pythonlib = dllbasename[:-4]
610 else:
610 else:
611 log.warn('could not determine Python DLL filename; '
611 log.warn('could not determine Python DLL filename; '
612 'assuming pythonXY')
612 'assuming pythonXY')
613
613
614 hv = sys.hexversion
614 hv = sys.hexversion
615 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
615 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
616
616
617 log.info('using %s as Python library name' % pythonlib)
617 log.info('using %s as Python library name' % pythonlib)
618 with open('mercurial/hgpythonlib.h', 'wb') as f:
618 with open('mercurial/hgpythonlib.h', 'wb') as f:
619 f.write('/* this file is autogenerated by setup.py */\n')
619 f.write('/* this file is autogenerated by setup.py */\n')
620 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
620 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
621 objects = self.compiler.compile(['mercurial/exewrapper.c'],
621 objects = self.compiler.compile(['mercurial/exewrapper.c'],
622 output_dir=self.build_temp)
622 output_dir=self.build_temp)
623 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
623 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
624 self.hgtarget = os.path.join(dir, 'hg')
624 self.hgtarget = os.path.join(dir, 'hg')
625 self.compiler.link_executable(objects, self.hgtarget,
625 self.compiler.link_executable(objects, self.hgtarget,
626 libraries=[],
626 libraries=[],
627 output_dir=self.build_temp)
627 output_dir=self.build_temp)
628 if self.long_paths_support:
628 if self.long_paths_support:
629 self.addlongpathsmanifest()
629 self.addlongpathsmanifest()
630
630
631 def addlongpathsmanifest(self):
631 def addlongpathsmanifest(self):
632 """Add manifest pieces so that hg.exe understands long paths
632 """Add manifest pieces so that hg.exe understands long paths
633
633
634 This is an EXPERIMENTAL feature, use with care.
634 This is an EXPERIMENTAL feature, use with care.
635 To enable long paths support, one needs to do two things:
635 To enable long paths support, one needs to do two things:
636 - build Mercurial with --long-paths-support option
636 - build Mercurial with --long-paths-support option
637 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
637 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
638 LongPathsEnabled to have value 1.
638 LongPathsEnabled to have value 1.
639
639
640 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
640 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
641 it happens because Mercurial uses mt.exe circa 2008, which is not
641 it happens because Mercurial uses mt.exe circa 2008, which is not
642 yet aware of long paths support in the manifest (I think so at least).
642 yet aware of long paths support in the manifest (I think so at least).
643 This does not stop mt.exe from embedding/merging the XML properly.
643 This does not stop mt.exe from embedding/merging the XML properly.
644
644
645 Why resource #1 should be used for .exe manifests? I don't know and
645 Why resource #1 should be used for .exe manifests? I don't know and
646 wasn't able to find an explanation for mortals. But it seems to work.
646 wasn't able to find an explanation for mortals. But it seems to work.
647 """
647 """
648 exefname = self.compiler.executable_filename(self.hgtarget)
648 exefname = self.compiler.executable_filename(self.hgtarget)
649 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
649 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
650 os.close(fdauto)
650 os.close(fdauto)
651 with open(manfname, 'w') as f:
651 with open(manfname, 'w') as f:
652 f.write(self.LONG_PATHS_MANIFEST)
652 f.write(self.LONG_PATHS_MANIFEST)
653 log.info("long paths manifest is written to '%s'" % manfname)
653 log.info("long paths manifest is written to '%s'" % manfname)
654 inputresource = '-inputresource:%s;#1' % exefname
654 inputresource = '-inputresource:%s;#1' % exefname
655 outputresource = '-outputresource:%s;#1' % exefname
655 outputresource = '-outputresource:%s;#1' % exefname
656 log.info("running mt.exe to update hg.exe's manifest in-place")
656 log.info("running mt.exe to update hg.exe's manifest in-place")
657 # supplying both -manifest and -inputresource to mt.exe makes
657 # supplying both -manifest and -inputresource to mt.exe makes
658 # it merge the embedded and supplied manifests in the -outputresource
658 # it merge the embedded and supplied manifests in the -outputresource
659 self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
659 self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
660 inputresource, outputresource])
660 inputresource, outputresource])
661 log.info("done updating hg.exe's manifest")
661 log.info("done updating hg.exe's manifest")
662 os.remove(manfname)
662 os.remove(manfname)
663
663
664 @property
664 @property
665 def hgexepath(self):
665 def hgexepath(self):
666 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
666 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
667 return os.path.join(self.build_temp, dir, 'hg.exe')
667 return os.path.join(self.build_temp, dir, 'hg.exe')
668
668
669 class hginstall(install):
669 class hginstall(install):
670
670
671 user_options = install.user_options + [
671 user_options = install.user_options + [
672 ('old-and-unmanageable', None,
672 ('old-and-unmanageable', None,
673 'noop, present for eggless setuptools compat'),
673 'noop, present for eggless setuptools compat'),
674 ('single-version-externally-managed', None,
674 ('single-version-externally-managed', None,
675 'noop, present for eggless setuptools compat'),
675 'noop, present for eggless setuptools compat'),
676 ]
676 ]
677
677
678 # Also helps setuptools not be sad while we refuse to create eggs.
678 # Also helps setuptools not be sad while we refuse to create eggs.
679 single_version_externally_managed = True
679 single_version_externally_managed = True
680
680
681 def get_sub_commands(self):
681 def get_sub_commands(self):
682 # Screen out egg related commands to prevent egg generation. But allow
682 # Screen out egg related commands to prevent egg generation. But allow
683 # mercurial.egg-info generation, since that is part of modern
683 # mercurial.egg-info generation, since that is part of modern
684 # packaging.
684 # packaging.
685 excl = set(['bdist_egg'])
685 excl = set(['bdist_egg'])
686 return filter(lambda x: x not in excl, install.get_sub_commands(self))
686 return filter(lambda x: x not in excl, install.get_sub_commands(self))
687
687
688 class hginstalllib(install_lib):
688 class hginstalllib(install_lib):
689 '''
689 '''
690 This is a specialization of install_lib that replaces the copy_file used
690 This is a specialization of install_lib that replaces the copy_file used
691 there so that it supports setting the mode of files after copying them,
691 there so that it supports setting the mode of files after copying them,
692 instead of just preserving the mode that the files originally had. If your
692 instead of just preserving the mode that the files originally had. If your
693 system has a umask of something like 027, preserving the permissions when
693 system has a umask of something like 027, preserving the permissions when
694 copying will lead to a broken install.
694 copying will lead to a broken install.
695
695
696 Note that just passing keep_permissions=False to copy_file would be
696 Note that just passing keep_permissions=False to copy_file would be
697 insufficient, as it might still be applying a umask.
697 insufficient, as it might still be applying a umask.
698 '''
698 '''
699
699
700 def run(self):
700 def run(self):
701 realcopyfile = file_util.copy_file
701 realcopyfile = file_util.copy_file
702 def copyfileandsetmode(*args, **kwargs):
702 def copyfileandsetmode(*args, **kwargs):
703 src, dst = args[0], args[1]
703 src, dst = args[0], args[1]
704 dst, copied = realcopyfile(*args, **kwargs)
704 dst, copied = realcopyfile(*args, **kwargs)
705 if copied:
705 if copied:
706 st = os.stat(src)
706 st = os.stat(src)
707 # Persist executable bit (apply it to group and other if user
707 # Persist executable bit (apply it to group and other if user
708 # has it)
708 # has it)
709 if st[stat.ST_MODE] & stat.S_IXUSR:
709 if st[stat.ST_MODE] & stat.S_IXUSR:
710 setmode = int('0755', 8)
710 setmode = int('0755', 8)
711 else:
711 else:
712 setmode = int('0644', 8)
712 setmode = int('0644', 8)
713 m = stat.S_IMODE(st[stat.ST_MODE])
713 m = stat.S_IMODE(st[stat.ST_MODE])
714 m = (m & ~int('0777', 8)) | setmode
714 m = (m & ~int('0777', 8)) | setmode
715 os.chmod(dst, m)
715 os.chmod(dst, m)
716 file_util.copy_file = copyfileandsetmode
716 file_util.copy_file = copyfileandsetmode
717 try:
717 try:
718 install_lib.run(self)
718 install_lib.run(self)
719 finally:
719 finally:
720 file_util.copy_file = realcopyfile
720 file_util.copy_file = realcopyfile
721
721
722 class hginstallscripts(install_scripts):
722 class hginstallscripts(install_scripts):
723 '''
723 '''
724 This is a specialization of install_scripts that replaces the @LIBDIR@ with
724 This is a specialization of install_scripts that replaces the @LIBDIR@ with
725 the configured directory for modules. If possible, the path is made relative
725 the configured directory for modules. If possible, the path is made relative
726 to the directory for scripts.
726 to the directory for scripts.
727 '''
727 '''
728
728
729 def initialize_options(self):
729 def initialize_options(self):
730 install_scripts.initialize_options(self)
730 install_scripts.initialize_options(self)
731
731
732 self.install_lib = None
732 self.install_lib = None
733
733
734 def finalize_options(self):
734 def finalize_options(self):
735 install_scripts.finalize_options(self)
735 install_scripts.finalize_options(self)
736 self.set_undefined_options('install',
736 self.set_undefined_options('install',
737 ('install_lib', 'install_lib'))
737 ('install_lib', 'install_lib'))
738
738
739 def run(self):
739 def run(self):
740 install_scripts.run(self)
740 install_scripts.run(self)
741
741
742 # It only makes sense to replace @LIBDIR@ with the install path if
742 # It only makes sense to replace @LIBDIR@ with the install path if
743 # the install path is known. For wheels, the logic below calculates
743 # the install path is known. For wheels, the logic below calculates
744 # the libdir to be "../..". This is because the internal layout of a
744 # the libdir to be "../..". This is because the internal layout of a
745 # wheel archive looks like:
745 # wheel archive looks like:
746 #
746 #
747 # mercurial-3.6.1.data/scripts/hg
747 # mercurial-3.6.1.data/scripts/hg
748 # mercurial/__init__.py
748 # mercurial/__init__.py
749 #
749 #
750 # When installing wheels, the subdirectories of the "<pkg>.data"
750 # When installing wheels, the subdirectories of the "<pkg>.data"
751 # directory are translated to system local paths and files therein
751 # directory are translated to system local paths and files therein
752 # are copied in place. The mercurial/* files are installed into the
752 # are copied in place. The mercurial/* files are installed into the
753 # site-packages directory. However, the site-packages directory
753 # site-packages directory. However, the site-packages directory
754 # isn't known until wheel install time. This means we have no clue
754 # isn't known until wheel install time. This means we have no clue
755 # at wheel generation time what the installed site-packages directory
755 # at wheel generation time what the installed site-packages directory
756 # will be. And, wheels don't appear to provide the ability to register
756 # will be. And, wheels don't appear to provide the ability to register
757 # custom code to run during wheel installation. This all means that
757 # custom code to run during wheel installation. This all means that
758 # we can't reliably set the libdir in wheels: the default behavior
758 # we can't reliably set the libdir in wheels: the default behavior
759 # of looking in sys.path must do.
759 # of looking in sys.path must do.
760
760
761 if (os.path.splitdrive(self.install_dir)[0] !=
761 if (os.path.splitdrive(self.install_dir)[0] !=
762 os.path.splitdrive(self.install_lib)[0]):
762 os.path.splitdrive(self.install_lib)[0]):
763 # can't make relative paths from one drive to another, so use an
763 # can't make relative paths from one drive to another, so use an
764 # absolute path instead
764 # absolute path instead
765 libdir = self.install_lib
765 libdir = self.install_lib
766 else:
766 else:
767 common = os.path.commonprefix((self.install_dir, self.install_lib))
767 common = os.path.commonprefix((self.install_dir, self.install_lib))
768 rest = self.install_dir[len(common):]
768 rest = self.install_dir[len(common):]
769 uplevel = len([n for n in os.path.split(rest) if n])
769 uplevel = len([n for n in os.path.split(rest) if n])
770
770
771 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
771 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
772
772
773 for outfile in self.outfiles:
773 for outfile in self.outfiles:
774 with open(outfile, 'rb') as fp:
774 with open(outfile, 'rb') as fp:
775 data = fp.read()
775 data = fp.read()
776
776
777 # skip binary files
777 # skip binary files
778 if b'\0' in data:
778 if b'\0' in data:
779 continue
779 continue
780
780
781 # During local installs, the shebang will be rewritten to the final
781 # During local installs, the shebang will be rewritten to the final
782 # install path. During wheel packaging, the shebang has a special
782 # install path. During wheel packaging, the shebang has a special
783 # value.
783 # value.
784 if data.startswith(b'#!python'):
784 if data.startswith(b'#!python'):
785 log.info('not rewriting @LIBDIR@ in %s because install path '
785 log.info('not rewriting @LIBDIR@ in %s because install path '
786 'not known' % outfile)
786 'not known' % outfile)
787 continue
787 continue
788
788
789 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
789 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
790 with open(outfile, 'wb') as fp:
790 with open(outfile, 'wb') as fp:
791 fp.write(data)
791 fp.write(data)
792
792
793 cmdclass = {'build': hgbuild,
793 cmdclass = {'build': hgbuild,
794 'build_mo': hgbuildmo,
794 'build_mo': hgbuildmo,
795 'build_ext': hgbuildext,
795 'build_ext': hgbuildext,
796 'build_py': hgbuildpy,
796 'build_py': hgbuildpy,
797 'build_scripts': hgbuildscripts,
797 'build_scripts': hgbuildscripts,
798 'build_hgextindex': buildhgextindex,
798 'build_hgextindex': buildhgextindex,
799 'install': hginstall,
799 'install': hginstall,
800 'install_lib': hginstalllib,
800 'install_lib': hginstalllib,
801 'install_scripts': hginstallscripts,
801 'install_scripts': hginstallscripts,
802 'build_hgexe': buildhgexe,
802 'build_hgexe': buildhgexe,
803 }
803 }
804
804
805 packages = ['mercurial',
805 packages = ['mercurial',
806 'mercurial.cext',
806 'mercurial.cext',
807 'mercurial.cffi',
807 'mercurial.cffi',
808 'mercurial.hgweb',
808 'mercurial.hgweb',
809 'mercurial.httpclient',
810 'mercurial.pure',
809 'mercurial.pure',
811 'mercurial.thirdparty',
810 'mercurial.thirdparty',
812 'mercurial.thirdparty.attr',
811 'mercurial.thirdparty.attr',
813 'hgext', 'hgext.convert', 'hgext.fsmonitor',
812 'hgext', 'hgext.convert', 'hgext.fsmonitor',
814 'hgext.fsmonitor.pywatchman', 'hgext.highlight',
813 'hgext.fsmonitor.pywatchman', 'hgext.highlight',
815 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow',
814 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow',
816 'hgext.zeroconf', 'hgext3rd',
815 'hgext.zeroconf', 'hgext3rd',
817 'hgdemandimport']
816 'hgdemandimport']
818
817
819 common_depends = ['mercurial/bitmanipulation.h',
818 common_depends = ['mercurial/bitmanipulation.h',
820 'mercurial/compat.h',
819 'mercurial/compat.h',
821 'mercurial/cext/util.h']
820 'mercurial/cext/util.h']
822 common_include_dirs = ['mercurial']
821 common_include_dirs = ['mercurial']
823
822
824 osutil_cflags = []
823 osutil_cflags = []
825 osutil_ldflags = []
824 osutil_ldflags = []
826
825
827 # platform specific macros
826 # platform specific macros
828 for plat, func in [('bsd', 'setproctitle')]:
827 for plat, func in [('bsd', 'setproctitle')]:
829 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
828 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
830 osutil_cflags.append('-DHAVE_%s' % func.upper())
829 osutil_cflags.append('-DHAVE_%s' % func.upper())
831
830
832 for plat, macro, code in [
831 for plat, macro, code in [
833 ('bsd|darwin', 'BSD_STATFS', '''
832 ('bsd|darwin', 'BSD_STATFS', '''
834 #include <sys/param.h>
833 #include <sys/param.h>
835 #include <sys/mount.h>
834 #include <sys/mount.h>
836 int main() { struct statfs s; return sizeof(s.f_fstypename); }
835 int main() { struct statfs s; return sizeof(s.f_fstypename); }
837 '''),
836 '''),
838 ('linux', 'LINUX_STATFS', '''
837 ('linux', 'LINUX_STATFS', '''
839 #include <linux/magic.h>
838 #include <linux/magic.h>
840 #include <sys/vfs.h>
839 #include <sys/vfs.h>
841 int main() { struct statfs s; return sizeof(s.f_type); }
840 int main() { struct statfs s; return sizeof(s.f_type); }
842 '''),
841 '''),
843 ]:
842 ]:
844 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
843 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
845 osutil_cflags.append('-DHAVE_%s' % macro)
844 osutil_cflags.append('-DHAVE_%s' % macro)
846
845
847 if sys.platform == 'darwin':
846 if sys.platform == 'darwin':
848 osutil_ldflags += ['-framework', 'ApplicationServices']
847 osutil_ldflags += ['-framework', 'ApplicationServices']
849
848
850 extmodules = [
849 extmodules = [
851 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
850 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
852 include_dirs=common_include_dirs,
851 include_dirs=common_include_dirs,
853 depends=common_depends),
852 depends=common_depends),
854 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
853 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
855 'mercurial/cext/bdiff.c'],
854 'mercurial/cext/bdiff.c'],
856 include_dirs=common_include_dirs,
855 include_dirs=common_include_dirs,
857 depends=common_depends + ['mercurial/bdiff.h']),
856 depends=common_depends + ['mercurial/bdiff.h']),
858 Extension('mercurial.cext.diffhelpers', ['mercurial/cext/diffhelpers.c'],
857 Extension('mercurial.cext.diffhelpers', ['mercurial/cext/diffhelpers.c'],
859 include_dirs=common_include_dirs,
858 include_dirs=common_include_dirs,
860 depends=common_depends),
859 depends=common_depends),
861 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
860 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
862 'mercurial/cext/mpatch.c'],
861 'mercurial/cext/mpatch.c'],
863 include_dirs=common_include_dirs,
862 include_dirs=common_include_dirs,
864 depends=common_depends),
863 depends=common_depends),
865 Extension('mercurial.cext.parsers', ['mercurial/cext/charencode.c',
864 Extension('mercurial.cext.parsers', ['mercurial/cext/charencode.c',
866 'mercurial/cext/dirs.c',
865 'mercurial/cext/dirs.c',
867 'mercurial/cext/manifest.c',
866 'mercurial/cext/manifest.c',
868 'mercurial/cext/parsers.c',
867 'mercurial/cext/parsers.c',
869 'mercurial/cext/pathencode.c',
868 'mercurial/cext/pathencode.c',
870 'mercurial/cext/revlog.c'],
869 'mercurial/cext/revlog.c'],
871 include_dirs=common_include_dirs,
870 include_dirs=common_include_dirs,
872 depends=common_depends + ['mercurial/cext/charencode.h']),
871 depends=common_depends + ['mercurial/cext/charencode.h']),
873 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
872 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
874 include_dirs=common_include_dirs,
873 include_dirs=common_include_dirs,
875 extra_compile_args=osutil_cflags,
874 extra_compile_args=osutil_cflags,
876 extra_link_args=osutil_ldflags,
875 extra_link_args=osutil_ldflags,
877 depends=common_depends),
876 depends=common_depends),
878 Extension('hgext.fsmonitor.pywatchman.bser',
877 Extension('hgext.fsmonitor.pywatchman.bser',
879 ['hgext/fsmonitor/pywatchman/bser.c']),
878 ['hgext/fsmonitor/pywatchman/bser.c']),
880 ]
879 ]
881
880
882 sys.path.insert(0, 'contrib/python-zstandard')
881 sys.path.insert(0, 'contrib/python-zstandard')
883 import setup_zstd
882 import setup_zstd
884 extmodules.append(setup_zstd.get_c_extension(name='mercurial.zstd'))
883 extmodules.append(setup_zstd.get_c_extension(name='mercurial.zstd'))
885
884
886 try:
885 try:
887 from distutils import cygwinccompiler
886 from distutils import cygwinccompiler
888
887
889 # the -mno-cygwin option has been deprecated for years
888 # the -mno-cygwin option has been deprecated for years
890 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
889 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
891
890
892 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
891 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
893 def __init__(self, *args, **kwargs):
892 def __init__(self, *args, **kwargs):
894 mingw32compilerclass.__init__(self, *args, **kwargs)
893 mingw32compilerclass.__init__(self, *args, **kwargs)
895 for i in 'compiler compiler_so linker_exe linker_so'.split():
894 for i in 'compiler compiler_so linker_exe linker_so'.split():
896 try:
895 try:
897 getattr(self, i).remove('-mno-cygwin')
896 getattr(self, i).remove('-mno-cygwin')
898 except ValueError:
897 except ValueError:
899 pass
898 pass
900
899
901 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
900 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
902 except ImportError:
901 except ImportError:
903 # the cygwinccompiler package is not available on some Python
902 # the cygwinccompiler package is not available on some Python
904 # distributions like the ones from the optware project for Synology
903 # distributions like the ones from the optware project for Synology
905 # DiskStation boxes
904 # DiskStation boxes
906 class HackedMingw32CCompiler(object):
905 class HackedMingw32CCompiler(object):
907 pass
906 pass
908
907
909 if os.name == 'nt':
908 if os.name == 'nt':
910 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
909 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
911 # extra_link_args to distutils.extensions.Extension() doesn't have any
910 # extra_link_args to distutils.extensions.Extension() doesn't have any
912 # effect.
911 # effect.
913 from distutils import msvccompiler
912 from distutils import msvccompiler
914
913
915 msvccompilerclass = msvccompiler.MSVCCompiler
914 msvccompilerclass = msvccompiler.MSVCCompiler
916
915
917 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
916 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
918 def initialize(self):
917 def initialize(self):
919 msvccompilerclass.initialize(self)
918 msvccompilerclass.initialize(self)
920 # "warning LNK4197: export 'func' specified multiple times"
919 # "warning LNK4197: export 'func' specified multiple times"
921 self.ldflags_shared.append('/ignore:4197')
920 self.ldflags_shared.append('/ignore:4197')
922 self.ldflags_shared_debug.append('/ignore:4197')
921 self.ldflags_shared_debug.append('/ignore:4197')
923
922
924 msvccompiler.MSVCCompiler = HackedMSVCCompiler
923 msvccompiler.MSVCCompiler = HackedMSVCCompiler
925
924
926 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
925 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
927 'help/*.txt',
926 'help/*.txt',
928 'help/internals/*.txt',
927 'help/internals/*.txt',
929 'default.d/*.rc',
928 'default.d/*.rc',
930 'dummycert.pem']}
929 'dummycert.pem']}
931
930
932 def ordinarypath(p):
931 def ordinarypath(p):
933 return p and p[0] != '.' and p[-1] != '~'
932 return p and p[0] != '.' and p[-1] != '~'
934
933
935 for root in ('templates',):
934 for root in ('templates',):
936 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
935 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
937 curdir = curdir.split(os.sep, 1)[1]
936 curdir = curdir.split(os.sep, 1)[1]
938 dirs[:] = filter(ordinarypath, dirs)
937 dirs[:] = filter(ordinarypath, dirs)
939 for f in filter(ordinarypath, files):
938 for f in filter(ordinarypath, files):
940 f = os.path.join(curdir, f)
939 f = os.path.join(curdir, f)
941 packagedata['mercurial'].append(f)
940 packagedata['mercurial'].append(f)
942
941
943 datafiles = []
942 datafiles = []
944
943
945 # distutils expects version to be str/unicode. Converting it to
944 # distutils expects version to be str/unicode. Converting it to
946 # unicode on Python 2 still works because it won't contain any
945 # unicode on Python 2 still works because it won't contain any
947 # non-ascii bytes and will be implicitly converted back to bytes
946 # non-ascii bytes and will be implicitly converted back to bytes
948 # when operated on.
947 # when operated on.
949 assert isinstance(version, bytes)
948 assert isinstance(version, bytes)
950 setupversion = version.decode('ascii')
949 setupversion = version.decode('ascii')
951
950
952 extra = {}
951 extra = {}
953
952
954 if issetuptools:
953 if issetuptools:
955 extra['python_requires'] = supportedpy
954 extra['python_requires'] = supportedpy
956 if py2exeloaded:
955 if py2exeloaded:
957 extra['console'] = [
956 extra['console'] = [
958 {'script':'hg',
957 {'script':'hg',
959 'copyright':'Copyright (C) 2005-2018 Matt Mackall and others',
958 'copyright':'Copyright (C) 2005-2018 Matt Mackall and others',
960 'product_version':version}]
959 'product_version':version}]
961 # sub command of 'build' because 'py2exe' does not handle sub_commands
960 # sub command of 'build' because 'py2exe' does not handle sub_commands
962 build.sub_commands.insert(0, ('build_hgextindex', None))
961 build.sub_commands.insert(0, ('build_hgextindex', None))
963 # put dlls in sub directory so that they won't pollute PATH
962 # put dlls in sub directory so that they won't pollute PATH
964 extra['zipfile'] = 'lib/library.zip'
963 extra['zipfile'] = 'lib/library.zip'
965
964
966 if os.name == 'nt':
965 if os.name == 'nt':
967 # Windows binary file versions for exe/dll files must have the
966 # Windows binary file versions for exe/dll files must have the
968 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
967 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
969 setupversion = version.split('+', 1)[0]
968 setupversion = version.split('+', 1)[0]
970
969
971 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
970 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
972 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
971 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
973 if version:
972 if version:
974 version = version[0]
973 version = version[0]
975 if sys.version_info[0] == 3:
974 if sys.version_info[0] == 3:
976 version = version.decode('utf-8')
975 version = version.decode('utf-8')
977 xcode4 = (version.startswith('Xcode') and
976 xcode4 = (version.startswith('Xcode') and
978 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
977 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
979 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
978 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
980 else:
979 else:
981 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
980 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
982 # installed, but instead with only command-line tools. Assume
981 # installed, but instead with only command-line tools. Assume
983 # that only happens on >= Lion, thus no PPC support.
982 # that only happens on >= Lion, thus no PPC support.
984 xcode4 = True
983 xcode4 = True
985 xcode51 = False
984 xcode51 = False
986
985
987 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
986 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
988 # distutils.sysconfig
987 # distutils.sysconfig
989 if xcode4:
988 if xcode4:
990 os.environ['ARCHFLAGS'] = ''
989 os.environ['ARCHFLAGS'] = ''
991
990
992 # XCode 5.1 changes clang such that it now fails to compile if the
991 # XCode 5.1 changes clang such that it now fails to compile if the
993 # -mno-fused-madd flag is passed, but the version of Python shipped with
992 # -mno-fused-madd flag is passed, but the version of Python shipped with
994 # OS X 10.9 Mavericks includes this flag. This causes problems in all
993 # OS X 10.9 Mavericks includes this flag. This causes problems in all
995 # C extension modules, and a bug has been filed upstream at
994 # C extension modules, and a bug has been filed upstream at
996 # http://bugs.python.org/issue21244. We also need to patch this here
995 # http://bugs.python.org/issue21244. We also need to patch this here
997 # so Mercurial can continue to compile in the meantime.
996 # so Mercurial can continue to compile in the meantime.
998 if xcode51:
997 if xcode51:
999 cflags = get_config_var('CFLAGS')
998 cflags = get_config_var('CFLAGS')
1000 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
999 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1001 os.environ['CFLAGS'] = (
1000 os.environ['CFLAGS'] = (
1002 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
1001 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
1003
1002
1004 setup(name='mercurial',
1003 setup(name='mercurial',
1005 version=setupversion,
1004 version=setupversion,
1006 author='Matt Mackall and many others',
1005 author='Matt Mackall and many others',
1007 author_email='mercurial@mercurial-scm.org',
1006 author_email='mercurial@mercurial-scm.org',
1008 url='https://mercurial-scm.org/',
1007 url='https://mercurial-scm.org/',
1009 download_url='https://mercurial-scm.org/release/',
1008 download_url='https://mercurial-scm.org/release/',
1010 description=('Fast scalable distributed SCM (revision control, version '
1009 description=('Fast scalable distributed SCM (revision control, version '
1011 'control) system'),
1010 'control) system'),
1012 long_description=('Mercurial is a distributed SCM tool written in Python.'
1011 long_description=('Mercurial is a distributed SCM tool written in Python.'
1013 ' It is used by a number of large projects that require'
1012 ' It is used by a number of large projects that require'
1014 ' fast, reliable distributed revision control, such as '
1013 ' fast, reliable distributed revision control, such as '
1015 'Mozilla.'),
1014 'Mozilla.'),
1016 license='GNU GPLv2 or any later version',
1015 license='GNU GPLv2 or any later version',
1017 classifiers=[
1016 classifiers=[
1018 'Development Status :: 6 - Mature',
1017 'Development Status :: 6 - Mature',
1019 'Environment :: Console',
1018 'Environment :: Console',
1020 'Intended Audience :: Developers',
1019 'Intended Audience :: Developers',
1021 'Intended Audience :: System Administrators',
1020 'Intended Audience :: System Administrators',
1022 'License :: OSI Approved :: GNU General Public License (GPL)',
1021 'License :: OSI Approved :: GNU General Public License (GPL)',
1023 'Natural Language :: Danish',
1022 'Natural Language :: Danish',
1024 'Natural Language :: English',
1023 'Natural Language :: English',
1025 'Natural Language :: German',
1024 'Natural Language :: German',
1026 'Natural Language :: Italian',
1025 'Natural Language :: Italian',
1027 'Natural Language :: Japanese',
1026 'Natural Language :: Japanese',
1028 'Natural Language :: Portuguese (Brazilian)',
1027 'Natural Language :: Portuguese (Brazilian)',
1029 'Operating System :: Microsoft :: Windows',
1028 'Operating System :: Microsoft :: Windows',
1030 'Operating System :: OS Independent',
1029 'Operating System :: OS Independent',
1031 'Operating System :: POSIX',
1030 'Operating System :: POSIX',
1032 'Programming Language :: C',
1031 'Programming Language :: C',
1033 'Programming Language :: Python',
1032 'Programming Language :: Python',
1034 'Topic :: Software Development :: Version Control',
1033 'Topic :: Software Development :: Version Control',
1035 ],
1034 ],
1036 scripts=scripts,
1035 scripts=scripts,
1037 packages=packages,
1036 packages=packages,
1038 ext_modules=extmodules,
1037 ext_modules=extmodules,
1039 data_files=datafiles,
1038 data_files=datafiles,
1040 package_data=packagedata,
1039 package_data=packagedata,
1041 cmdclass=cmdclass,
1040 cmdclass=cmdclass,
1042 distclass=hgdist,
1041 distclass=hgdist,
1043 options={'py2exe': {'packages': ['hgdemandimport', 'hgext', 'email',
1042 options={'py2exe': {'packages': ['hgdemandimport', 'hgext', 'email',
1044 # implicitly imported per module policy
1043 # implicitly imported per module policy
1045 # (cffi wouldn't be used as a frozen exe)
1044 # (cffi wouldn't be used as a frozen exe)
1046 'mercurial.cext',
1045 'mercurial.cext',
1047 #'mercurial.cffi',
1046 #'mercurial.cffi',
1048 'mercurial.pure']},
1047 'mercurial.pure']},
1049 'bdist_mpkg': {'zipdist': False,
1048 'bdist_mpkg': {'zipdist': False,
1050 'license': 'COPYING',
1049 'license': 'COPYING',
1051 'readme': 'contrib/macosx/Readme.html',
1050 'readme': 'contrib/macosx/Readme.html',
1052 'welcome': 'contrib/macosx/Welcome.html',
1051 'welcome': 'contrib/macosx/Welcome.html',
1053 },
1052 },
1054 },
1053 },
1055 **extra)
1054 **extra)
@@ -1,55 +1,53 b''
1 #require test-repo
1 #require test-repo
2
2
3 $ . "$TESTDIR/helpers-testrepo.sh"
3 $ . "$TESTDIR/helpers-testrepo.sh"
4 $ check_code="$TESTDIR"/../contrib/check-code.py
4 $ check_code="$TESTDIR"/../contrib/check-code.py
5 $ cd "$TESTDIR"/..
5 $ cd "$TESTDIR"/..
6
6
7 New errors are not allowed. Warnings are strongly discouraged.
7 New errors are not allowed. Warnings are strongly discouraged.
8 (The writing "no-che?k-code" is for not skipping this file when checking.)
8 (The writing "no-che?k-code" is for not skipping this file when checking.)
9
9
10 $ testrepohg locate \
10 $ testrepohg locate \
11 > -X contrib/python-zstandard \
11 > -X contrib/python-zstandard \
12 > -X hgext/fsmonitor/pywatchman \
12 > -X hgext/fsmonitor/pywatchman \
13 > -X mercurial/thirdparty \
13 > -X mercurial/thirdparty \
14 > | sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false
14 > | sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false
15 Skipping i18n/polib.py it has no-che?k-code (glob)
15 Skipping i18n/polib.py it has no-che?k-code (glob)
16 Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
17 Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
18 Skipping mercurial/statprof.py it has no-che?k-code (glob)
16 Skipping mercurial/statprof.py it has no-che?k-code (glob)
19 Skipping tests/badserverext.py it has no-che?k-code (glob)
17 Skipping tests/badserverext.py it has no-che?k-code (glob)
20
18
21 @commands in debugcommands.py should be in alphabetical order.
19 @commands in debugcommands.py should be in alphabetical order.
22
20
23 >>> import re
21 >>> import re
24 >>> commands = []
22 >>> commands = []
25 >>> with open('mercurial/debugcommands.py', 'rb') as fh:
23 >>> with open('mercurial/debugcommands.py', 'rb') as fh:
26 ... for line in fh:
24 ... for line in fh:
27 ... m = re.match("^@command\('([a-z]+)", line)
25 ... m = re.match("^@command\('([a-z]+)", line)
28 ... if m:
26 ... if m:
29 ... commands.append(m.group(1))
27 ... commands.append(m.group(1))
30 >>> scommands = list(sorted(commands))
28 >>> scommands = list(sorted(commands))
31 >>> for i, command in enumerate(scommands):
29 >>> for i, command in enumerate(scommands):
32 ... if command != commands[i]:
30 ... if command != commands[i]:
33 ... print('commands in debugcommands.py not sorted; first differing '
31 ... print('commands in debugcommands.py not sorted; first differing '
34 ... 'command is %s; expected %s' % (commands[i], command))
32 ... 'command is %s; expected %s' % (commands[i], command))
35 ... break
33 ... break
36
34
37 Prevent adding new files in the root directory accidentally.
35 Prevent adding new files in the root directory accidentally.
38
36
39 $ testrepohg files 'glob:*'
37 $ testrepohg files 'glob:*'
40 .arcconfig
38 .arcconfig
41 .clang-format
39 .clang-format
42 .editorconfig
40 .editorconfig
43 .hgignore
41 .hgignore
44 .hgsigs
42 .hgsigs
45 .hgtags
43 .hgtags
46 .jshintrc
44 .jshintrc
47 CONTRIBUTING
45 CONTRIBUTING
48 CONTRIBUTORS
46 CONTRIBUTORS
49 COPYING
47 COPYING
50 Makefile
48 Makefile
51 README.rst
49 README.rst
52 hg
50 hg
53 hgeditor
51 hgeditor
54 hgweb.cgi
52 hgweb.cgi
55 setup.py
53 setup.py
@@ -1,1024 +1,1022 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, print_function
16 >>> from __future__ import absolute_import, print_function
17 >>> import os
17 >>> import os
18 >>> import sys
18 >>> import sys
19 >>> from hgclient import check, readchannel, runcommand
19 >>> from hgclient import 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 ... print('%c, %r' % (ch, data))
23 ... print('%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, ['id'])
26 ... runcommand(server, ['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('unknowncommand\n')
34 ... server.stdin.write('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, ['id', '--quiet'])
47 ... runcommand(server, ['id', '--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, ['id'])
50 ... runcommand(server, ['id'])
51 ...
51 ...
52 ... # --config
52 ... # --config
53 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
53 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
54 ...
54 ...
55 ... # make sure --config doesn't stick
55 ... # make sure --config doesn't stick
56 ... runcommand(server, ['id'])
56 ... runcommand(server, ['id'])
57 ...
57 ...
58 ... # negative return code should be masked
58 ... # negative return code should be masked
59 ... runcommand(server, ['id', '-runknown'])
59 ... runcommand(server, ['id', '-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 check, readchannel
96 >>> from hgclient import check, readchannel
97 >>> @check
97 >>> @check
98 ... def inputeof(server):
98 ... def inputeof(server):
99 ... readchannel(server)
99 ... readchannel(server)
100 ... server.stdin.write('runcommand\n')
100 ... server.stdin.write('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 ... print('server exit code =', server.wait())
105 ... print('server exit code =', 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 = """
113 ... patch = """
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, ['import', '-'], input=stringio(patch))
128 ... runcommand(server, ['import', '-'], input=stringio(patch))
129 ... runcommand(server, ['log'])
129 ... runcommand(server, ['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, ['log', '-b', '--config=alias.log=!echo pwned',
148 ... runcommand(server, ['log', '-b', '--config=alias.log=!echo pwned',
149 ... 'default'])
149 ... '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 >>> import cStringIO
156 >>> import cStringIO
157 >>> from hgclient import check, readchannel, runcommand
157 >>> from hgclient import check, readchannel, runcommand
158 >>> @check
158 >>> @check
159 ... def serverinput(server):
159 ... def serverinput(server):
160 ... readchannel(server)
160 ... readchannel(server)
161 ... rules = 'pick eff892de26ec\n'
161 ... rules = 'pick eff892de26ec\n'
162 ... runcommand(server, ['histedit', '0', '--commands=-',
162 ... runcommand(server, ['histedit', '0', '--commands=-',
163 ... '--config', 'extensions.histedit='],
163 ... '--config', 'extensions.histedit='],
164 ... input=cStringIO.StringIO(rules))
164 ... input=cStringIO.StringIO(rules))
165 *** runcommand histedit 0 --commands=- --config extensions.histedit=
165 *** runcommand histedit 0 --commands=- --config extensions.histedit=
166
166
167 check that --cwd doesn't persist between requests:
167 check that --cwd doesn't persist between requests:
168
168
169 $ mkdir foo
169 $ mkdir foo
170 $ touch foo/bar
170 $ touch foo/bar
171 >>> from hgclient import check, readchannel, runcommand
171 >>> from hgclient import check, readchannel, runcommand
172 >>> @check
172 >>> @check
173 ... def cwd(server):
173 ... def cwd(server):
174 ... readchannel(server)
174 ... readchannel(server)
175 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
175 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
176 ... runcommand(server, ['st', 'foo/bar'])
176 ... runcommand(server, ['st', 'foo/bar'])
177 *** runcommand --cwd foo st bar
177 *** runcommand --cwd foo st bar
178 ? bar
178 ? bar
179 *** runcommand st foo/bar
179 *** runcommand st foo/bar
180 ? foo/bar
180 ? foo/bar
181
181
182 $ rm foo/bar
182 $ rm foo/bar
183
183
184
184
185 check that local configs for the cached repo aren't inherited when -R is used:
185 check that local configs for the cached repo aren't inherited when -R is used:
186
186
187 $ cat <<EOF >> .hg/hgrc
187 $ cat <<EOF >> .hg/hgrc
188 > [ui]
188 > [ui]
189 > foo = bar
189 > foo = bar
190 > EOF
190 > EOF
191
191
192 >>> from hgclient import check, readchannel, runcommand, sep
192 >>> from hgclient import check, readchannel, runcommand, sep
193 >>> @check
193 >>> @check
194 ... def localhgrc(server):
194 ... def localhgrc(server):
195 ... readchannel(server)
195 ... readchannel(server)
196 ...
196 ...
197 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
197 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
198 ... # show it
198 ... # show it
199 ... runcommand(server, ['showconfig'], outfilter=sep)
199 ... runcommand(server, ['showconfig'], outfilter=sep)
200 ...
200 ...
201 ... # but not for this repo
201 ... # but not for this repo
202 ... runcommand(server, ['init', 'foo'])
202 ... runcommand(server, ['init', 'foo'])
203 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
203 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
204 *** runcommand showconfig
204 *** runcommand showconfig
205 bundle.mainreporoot=$TESTTMP/repo
205 bundle.mainreporoot=$TESTTMP/repo
206 devel.all-warnings=true
206 devel.all-warnings=true
207 devel.default-date=0 0
207 devel.default-date=0 0
208 extensions.fsmonitor= (fsmonitor !)
208 extensions.fsmonitor= (fsmonitor !)
209 largefiles.usercache=$TESTTMP/.cache/largefiles
209 largefiles.usercache=$TESTTMP/.cache/largefiles
210 lfs.usercache=$TESTTMP/.cache/lfs
210 lfs.usercache=$TESTTMP/.cache/lfs
211 ui.slash=True
211 ui.slash=True
212 ui.interactive=False
212 ui.interactive=False
213 ui.mergemarkers=detailed
213 ui.mergemarkers=detailed
214 ui.usehttp2=true (?)
215 ui.foo=bar
214 ui.foo=bar
216 ui.nontty=true
215 ui.nontty=true
217 web.address=localhost
216 web.address=localhost
218 web\.ipv6=(?:True|False) (re)
217 web\.ipv6=(?:True|False) (re)
219 *** runcommand init foo
218 *** runcommand init foo
220 *** runcommand -R foo showconfig ui defaults
219 *** runcommand -R foo showconfig ui defaults
221 ui.slash=True
220 ui.slash=True
222 ui.interactive=False
221 ui.interactive=False
223 ui.mergemarkers=detailed
222 ui.mergemarkers=detailed
224 ui.usehttp2=true (?)
225 ui.nontty=true
223 ui.nontty=true
226
224
227 $ rm -R foo
225 $ rm -R foo
228
226
229 #if windows
227 #if windows
230 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
228 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
231 #else
229 #else
232 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
230 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
233 #endif
231 #endif
234
232
235 $ cat <<EOF > hook.py
233 $ cat <<EOF > hook.py
236 > from __future__ import print_function
234 > from __future__ import print_function
237 > import sys
235 > import sys
238 > def hook(**args):
236 > def hook(**args):
239 > print('hook talking')
237 > print('hook talking')
240 > print('now try to read something: %r' % sys.stdin.read())
238 > print('now try to read something: %r' % sys.stdin.read())
241 > EOF
239 > EOF
242
240
243 >>> from hgclient import check, readchannel, runcommand, stringio
241 >>> from hgclient import check, readchannel, runcommand, stringio
244 >>> @check
242 >>> @check
245 ... def hookoutput(server):
243 ... def hookoutput(server):
246 ... readchannel(server)
244 ... readchannel(server)
247 ... runcommand(server, ['--config',
245 ... runcommand(server, ['--config',
248 ... 'hooks.pre-identify=python:hook.hook',
246 ... 'hooks.pre-identify=python:hook.hook',
249 ... 'id'],
247 ... 'id'],
250 ... input=stringio('some input'))
248 ... input=stringio('some input'))
251 *** runcommand --config hooks.pre-identify=python:hook.hook id
249 *** runcommand --config hooks.pre-identify=python:hook.hook id
252 eff892de26ec tip
250 eff892de26ec tip
253
251
254 Clean hook cached version
252 Clean hook cached version
255 $ rm hook.py*
253 $ rm hook.py*
256 $ rm -Rf __pycache__
254 $ rm -Rf __pycache__
257
255
258 $ echo a >> a
256 $ echo a >> a
259 >>> import os
257 >>> import os
260 >>> from hgclient import check, readchannel, runcommand
258 >>> from hgclient import check, readchannel, runcommand
261 >>> @check
259 >>> @check
262 ... def outsidechanges(server):
260 ... def outsidechanges(server):
263 ... readchannel(server)
261 ... readchannel(server)
264 ... runcommand(server, ['status'])
262 ... runcommand(server, ['status'])
265 ... os.system('hg ci -Am2')
263 ... os.system('hg ci -Am2')
266 ... runcommand(server, ['tip'])
264 ... runcommand(server, ['tip'])
267 ... runcommand(server, ['status'])
265 ... runcommand(server, ['status'])
268 *** runcommand status
266 *** runcommand status
269 M a
267 M a
270 *** runcommand tip
268 *** runcommand tip
271 changeset: 1:d3a0a68be6de
269 changeset: 1:d3a0a68be6de
272 tag: tip
270 tag: tip
273 user: test
271 user: test
274 date: Thu Jan 01 00:00:00 1970 +0000
272 date: Thu Jan 01 00:00:00 1970 +0000
275 summary: 2
273 summary: 2
276
274
277 *** runcommand status
275 *** runcommand status
278
276
279 >>> import os
277 >>> import os
280 >>> from hgclient import check, readchannel, runcommand
278 >>> from hgclient import check, readchannel, runcommand
281 >>> @check
279 >>> @check
282 ... def bookmarks(server):
280 ... def bookmarks(server):
283 ... readchannel(server)
281 ... readchannel(server)
284 ... runcommand(server, ['bookmarks'])
282 ... runcommand(server, ['bookmarks'])
285 ...
283 ...
286 ... # changes .hg/bookmarks
284 ... # changes .hg/bookmarks
287 ... os.system('hg bookmark -i bm1')
285 ... os.system('hg bookmark -i bm1')
288 ... os.system('hg bookmark -i bm2')
286 ... os.system('hg bookmark -i bm2')
289 ... runcommand(server, ['bookmarks'])
287 ... runcommand(server, ['bookmarks'])
290 ...
288 ...
291 ... # changes .hg/bookmarks.current
289 ... # changes .hg/bookmarks.current
292 ... os.system('hg upd bm1 -q')
290 ... os.system('hg upd bm1 -q')
293 ... runcommand(server, ['bookmarks'])
291 ... runcommand(server, ['bookmarks'])
294 ...
292 ...
295 ... runcommand(server, ['bookmarks', 'bm3'])
293 ... runcommand(server, ['bookmarks', 'bm3'])
296 ... f = open('a', 'ab')
294 ... f = open('a', 'ab')
297 ... f.write('a\n')
295 ... f.write('a\n')
298 ... f.close()
296 ... f.close()
299 ... runcommand(server, ['commit', '-Amm'])
297 ... runcommand(server, ['commit', '-Amm'])
300 ... runcommand(server, ['bookmarks'])
298 ... runcommand(server, ['bookmarks'])
301 ... print('')
299 ... print('')
302 *** runcommand bookmarks
300 *** runcommand bookmarks
303 no bookmarks set
301 no bookmarks set
304 *** runcommand bookmarks
302 *** runcommand bookmarks
305 bm1 1:d3a0a68be6de
303 bm1 1:d3a0a68be6de
306 bm2 1:d3a0a68be6de
304 bm2 1:d3a0a68be6de
307 *** runcommand bookmarks
305 *** runcommand bookmarks
308 * bm1 1:d3a0a68be6de
306 * bm1 1:d3a0a68be6de
309 bm2 1:d3a0a68be6de
307 bm2 1:d3a0a68be6de
310 *** runcommand bookmarks bm3
308 *** runcommand bookmarks bm3
311 *** runcommand commit -Amm
309 *** runcommand commit -Amm
312 *** runcommand bookmarks
310 *** runcommand bookmarks
313 bm1 1:d3a0a68be6de
311 bm1 1:d3a0a68be6de
314 bm2 1:d3a0a68be6de
312 bm2 1:d3a0a68be6de
315 * bm3 2:aef17e88f5f0
313 * bm3 2:aef17e88f5f0
316
314
317
315
318 >>> import os
316 >>> import os
319 >>> from hgclient import check, readchannel, runcommand
317 >>> from hgclient import check, readchannel, runcommand
320 >>> @check
318 >>> @check
321 ... def tagscache(server):
319 ... def tagscache(server):
322 ... readchannel(server)
320 ... readchannel(server)
323 ... runcommand(server, ['id', '-t', '-r', '0'])
321 ... runcommand(server, ['id', '-t', '-r', '0'])
324 ... os.system('hg tag -r 0 foo')
322 ... os.system('hg tag -r 0 foo')
325 ... runcommand(server, ['id', '-t', '-r', '0'])
323 ... runcommand(server, ['id', '-t', '-r', '0'])
326 *** runcommand id -t -r 0
324 *** runcommand id -t -r 0
327
325
328 *** runcommand id -t -r 0
326 *** runcommand id -t -r 0
329 foo
327 foo
330
328
331 >>> import os
329 >>> import os
332 >>> from hgclient import check, readchannel, runcommand
330 >>> from hgclient import check, readchannel, runcommand
333 >>> @check
331 >>> @check
334 ... def setphase(server):
332 ... def setphase(server):
335 ... readchannel(server)
333 ... readchannel(server)
336 ... runcommand(server, ['phase', '-r', '.'])
334 ... runcommand(server, ['phase', '-r', '.'])
337 ... os.system('hg phase -r . -p')
335 ... os.system('hg phase -r . -p')
338 ... runcommand(server, ['phase', '-r', '.'])
336 ... runcommand(server, ['phase', '-r', '.'])
339 *** runcommand phase -r .
337 *** runcommand phase -r .
340 3: draft
338 3: draft
341 *** runcommand phase -r .
339 *** runcommand phase -r .
342 3: public
340 3: public
343
341
344 $ echo a >> a
342 $ echo a >> a
345 >>> from hgclient import check, readchannel, runcommand
343 >>> from hgclient import check, readchannel, runcommand
346 >>> @check
344 >>> @check
347 ... def rollback(server):
345 ... def rollback(server):
348 ... readchannel(server)
346 ... readchannel(server)
349 ... runcommand(server, ['phase', '-r', '.', '-p'])
347 ... runcommand(server, ['phase', '-r', '.', '-p'])
350 ... runcommand(server, ['commit', '-Am.'])
348 ... runcommand(server, ['commit', '-Am.'])
351 ... runcommand(server, ['rollback'])
349 ... runcommand(server, ['rollback'])
352 ... runcommand(server, ['phase', '-r', '.'])
350 ... runcommand(server, ['phase', '-r', '.'])
353 ... print('')
351 ... print('')
354 *** runcommand phase -r . -p
352 *** runcommand phase -r . -p
355 no phases changed
353 no phases changed
356 *** runcommand commit -Am.
354 *** runcommand commit -Am.
357 *** runcommand rollback
355 *** runcommand rollback
358 repository tip rolled back to revision 3 (undo commit)
356 repository tip rolled back to revision 3 (undo commit)
359 working directory now based on revision 3
357 working directory now based on revision 3
360 *** runcommand phase -r .
358 *** runcommand phase -r .
361 3: public
359 3: public
362
360
363
361
364 >>> import os
362 >>> import os
365 >>> from hgclient import check, readchannel, runcommand
363 >>> from hgclient import check, readchannel, runcommand
366 >>> @check
364 >>> @check
367 ... def branch(server):
365 ... def branch(server):
368 ... readchannel(server)
366 ... readchannel(server)
369 ... runcommand(server, ['branch'])
367 ... runcommand(server, ['branch'])
370 ... os.system('hg branch foo')
368 ... os.system('hg branch foo')
371 ... runcommand(server, ['branch'])
369 ... runcommand(server, ['branch'])
372 ... os.system('hg branch default')
370 ... os.system('hg branch default')
373 *** runcommand branch
371 *** runcommand branch
374 default
372 default
375 marked working directory as branch foo
373 marked working directory as branch foo
376 (branches are permanent and global, did you want a bookmark?)
374 (branches are permanent and global, did you want a bookmark?)
377 *** runcommand branch
375 *** runcommand branch
378 foo
376 foo
379 marked working directory as branch default
377 marked working directory as branch default
380 (branches are permanent and global, did you want a bookmark?)
378 (branches are permanent and global, did you want a bookmark?)
381
379
382 $ touch .hgignore
380 $ touch .hgignore
383 >>> import os
381 >>> import os
384 >>> from hgclient import check, readchannel, runcommand
382 >>> from hgclient import check, readchannel, runcommand
385 >>> @check
383 >>> @check
386 ... def hgignore(server):
384 ... def hgignore(server):
387 ... readchannel(server)
385 ... readchannel(server)
388 ... runcommand(server, ['commit', '-Am.'])
386 ... runcommand(server, ['commit', '-Am.'])
389 ... f = open('ignored-file', 'ab')
387 ... f = open('ignored-file', 'ab')
390 ... f.write('')
388 ... f.write('')
391 ... f.close()
389 ... f.close()
392 ... f = open('.hgignore', 'ab')
390 ... f = open('.hgignore', 'ab')
393 ... f.write('ignored-file')
391 ... f.write('ignored-file')
394 ... f.close()
392 ... f.close()
395 ... runcommand(server, ['status', '-i', '-u'])
393 ... runcommand(server, ['status', '-i', '-u'])
396 ... print('')
394 ... print('')
397 *** runcommand commit -Am.
395 *** runcommand commit -Am.
398 adding .hgignore
396 adding .hgignore
399 *** runcommand status -i -u
397 *** runcommand status -i -u
400 I ignored-file
398 I ignored-file
401
399
402
400
403 cache of non-public revisions should be invalidated on repository change
401 cache of non-public revisions should be invalidated on repository change
404 (issue4855):
402 (issue4855):
405
403
406 >>> import os
404 >>> import os
407 >>> from hgclient import check, readchannel, runcommand
405 >>> from hgclient import check, readchannel, runcommand
408 >>> @check
406 >>> @check
409 ... def phasesetscacheaftercommit(server):
407 ... def phasesetscacheaftercommit(server):
410 ... readchannel(server)
408 ... readchannel(server)
411 ... # load _phasecache._phaserevs and _phasesets
409 ... # load _phasecache._phaserevs and _phasesets
412 ... runcommand(server, ['log', '-qr', 'draft()'])
410 ... runcommand(server, ['log', '-qr', 'draft()'])
413 ... # create draft commits by another process
411 ... # create draft commits by another process
414 ... for i in range(5, 7):
412 ... for i in range(5, 7):
415 ... f = open('a', 'ab')
413 ... f = open('a', 'ab')
416 ... f.seek(0, os.SEEK_END)
414 ... f.seek(0, os.SEEK_END)
417 ... f.write('a\n')
415 ... f.write('a\n')
418 ... f.close()
416 ... f.close()
419 ... os.system('hg commit -Aqm%d' % i)
417 ... os.system('hg commit -Aqm%d' % i)
420 ... # new commits should be listed as draft revisions
418 ... # new commits should be listed as draft revisions
421 ... runcommand(server, ['log', '-qr', 'draft()'])
419 ... runcommand(server, ['log', '-qr', 'draft()'])
422 ... print('')
420 ... print('')
423 *** runcommand log -qr draft()
421 *** runcommand log -qr draft()
424 4:7966c8e3734d
422 4:7966c8e3734d
425 *** runcommand log -qr draft()
423 *** runcommand log -qr draft()
426 4:7966c8e3734d
424 4:7966c8e3734d
427 5:41f6602d1c4f
425 5:41f6602d1c4f
428 6:10501e202c35
426 6:10501e202c35
429
427
430
428
431 >>> import os
429 >>> import os
432 >>> from hgclient import check, readchannel, runcommand
430 >>> from hgclient import check, readchannel, runcommand
433 >>> @check
431 >>> @check
434 ... def phasesetscacheafterstrip(server):
432 ... def phasesetscacheafterstrip(server):
435 ... readchannel(server)
433 ... readchannel(server)
436 ... # load _phasecache._phaserevs and _phasesets
434 ... # load _phasecache._phaserevs and _phasesets
437 ... runcommand(server, ['log', '-qr', 'draft()'])
435 ... runcommand(server, ['log', '-qr', 'draft()'])
438 ... # strip cached revisions by another process
436 ... # strip cached revisions by another process
439 ... os.system('hg --config extensions.strip= strip -q 5')
437 ... os.system('hg --config extensions.strip= strip -q 5')
440 ... # shouldn't abort by "unknown revision '6'"
438 ... # shouldn't abort by "unknown revision '6'"
441 ... runcommand(server, ['log', '-qr', 'draft()'])
439 ... runcommand(server, ['log', '-qr', 'draft()'])
442 ... print('')
440 ... print('')
443 *** runcommand log -qr draft()
441 *** runcommand log -qr draft()
444 4:7966c8e3734d
442 4:7966c8e3734d
445 5:41f6602d1c4f
443 5:41f6602d1c4f
446 6:10501e202c35
444 6:10501e202c35
447 *** runcommand log -qr draft()
445 *** runcommand log -qr draft()
448 4:7966c8e3734d
446 4:7966c8e3734d
449
447
450
448
451 cache of phase roots should be invalidated on strip (issue3827):
449 cache of phase roots should be invalidated on strip (issue3827):
452
450
453 >>> import os
451 >>> import os
454 >>> from hgclient import check, readchannel, runcommand, sep
452 >>> from hgclient import check, readchannel, runcommand, sep
455 >>> @check
453 >>> @check
456 ... def phasecacheafterstrip(server):
454 ... def phasecacheafterstrip(server):
457 ... readchannel(server)
455 ... readchannel(server)
458 ...
456 ...
459 ... # create new head, 5:731265503d86
457 ... # create new head, 5:731265503d86
460 ... runcommand(server, ['update', '-C', '0'])
458 ... runcommand(server, ['update', '-C', '0'])
461 ... f = open('a', 'ab')
459 ... f = open('a', 'ab')
462 ... f.write('a\n')
460 ... f.write('a\n')
463 ... f.close()
461 ... f.close()
464 ... runcommand(server, ['commit', '-Am.', 'a'])
462 ... runcommand(server, ['commit', '-Am.', 'a'])
465 ... runcommand(server, ['log', '-Gq'])
463 ... runcommand(server, ['log', '-Gq'])
466 ...
464 ...
467 ... # make it public; draft marker moves to 4:7966c8e3734d
465 ... # make it public; draft marker moves to 4:7966c8e3734d
468 ... runcommand(server, ['phase', '-p', '.'])
466 ... runcommand(server, ['phase', '-p', '.'])
469 ... # load _phasecache.phaseroots
467 ... # load _phasecache.phaseroots
470 ... runcommand(server, ['phase', '.'], outfilter=sep)
468 ... runcommand(server, ['phase', '.'], outfilter=sep)
471 ...
469 ...
472 ... # strip 1::4 outside server
470 ... # strip 1::4 outside server
473 ... os.system('hg -q --config extensions.mq= strip 1')
471 ... os.system('hg -q --config extensions.mq= strip 1')
474 ...
472 ...
475 ... # shouldn't raise "7966c8e3734d: no node!"
473 ... # shouldn't raise "7966c8e3734d: no node!"
476 ... runcommand(server, ['branches'])
474 ... runcommand(server, ['branches'])
477 *** runcommand update -C 0
475 *** runcommand update -C 0
478 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
476 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
479 (leaving bookmark bm3)
477 (leaving bookmark bm3)
480 *** runcommand commit -Am. a
478 *** runcommand commit -Am. a
481 created new head
479 created new head
482 *** runcommand log -Gq
480 *** runcommand log -Gq
483 @ 5:731265503d86
481 @ 5:731265503d86
484 |
482 |
485 | o 4:7966c8e3734d
483 | o 4:7966c8e3734d
486 | |
484 | |
487 | o 3:b9b85890c400
485 | o 3:b9b85890c400
488 | |
486 | |
489 | o 2:aef17e88f5f0
487 | o 2:aef17e88f5f0
490 | |
488 | |
491 | o 1:d3a0a68be6de
489 | o 1:d3a0a68be6de
492 |/
490 |/
493 o 0:eff892de26ec
491 o 0:eff892de26ec
494
492
495 *** runcommand phase -p .
493 *** runcommand phase -p .
496 *** runcommand phase .
494 *** runcommand phase .
497 5: public
495 5: public
498 *** runcommand branches
496 *** runcommand branches
499 default 1:731265503d86
497 default 1:731265503d86
500
498
501 in-memory cache must be reloaded if transaction is aborted. otherwise
499 in-memory cache must be reloaded if transaction is aborted. otherwise
502 changelog and manifest would have invalid node:
500 changelog and manifest would have invalid node:
503
501
504 $ echo a >> a
502 $ echo a >> a
505 >>> from hgclient import check, readchannel, runcommand
503 >>> from hgclient import check, readchannel, runcommand
506 >>> @check
504 >>> @check
507 ... def txabort(server):
505 ... def txabort(server):
508 ... readchannel(server)
506 ... readchannel(server)
509 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
507 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
510 ... '-mfoo'])
508 ... '-mfoo'])
511 ... runcommand(server, ['verify'])
509 ... runcommand(server, ['verify'])
512 *** runcommand commit --config hooks.pretxncommit=false -mfoo
510 *** runcommand commit --config hooks.pretxncommit=false -mfoo
513 transaction abort!
511 transaction abort!
514 rollback completed
512 rollback completed
515 abort: pretxncommit hook exited with status 1
513 abort: pretxncommit hook exited with status 1
516 [255]
514 [255]
517 *** runcommand verify
515 *** runcommand verify
518 checking changesets
516 checking changesets
519 checking manifests
517 checking manifests
520 crosschecking files in changesets and manifests
518 crosschecking files in changesets and manifests
521 checking files
519 checking files
522 1 files, 2 changesets, 2 total revisions
520 1 files, 2 changesets, 2 total revisions
523 $ hg revert --no-backup -aq
521 $ hg revert --no-backup -aq
524
522
525 $ cat >> .hg/hgrc << EOF
523 $ cat >> .hg/hgrc << EOF
526 > [experimental]
524 > [experimental]
527 > evolution.createmarkers=True
525 > evolution.createmarkers=True
528 > EOF
526 > EOF
529
527
530 >>> import os
528 >>> import os
531 >>> from hgclient import check, readchannel, runcommand
529 >>> from hgclient import check, readchannel, runcommand
532 >>> @check
530 >>> @check
533 ... def obsolete(server):
531 ... def obsolete(server):
534 ... readchannel(server)
532 ... readchannel(server)
535 ...
533 ...
536 ... runcommand(server, ['up', 'null'])
534 ... runcommand(server, ['up', 'null'])
537 ... runcommand(server, ['phase', '-df', 'tip'])
535 ... runcommand(server, ['phase', '-df', 'tip'])
538 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
536 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
539 ... if os.name == 'nt':
537 ... if os.name == 'nt':
540 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
538 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
541 ... os.system(cmd)
539 ... os.system(cmd)
542 ... runcommand(server, ['log', '--hidden'])
540 ... runcommand(server, ['log', '--hidden'])
543 ... runcommand(server, ['log'])
541 ... runcommand(server, ['log'])
544 *** runcommand up null
542 *** runcommand up null
545 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
543 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
546 *** runcommand phase -df tip
544 *** runcommand phase -df tip
547 obsoleted 1 changesets
545 obsoleted 1 changesets
548 *** runcommand log --hidden
546 *** runcommand log --hidden
549 changeset: 1:731265503d86
547 changeset: 1:731265503d86
550 tag: tip
548 tag: tip
551 user: test
549 user: test
552 date: Thu Jan 01 00:00:00 1970 +0000
550 date: Thu Jan 01 00:00:00 1970 +0000
553 obsolete: pruned
551 obsolete: pruned
554 summary: .
552 summary: .
555
553
556 changeset: 0:eff892de26ec
554 changeset: 0:eff892de26ec
557 bookmark: bm1
555 bookmark: bm1
558 bookmark: bm2
556 bookmark: bm2
559 bookmark: bm3
557 bookmark: bm3
560 user: test
558 user: test
561 date: Thu Jan 01 00:00:00 1970 +0000
559 date: Thu Jan 01 00:00:00 1970 +0000
562 summary: 1
560 summary: 1
563
561
564 *** runcommand log
562 *** runcommand log
565 changeset: 0:eff892de26ec
563 changeset: 0:eff892de26ec
566 bookmark: bm1
564 bookmark: bm1
567 bookmark: bm2
565 bookmark: bm2
568 bookmark: bm3
566 bookmark: bm3
569 tag: tip
567 tag: tip
570 user: test
568 user: test
571 date: Thu Jan 01 00:00:00 1970 +0000
569 date: Thu Jan 01 00:00:00 1970 +0000
572 summary: 1
570 summary: 1
573
571
574
572
575 $ cat <<EOF >> .hg/hgrc
573 $ cat <<EOF >> .hg/hgrc
576 > [extensions]
574 > [extensions]
577 > mq =
575 > mq =
578 > EOF
576 > EOF
579
577
580 >>> import os
578 >>> import os
581 >>> from hgclient import check, readchannel, runcommand
579 >>> from hgclient import check, readchannel, runcommand
582 >>> @check
580 >>> @check
583 ... def mqoutsidechanges(server):
581 ... def mqoutsidechanges(server):
584 ... readchannel(server)
582 ... readchannel(server)
585 ...
583 ...
586 ... # load repo.mq
584 ... # load repo.mq
587 ... runcommand(server, ['qapplied'])
585 ... runcommand(server, ['qapplied'])
588 ... os.system('hg qnew 0.diff')
586 ... os.system('hg qnew 0.diff')
589 ... # repo.mq should be invalidated
587 ... # repo.mq should be invalidated
590 ... runcommand(server, ['qapplied'])
588 ... runcommand(server, ['qapplied'])
591 ...
589 ...
592 ... runcommand(server, ['qpop', '--all'])
590 ... runcommand(server, ['qpop', '--all'])
593 ... os.system('hg qqueue --create foo')
591 ... os.system('hg qqueue --create foo')
594 ... # repo.mq should be recreated to point to new queue
592 ... # repo.mq should be recreated to point to new queue
595 ... runcommand(server, ['qqueue', '--active'])
593 ... runcommand(server, ['qqueue', '--active'])
596 *** runcommand qapplied
594 *** runcommand qapplied
597 *** runcommand qapplied
595 *** runcommand qapplied
598 0.diff
596 0.diff
599 *** runcommand qpop --all
597 *** runcommand qpop --all
600 popping 0.diff
598 popping 0.diff
601 patch queue now empty
599 patch queue now empty
602 *** runcommand qqueue --active
600 *** runcommand qqueue --active
603 foo
601 foo
604
602
605 $ cat <<EOF > dbgui.py
603 $ cat <<EOF > dbgui.py
606 > import os
604 > import os
607 > import sys
605 > import sys
608 > from mercurial import commands, registrar
606 > from mercurial import commands, registrar
609 > cmdtable = {}
607 > cmdtable = {}
610 > command = registrar.command(cmdtable)
608 > command = registrar.command(cmdtable)
611 > @command(b"debuggetpass", norepo=True)
609 > @command(b"debuggetpass", norepo=True)
612 > def debuggetpass(ui):
610 > def debuggetpass(ui):
613 > ui.write("%s\\n" % ui.getpass())
611 > ui.write("%s\\n" % ui.getpass())
614 > @command(b"debugprompt", norepo=True)
612 > @command(b"debugprompt", norepo=True)
615 > def debugprompt(ui):
613 > def debugprompt(ui):
616 > ui.write("%s\\n" % ui.prompt("prompt:"))
614 > ui.write("%s\\n" % ui.prompt("prompt:"))
617 > @command(b"debugreadstdin", norepo=True)
615 > @command(b"debugreadstdin", norepo=True)
618 > def debugreadstdin(ui):
616 > def debugreadstdin(ui):
619 > ui.write("read: %r\n" % sys.stdin.read(1))
617 > ui.write("read: %r\n" % sys.stdin.read(1))
620 > @command(b"debugwritestdout", norepo=True)
618 > @command(b"debugwritestdout", norepo=True)
621 > def debugwritestdout(ui):
619 > def debugwritestdout(ui):
622 > os.write(1, "low-level stdout fd and\n")
620 > os.write(1, "low-level stdout fd and\n")
623 > sys.stdout.write("stdout should be redirected to /dev/null\n")
621 > sys.stdout.write("stdout should be redirected to /dev/null\n")
624 > sys.stdout.flush()
622 > sys.stdout.flush()
625 > EOF
623 > EOF
626 $ cat <<EOF >> .hg/hgrc
624 $ cat <<EOF >> .hg/hgrc
627 > [extensions]
625 > [extensions]
628 > dbgui = dbgui.py
626 > dbgui = dbgui.py
629 > EOF
627 > EOF
630
628
631 >>> from hgclient import check, readchannel, runcommand, stringio
629 >>> from hgclient import check, readchannel, runcommand, stringio
632 >>> @check
630 >>> @check
633 ... def getpass(server):
631 ... def getpass(server):
634 ... readchannel(server)
632 ... readchannel(server)
635 ... runcommand(server, ['debuggetpass', '--config',
633 ... runcommand(server, ['debuggetpass', '--config',
636 ... 'ui.interactive=True'],
634 ... 'ui.interactive=True'],
637 ... input=stringio('1234\n'))
635 ... input=stringio('1234\n'))
638 ... runcommand(server, ['debuggetpass', '--config',
636 ... runcommand(server, ['debuggetpass', '--config',
639 ... 'ui.interactive=True'],
637 ... 'ui.interactive=True'],
640 ... input=stringio('\n'))
638 ... input=stringio('\n'))
641 ... runcommand(server, ['debuggetpass', '--config',
639 ... runcommand(server, ['debuggetpass', '--config',
642 ... 'ui.interactive=True'],
640 ... 'ui.interactive=True'],
643 ... input=stringio(''))
641 ... input=stringio(''))
644 ... runcommand(server, ['debugprompt', '--config',
642 ... runcommand(server, ['debugprompt', '--config',
645 ... 'ui.interactive=True'],
643 ... 'ui.interactive=True'],
646 ... input=stringio('5678\n'))
644 ... input=stringio('5678\n'))
647 ... runcommand(server, ['debugreadstdin'])
645 ... runcommand(server, ['debugreadstdin'])
648 ... runcommand(server, ['debugwritestdout'])
646 ... runcommand(server, ['debugwritestdout'])
649 *** runcommand debuggetpass --config ui.interactive=True
647 *** runcommand debuggetpass --config ui.interactive=True
650 password: 1234
648 password: 1234
651 *** runcommand debuggetpass --config ui.interactive=True
649 *** runcommand debuggetpass --config ui.interactive=True
652 password:
650 password:
653 *** runcommand debuggetpass --config ui.interactive=True
651 *** runcommand debuggetpass --config ui.interactive=True
654 password: abort: response expected
652 password: abort: response expected
655 [255]
653 [255]
656 *** runcommand debugprompt --config ui.interactive=True
654 *** runcommand debugprompt --config ui.interactive=True
657 prompt: 5678
655 prompt: 5678
658 *** runcommand debugreadstdin
656 *** runcommand debugreadstdin
659 read: ''
657 read: ''
660 *** runcommand debugwritestdout
658 *** runcommand debugwritestdout
661
659
662
660
663 run commandserver in commandserver, which is silly but should work:
661 run commandserver in commandserver, which is silly but should work:
664
662
665 >>> from __future__ import print_function
663 >>> from __future__ import print_function
666 >>> from hgclient import check, readchannel, runcommand, stringio
664 >>> from hgclient import check, readchannel, runcommand, stringio
667 >>> @check
665 >>> @check
668 ... def nested(server):
666 ... def nested(server):
669 ... print('%c, %r' % readchannel(server))
667 ... print('%c, %r' % readchannel(server))
670 ... class nestedserver(object):
668 ... class nestedserver(object):
671 ... stdin = stringio('getencoding\n')
669 ... stdin = stringio('getencoding\n')
672 ... stdout = stringio()
670 ... stdout = stringio()
673 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
671 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
674 ... output=nestedserver.stdout, input=nestedserver.stdin)
672 ... output=nestedserver.stdout, input=nestedserver.stdin)
675 ... nestedserver.stdout.seek(0)
673 ... nestedserver.stdout.seek(0)
676 ... print('%c, %r' % readchannel(nestedserver)) # hello
674 ... print('%c, %r' % readchannel(nestedserver)) # hello
677 ... print('%c, %r' % readchannel(nestedserver)) # getencoding
675 ... print('%c, %r' % readchannel(nestedserver)) # getencoding
678 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
676 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
679 *** runcommand serve --cmdserver pipe
677 *** runcommand serve --cmdserver pipe
680 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
678 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
681 r, '*' (glob)
679 r, '*' (glob)
682
680
683
681
684 start without repository:
682 start without repository:
685
683
686 $ cd ..
684 $ cd ..
687
685
688 >>> from __future__ import print_function
686 >>> from __future__ import print_function
689 >>> from hgclient import check, readchannel, runcommand
687 >>> from hgclient import check, readchannel, runcommand
690 >>> @check
688 >>> @check
691 ... def hellomessage(server):
689 ... def hellomessage(server):
692 ... ch, data = readchannel(server)
690 ... ch, data = readchannel(server)
693 ... print('%c, %r' % (ch, data))
691 ... print('%c, %r' % (ch, data))
694 ... # run an arbitrary command to make sure the next thing the server
692 ... # run an arbitrary command to make sure the next thing the server
695 ... # sends isn't part of the hello message
693 ... # sends isn't part of the hello message
696 ... runcommand(server, ['id'])
694 ... runcommand(server, ['id'])
697 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
695 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
698 *** runcommand id
696 *** runcommand id
699 abort: there is no Mercurial repository here (.hg not found)
697 abort: there is no Mercurial repository here (.hg not found)
700 [255]
698 [255]
701
699
702 >>> from hgclient import check, readchannel, runcommand
700 >>> from hgclient import check, readchannel, runcommand
703 >>> @check
701 >>> @check
704 ... def startwithoutrepo(server):
702 ... def startwithoutrepo(server):
705 ... readchannel(server)
703 ... readchannel(server)
706 ... runcommand(server, ['init', 'repo2'])
704 ... runcommand(server, ['init', 'repo2'])
707 ... runcommand(server, ['id', '-R', 'repo2'])
705 ... runcommand(server, ['id', '-R', 'repo2'])
708 *** runcommand init repo2
706 *** runcommand init repo2
709 *** runcommand id -R repo2
707 *** runcommand id -R repo2
710 000000000000 tip
708 000000000000 tip
711
709
712
710
713 don't fall back to cwd if invalid -R path is specified (issue4805):
711 don't fall back to cwd if invalid -R path is specified (issue4805):
714
712
715 $ cd repo
713 $ cd repo
716 $ hg serve --cmdserver pipe -R ../nonexistent
714 $ hg serve --cmdserver pipe -R ../nonexistent
717 abort: repository ../nonexistent not found!
715 abort: repository ../nonexistent not found!
718 [255]
716 [255]
719 $ cd ..
717 $ cd ..
720
718
721
719
722 unix domain socket:
720 unix domain socket:
723
721
724 $ cd repo
722 $ cd repo
725 $ hg update -q
723 $ hg update -q
726
724
727 #if unix-socket unix-permissions
725 #if unix-socket unix-permissions
728
726
729 >>> from __future__ import print_function
727 >>> from __future__ import print_function
730 >>> from hgclient import check, readchannel, runcommand, stringio, unixserver
728 >>> from hgclient import check, readchannel, runcommand, stringio, unixserver
731 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
729 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
732 >>> def hellomessage(conn):
730 >>> def hellomessage(conn):
733 ... ch, data = readchannel(conn)
731 ... ch, data = readchannel(conn)
734 ... print('%c, %r' % (ch, data))
732 ... print('%c, %r' % (ch, data))
735 ... runcommand(conn, ['id'])
733 ... runcommand(conn, ['id'])
736 >>> check(hellomessage, server.connect)
734 >>> check(hellomessage, server.connect)
737 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
735 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
738 *** runcommand id
736 *** runcommand id
739 eff892de26ec tip bm1/bm2/bm3
737 eff892de26ec tip bm1/bm2/bm3
740 >>> def unknowncommand(conn):
738 >>> def unknowncommand(conn):
741 ... readchannel(conn)
739 ... readchannel(conn)
742 ... conn.stdin.write('unknowncommand\n')
740 ... conn.stdin.write('unknowncommand\n')
743 >>> check(unknowncommand, server.connect) # error sent to server.log
741 >>> check(unknowncommand, server.connect) # error sent to server.log
744 >>> def serverinput(conn):
742 >>> def serverinput(conn):
745 ... readchannel(conn)
743 ... readchannel(conn)
746 ... patch = """
744 ... patch = """
747 ... # HG changeset patch
745 ... # HG changeset patch
748 ... # User test
746 ... # User test
749 ... # Date 0 0
747 ... # Date 0 0
750 ... 2
748 ... 2
751 ...
749 ...
752 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
750 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
753 ... --- a/a
751 ... --- a/a
754 ... +++ b/a
752 ... +++ b/a
755 ... @@ -1,1 +1,2 @@
753 ... @@ -1,1 +1,2 @@
756 ... 1
754 ... 1
757 ... +2
755 ... +2
758 ... """
756 ... """
759 ... runcommand(conn, ['import', '-'], input=stringio(patch))
757 ... runcommand(conn, ['import', '-'], input=stringio(patch))
760 ... runcommand(conn, ['log', '-rtip', '-q'])
758 ... runcommand(conn, ['log', '-rtip', '-q'])
761 >>> check(serverinput, server.connect)
759 >>> check(serverinput, server.connect)
762 *** runcommand import -
760 *** runcommand import -
763 applying patch from stdin
761 applying patch from stdin
764 *** runcommand log -rtip -q
762 *** runcommand log -rtip -q
765 2:1ed24be7e7a0
763 2:1ed24be7e7a0
766 >>> server.shutdown()
764 >>> server.shutdown()
767
765
768 $ cat .hg/server.log
766 $ cat .hg/server.log
769 listening at .hg/server.sock
767 listening at .hg/server.sock
770 abort: unknown command unknowncommand
768 abort: unknown command unknowncommand
771 killed!
769 killed!
772 $ rm .hg/server.log
770 $ rm .hg/server.log
773
771
774 if server crashed before hello, traceback will be sent to 'e' channel as
772 if server crashed before hello, traceback will be sent to 'e' channel as
775 last ditch:
773 last ditch:
776
774
777 $ cat <<EOF >> .hg/hgrc
775 $ cat <<EOF >> .hg/hgrc
778 > [cmdserver]
776 > [cmdserver]
779 > log = inexistent/path.log
777 > log = inexistent/path.log
780 > EOF
778 > EOF
781 >>> from __future__ import print_function
779 >>> from __future__ import print_function
782 >>> from hgclient import check, readchannel, unixserver
780 >>> from hgclient import check, readchannel, unixserver
783 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
781 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
784 >>> def earlycrash(conn):
782 >>> def earlycrash(conn):
785 ... while True:
783 ... while True:
786 ... try:
784 ... try:
787 ... ch, data = readchannel(conn)
785 ... ch, data = readchannel(conn)
788 ... if not data.startswith(' '):
786 ... if not data.startswith(' '):
789 ... print('%c, %r' % (ch, data))
787 ... print('%c, %r' % (ch, data))
790 ... except EOFError:
788 ... except EOFError:
791 ... break
789 ... break
792 >>> check(earlycrash, server.connect)
790 >>> check(earlycrash, server.connect)
793 e, 'Traceback (most recent call last):\n'
791 e, 'Traceback (most recent call last):\n'
794 e, "IOError: *" (glob)
792 e, "IOError: *" (glob)
795 >>> server.shutdown()
793 >>> server.shutdown()
796
794
797 $ cat .hg/server.log | grep -v '^ '
795 $ cat .hg/server.log | grep -v '^ '
798 listening at .hg/server.sock
796 listening at .hg/server.sock
799 Traceback (most recent call last):
797 Traceback (most recent call last):
800 IOError: * (glob)
798 IOError: * (glob)
801 killed!
799 killed!
802 #endif
800 #endif
803 #if no-unix-socket
801 #if no-unix-socket
804
802
805 $ hg serve --cmdserver unix -a .hg/server.sock
803 $ hg serve --cmdserver unix -a .hg/server.sock
806 abort: unsupported platform
804 abort: unsupported platform
807 [255]
805 [255]
808
806
809 #endif
807 #endif
810
808
811 $ cd ..
809 $ cd ..
812
810
813 Test that accessing to invalid changelog cache is avoided at
811 Test that accessing to invalid changelog cache is avoided at
814 subsequent operations even if repo object is reused even after failure
812 subsequent operations even if repo object is reused even after failure
815 of transaction (see 0a7610758c42 also)
813 of transaction (see 0a7610758c42 also)
816
814
817 "hg log" after failure of transaction is needed to detect invalid
815 "hg log" after failure of transaction is needed to detect invalid
818 cache in repoview: this can't detect by "hg verify" only.
816 cache in repoview: this can't detect by "hg verify" only.
819
817
820 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
818 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
821 4) are tested, because '00changelog.i' are differently changed in each
819 4) are tested, because '00changelog.i' are differently changed in each
822 cases.
820 cases.
823
821
824 $ cat > $TESTTMP/failafterfinalize.py <<EOF
822 $ cat > $TESTTMP/failafterfinalize.py <<EOF
825 > # extension to abort transaction after finalization forcibly
823 > # extension to abort transaction after finalization forcibly
826 > from mercurial import commands, error, extensions, lock as lockmod
824 > from mercurial import commands, error, extensions, lock as lockmod
827 > from mercurial import registrar
825 > from mercurial import registrar
828 > cmdtable = {}
826 > cmdtable = {}
829 > command = registrar.command(cmdtable)
827 > command = registrar.command(cmdtable)
830 > configtable = {}
828 > configtable = {}
831 > configitem = registrar.configitem(configtable)
829 > configitem = registrar.configitem(configtable)
832 > configitem('failafterfinalize', 'fail',
830 > configitem('failafterfinalize', 'fail',
833 > default=None,
831 > default=None,
834 > )
832 > )
835 > def fail(tr):
833 > def fail(tr):
836 > raise error.Abort('fail after finalization')
834 > raise error.Abort('fail after finalization')
837 > def reposetup(ui, repo):
835 > def reposetup(ui, repo):
838 > class failrepo(repo.__class__):
836 > class failrepo(repo.__class__):
839 > def commitctx(self, ctx, error=False):
837 > def commitctx(self, ctx, error=False):
840 > if self.ui.configbool('failafterfinalize', 'fail'):
838 > if self.ui.configbool('failafterfinalize', 'fail'):
841 > # 'sorted()' by ASCII code on category names causes
839 > # 'sorted()' by ASCII code on category names causes
842 > # invoking 'fail' after finalization of changelog
840 > # invoking 'fail' after finalization of changelog
843 > # using "'cl-%i' % id(self)" as category name
841 > # using "'cl-%i' % id(self)" as category name
844 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
842 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
845 > return super(failrepo, self).commitctx(ctx, error)
843 > return super(failrepo, self).commitctx(ctx, error)
846 > repo.__class__ = failrepo
844 > repo.__class__ = failrepo
847 > EOF
845 > EOF
848
846
849 $ hg init repo3
847 $ hg init repo3
850 $ cd repo3
848 $ cd repo3
851
849
852 $ cat <<EOF >> $HGRCPATH
850 $ cat <<EOF >> $HGRCPATH
853 > [ui]
851 > [ui]
854 > logtemplate = {rev} {desc|firstline} ({files})\n
852 > logtemplate = {rev} {desc|firstline} ({files})\n
855 >
853 >
856 > [extensions]
854 > [extensions]
857 > failafterfinalize = $TESTTMP/failafterfinalize.py
855 > failafterfinalize = $TESTTMP/failafterfinalize.py
858 > EOF
856 > EOF
859
857
860 - test failure with "empty changelog"
858 - test failure with "empty changelog"
861
859
862 $ echo foo > foo
860 $ echo foo > foo
863 $ hg add foo
861 $ hg add foo
864
862
865 (failure before finalization)
863 (failure before finalization)
866
864
867 >>> from hgclient import check, readchannel, runcommand
865 >>> from hgclient import check, readchannel, runcommand
868 >>> @check
866 >>> @check
869 ... def abort(server):
867 ... def abort(server):
870 ... readchannel(server)
868 ... readchannel(server)
871 ... runcommand(server, ['commit',
869 ... runcommand(server, ['commit',
872 ... '--config', 'hooks.pretxncommit=false',
870 ... '--config', 'hooks.pretxncommit=false',
873 ... '-mfoo'])
871 ... '-mfoo'])
874 ... runcommand(server, ['log'])
872 ... runcommand(server, ['log'])
875 ... runcommand(server, ['verify', '-q'])
873 ... runcommand(server, ['verify', '-q'])
876 *** runcommand commit --config hooks.pretxncommit=false -mfoo
874 *** runcommand commit --config hooks.pretxncommit=false -mfoo
877 transaction abort!
875 transaction abort!
878 rollback completed
876 rollback completed
879 abort: pretxncommit hook exited with status 1
877 abort: pretxncommit hook exited with status 1
880 [255]
878 [255]
881 *** runcommand log
879 *** runcommand log
882 *** runcommand verify -q
880 *** runcommand verify -q
883
881
884 (failure after finalization)
882 (failure after finalization)
885
883
886 >>> from hgclient import check, readchannel, runcommand
884 >>> from hgclient import check, readchannel, runcommand
887 >>> @check
885 >>> @check
888 ... def abort(server):
886 ... def abort(server):
889 ... readchannel(server)
887 ... readchannel(server)
890 ... runcommand(server, ['commit',
888 ... runcommand(server, ['commit',
891 ... '--config', 'failafterfinalize.fail=true',
889 ... '--config', 'failafterfinalize.fail=true',
892 ... '-mfoo'])
890 ... '-mfoo'])
893 ... runcommand(server, ['log'])
891 ... runcommand(server, ['log'])
894 ... runcommand(server, ['verify', '-q'])
892 ... runcommand(server, ['verify', '-q'])
895 *** runcommand commit --config failafterfinalize.fail=true -mfoo
893 *** runcommand commit --config failafterfinalize.fail=true -mfoo
896 transaction abort!
894 transaction abort!
897 rollback completed
895 rollback completed
898 abort: fail after finalization
896 abort: fail after finalization
899 [255]
897 [255]
900 *** runcommand log
898 *** runcommand log
901 *** runcommand verify -q
899 *** runcommand verify -q
902
900
903 - test failure with "not-empty changelog"
901 - test failure with "not-empty changelog"
904
902
905 $ echo bar > bar
903 $ echo bar > bar
906 $ hg add bar
904 $ hg add bar
907 $ hg commit -mbar bar
905 $ hg commit -mbar bar
908
906
909 (failure before finalization)
907 (failure before finalization)
910
908
911 >>> from hgclient import check, readchannel, runcommand
909 >>> from hgclient import check, readchannel, runcommand
912 >>> @check
910 >>> @check
913 ... def abort(server):
911 ... def abort(server):
914 ... readchannel(server)
912 ... readchannel(server)
915 ... runcommand(server, ['commit',
913 ... runcommand(server, ['commit',
916 ... '--config', 'hooks.pretxncommit=false',
914 ... '--config', 'hooks.pretxncommit=false',
917 ... '-mfoo', 'foo'])
915 ... '-mfoo', 'foo'])
918 ... runcommand(server, ['log'])
916 ... runcommand(server, ['log'])
919 ... runcommand(server, ['verify', '-q'])
917 ... runcommand(server, ['verify', '-q'])
920 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
918 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
921 transaction abort!
919 transaction abort!
922 rollback completed
920 rollback completed
923 abort: pretxncommit hook exited with status 1
921 abort: pretxncommit hook exited with status 1
924 [255]
922 [255]
925 *** runcommand log
923 *** runcommand log
926 0 bar (bar)
924 0 bar (bar)
927 *** runcommand verify -q
925 *** runcommand verify -q
928
926
929 (failure after finalization)
927 (failure after finalization)
930
928
931 >>> from hgclient import check, readchannel, runcommand
929 >>> from hgclient import check, readchannel, runcommand
932 >>> @check
930 >>> @check
933 ... def abort(server):
931 ... def abort(server):
934 ... readchannel(server)
932 ... readchannel(server)
935 ... runcommand(server, ['commit',
933 ... runcommand(server, ['commit',
936 ... '--config', 'failafterfinalize.fail=true',
934 ... '--config', 'failafterfinalize.fail=true',
937 ... '-mfoo', 'foo'])
935 ... '-mfoo', 'foo'])
938 ... runcommand(server, ['log'])
936 ... runcommand(server, ['log'])
939 ... runcommand(server, ['verify', '-q'])
937 ... runcommand(server, ['verify', '-q'])
940 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
938 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
941 transaction abort!
939 transaction abort!
942 rollback completed
940 rollback completed
943 abort: fail after finalization
941 abort: fail after finalization
944 [255]
942 [255]
945 *** runcommand log
943 *** runcommand log
946 0 bar (bar)
944 0 bar (bar)
947 *** runcommand verify -q
945 *** runcommand verify -q
948
946
949 $ cd ..
947 $ cd ..
950
948
951 Test symlink traversal over cached audited paths:
949 Test symlink traversal over cached audited paths:
952 -------------------------------------------------
950 -------------------------------------------------
953
951
954 #if symlink
952 #if symlink
955
953
956 set up symlink hell
954 set up symlink hell
957
955
958 $ mkdir merge-symlink-out
956 $ mkdir merge-symlink-out
959 $ hg init merge-symlink
957 $ hg init merge-symlink
960 $ cd merge-symlink
958 $ cd merge-symlink
961 $ touch base
959 $ touch base
962 $ hg commit -qAm base
960 $ hg commit -qAm base
963 $ ln -s ../merge-symlink-out a
961 $ ln -s ../merge-symlink-out a
964 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
962 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
965 $ hg up -q 0
963 $ hg up -q 0
966 $ mkdir a
964 $ mkdir a
967 $ touch a/poisoned
965 $ touch a/poisoned
968 $ hg commit -qAm 'file a/poisoned'
966 $ hg commit -qAm 'file a/poisoned'
969 $ hg log -G -T '{rev}: {desc}\n'
967 $ hg log -G -T '{rev}: {desc}\n'
970 @ 2: file a/poisoned
968 @ 2: file a/poisoned
971 |
969 |
972 | o 1: symlink a -> ../merge-symlink-out
970 | o 1: symlink a -> ../merge-symlink-out
973 |/
971 |/
974 o 0: base
972 o 0: base
975
973
976
974
977 try trivial merge after update: cache of audited paths should be discarded,
975 try trivial merge after update: cache of audited paths should be discarded,
978 and the merge should fail (issue5628)
976 and the merge should fail (issue5628)
979
977
980 $ hg up -q null
978 $ hg up -q null
981 >>> from hgclient import check, readchannel, runcommand
979 >>> from hgclient import check, readchannel, runcommand
982 >>> @check
980 >>> @check
983 ... def merge(server):
981 ... def merge(server):
984 ... readchannel(server)
982 ... readchannel(server)
985 ... # audit a/poisoned as a good path
983 ... # audit a/poisoned as a good path
986 ... runcommand(server, ['up', '-qC', '2'])
984 ... runcommand(server, ['up', '-qC', '2'])
987 ... runcommand(server, ['up', '-qC', '1'])
985 ... runcommand(server, ['up', '-qC', '1'])
988 ... # here a is a symlink, so a/poisoned is bad
986 ... # here a is a symlink, so a/poisoned is bad
989 ... runcommand(server, ['merge', '2'])
987 ... runcommand(server, ['merge', '2'])
990 *** runcommand up -qC 2
988 *** runcommand up -qC 2
991 *** runcommand up -qC 1
989 *** runcommand up -qC 1
992 *** runcommand merge 2
990 *** runcommand merge 2
993 abort: path 'a/poisoned' traverses symbolic link 'a'
991 abort: path 'a/poisoned' traverses symbolic link 'a'
994 [255]
992 [255]
995 $ ls ../merge-symlink-out
993 $ ls ../merge-symlink-out
996
994
997 cache of repo.auditor should be discarded, so matcher would never traverse
995 cache of repo.auditor should be discarded, so matcher would never traverse
998 symlinks:
996 symlinks:
999
997
1000 $ hg up -qC 0
998 $ hg up -qC 0
1001 $ touch ../merge-symlink-out/poisoned
999 $ touch ../merge-symlink-out/poisoned
1002 >>> from hgclient import check, readchannel, runcommand
1000 >>> from hgclient import check, readchannel, runcommand
1003 >>> @check
1001 >>> @check
1004 ... def files(server):
1002 ... def files(server):
1005 ... readchannel(server)
1003 ... readchannel(server)
1006 ... runcommand(server, ['up', '-qC', '2'])
1004 ... runcommand(server, ['up', '-qC', '2'])
1007 ... # audit a/poisoned as a good path
1005 ... # audit a/poisoned as a good path
1008 ... runcommand(server, ['files', 'a/poisoned'])
1006 ... runcommand(server, ['files', 'a/poisoned'])
1009 ... runcommand(server, ['up', '-qC', '0'])
1007 ... runcommand(server, ['up', '-qC', '0'])
1010 ... runcommand(server, ['up', '-qC', '1'])
1008 ... runcommand(server, ['up', '-qC', '1'])
1011 ... # here 'a' is a symlink, so a/poisoned should be warned
1009 ... # here 'a' is a symlink, so a/poisoned should be warned
1012 ... runcommand(server, ['files', 'a/poisoned'])
1010 ... runcommand(server, ['files', 'a/poisoned'])
1013 *** runcommand up -qC 2
1011 *** runcommand up -qC 2
1014 *** runcommand files a/poisoned
1012 *** runcommand files a/poisoned
1015 a/poisoned
1013 a/poisoned
1016 *** runcommand up -qC 0
1014 *** runcommand up -qC 0
1017 *** runcommand up -qC 1
1015 *** runcommand up -qC 1
1018 *** runcommand files a/poisoned
1016 *** runcommand files a/poisoned
1019 abort: path 'a/poisoned' traverses symbolic link 'a'
1017 abort: path 'a/poisoned' traverses symbolic link 'a'
1020 [255]
1018 [255]
1021
1019
1022 $ cd ..
1020 $ cd ..
1023
1021
1024 #endif
1022 #endif
1 NO CONTENT: file was removed
NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (912 lines changed) Show them Hide them
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now