##// END OF EJS Templates
wireprotov2: send protocol settings frame from client...
Gregory Szorc -
r40168:762ef19a default
parent child Browse files
Show More
@@ -1,1418 +1,1421
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=dynamicdefault,
117 default=dynamicdefault,
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('annotate', 'word-diff',
150 coreconfigitem('annotate', 'word-diff',
151 default=False,
151 default=False,
152 )
152 )
153 coreconfigitem('auth', 'cookiefile',
153 coreconfigitem('auth', 'cookiefile',
154 default=None,
154 default=None,
155 )
155 )
156 # bookmarks.pushing: internal hack for discovery
156 # bookmarks.pushing: internal hack for discovery
157 coreconfigitem('bookmarks', 'pushing',
157 coreconfigitem('bookmarks', 'pushing',
158 default=list,
158 default=list,
159 )
159 )
160 # bundle.mainreporoot: internal hack for bundlerepo
160 # bundle.mainreporoot: internal hack for bundlerepo
161 coreconfigitem('bundle', 'mainreporoot',
161 coreconfigitem('bundle', 'mainreporoot',
162 default='',
162 default='',
163 )
163 )
164 coreconfigitem('censor', 'policy',
164 coreconfigitem('censor', 'policy',
165 default='abort',
165 default='abort',
166 )
166 )
167 coreconfigitem('chgserver', 'idletimeout',
167 coreconfigitem('chgserver', 'idletimeout',
168 default=3600,
168 default=3600,
169 )
169 )
170 coreconfigitem('chgserver', 'skiphash',
170 coreconfigitem('chgserver', 'skiphash',
171 default=False,
171 default=False,
172 )
172 )
173 coreconfigitem('cmdserver', 'log',
173 coreconfigitem('cmdserver', 'log',
174 default=None,
174 default=None,
175 )
175 )
176 coreconfigitem('color', '.*',
176 coreconfigitem('color', '.*',
177 default=None,
177 default=None,
178 generic=True,
178 generic=True,
179 )
179 )
180 coreconfigitem('color', 'mode',
180 coreconfigitem('color', 'mode',
181 default='auto',
181 default='auto',
182 )
182 )
183 coreconfigitem('color', 'pagermode',
183 coreconfigitem('color', 'pagermode',
184 default=dynamicdefault,
184 default=dynamicdefault,
185 )
185 )
186 coreconfigitem('commands', 'grep.all-files',
186 coreconfigitem('commands', 'grep.all-files',
187 default=False,
187 default=False,
188 )
188 )
189 coreconfigitem('commands', 'resolve.confirm',
189 coreconfigitem('commands', 'resolve.confirm',
190 default=False,
190 default=False,
191 )
191 )
192 coreconfigitem('commands', 'resolve.explicit-re-merge',
192 coreconfigitem('commands', 'resolve.explicit-re-merge',
193 default=False,
193 default=False,
194 )
194 )
195 coreconfigitem('commands', 'resolve.mark-check',
195 coreconfigitem('commands', 'resolve.mark-check',
196 default='none',
196 default='none',
197 )
197 )
198 coreconfigitem('commands', 'show.aliasprefix',
198 coreconfigitem('commands', 'show.aliasprefix',
199 default=list,
199 default=list,
200 )
200 )
201 coreconfigitem('commands', 'status.relative',
201 coreconfigitem('commands', 'status.relative',
202 default=False,
202 default=False,
203 )
203 )
204 coreconfigitem('commands', 'status.skipstates',
204 coreconfigitem('commands', 'status.skipstates',
205 default=[],
205 default=[],
206 )
206 )
207 coreconfigitem('commands', 'status.terse',
207 coreconfigitem('commands', 'status.terse',
208 default='',
208 default='',
209 )
209 )
210 coreconfigitem('commands', 'status.verbose',
210 coreconfigitem('commands', 'status.verbose',
211 default=False,
211 default=False,
212 )
212 )
213 coreconfigitem('commands', 'update.check',
213 coreconfigitem('commands', 'update.check',
214 default=None,
214 default=None,
215 )
215 )
216 coreconfigitem('commands', 'update.requiredest',
216 coreconfigitem('commands', 'update.requiredest',
217 default=False,
217 default=False,
218 )
218 )
219 coreconfigitem('committemplate', '.*',
219 coreconfigitem('committemplate', '.*',
220 default=None,
220 default=None,
221 generic=True,
221 generic=True,
222 )
222 )
223 coreconfigitem('convert', 'bzr.saverev',
223 coreconfigitem('convert', 'bzr.saverev',
224 default=True,
224 default=True,
225 )
225 )
226 coreconfigitem('convert', 'cvsps.cache',
226 coreconfigitem('convert', 'cvsps.cache',
227 default=True,
227 default=True,
228 )
228 )
229 coreconfigitem('convert', 'cvsps.fuzz',
229 coreconfigitem('convert', 'cvsps.fuzz',
230 default=60,
230 default=60,
231 )
231 )
232 coreconfigitem('convert', 'cvsps.logencoding',
232 coreconfigitem('convert', 'cvsps.logencoding',
233 default=None,
233 default=None,
234 )
234 )
235 coreconfigitem('convert', 'cvsps.mergefrom',
235 coreconfigitem('convert', 'cvsps.mergefrom',
236 default=None,
236 default=None,
237 )
237 )
238 coreconfigitem('convert', 'cvsps.mergeto',
238 coreconfigitem('convert', 'cvsps.mergeto',
239 default=None,
239 default=None,
240 )
240 )
241 coreconfigitem('convert', 'git.committeractions',
241 coreconfigitem('convert', 'git.committeractions',
242 default=lambda: ['messagedifferent'],
242 default=lambda: ['messagedifferent'],
243 )
243 )
244 coreconfigitem('convert', 'git.extrakeys',
244 coreconfigitem('convert', 'git.extrakeys',
245 default=list,
245 default=list,
246 )
246 )
247 coreconfigitem('convert', 'git.findcopiesharder',
247 coreconfigitem('convert', 'git.findcopiesharder',
248 default=False,
248 default=False,
249 )
249 )
250 coreconfigitem('convert', 'git.remoteprefix',
250 coreconfigitem('convert', 'git.remoteprefix',
251 default='remote',
251 default='remote',
252 )
252 )
253 coreconfigitem('convert', 'git.renamelimit',
253 coreconfigitem('convert', 'git.renamelimit',
254 default=400,
254 default=400,
255 )
255 )
256 coreconfigitem('convert', 'git.saverev',
256 coreconfigitem('convert', 'git.saverev',
257 default=True,
257 default=True,
258 )
258 )
259 coreconfigitem('convert', 'git.similarity',
259 coreconfigitem('convert', 'git.similarity',
260 default=50,
260 default=50,
261 )
261 )
262 coreconfigitem('convert', 'git.skipsubmodules',
262 coreconfigitem('convert', 'git.skipsubmodules',
263 default=False,
263 default=False,
264 )
264 )
265 coreconfigitem('convert', 'hg.clonebranches',
265 coreconfigitem('convert', 'hg.clonebranches',
266 default=False,
266 default=False,
267 )
267 )
268 coreconfigitem('convert', 'hg.ignoreerrors',
268 coreconfigitem('convert', 'hg.ignoreerrors',
269 default=False,
269 default=False,
270 )
270 )
271 coreconfigitem('convert', 'hg.revs',
271 coreconfigitem('convert', 'hg.revs',
272 default=None,
272 default=None,
273 )
273 )
274 coreconfigitem('convert', 'hg.saverev',
274 coreconfigitem('convert', 'hg.saverev',
275 default=False,
275 default=False,
276 )
276 )
277 coreconfigitem('convert', 'hg.sourcename',
277 coreconfigitem('convert', 'hg.sourcename',
278 default=None,
278 default=None,
279 )
279 )
280 coreconfigitem('convert', 'hg.startrev',
280 coreconfigitem('convert', 'hg.startrev',
281 default=None,
281 default=None,
282 )
282 )
283 coreconfigitem('convert', 'hg.tagsbranch',
283 coreconfigitem('convert', 'hg.tagsbranch',
284 default='default',
284 default='default',
285 )
285 )
286 coreconfigitem('convert', 'hg.usebranchnames',
286 coreconfigitem('convert', 'hg.usebranchnames',
287 default=True,
287 default=True,
288 )
288 )
289 coreconfigitem('convert', 'ignoreancestorcheck',
289 coreconfigitem('convert', 'ignoreancestorcheck',
290 default=False,
290 default=False,
291 )
291 )
292 coreconfigitem('convert', 'localtimezone',
292 coreconfigitem('convert', 'localtimezone',
293 default=False,
293 default=False,
294 )
294 )
295 coreconfigitem('convert', 'p4.encoding',
295 coreconfigitem('convert', 'p4.encoding',
296 default=dynamicdefault,
296 default=dynamicdefault,
297 )
297 )
298 coreconfigitem('convert', 'p4.startrev',
298 coreconfigitem('convert', 'p4.startrev',
299 default=0,
299 default=0,
300 )
300 )
301 coreconfigitem('convert', 'skiptags',
301 coreconfigitem('convert', 'skiptags',
302 default=False,
302 default=False,
303 )
303 )
304 coreconfigitem('convert', 'svn.debugsvnlog',
304 coreconfigitem('convert', 'svn.debugsvnlog',
305 default=True,
305 default=True,
306 )
306 )
307 coreconfigitem('convert', 'svn.trunk',
307 coreconfigitem('convert', 'svn.trunk',
308 default=None,
308 default=None,
309 )
309 )
310 coreconfigitem('convert', 'svn.tags',
310 coreconfigitem('convert', 'svn.tags',
311 default=None,
311 default=None,
312 )
312 )
313 coreconfigitem('convert', 'svn.branches',
313 coreconfigitem('convert', 'svn.branches',
314 default=None,
314 default=None,
315 )
315 )
316 coreconfigitem('convert', 'svn.startrev',
316 coreconfigitem('convert', 'svn.startrev',
317 default=0,
317 default=0,
318 )
318 )
319 coreconfigitem('debug', 'dirstate.delaywrite',
319 coreconfigitem('debug', 'dirstate.delaywrite',
320 default=0,
320 default=0,
321 )
321 )
322 coreconfigitem('defaults', '.*',
322 coreconfigitem('defaults', '.*',
323 default=None,
323 default=None,
324 generic=True,
324 generic=True,
325 )
325 )
326 coreconfigitem('devel', 'all-warnings',
326 coreconfigitem('devel', 'all-warnings',
327 default=False,
327 default=False,
328 )
328 )
329 coreconfigitem('devel', 'bundle2.debug',
329 coreconfigitem('devel', 'bundle2.debug',
330 default=False,
330 default=False,
331 )
331 )
332 coreconfigitem('devel', 'cache-vfs',
332 coreconfigitem('devel', 'cache-vfs',
333 default=None,
333 default=None,
334 )
334 )
335 coreconfigitem('devel', 'check-locks',
335 coreconfigitem('devel', 'check-locks',
336 default=False,
336 default=False,
337 )
337 )
338 coreconfigitem('devel', 'check-relroot',
338 coreconfigitem('devel', 'check-relroot',
339 default=False,
339 default=False,
340 )
340 )
341 coreconfigitem('devel', 'default-date',
341 coreconfigitem('devel', 'default-date',
342 default=None,
342 default=None,
343 )
343 )
344 coreconfigitem('devel', 'deprec-warn',
344 coreconfigitem('devel', 'deprec-warn',
345 default=False,
345 default=False,
346 )
346 )
347 coreconfigitem('devel', 'disableloaddefaultcerts',
347 coreconfigitem('devel', 'disableloaddefaultcerts',
348 default=False,
348 default=False,
349 )
349 )
350 coreconfigitem('devel', 'warn-empty-changegroup',
350 coreconfigitem('devel', 'warn-empty-changegroup',
351 default=False,
351 default=False,
352 )
352 )
353 coreconfigitem('devel', 'legacy.exchange',
353 coreconfigitem('devel', 'legacy.exchange',
354 default=list,
354 default=list,
355 )
355 )
356 coreconfigitem('devel', 'servercafile',
356 coreconfigitem('devel', 'servercafile',
357 default='',
357 default='',
358 )
358 )
359 coreconfigitem('devel', 'serverexactprotocol',
359 coreconfigitem('devel', 'serverexactprotocol',
360 default='',
360 default='',
361 )
361 )
362 coreconfigitem('devel', 'serverrequirecert',
362 coreconfigitem('devel', 'serverrequirecert',
363 default=False,
363 default=False,
364 )
364 )
365 coreconfigitem('devel', 'strip-obsmarkers',
365 coreconfigitem('devel', 'strip-obsmarkers',
366 default=True,
366 default=True,
367 )
367 )
368 coreconfigitem('devel', 'warn-config',
368 coreconfigitem('devel', 'warn-config',
369 default=None,
369 default=None,
370 )
370 )
371 coreconfigitem('devel', 'warn-config-default',
371 coreconfigitem('devel', 'warn-config-default',
372 default=None,
372 default=None,
373 )
373 )
374 coreconfigitem('devel', 'user.obsmarker',
374 coreconfigitem('devel', 'user.obsmarker',
375 default=None,
375 default=None,
376 )
376 )
377 coreconfigitem('devel', 'warn-config-unknown',
377 coreconfigitem('devel', 'warn-config-unknown',
378 default=None,
378 default=None,
379 )
379 )
380 coreconfigitem('devel', 'debug.copies',
380 coreconfigitem('devel', 'debug.copies',
381 default=False,
381 default=False,
382 )
382 )
383 coreconfigitem('devel', 'debug.extensions',
383 coreconfigitem('devel', 'debug.extensions',
384 default=False,
384 default=False,
385 )
385 )
386 coreconfigitem('devel', 'debug.peer-request',
386 coreconfigitem('devel', 'debug.peer-request',
387 default=False,
387 default=False,
388 )
388 )
389 coreconfigitem('diff', 'nodates',
389 coreconfigitem('diff', 'nodates',
390 default=False,
390 default=False,
391 )
391 )
392 coreconfigitem('diff', 'showfunc',
392 coreconfigitem('diff', 'showfunc',
393 default=False,
393 default=False,
394 )
394 )
395 coreconfigitem('diff', 'unified',
395 coreconfigitem('diff', 'unified',
396 default=None,
396 default=None,
397 )
397 )
398 coreconfigitem('diff', 'git',
398 coreconfigitem('diff', 'git',
399 default=False,
399 default=False,
400 )
400 )
401 coreconfigitem('diff', 'ignorews',
401 coreconfigitem('diff', 'ignorews',
402 default=False,
402 default=False,
403 )
403 )
404 coreconfigitem('diff', 'ignorewsamount',
404 coreconfigitem('diff', 'ignorewsamount',
405 default=False,
405 default=False,
406 )
406 )
407 coreconfigitem('diff', 'ignoreblanklines',
407 coreconfigitem('diff', 'ignoreblanklines',
408 default=False,
408 default=False,
409 )
409 )
410 coreconfigitem('diff', 'ignorewseol',
410 coreconfigitem('diff', 'ignorewseol',
411 default=False,
411 default=False,
412 )
412 )
413 coreconfigitem('diff', 'nobinary',
413 coreconfigitem('diff', 'nobinary',
414 default=False,
414 default=False,
415 )
415 )
416 coreconfigitem('diff', 'noprefix',
416 coreconfigitem('diff', 'noprefix',
417 default=False,
417 default=False,
418 )
418 )
419 coreconfigitem('diff', 'word-diff',
419 coreconfigitem('diff', 'word-diff',
420 default=False,
420 default=False,
421 )
421 )
422 coreconfigitem('email', 'bcc',
422 coreconfigitem('email', 'bcc',
423 default=None,
423 default=None,
424 )
424 )
425 coreconfigitem('email', 'cc',
425 coreconfigitem('email', 'cc',
426 default=None,
426 default=None,
427 )
427 )
428 coreconfigitem('email', 'charsets',
428 coreconfigitem('email', 'charsets',
429 default=list,
429 default=list,
430 )
430 )
431 coreconfigitem('email', 'from',
431 coreconfigitem('email', 'from',
432 default=None,
432 default=None,
433 )
433 )
434 coreconfigitem('email', 'method',
434 coreconfigitem('email', 'method',
435 default='smtp',
435 default='smtp',
436 )
436 )
437 coreconfigitem('email', 'reply-to',
437 coreconfigitem('email', 'reply-to',
438 default=None,
438 default=None,
439 )
439 )
440 coreconfigitem('email', 'to',
440 coreconfigitem('email', 'to',
441 default=None,
441 default=None,
442 )
442 )
443 coreconfigitem('experimental', 'archivemetatemplate',
443 coreconfigitem('experimental', 'archivemetatemplate',
444 default=dynamicdefault,
444 default=dynamicdefault,
445 )
445 )
446 coreconfigitem('experimental', 'bundle-phases',
446 coreconfigitem('experimental', 'bundle-phases',
447 default=False,
447 default=False,
448 )
448 )
449 coreconfigitem('experimental', 'bundle2-advertise',
449 coreconfigitem('experimental', 'bundle2-advertise',
450 default=True,
450 default=True,
451 )
451 )
452 coreconfigitem('experimental', 'bundle2-output-capture',
452 coreconfigitem('experimental', 'bundle2-output-capture',
453 default=False,
453 default=False,
454 )
454 )
455 coreconfigitem('experimental', 'bundle2.pushback',
455 coreconfigitem('experimental', 'bundle2.pushback',
456 default=False,
456 default=False,
457 )
457 )
458 coreconfigitem('experimental', 'bundle2lazylocking',
458 coreconfigitem('experimental', 'bundle2lazylocking',
459 default=False,
459 default=False,
460 )
460 )
461 coreconfigitem('experimental', 'bundlecomplevel',
461 coreconfigitem('experimental', 'bundlecomplevel',
462 default=None,
462 default=None,
463 )
463 )
464 coreconfigitem('experimental', 'bundlecomplevel.bzip2',
464 coreconfigitem('experimental', 'bundlecomplevel.bzip2',
465 default=None,
465 default=None,
466 )
466 )
467 coreconfigitem('experimental', 'bundlecomplevel.gzip',
467 coreconfigitem('experimental', 'bundlecomplevel.gzip',
468 default=None,
468 default=None,
469 )
469 )
470 coreconfigitem('experimental', 'bundlecomplevel.none',
470 coreconfigitem('experimental', 'bundlecomplevel.none',
471 default=None,
471 default=None,
472 )
472 )
473 coreconfigitem('experimental', 'bundlecomplevel.zstd',
473 coreconfigitem('experimental', 'bundlecomplevel.zstd',
474 default=None,
474 default=None,
475 )
475 )
476 coreconfigitem('experimental', 'changegroup3',
476 coreconfigitem('experimental', 'changegroup3',
477 default=False,
477 default=False,
478 )
478 )
479 coreconfigitem('experimental', 'clientcompressionengines',
479 coreconfigitem('experimental', 'clientcompressionengines',
480 default=list,
480 default=list,
481 )
481 )
482 coreconfigitem('experimental', 'copytrace',
482 coreconfigitem('experimental', 'copytrace',
483 default='on',
483 default='on',
484 )
484 )
485 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
485 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
486 default=100,
486 default=100,
487 )
487 )
488 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
488 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
489 default=100,
489 default=100,
490 )
490 )
491 coreconfigitem('experimental', 'crecordtest',
491 coreconfigitem('experimental', 'crecordtest',
492 default=None,
492 default=None,
493 )
493 )
494 coreconfigitem('experimental', 'directaccess',
494 coreconfigitem('experimental', 'directaccess',
495 default=False,
495 default=False,
496 )
496 )
497 coreconfigitem('experimental', 'directaccess.revnums',
497 coreconfigitem('experimental', 'directaccess.revnums',
498 default=False,
498 default=False,
499 )
499 )
500 coreconfigitem('experimental', 'editortmpinhg',
500 coreconfigitem('experimental', 'editortmpinhg',
501 default=False,
501 default=False,
502 )
502 )
503 coreconfigitem('experimental', 'evolution',
503 coreconfigitem('experimental', 'evolution',
504 default=list,
504 default=list,
505 )
505 )
506 coreconfigitem('experimental', 'evolution.allowdivergence',
506 coreconfigitem('experimental', 'evolution.allowdivergence',
507 default=False,
507 default=False,
508 alias=[('experimental', 'allowdivergence')]
508 alias=[('experimental', 'allowdivergence')]
509 )
509 )
510 coreconfigitem('experimental', 'evolution.allowunstable',
510 coreconfigitem('experimental', 'evolution.allowunstable',
511 default=None,
511 default=None,
512 )
512 )
513 coreconfigitem('experimental', 'evolution.createmarkers',
513 coreconfigitem('experimental', 'evolution.createmarkers',
514 default=None,
514 default=None,
515 )
515 )
516 coreconfigitem('experimental', 'evolution.effect-flags',
516 coreconfigitem('experimental', 'evolution.effect-flags',
517 default=True,
517 default=True,
518 alias=[('experimental', 'effect-flags')]
518 alias=[('experimental', 'effect-flags')]
519 )
519 )
520 coreconfigitem('experimental', 'evolution.exchange',
520 coreconfigitem('experimental', 'evolution.exchange',
521 default=None,
521 default=None,
522 )
522 )
523 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
523 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
524 default=False,
524 default=False,
525 )
525 )
526 coreconfigitem('experimental', 'evolution.report-instabilities',
526 coreconfigitem('experimental', 'evolution.report-instabilities',
527 default=True,
527 default=True,
528 )
528 )
529 coreconfigitem('experimental', 'evolution.track-operation',
529 coreconfigitem('experimental', 'evolution.track-operation',
530 default=True,
530 default=True,
531 )
531 )
532 coreconfigitem('experimental', 'maxdeltachainspan',
532 coreconfigitem('experimental', 'maxdeltachainspan',
533 default=-1,
533 default=-1,
534 )
534 )
535 coreconfigitem('experimental', 'mergetempdirprefix',
535 coreconfigitem('experimental', 'mergetempdirprefix',
536 default=None,
536 default=None,
537 )
537 )
538 coreconfigitem('experimental', 'mmapindexthreshold',
538 coreconfigitem('experimental', 'mmapindexthreshold',
539 default=None,
539 default=None,
540 )
540 )
541 coreconfigitem('experimental', 'narrow',
541 coreconfigitem('experimental', 'narrow',
542 default=False,
542 default=False,
543 )
543 )
544 coreconfigitem('experimental', 'nonnormalparanoidcheck',
544 coreconfigitem('experimental', 'nonnormalparanoidcheck',
545 default=False,
545 default=False,
546 )
546 )
547 coreconfigitem('experimental', 'exportableenviron',
547 coreconfigitem('experimental', 'exportableenviron',
548 default=list,
548 default=list,
549 )
549 )
550 coreconfigitem('experimental', 'extendedheader.index',
550 coreconfigitem('experimental', 'extendedheader.index',
551 default=None,
551 default=None,
552 )
552 )
553 coreconfigitem('experimental', 'extendedheader.similarity',
553 coreconfigitem('experimental', 'extendedheader.similarity',
554 default=False,
554 default=False,
555 )
555 )
556 coreconfigitem('experimental', 'format.compression',
556 coreconfigitem('experimental', 'format.compression',
557 default='zlib',
557 default='zlib',
558 )
558 )
559 coreconfigitem('experimental', 'graphshorten',
559 coreconfigitem('experimental', 'graphshorten',
560 default=False,
560 default=False,
561 )
561 )
562 coreconfigitem('experimental', 'graphstyle.parent',
562 coreconfigitem('experimental', 'graphstyle.parent',
563 default=dynamicdefault,
563 default=dynamicdefault,
564 )
564 )
565 coreconfigitem('experimental', 'graphstyle.missing',
565 coreconfigitem('experimental', 'graphstyle.missing',
566 default=dynamicdefault,
566 default=dynamicdefault,
567 )
567 )
568 coreconfigitem('experimental', 'graphstyle.grandparent',
568 coreconfigitem('experimental', 'graphstyle.grandparent',
569 default=dynamicdefault,
569 default=dynamicdefault,
570 )
570 )
571 coreconfigitem('experimental', 'hook-track-tags',
571 coreconfigitem('experimental', 'hook-track-tags',
572 default=False,
572 default=False,
573 )
573 )
574 coreconfigitem('experimental', 'httppeer.advertise-v2',
574 coreconfigitem('experimental', 'httppeer.advertise-v2',
575 default=False,
575 default=False,
576 )
576 )
577 coreconfigitem('experimental', 'httppeer.v2-encoder-order',
578 default=None,
579 )
577 coreconfigitem('experimental', 'httppostargs',
580 coreconfigitem('experimental', 'httppostargs',
578 default=False,
581 default=False,
579 )
582 )
580 coreconfigitem('experimental', 'mergedriver',
583 coreconfigitem('experimental', 'mergedriver',
581 default=None,
584 default=None,
582 )
585 )
583 coreconfigitem('experimental', 'nointerrupt', default=False)
586 coreconfigitem('experimental', 'nointerrupt', default=False)
584 coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
587 coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
585
588
586 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
589 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
587 default=False,
590 default=False,
588 )
591 )
589 coreconfigitem('experimental', 'remotenames',
592 coreconfigitem('experimental', 'remotenames',
590 default=False,
593 default=False,
591 )
594 )
592 coreconfigitem('experimental', 'removeemptydirs',
595 coreconfigitem('experimental', 'removeemptydirs',
593 default=True,
596 default=True,
594 )
597 )
595 coreconfigitem('experimental', 'revisions.prefixhexnode',
598 coreconfigitem('experimental', 'revisions.prefixhexnode',
596 default=False,
599 default=False,
597 )
600 )
598 coreconfigitem('experimental', 'revlogv2',
601 coreconfigitem('experimental', 'revlogv2',
599 default=None,
602 default=None,
600 )
603 )
601 coreconfigitem('experimental', 'revisions.disambiguatewithin',
604 coreconfigitem('experimental', 'revisions.disambiguatewithin',
602 default=None,
605 default=None,
603 )
606 )
604 coreconfigitem('experimental', 'single-head-per-branch',
607 coreconfigitem('experimental', 'single-head-per-branch',
605 default=False,
608 default=False,
606 )
609 )
607 coreconfigitem('experimental', 'sshserver.support-v2',
610 coreconfigitem('experimental', 'sshserver.support-v2',
608 default=False,
611 default=False,
609 )
612 )
610 coreconfigitem('experimental', 'spacemovesdown',
613 coreconfigitem('experimental', 'spacemovesdown',
611 default=False,
614 default=False,
612 )
615 )
613 coreconfigitem('experimental', 'sparse-read',
616 coreconfigitem('experimental', 'sparse-read',
614 default=False,
617 default=False,
615 )
618 )
616 coreconfigitem('experimental', 'sparse-read.density-threshold',
619 coreconfigitem('experimental', 'sparse-read.density-threshold',
617 default=0.50,
620 default=0.50,
618 )
621 )
619 coreconfigitem('experimental', 'sparse-read.min-gap-size',
622 coreconfigitem('experimental', 'sparse-read.min-gap-size',
620 default='65K',
623 default='65K',
621 )
624 )
622 coreconfigitem('experimental', 'treemanifest',
625 coreconfigitem('experimental', 'treemanifest',
623 default=False,
626 default=False,
624 )
627 )
625 coreconfigitem('experimental', 'update.atomic-file',
628 coreconfigitem('experimental', 'update.atomic-file',
626 default=False,
629 default=False,
627 )
630 )
628 coreconfigitem('experimental', 'sshpeer.advertise-v2',
631 coreconfigitem('experimental', 'sshpeer.advertise-v2',
629 default=False,
632 default=False,
630 )
633 )
631 coreconfigitem('experimental', 'web.apiserver',
634 coreconfigitem('experimental', 'web.apiserver',
632 default=False,
635 default=False,
633 )
636 )
634 coreconfigitem('experimental', 'web.api.http-v2',
637 coreconfigitem('experimental', 'web.api.http-v2',
635 default=False,
638 default=False,
636 )
639 )
637 coreconfigitem('experimental', 'web.api.debugreflect',
640 coreconfigitem('experimental', 'web.api.debugreflect',
638 default=False,
641 default=False,
639 )
642 )
640 coreconfigitem('experimental', 'worker.wdir-get-thread-safe',
643 coreconfigitem('experimental', 'worker.wdir-get-thread-safe',
641 default=False,
644 default=False,
642 )
645 )
643 coreconfigitem('experimental', 'xdiff',
646 coreconfigitem('experimental', 'xdiff',
644 default=False,
647 default=False,
645 )
648 )
646 coreconfigitem('extensions', '.*',
649 coreconfigitem('extensions', '.*',
647 default=None,
650 default=None,
648 generic=True,
651 generic=True,
649 )
652 )
650 coreconfigitem('extdata', '.*',
653 coreconfigitem('extdata', '.*',
651 default=None,
654 default=None,
652 generic=True,
655 generic=True,
653 )
656 )
654 coreconfigitem('format', 'chunkcachesize',
657 coreconfigitem('format', 'chunkcachesize',
655 default=None,
658 default=None,
656 )
659 )
657 coreconfigitem('format', 'dotencode',
660 coreconfigitem('format', 'dotencode',
658 default=True,
661 default=True,
659 )
662 )
660 coreconfigitem('format', 'generaldelta',
663 coreconfigitem('format', 'generaldelta',
661 default=False,
664 default=False,
662 )
665 )
663 coreconfigitem('format', 'manifestcachesize',
666 coreconfigitem('format', 'manifestcachesize',
664 default=None,
667 default=None,
665 )
668 )
666 coreconfigitem('format', 'maxchainlen',
669 coreconfigitem('format', 'maxchainlen',
667 default=dynamicdefault,
670 default=dynamicdefault,
668 )
671 )
669 coreconfigitem('format', 'obsstore-version',
672 coreconfigitem('format', 'obsstore-version',
670 default=None,
673 default=None,
671 )
674 )
672 coreconfigitem('format', 'sparse-revlog',
675 coreconfigitem('format', 'sparse-revlog',
673 default=False,
676 default=False,
674 )
677 )
675 coreconfigitem('format', 'usefncache',
678 coreconfigitem('format', 'usefncache',
676 default=True,
679 default=True,
677 )
680 )
678 coreconfigitem('format', 'usegeneraldelta',
681 coreconfigitem('format', 'usegeneraldelta',
679 default=True,
682 default=True,
680 )
683 )
681 coreconfigitem('format', 'usestore',
684 coreconfigitem('format', 'usestore',
682 default=True,
685 default=True,
683 )
686 )
684 coreconfigitem('format', 'internal-phase',
687 coreconfigitem('format', 'internal-phase',
685 default=False,
688 default=False,
686 )
689 )
687 coreconfigitem('fsmonitor', 'warn_when_unused',
690 coreconfigitem('fsmonitor', 'warn_when_unused',
688 default=True,
691 default=True,
689 )
692 )
690 coreconfigitem('fsmonitor', 'warn_update_file_count',
693 coreconfigitem('fsmonitor', 'warn_update_file_count',
691 default=50000,
694 default=50000,
692 )
695 )
693 coreconfigitem('hooks', '.*',
696 coreconfigitem('hooks', '.*',
694 default=dynamicdefault,
697 default=dynamicdefault,
695 generic=True,
698 generic=True,
696 )
699 )
697 coreconfigitem('hgweb-paths', '.*',
700 coreconfigitem('hgweb-paths', '.*',
698 default=list,
701 default=list,
699 generic=True,
702 generic=True,
700 )
703 )
701 coreconfigitem('hostfingerprints', '.*',
704 coreconfigitem('hostfingerprints', '.*',
702 default=list,
705 default=list,
703 generic=True,
706 generic=True,
704 )
707 )
705 coreconfigitem('hostsecurity', 'ciphers',
708 coreconfigitem('hostsecurity', 'ciphers',
706 default=None,
709 default=None,
707 )
710 )
708 coreconfigitem('hostsecurity', 'disabletls10warning',
711 coreconfigitem('hostsecurity', 'disabletls10warning',
709 default=False,
712 default=False,
710 )
713 )
711 coreconfigitem('hostsecurity', 'minimumprotocol',
714 coreconfigitem('hostsecurity', 'minimumprotocol',
712 default=dynamicdefault,
715 default=dynamicdefault,
713 )
716 )
714 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
717 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
715 default=dynamicdefault,
718 default=dynamicdefault,
716 generic=True,
719 generic=True,
717 )
720 )
718 coreconfigitem('hostsecurity', '.*:ciphers$',
721 coreconfigitem('hostsecurity', '.*:ciphers$',
719 default=dynamicdefault,
722 default=dynamicdefault,
720 generic=True,
723 generic=True,
721 )
724 )
722 coreconfigitem('hostsecurity', '.*:fingerprints$',
725 coreconfigitem('hostsecurity', '.*:fingerprints$',
723 default=list,
726 default=list,
724 generic=True,
727 generic=True,
725 )
728 )
726 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
729 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
727 default=None,
730 default=None,
728 generic=True,
731 generic=True,
729 )
732 )
730
733
731 coreconfigitem('http_proxy', 'always',
734 coreconfigitem('http_proxy', 'always',
732 default=False,
735 default=False,
733 )
736 )
734 coreconfigitem('http_proxy', 'host',
737 coreconfigitem('http_proxy', 'host',
735 default=None,
738 default=None,
736 )
739 )
737 coreconfigitem('http_proxy', 'no',
740 coreconfigitem('http_proxy', 'no',
738 default=list,
741 default=list,
739 )
742 )
740 coreconfigitem('http_proxy', 'passwd',
743 coreconfigitem('http_proxy', 'passwd',
741 default=None,
744 default=None,
742 )
745 )
743 coreconfigitem('http_proxy', 'user',
746 coreconfigitem('http_proxy', 'user',
744 default=None,
747 default=None,
745 )
748 )
746
749
747 coreconfigitem('http', 'timeout',
750 coreconfigitem('http', 'timeout',
748 default=None,
751 default=None,
749 )
752 )
750
753
751 coreconfigitem('logtoprocess', 'commandexception',
754 coreconfigitem('logtoprocess', 'commandexception',
752 default=None,
755 default=None,
753 )
756 )
754 coreconfigitem('logtoprocess', 'commandfinish',
757 coreconfigitem('logtoprocess', 'commandfinish',
755 default=None,
758 default=None,
756 )
759 )
757 coreconfigitem('logtoprocess', 'command',
760 coreconfigitem('logtoprocess', 'command',
758 default=None,
761 default=None,
759 )
762 )
760 coreconfigitem('logtoprocess', 'develwarn',
763 coreconfigitem('logtoprocess', 'develwarn',
761 default=None,
764 default=None,
762 )
765 )
763 coreconfigitem('logtoprocess', 'uiblocked',
766 coreconfigitem('logtoprocess', 'uiblocked',
764 default=None,
767 default=None,
765 )
768 )
766 coreconfigitem('merge', 'checkunknown',
769 coreconfigitem('merge', 'checkunknown',
767 default='abort',
770 default='abort',
768 )
771 )
769 coreconfigitem('merge', 'checkignored',
772 coreconfigitem('merge', 'checkignored',
770 default='abort',
773 default='abort',
771 )
774 )
772 coreconfigitem('experimental', 'merge.checkpathconflicts',
775 coreconfigitem('experimental', 'merge.checkpathconflicts',
773 default=False,
776 default=False,
774 )
777 )
775 coreconfigitem('merge', 'followcopies',
778 coreconfigitem('merge', 'followcopies',
776 default=True,
779 default=True,
777 )
780 )
778 coreconfigitem('merge', 'on-failure',
781 coreconfigitem('merge', 'on-failure',
779 default='continue',
782 default='continue',
780 )
783 )
781 coreconfigitem('merge', 'preferancestor',
784 coreconfigitem('merge', 'preferancestor',
782 default=lambda: ['*'],
785 default=lambda: ['*'],
783 )
786 )
784 coreconfigitem('merge', 'strict-capability-check',
787 coreconfigitem('merge', 'strict-capability-check',
785 default=False,
788 default=False,
786 )
789 )
787 coreconfigitem('merge-tools', '.*',
790 coreconfigitem('merge-tools', '.*',
788 default=None,
791 default=None,
789 generic=True,
792 generic=True,
790 )
793 )
791 coreconfigitem('merge-tools', br'.*\.args$',
794 coreconfigitem('merge-tools', br'.*\.args$',
792 default="$local $base $other",
795 default="$local $base $other",
793 generic=True,
796 generic=True,
794 priority=-1,
797 priority=-1,
795 )
798 )
796 coreconfigitem('merge-tools', br'.*\.binary$',
799 coreconfigitem('merge-tools', br'.*\.binary$',
797 default=False,
800 default=False,
798 generic=True,
801 generic=True,
799 priority=-1,
802 priority=-1,
800 )
803 )
801 coreconfigitem('merge-tools', br'.*\.check$',
804 coreconfigitem('merge-tools', br'.*\.check$',
802 default=list,
805 default=list,
803 generic=True,
806 generic=True,
804 priority=-1,
807 priority=-1,
805 )
808 )
806 coreconfigitem('merge-tools', br'.*\.checkchanged$',
809 coreconfigitem('merge-tools', br'.*\.checkchanged$',
807 default=False,
810 default=False,
808 generic=True,
811 generic=True,
809 priority=-1,
812 priority=-1,
810 )
813 )
811 coreconfigitem('merge-tools', br'.*\.executable$',
814 coreconfigitem('merge-tools', br'.*\.executable$',
812 default=dynamicdefault,
815 default=dynamicdefault,
813 generic=True,
816 generic=True,
814 priority=-1,
817 priority=-1,
815 )
818 )
816 coreconfigitem('merge-tools', br'.*\.fixeol$',
819 coreconfigitem('merge-tools', br'.*\.fixeol$',
817 default=False,
820 default=False,
818 generic=True,
821 generic=True,
819 priority=-1,
822 priority=-1,
820 )
823 )
821 coreconfigitem('merge-tools', br'.*\.gui$',
824 coreconfigitem('merge-tools', br'.*\.gui$',
822 default=False,
825 default=False,
823 generic=True,
826 generic=True,
824 priority=-1,
827 priority=-1,
825 )
828 )
826 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
829 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
827 default='basic',
830 default='basic',
828 generic=True,
831 generic=True,
829 priority=-1,
832 priority=-1,
830 )
833 )
831 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
834 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
832 default=dynamicdefault, # take from ui.mergemarkertemplate
835 default=dynamicdefault, # take from ui.mergemarkertemplate
833 generic=True,
836 generic=True,
834 priority=-1,
837 priority=-1,
835 )
838 )
836 coreconfigitem('merge-tools', br'.*\.priority$',
839 coreconfigitem('merge-tools', br'.*\.priority$',
837 default=0,
840 default=0,
838 generic=True,
841 generic=True,
839 priority=-1,
842 priority=-1,
840 )
843 )
841 coreconfigitem('merge-tools', br'.*\.premerge$',
844 coreconfigitem('merge-tools', br'.*\.premerge$',
842 default=dynamicdefault,
845 default=dynamicdefault,
843 generic=True,
846 generic=True,
844 priority=-1,
847 priority=-1,
845 )
848 )
846 coreconfigitem('merge-tools', br'.*\.symlink$',
849 coreconfigitem('merge-tools', br'.*\.symlink$',
847 default=False,
850 default=False,
848 generic=True,
851 generic=True,
849 priority=-1,
852 priority=-1,
850 )
853 )
851 coreconfigitem('pager', 'attend-.*',
854 coreconfigitem('pager', 'attend-.*',
852 default=dynamicdefault,
855 default=dynamicdefault,
853 generic=True,
856 generic=True,
854 )
857 )
855 coreconfigitem('pager', 'ignore',
858 coreconfigitem('pager', 'ignore',
856 default=list,
859 default=list,
857 )
860 )
858 coreconfigitem('pager', 'pager',
861 coreconfigitem('pager', 'pager',
859 default=dynamicdefault,
862 default=dynamicdefault,
860 )
863 )
861 coreconfigitem('patch', 'eol',
864 coreconfigitem('patch', 'eol',
862 default='strict',
865 default='strict',
863 )
866 )
864 coreconfigitem('patch', 'fuzz',
867 coreconfigitem('patch', 'fuzz',
865 default=2,
868 default=2,
866 )
869 )
867 coreconfigitem('paths', 'default',
870 coreconfigitem('paths', 'default',
868 default=None,
871 default=None,
869 )
872 )
870 coreconfigitem('paths', 'default-push',
873 coreconfigitem('paths', 'default-push',
871 default=None,
874 default=None,
872 )
875 )
873 coreconfigitem('paths', '.*',
876 coreconfigitem('paths', '.*',
874 default=None,
877 default=None,
875 generic=True,
878 generic=True,
876 )
879 )
877 coreconfigitem('phases', 'checksubrepos',
880 coreconfigitem('phases', 'checksubrepos',
878 default='follow',
881 default='follow',
879 )
882 )
880 coreconfigitem('phases', 'new-commit',
883 coreconfigitem('phases', 'new-commit',
881 default='draft',
884 default='draft',
882 )
885 )
883 coreconfigitem('phases', 'publish',
886 coreconfigitem('phases', 'publish',
884 default=True,
887 default=True,
885 )
888 )
886 coreconfigitem('profiling', 'enabled',
889 coreconfigitem('profiling', 'enabled',
887 default=False,
890 default=False,
888 )
891 )
889 coreconfigitem('profiling', 'format',
892 coreconfigitem('profiling', 'format',
890 default='text',
893 default='text',
891 )
894 )
892 coreconfigitem('profiling', 'freq',
895 coreconfigitem('profiling', 'freq',
893 default=1000,
896 default=1000,
894 )
897 )
895 coreconfigitem('profiling', 'limit',
898 coreconfigitem('profiling', 'limit',
896 default=30,
899 default=30,
897 )
900 )
898 coreconfigitem('profiling', 'nested',
901 coreconfigitem('profiling', 'nested',
899 default=0,
902 default=0,
900 )
903 )
901 coreconfigitem('profiling', 'output',
904 coreconfigitem('profiling', 'output',
902 default=None,
905 default=None,
903 )
906 )
904 coreconfigitem('profiling', 'showmax',
907 coreconfigitem('profiling', 'showmax',
905 default=0.999,
908 default=0.999,
906 )
909 )
907 coreconfigitem('profiling', 'showmin',
910 coreconfigitem('profiling', 'showmin',
908 default=dynamicdefault,
911 default=dynamicdefault,
909 )
912 )
910 coreconfigitem('profiling', 'sort',
913 coreconfigitem('profiling', 'sort',
911 default='inlinetime',
914 default='inlinetime',
912 )
915 )
913 coreconfigitem('profiling', 'statformat',
916 coreconfigitem('profiling', 'statformat',
914 default='hotpath',
917 default='hotpath',
915 )
918 )
916 coreconfigitem('profiling', 'time-track',
919 coreconfigitem('profiling', 'time-track',
917 default='cpu',
920 default='cpu',
918 )
921 )
919 coreconfigitem('profiling', 'type',
922 coreconfigitem('profiling', 'type',
920 default='stat',
923 default='stat',
921 )
924 )
922 coreconfigitem('progress', 'assume-tty',
925 coreconfigitem('progress', 'assume-tty',
923 default=False,
926 default=False,
924 )
927 )
925 coreconfigitem('progress', 'changedelay',
928 coreconfigitem('progress', 'changedelay',
926 default=1,
929 default=1,
927 )
930 )
928 coreconfigitem('progress', 'clear-complete',
931 coreconfigitem('progress', 'clear-complete',
929 default=True,
932 default=True,
930 )
933 )
931 coreconfigitem('progress', 'debug',
934 coreconfigitem('progress', 'debug',
932 default=False,
935 default=False,
933 )
936 )
934 coreconfigitem('progress', 'delay',
937 coreconfigitem('progress', 'delay',
935 default=3,
938 default=3,
936 )
939 )
937 coreconfigitem('progress', 'disable',
940 coreconfigitem('progress', 'disable',
938 default=False,
941 default=False,
939 )
942 )
940 coreconfigitem('progress', 'estimateinterval',
943 coreconfigitem('progress', 'estimateinterval',
941 default=60.0,
944 default=60.0,
942 )
945 )
943 coreconfigitem('progress', 'format',
946 coreconfigitem('progress', 'format',
944 default=lambda: ['topic', 'bar', 'number', 'estimate'],
947 default=lambda: ['topic', 'bar', 'number', 'estimate'],
945 )
948 )
946 coreconfigitem('progress', 'refresh',
949 coreconfigitem('progress', 'refresh',
947 default=0.1,
950 default=0.1,
948 )
951 )
949 coreconfigitem('progress', 'width',
952 coreconfigitem('progress', 'width',
950 default=dynamicdefault,
953 default=dynamicdefault,
951 )
954 )
952 coreconfigitem('push', 'pushvars.server',
955 coreconfigitem('push', 'pushvars.server',
953 default=False,
956 default=False,
954 )
957 )
955 coreconfigitem('storage', 'new-repo-backend',
958 coreconfigitem('storage', 'new-repo-backend',
956 default='revlogv1',
959 default='revlogv1',
957 )
960 )
958 coreconfigitem('storage', 'revlog.optimize-delta-parent-choice',
961 coreconfigitem('storage', 'revlog.optimize-delta-parent-choice',
959 default=True,
962 default=True,
960 alias=[('format', 'aggressivemergedeltas')],
963 alias=[('format', 'aggressivemergedeltas')],
961 )
964 )
962 coreconfigitem('server', 'bookmarks-pushkey-compat',
965 coreconfigitem('server', 'bookmarks-pushkey-compat',
963 default=True,
966 default=True,
964 )
967 )
965 coreconfigitem('server', 'bundle1',
968 coreconfigitem('server', 'bundle1',
966 default=True,
969 default=True,
967 )
970 )
968 coreconfigitem('server', 'bundle1gd',
971 coreconfigitem('server', 'bundle1gd',
969 default=None,
972 default=None,
970 )
973 )
971 coreconfigitem('server', 'bundle1.pull',
974 coreconfigitem('server', 'bundle1.pull',
972 default=None,
975 default=None,
973 )
976 )
974 coreconfigitem('server', 'bundle1gd.pull',
977 coreconfigitem('server', 'bundle1gd.pull',
975 default=None,
978 default=None,
976 )
979 )
977 coreconfigitem('server', 'bundle1.push',
980 coreconfigitem('server', 'bundle1.push',
978 default=None,
981 default=None,
979 )
982 )
980 coreconfigitem('server', 'bundle1gd.push',
983 coreconfigitem('server', 'bundle1gd.push',
981 default=None,
984 default=None,
982 )
985 )
983 coreconfigitem('server', 'bundle2.stream',
986 coreconfigitem('server', 'bundle2.stream',
984 default=True,
987 default=True,
985 alias=[('experimental', 'bundle2.stream')]
988 alias=[('experimental', 'bundle2.stream')]
986 )
989 )
987 coreconfigitem('server', 'compressionengines',
990 coreconfigitem('server', 'compressionengines',
988 default=list,
991 default=list,
989 )
992 )
990 coreconfigitem('server', 'concurrent-push-mode',
993 coreconfigitem('server', 'concurrent-push-mode',
991 default='strict',
994 default='strict',
992 )
995 )
993 coreconfigitem('server', 'disablefullbundle',
996 coreconfigitem('server', 'disablefullbundle',
994 default=False,
997 default=False,
995 )
998 )
996 coreconfigitem('server', 'maxhttpheaderlen',
999 coreconfigitem('server', 'maxhttpheaderlen',
997 default=1024,
1000 default=1024,
998 )
1001 )
999 coreconfigitem('server', 'pullbundle',
1002 coreconfigitem('server', 'pullbundle',
1000 default=False,
1003 default=False,
1001 )
1004 )
1002 coreconfigitem('server', 'preferuncompressed',
1005 coreconfigitem('server', 'preferuncompressed',
1003 default=False,
1006 default=False,
1004 )
1007 )
1005 coreconfigitem('server', 'streamunbundle',
1008 coreconfigitem('server', 'streamunbundle',
1006 default=False,
1009 default=False,
1007 )
1010 )
1008 coreconfigitem('server', 'uncompressed',
1011 coreconfigitem('server', 'uncompressed',
1009 default=True,
1012 default=True,
1010 )
1013 )
1011 coreconfigitem('server', 'uncompressedallowsecret',
1014 coreconfigitem('server', 'uncompressedallowsecret',
1012 default=False,
1015 default=False,
1013 )
1016 )
1014 coreconfigitem('server', 'validate',
1017 coreconfigitem('server', 'validate',
1015 default=False,
1018 default=False,
1016 )
1019 )
1017 coreconfigitem('server', 'zliblevel',
1020 coreconfigitem('server', 'zliblevel',
1018 default=-1,
1021 default=-1,
1019 )
1022 )
1020 coreconfigitem('server', 'zstdlevel',
1023 coreconfigitem('server', 'zstdlevel',
1021 default=3,
1024 default=3,
1022 )
1025 )
1023 coreconfigitem('share', 'pool',
1026 coreconfigitem('share', 'pool',
1024 default=None,
1027 default=None,
1025 )
1028 )
1026 coreconfigitem('share', 'poolnaming',
1029 coreconfigitem('share', 'poolnaming',
1027 default='identity',
1030 default='identity',
1028 )
1031 )
1029 coreconfigitem('smtp', 'host',
1032 coreconfigitem('smtp', 'host',
1030 default=None,
1033 default=None,
1031 )
1034 )
1032 coreconfigitem('smtp', 'local_hostname',
1035 coreconfigitem('smtp', 'local_hostname',
1033 default=None,
1036 default=None,
1034 )
1037 )
1035 coreconfigitem('smtp', 'password',
1038 coreconfigitem('smtp', 'password',
1036 default=None,
1039 default=None,
1037 )
1040 )
1038 coreconfigitem('smtp', 'port',
1041 coreconfigitem('smtp', 'port',
1039 default=dynamicdefault,
1042 default=dynamicdefault,
1040 )
1043 )
1041 coreconfigitem('smtp', 'tls',
1044 coreconfigitem('smtp', 'tls',
1042 default='none',
1045 default='none',
1043 )
1046 )
1044 coreconfigitem('smtp', 'username',
1047 coreconfigitem('smtp', 'username',
1045 default=None,
1048 default=None,
1046 )
1049 )
1047 coreconfigitem('sparse', 'missingwarning',
1050 coreconfigitem('sparse', 'missingwarning',
1048 default=True,
1051 default=True,
1049 )
1052 )
1050 coreconfigitem('subrepos', 'allowed',
1053 coreconfigitem('subrepos', 'allowed',
1051 default=dynamicdefault, # to make backporting simpler
1054 default=dynamicdefault, # to make backporting simpler
1052 )
1055 )
1053 coreconfigitem('subrepos', 'hg:allowed',
1056 coreconfigitem('subrepos', 'hg:allowed',
1054 default=dynamicdefault,
1057 default=dynamicdefault,
1055 )
1058 )
1056 coreconfigitem('subrepos', 'git:allowed',
1059 coreconfigitem('subrepos', 'git:allowed',
1057 default=dynamicdefault,
1060 default=dynamicdefault,
1058 )
1061 )
1059 coreconfigitem('subrepos', 'svn:allowed',
1062 coreconfigitem('subrepos', 'svn:allowed',
1060 default=dynamicdefault,
1063 default=dynamicdefault,
1061 )
1064 )
1062 coreconfigitem('templates', '.*',
1065 coreconfigitem('templates', '.*',
1063 default=None,
1066 default=None,
1064 generic=True,
1067 generic=True,
1065 )
1068 )
1066 coreconfigitem('trusted', 'groups',
1069 coreconfigitem('trusted', 'groups',
1067 default=list,
1070 default=list,
1068 )
1071 )
1069 coreconfigitem('trusted', 'users',
1072 coreconfigitem('trusted', 'users',
1070 default=list,
1073 default=list,
1071 )
1074 )
1072 coreconfigitem('ui', '_usedassubrepo',
1075 coreconfigitem('ui', '_usedassubrepo',
1073 default=False,
1076 default=False,
1074 )
1077 )
1075 coreconfigitem('ui', 'allowemptycommit',
1078 coreconfigitem('ui', 'allowemptycommit',
1076 default=False,
1079 default=False,
1077 )
1080 )
1078 coreconfigitem('ui', 'archivemeta',
1081 coreconfigitem('ui', 'archivemeta',
1079 default=True,
1082 default=True,
1080 )
1083 )
1081 coreconfigitem('ui', 'askusername',
1084 coreconfigitem('ui', 'askusername',
1082 default=False,
1085 default=False,
1083 )
1086 )
1084 coreconfigitem('ui', 'clonebundlefallback',
1087 coreconfigitem('ui', 'clonebundlefallback',
1085 default=False,
1088 default=False,
1086 )
1089 )
1087 coreconfigitem('ui', 'clonebundleprefers',
1090 coreconfigitem('ui', 'clonebundleprefers',
1088 default=list,
1091 default=list,
1089 )
1092 )
1090 coreconfigitem('ui', 'clonebundles',
1093 coreconfigitem('ui', 'clonebundles',
1091 default=True,
1094 default=True,
1092 )
1095 )
1093 coreconfigitem('ui', 'color',
1096 coreconfigitem('ui', 'color',
1094 default='auto',
1097 default='auto',
1095 )
1098 )
1096 coreconfigitem('ui', 'commitsubrepos',
1099 coreconfigitem('ui', 'commitsubrepos',
1097 default=False,
1100 default=False,
1098 )
1101 )
1099 coreconfigitem('ui', 'debug',
1102 coreconfigitem('ui', 'debug',
1100 default=False,
1103 default=False,
1101 )
1104 )
1102 coreconfigitem('ui', 'debugger',
1105 coreconfigitem('ui', 'debugger',
1103 default=None,
1106 default=None,
1104 )
1107 )
1105 coreconfigitem('ui', 'editor',
1108 coreconfigitem('ui', 'editor',
1106 default=dynamicdefault,
1109 default=dynamicdefault,
1107 )
1110 )
1108 coreconfigitem('ui', 'fallbackencoding',
1111 coreconfigitem('ui', 'fallbackencoding',
1109 default=None,
1112 default=None,
1110 )
1113 )
1111 coreconfigitem('ui', 'forcecwd',
1114 coreconfigitem('ui', 'forcecwd',
1112 default=None,
1115 default=None,
1113 )
1116 )
1114 coreconfigitem('ui', 'forcemerge',
1117 coreconfigitem('ui', 'forcemerge',
1115 default=None,
1118 default=None,
1116 )
1119 )
1117 coreconfigitem('ui', 'formatdebug',
1120 coreconfigitem('ui', 'formatdebug',
1118 default=False,
1121 default=False,
1119 )
1122 )
1120 coreconfigitem('ui', 'formatjson',
1123 coreconfigitem('ui', 'formatjson',
1121 default=False,
1124 default=False,
1122 )
1125 )
1123 coreconfigitem('ui', 'formatted',
1126 coreconfigitem('ui', 'formatted',
1124 default=None,
1127 default=None,
1125 )
1128 )
1126 coreconfigitem('ui', 'graphnodetemplate',
1129 coreconfigitem('ui', 'graphnodetemplate',
1127 default=None,
1130 default=None,
1128 )
1131 )
1129 coreconfigitem('ui', 'history-editing-backup',
1132 coreconfigitem('ui', 'history-editing-backup',
1130 default=True,
1133 default=True,
1131 )
1134 )
1132 coreconfigitem('ui', 'interactive',
1135 coreconfigitem('ui', 'interactive',
1133 default=None,
1136 default=None,
1134 )
1137 )
1135 coreconfigitem('ui', 'interface',
1138 coreconfigitem('ui', 'interface',
1136 default=None,
1139 default=None,
1137 )
1140 )
1138 coreconfigitem('ui', 'interface.chunkselector',
1141 coreconfigitem('ui', 'interface.chunkselector',
1139 default=None,
1142 default=None,
1140 )
1143 )
1141 coreconfigitem('ui', 'large-file-limit',
1144 coreconfigitem('ui', 'large-file-limit',
1142 default=10000000,
1145 default=10000000,
1143 )
1146 )
1144 coreconfigitem('ui', 'logblockedtimes',
1147 coreconfigitem('ui', 'logblockedtimes',
1145 default=False,
1148 default=False,
1146 )
1149 )
1147 coreconfigitem('ui', 'logtemplate',
1150 coreconfigitem('ui', 'logtemplate',
1148 default=None,
1151 default=None,
1149 )
1152 )
1150 coreconfigitem('ui', 'merge',
1153 coreconfigitem('ui', 'merge',
1151 default=None,
1154 default=None,
1152 )
1155 )
1153 coreconfigitem('ui', 'mergemarkers',
1156 coreconfigitem('ui', 'mergemarkers',
1154 default='basic',
1157 default='basic',
1155 )
1158 )
1156 coreconfigitem('ui', 'mergemarkertemplate',
1159 coreconfigitem('ui', 'mergemarkertemplate',
1157 default=('{node|short} '
1160 default=('{node|short} '
1158 '{ifeq(tags, "tip", "", '
1161 '{ifeq(tags, "tip", "", '
1159 'ifeq(tags, "", "", "{tags} "))}'
1162 'ifeq(tags, "", "", "{tags} "))}'
1160 '{if(bookmarks, "{bookmarks} ")}'
1163 '{if(bookmarks, "{bookmarks} ")}'
1161 '{ifeq(branch, "default", "", "{branch} ")}'
1164 '{ifeq(branch, "default", "", "{branch} ")}'
1162 '- {author|user}: {desc|firstline}')
1165 '- {author|user}: {desc|firstline}')
1163 )
1166 )
1164 coreconfigitem('ui', 'nontty',
1167 coreconfigitem('ui', 'nontty',
1165 default=False,
1168 default=False,
1166 )
1169 )
1167 coreconfigitem('ui', 'origbackuppath',
1170 coreconfigitem('ui', 'origbackuppath',
1168 default=None,
1171 default=None,
1169 )
1172 )
1170 coreconfigitem('ui', 'paginate',
1173 coreconfigitem('ui', 'paginate',
1171 default=True,
1174 default=True,
1172 )
1175 )
1173 coreconfigitem('ui', 'patch',
1176 coreconfigitem('ui', 'patch',
1174 default=None,
1177 default=None,
1175 )
1178 )
1176 coreconfigitem('ui', 'portablefilenames',
1179 coreconfigitem('ui', 'portablefilenames',
1177 default='warn',
1180 default='warn',
1178 )
1181 )
1179 coreconfigitem('ui', 'promptecho',
1182 coreconfigitem('ui', 'promptecho',
1180 default=False,
1183 default=False,
1181 )
1184 )
1182 coreconfigitem('ui', 'quiet',
1185 coreconfigitem('ui', 'quiet',
1183 default=False,
1186 default=False,
1184 )
1187 )
1185 coreconfigitem('ui', 'quietbookmarkmove',
1188 coreconfigitem('ui', 'quietbookmarkmove',
1186 default=False,
1189 default=False,
1187 )
1190 )
1188 coreconfigitem('ui', 'remotecmd',
1191 coreconfigitem('ui', 'remotecmd',
1189 default='hg',
1192 default='hg',
1190 )
1193 )
1191 coreconfigitem('ui', 'report_untrusted',
1194 coreconfigitem('ui', 'report_untrusted',
1192 default=True,
1195 default=True,
1193 )
1196 )
1194 coreconfigitem('ui', 'rollback',
1197 coreconfigitem('ui', 'rollback',
1195 default=True,
1198 default=True,
1196 )
1199 )
1197 coreconfigitem('ui', 'signal-safe-lock',
1200 coreconfigitem('ui', 'signal-safe-lock',
1198 default=True,
1201 default=True,
1199 )
1202 )
1200 coreconfigitem('ui', 'slash',
1203 coreconfigitem('ui', 'slash',
1201 default=False,
1204 default=False,
1202 )
1205 )
1203 coreconfigitem('ui', 'ssh',
1206 coreconfigitem('ui', 'ssh',
1204 default='ssh',
1207 default='ssh',
1205 )
1208 )
1206 coreconfigitem('ui', 'ssherrorhint',
1209 coreconfigitem('ui', 'ssherrorhint',
1207 default=None,
1210 default=None,
1208 )
1211 )
1209 coreconfigitem('ui', 'statuscopies',
1212 coreconfigitem('ui', 'statuscopies',
1210 default=False,
1213 default=False,
1211 )
1214 )
1212 coreconfigitem('ui', 'strict',
1215 coreconfigitem('ui', 'strict',
1213 default=False,
1216 default=False,
1214 )
1217 )
1215 coreconfigitem('ui', 'style',
1218 coreconfigitem('ui', 'style',
1216 default='',
1219 default='',
1217 )
1220 )
1218 coreconfigitem('ui', 'supportcontact',
1221 coreconfigitem('ui', 'supportcontact',
1219 default=None,
1222 default=None,
1220 )
1223 )
1221 coreconfigitem('ui', 'textwidth',
1224 coreconfigitem('ui', 'textwidth',
1222 default=78,
1225 default=78,
1223 )
1226 )
1224 coreconfigitem('ui', 'timeout',
1227 coreconfigitem('ui', 'timeout',
1225 default='600',
1228 default='600',
1226 )
1229 )
1227 coreconfigitem('ui', 'timeout.warn',
1230 coreconfigitem('ui', 'timeout.warn',
1228 default=0,
1231 default=0,
1229 )
1232 )
1230 coreconfigitem('ui', 'traceback',
1233 coreconfigitem('ui', 'traceback',
1231 default=False,
1234 default=False,
1232 )
1235 )
1233 coreconfigitem('ui', 'tweakdefaults',
1236 coreconfigitem('ui', 'tweakdefaults',
1234 default=False,
1237 default=False,
1235 )
1238 )
1236 coreconfigitem('ui', 'username',
1239 coreconfigitem('ui', 'username',
1237 alias=[('ui', 'user')]
1240 alias=[('ui', 'user')]
1238 )
1241 )
1239 coreconfigitem('ui', 'verbose',
1242 coreconfigitem('ui', 'verbose',
1240 default=False,
1243 default=False,
1241 )
1244 )
1242 coreconfigitem('verify', 'skipflags',
1245 coreconfigitem('verify', 'skipflags',
1243 default=None,
1246 default=None,
1244 )
1247 )
1245 coreconfigitem('web', 'allowbz2',
1248 coreconfigitem('web', 'allowbz2',
1246 default=False,
1249 default=False,
1247 )
1250 )
1248 coreconfigitem('web', 'allowgz',
1251 coreconfigitem('web', 'allowgz',
1249 default=False,
1252 default=False,
1250 )
1253 )
1251 coreconfigitem('web', 'allow-pull',
1254 coreconfigitem('web', 'allow-pull',
1252 alias=[('web', 'allowpull')],
1255 alias=[('web', 'allowpull')],
1253 default=True,
1256 default=True,
1254 )
1257 )
1255 coreconfigitem('web', 'allow-push',
1258 coreconfigitem('web', 'allow-push',
1256 alias=[('web', 'allow_push')],
1259 alias=[('web', 'allow_push')],
1257 default=list,
1260 default=list,
1258 )
1261 )
1259 coreconfigitem('web', 'allowzip',
1262 coreconfigitem('web', 'allowzip',
1260 default=False,
1263 default=False,
1261 )
1264 )
1262 coreconfigitem('web', 'archivesubrepos',
1265 coreconfigitem('web', 'archivesubrepos',
1263 default=False,
1266 default=False,
1264 )
1267 )
1265 coreconfigitem('web', 'cache',
1268 coreconfigitem('web', 'cache',
1266 default=True,
1269 default=True,
1267 )
1270 )
1268 coreconfigitem('web', 'contact',
1271 coreconfigitem('web', 'contact',
1269 default=None,
1272 default=None,
1270 )
1273 )
1271 coreconfigitem('web', 'deny_push',
1274 coreconfigitem('web', 'deny_push',
1272 default=list,
1275 default=list,
1273 )
1276 )
1274 coreconfigitem('web', 'guessmime',
1277 coreconfigitem('web', 'guessmime',
1275 default=False,
1278 default=False,
1276 )
1279 )
1277 coreconfigitem('web', 'hidden',
1280 coreconfigitem('web', 'hidden',
1278 default=False,
1281 default=False,
1279 )
1282 )
1280 coreconfigitem('web', 'labels',
1283 coreconfigitem('web', 'labels',
1281 default=list,
1284 default=list,
1282 )
1285 )
1283 coreconfigitem('web', 'logoimg',
1286 coreconfigitem('web', 'logoimg',
1284 default='hglogo.png',
1287 default='hglogo.png',
1285 )
1288 )
1286 coreconfigitem('web', 'logourl',
1289 coreconfigitem('web', 'logourl',
1287 default='https://mercurial-scm.org/',
1290 default='https://mercurial-scm.org/',
1288 )
1291 )
1289 coreconfigitem('web', 'accesslog',
1292 coreconfigitem('web', 'accesslog',
1290 default='-',
1293 default='-',
1291 )
1294 )
1292 coreconfigitem('web', 'address',
1295 coreconfigitem('web', 'address',
1293 default='',
1296 default='',
1294 )
1297 )
1295 coreconfigitem('web', 'allow-archive',
1298 coreconfigitem('web', 'allow-archive',
1296 alias=[('web', 'allow_archive')],
1299 alias=[('web', 'allow_archive')],
1297 default=list,
1300 default=list,
1298 )
1301 )
1299 coreconfigitem('web', 'allow_read',
1302 coreconfigitem('web', 'allow_read',
1300 default=list,
1303 default=list,
1301 )
1304 )
1302 coreconfigitem('web', 'baseurl',
1305 coreconfigitem('web', 'baseurl',
1303 default=None,
1306 default=None,
1304 )
1307 )
1305 coreconfigitem('web', 'cacerts',
1308 coreconfigitem('web', 'cacerts',
1306 default=None,
1309 default=None,
1307 )
1310 )
1308 coreconfigitem('web', 'certificate',
1311 coreconfigitem('web', 'certificate',
1309 default=None,
1312 default=None,
1310 )
1313 )
1311 coreconfigitem('web', 'collapse',
1314 coreconfigitem('web', 'collapse',
1312 default=False,
1315 default=False,
1313 )
1316 )
1314 coreconfigitem('web', 'csp',
1317 coreconfigitem('web', 'csp',
1315 default=None,
1318 default=None,
1316 )
1319 )
1317 coreconfigitem('web', 'deny_read',
1320 coreconfigitem('web', 'deny_read',
1318 default=list,
1321 default=list,
1319 )
1322 )
1320 coreconfigitem('web', 'descend',
1323 coreconfigitem('web', 'descend',
1321 default=True,
1324 default=True,
1322 )
1325 )
1323 coreconfigitem('web', 'description',
1326 coreconfigitem('web', 'description',
1324 default="",
1327 default="",
1325 )
1328 )
1326 coreconfigitem('web', 'encoding',
1329 coreconfigitem('web', 'encoding',
1327 default=lambda: encoding.encoding,
1330 default=lambda: encoding.encoding,
1328 )
1331 )
1329 coreconfigitem('web', 'errorlog',
1332 coreconfigitem('web', 'errorlog',
1330 default='-',
1333 default='-',
1331 )
1334 )
1332 coreconfigitem('web', 'ipv6',
1335 coreconfigitem('web', 'ipv6',
1333 default=False,
1336 default=False,
1334 )
1337 )
1335 coreconfigitem('web', 'maxchanges',
1338 coreconfigitem('web', 'maxchanges',
1336 default=10,
1339 default=10,
1337 )
1340 )
1338 coreconfigitem('web', 'maxfiles',
1341 coreconfigitem('web', 'maxfiles',
1339 default=10,
1342 default=10,
1340 )
1343 )
1341 coreconfigitem('web', 'maxshortchanges',
1344 coreconfigitem('web', 'maxshortchanges',
1342 default=60,
1345 default=60,
1343 )
1346 )
1344 coreconfigitem('web', 'motd',
1347 coreconfigitem('web', 'motd',
1345 default='',
1348 default='',
1346 )
1349 )
1347 coreconfigitem('web', 'name',
1350 coreconfigitem('web', 'name',
1348 default=dynamicdefault,
1351 default=dynamicdefault,
1349 )
1352 )
1350 coreconfigitem('web', 'port',
1353 coreconfigitem('web', 'port',
1351 default=8000,
1354 default=8000,
1352 )
1355 )
1353 coreconfigitem('web', 'prefix',
1356 coreconfigitem('web', 'prefix',
1354 default='',
1357 default='',
1355 )
1358 )
1356 coreconfigitem('web', 'push_ssl',
1359 coreconfigitem('web', 'push_ssl',
1357 default=True,
1360 default=True,
1358 )
1361 )
1359 coreconfigitem('web', 'refreshinterval',
1362 coreconfigitem('web', 'refreshinterval',
1360 default=20,
1363 default=20,
1361 )
1364 )
1362 coreconfigitem('web', 'server-header',
1365 coreconfigitem('web', 'server-header',
1363 default=None,
1366 default=None,
1364 )
1367 )
1365 coreconfigitem('web', 'static',
1368 coreconfigitem('web', 'static',
1366 default=None,
1369 default=None,
1367 )
1370 )
1368 coreconfigitem('web', 'staticurl',
1371 coreconfigitem('web', 'staticurl',
1369 default=None,
1372 default=None,
1370 )
1373 )
1371 coreconfigitem('web', 'stripes',
1374 coreconfigitem('web', 'stripes',
1372 default=1,
1375 default=1,
1373 )
1376 )
1374 coreconfigitem('web', 'style',
1377 coreconfigitem('web', 'style',
1375 default='paper',
1378 default='paper',
1376 )
1379 )
1377 coreconfigitem('web', 'templates',
1380 coreconfigitem('web', 'templates',
1378 default=None,
1381 default=None,
1379 )
1382 )
1380 coreconfigitem('web', 'view',
1383 coreconfigitem('web', 'view',
1381 default='served',
1384 default='served',
1382 )
1385 )
1383 coreconfigitem('worker', 'backgroundclose',
1386 coreconfigitem('worker', 'backgroundclose',
1384 default=dynamicdefault,
1387 default=dynamicdefault,
1385 )
1388 )
1386 # Windows defaults to a limit of 512 open files. A buffer of 128
1389 # Windows defaults to a limit of 512 open files. A buffer of 128
1387 # should give us enough headway.
1390 # should give us enough headway.
1388 coreconfigitem('worker', 'backgroundclosemaxqueue',
1391 coreconfigitem('worker', 'backgroundclosemaxqueue',
1389 default=384,
1392 default=384,
1390 )
1393 )
1391 coreconfigitem('worker', 'backgroundcloseminfilecount',
1394 coreconfigitem('worker', 'backgroundcloseminfilecount',
1392 default=2048,
1395 default=2048,
1393 )
1396 )
1394 coreconfigitem('worker', 'backgroundclosethreadcount',
1397 coreconfigitem('worker', 'backgroundclosethreadcount',
1395 default=4,
1398 default=4,
1396 )
1399 )
1397 coreconfigitem('worker', 'enabled',
1400 coreconfigitem('worker', 'enabled',
1398 default=True,
1401 default=True,
1399 )
1402 )
1400 coreconfigitem('worker', 'numcpus',
1403 coreconfigitem('worker', 'numcpus',
1401 default=None,
1404 default=None,
1402 )
1405 )
1403
1406
1404 # Rebase related configuration moved to core because other extension are doing
1407 # Rebase related configuration moved to core because other extension are doing
1405 # strange things. For example, shelve import the extensions to reuse some bit
1408 # strange things. For example, shelve import the extensions to reuse some bit
1406 # without formally loading it.
1409 # without formally loading it.
1407 coreconfigitem('commands', 'rebase.requiredest',
1410 coreconfigitem('commands', 'rebase.requiredest',
1408 default=False,
1411 default=False,
1409 )
1412 )
1410 coreconfigitem('experimental', 'rebaseskipobsolete',
1413 coreconfigitem('experimental', 'rebaseskipobsolete',
1411 default=True,
1414 default=True,
1412 )
1415 )
1413 coreconfigitem('rebase', 'singletransaction',
1416 coreconfigitem('rebase', 'singletransaction',
1414 default=False,
1417 default=False,
1415 )
1418 )
1416 coreconfigitem('rebase', 'experimental.inmemory',
1419 coreconfigitem('rebase', 'experimental.inmemory',
1417 default=False,
1420 default=False,
1418 )
1421 )
@@ -1,987 +1,1005
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 weakref
16 import weakref
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 repository,
24 repository,
25 statichttprepo,
25 statichttprepo,
26 url as urlmod,
26 url as urlmod,
27 util,
27 util,
28 wireprotoframing,
28 wireprotoframing,
29 wireprototypes,
29 wireprototypes,
30 wireprotov1peer,
30 wireprotov1peer,
31 wireprotov2peer,
31 wireprotov2peer,
32 wireprotov2server,
32 wireprotov2server,
33 )
33 )
34 from .utils import (
34 from .utils import (
35 cborutil,
35 cborutil,
36 interfaceutil,
36 interfaceutil,
37 stringutil,
37 stringutil,
38 )
38 )
39
39
40 httplib = util.httplib
40 httplib = util.httplib
41 urlerr = util.urlerr
41 urlerr = util.urlerr
42 urlreq = util.urlreq
42 urlreq = util.urlreq
43
43
44 def encodevalueinheaders(value, header, limit):
44 def encodevalueinheaders(value, header, limit):
45 """Encode a string value into multiple HTTP headers.
45 """Encode a string value into multiple HTTP headers.
46
46
47 ``value`` will be encoded into 1 or more HTTP headers with the names
47 ``value`` will be encoded into 1 or more HTTP headers with the names
48 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
48 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
49 name + value will be at most ``limit`` bytes long.
49 name + value will be at most ``limit`` bytes long.
50
50
51 Returns an iterable of 2-tuples consisting of header names and
51 Returns an iterable of 2-tuples consisting of header names and
52 values as native strings.
52 values as native strings.
53 """
53 """
54 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
54 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
55 # not bytes. This function always takes bytes in as arguments.
55 # not bytes. This function always takes bytes in as arguments.
56 fmt = pycompat.strurl(header) + r'-%s'
56 fmt = pycompat.strurl(header) + r'-%s'
57 # Note: it is *NOT* a bug that the last bit here is a bytestring
57 # Note: it is *NOT* a bug that the last bit here is a bytestring
58 # and not a unicode: we're just getting the encoded length anyway,
58 # and not a unicode: we're just getting the encoded length anyway,
59 # and using an r-string to make it portable between Python 2 and 3
59 # and using an r-string to make it portable between Python 2 and 3
60 # doesn't work because then the \r is a literal backslash-r
60 # doesn't work because then the \r is a literal backslash-r
61 # instead of a carriage return.
61 # instead of a carriage return.
62 valuelen = limit - len(fmt % r'000') - len(': \r\n')
62 valuelen = limit - len(fmt % r'000') - len(': \r\n')
63 result = []
63 result = []
64
64
65 n = 0
65 n = 0
66 for i in pycompat.xrange(0, len(value), valuelen):
66 for i in pycompat.xrange(0, len(value), valuelen):
67 n += 1
67 n += 1
68 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
68 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
69
69
70 return result
70 return result
71
71
72 class _multifile(object):
72 class _multifile(object):
73 def __init__(self, *fileobjs):
73 def __init__(self, *fileobjs):
74 for f in fileobjs:
74 for f in fileobjs:
75 if not util.safehasattr(f, 'length'):
75 if not util.safehasattr(f, 'length'):
76 raise ValueError(
76 raise ValueError(
77 '_multifile only supports file objects that '
77 '_multifile only supports file objects that '
78 'have a length but this one does not:', type(f), f)
78 'have a length but this one does not:', type(f), f)
79 self._fileobjs = fileobjs
79 self._fileobjs = fileobjs
80 self._index = 0
80 self._index = 0
81
81
82 @property
82 @property
83 def length(self):
83 def length(self):
84 return sum(f.length for f in self._fileobjs)
84 return sum(f.length for f in self._fileobjs)
85
85
86 def read(self, amt=None):
86 def read(self, amt=None):
87 if amt <= 0:
87 if amt <= 0:
88 return ''.join(f.read() for f in self._fileobjs)
88 return ''.join(f.read() for f in self._fileobjs)
89 parts = []
89 parts = []
90 while amt and self._index < len(self._fileobjs):
90 while amt and self._index < len(self._fileobjs):
91 parts.append(self._fileobjs[self._index].read(amt))
91 parts.append(self._fileobjs[self._index].read(amt))
92 got = len(parts[-1])
92 got = len(parts[-1])
93 if got < amt:
93 if got < amt:
94 self._index += 1
94 self._index += 1
95 amt -= got
95 amt -= got
96 return ''.join(parts)
96 return ''.join(parts)
97
97
98 def seek(self, offset, whence=os.SEEK_SET):
98 def seek(self, offset, whence=os.SEEK_SET):
99 if whence != os.SEEK_SET:
99 if whence != os.SEEK_SET:
100 raise NotImplementedError(
100 raise NotImplementedError(
101 '_multifile does not support anything other'
101 '_multifile does not support anything other'
102 ' than os.SEEK_SET for whence on seek()')
102 ' than os.SEEK_SET for whence on seek()')
103 if offset != 0:
103 if offset != 0:
104 raise NotImplementedError(
104 raise NotImplementedError(
105 '_multifile only supports seeking to start, but that '
105 '_multifile only supports seeking to start, but that '
106 'could be fixed if you need it')
106 'could be fixed if you need it')
107 for f in self._fileobjs:
107 for f in self._fileobjs:
108 f.seek(0)
108 f.seek(0)
109 self._index = 0
109 self._index = 0
110
110
111 def makev1commandrequest(ui, requestbuilder, caps, capablefn,
111 def makev1commandrequest(ui, requestbuilder, caps, capablefn,
112 repobaseurl, cmd, args):
112 repobaseurl, cmd, args):
113 """Make an HTTP request to run a command for a version 1 client.
113 """Make an HTTP request to run a command for a version 1 client.
114
114
115 ``caps`` is a set of known server capabilities. The value may be
115 ``caps`` is a set of known server capabilities. The value may be
116 None if capabilities are not yet known.
116 None if capabilities are not yet known.
117
117
118 ``capablefn`` is a function to evaluate a capability.
118 ``capablefn`` is a function to evaluate a capability.
119
119
120 ``cmd``, ``args``, and ``data`` define the command, its arguments, and
120 ``cmd``, ``args``, and ``data`` define the command, its arguments, and
121 raw data to pass to it.
121 raw data to pass to it.
122 """
122 """
123 if cmd == 'pushkey':
123 if cmd == 'pushkey':
124 args['data'] = ''
124 args['data'] = ''
125 data = args.pop('data', None)
125 data = args.pop('data', None)
126 headers = args.pop('headers', {})
126 headers = args.pop('headers', {})
127
127
128 ui.debug("sending %s command\n" % cmd)
128 ui.debug("sending %s command\n" % cmd)
129 q = [('cmd', cmd)]
129 q = [('cmd', cmd)]
130 headersize = 0
130 headersize = 0
131 # Important: don't use self.capable() here or else you end up
131 # Important: don't use self.capable() here or else you end up
132 # with infinite recursion when trying to look up capabilities
132 # with infinite recursion when trying to look up capabilities
133 # for the first time.
133 # for the first time.
134 postargsok = caps is not None and 'httppostargs' in caps
134 postargsok = caps is not None and 'httppostargs' in caps
135
135
136 # Send arguments via POST.
136 # Send arguments via POST.
137 if postargsok and args:
137 if postargsok and args:
138 strargs = urlreq.urlencode(sorted(args.items()))
138 strargs = urlreq.urlencode(sorted(args.items()))
139 if not data:
139 if not data:
140 data = strargs
140 data = strargs
141 else:
141 else:
142 if isinstance(data, bytes):
142 if isinstance(data, bytes):
143 i = io.BytesIO(data)
143 i = io.BytesIO(data)
144 i.length = len(data)
144 i.length = len(data)
145 data = i
145 data = i
146 argsio = io.BytesIO(strargs)
146 argsio = io.BytesIO(strargs)
147 argsio.length = len(strargs)
147 argsio.length = len(strargs)
148 data = _multifile(argsio, data)
148 data = _multifile(argsio, data)
149 headers[r'X-HgArgs-Post'] = len(strargs)
149 headers[r'X-HgArgs-Post'] = len(strargs)
150 elif args:
150 elif args:
151 # Calling self.capable() can infinite loop if we are calling
151 # Calling self.capable() can infinite loop if we are calling
152 # "capabilities". But that command should never accept wire
152 # "capabilities". But that command should never accept wire
153 # protocol arguments. So this should never happen.
153 # protocol arguments. So this should never happen.
154 assert cmd != 'capabilities'
154 assert cmd != 'capabilities'
155 httpheader = capablefn('httpheader')
155 httpheader = capablefn('httpheader')
156 if httpheader:
156 if httpheader:
157 headersize = int(httpheader.split(',', 1)[0])
157 headersize = int(httpheader.split(',', 1)[0])
158
158
159 # Send arguments via HTTP headers.
159 # Send arguments via HTTP headers.
160 if headersize > 0:
160 if headersize > 0:
161 # The headers can typically carry more data than the URL.
161 # The headers can typically carry more data than the URL.
162 encargs = urlreq.urlencode(sorted(args.items()))
162 encargs = urlreq.urlencode(sorted(args.items()))
163 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
163 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
164 headersize):
164 headersize):
165 headers[header] = value
165 headers[header] = value
166 # Send arguments via query string (Mercurial <1.9).
166 # Send arguments via query string (Mercurial <1.9).
167 else:
167 else:
168 q += sorted(args.items())
168 q += sorted(args.items())
169
169
170 qs = '?%s' % urlreq.urlencode(q)
170 qs = '?%s' % urlreq.urlencode(q)
171 cu = "%s%s" % (repobaseurl, qs)
171 cu = "%s%s" % (repobaseurl, qs)
172 size = 0
172 size = 0
173 if util.safehasattr(data, 'length'):
173 if util.safehasattr(data, 'length'):
174 size = data.length
174 size = data.length
175 elif data is not None:
175 elif data is not None:
176 size = len(data)
176 size = len(data)
177 if data is not None and r'Content-Type' not in headers:
177 if data is not None and r'Content-Type' not in headers:
178 headers[r'Content-Type'] = r'application/mercurial-0.1'
178 headers[r'Content-Type'] = r'application/mercurial-0.1'
179
179
180 # Tell the server we accept application/mercurial-0.2 and multiple
180 # Tell the server we accept application/mercurial-0.2 and multiple
181 # compression formats if the server is capable of emitting those
181 # compression formats if the server is capable of emitting those
182 # payloads.
182 # payloads.
183 # Note: Keep this set empty by default, as client advertisement of
183 # Note: Keep this set empty by default, as client advertisement of
184 # protocol parameters should only occur after the handshake.
184 # protocol parameters should only occur after the handshake.
185 protoparams = set()
185 protoparams = set()
186
186
187 mediatypes = set()
187 mediatypes = set()
188 if caps is not None:
188 if caps is not None:
189 mt = capablefn('httpmediatype')
189 mt = capablefn('httpmediatype')
190 if mt:
190 if mt:
191 protoparams.add('0.1')
191 protoparams.add('0.1')
192 mediatypes = set(mt.split(','))
192 mediatypes = set(mt.split(','))
193
193
194 protoparams.add('partial-pull')
194 protoparams.add('partial-pull')
195
195
196 if '0.2tx' in mediatypes:
196 if '0.2tx' in mediatypes:
197 protoparams.add('0.2')
197 protoparams.add('0.2')
198
198
199 if '0.2tx' in mediatypes and capablefn('compression'):
199 if '0.2tx' in mediatypes and capablefn('compression'):
200 # We /could/ compare supported compression formats and prune
200 # We /could/ compare supported compression formats and prune
201 # non-mutually supported or error if nothing is mutually supported.
201 # non-mutually supported or error if nothing is mutually supported.
202 # For now, send the full list to the server and have it error.
202 # For now, send the full list to the server and have it error.
203 comps = [e.wireprotosupport().name for e in
203 comps = [e.wireprotosupport().name for e in
204 util.compengines.supportedwireengines(util.CLIENTROLE)]
204 util.compengines.supportedwireengines(util.CLIENTROLE)]
205 protoparams.add('comp=%s' % ','.join(comps))
205 protoparams.add('comp=%s' % ','.join(comps))
206
206
207 if protoparams:
207 if protoparams:
208 protoheaders = encodevalueinheaders(' '.join(sorted(protoparams)),
208 protoheaders = encodevalueinheaders(' '.join(sorted(protoparams)),
209 'X-HgProto',
209 'X-HgProto',
210 headersize or 1024)
210 headersize or 1024)
211 for header, value in protoheaders:
211 for header, value in protoheaders:
212 headers[header] = value
212 headers[header] = value
213
213
214 varyheaders = []
214 varyheaders = []
215 for header in headers:
215 for header in headers:
216 if header.lower().startswith(r'x-hg'):
216 if header.lower().startswith(r'x-hg'):
217 varyheaders.append(header)
217 varyheaders.append(header)
218
218
219 if varyheaders:
219 if varyheaders:
220 headers[r'Vary'] = r','.join(sorted(varyheaders))
220 headers[r'Vary'] = r','.join(sorted(varyheaders))
221
221
222 req = requestbuilder(pycompat.strurl(cu), data, headers)
222 req = requestbuilder(pycompat.strurl(cu), data, headers)
223
223
224 if data is not None:
224 if data is not None:
225 ui.debug("sending %d bytes\n" % size)
225 ui.debug("sending %d bytes\n" % size)
226 req.add_unredirected_header(r'Content-Length', r'%d' % size)
226 req.add_unredirected_header(r'Content-Length', r'%d' % size)
227
227
228 return req, cu, qs
228 return req, cu, qs
229
229
230 def _reqdata(req):
230 def _reqdata(req):
231 """Get request data, if any. If no data, returns None."""
231 """Get request data, if any. If no data, returns None."""
232 if pycompat.ispy3:
232 if pycompat.ispy3:
233 return req.data
233 return req.data
234 if not req.has_data():
234 if not req.has_data():
235 return None
235 return None
236 return req.get_data()
236 return req.get_data()
237
237
238 def sendrequest(ui, opener, req):
238 def sendrequest(ui, opener, req):
239 """Send a prepared HTTP request.
239 """Send a prepared HTTP request.
240
240
241 Returns the response object.
241 Returns the response object.
242 """
242 """
243 dbg = ui.debug
243 dbg = ui.debug
244 if (ui.debugflag
244 if (ui.debugflag
245 and ui.configbool('devel', 'debug.peer-request')):
245 and ui.configbool('devel', 'debug.peer-request')):
246 line = 'devel-peer-request: %s\n'
246 line = 'devel-peer-request: %s\n'
247 dbg(line % '%s %s' % (pycompat.bytesurl(req.get_method()),
247 dbg(line % '%s %s' % (pycompat.bytesurl(req.get_method()),
248 pycompat.bytesurl(req.get_full_url())))
248 pycompat.bytesurl(req.get_full_url())))
249 hgargssize = None
249 hgargssize = None
250
250
251 for header, value in sorted(req.header_items()):
251 for header, value in sorted(req.header_items()):
252 header = pycompat.bytesurl(header)
252 header = pycompat.bytesurl(header)
253 value = pycompat.bytesurl(value)
253 value = pycompat.bytesurl(value)
254 if header.startswith('X-hgarg-'):
254 if header.startswith('X-hgarg-'):
255 if hgargssize is None:
255 if hgargssize is None:
256 hgargssize = 0
256 hgargssize = 0
257 hgargssize += len(value)
257 hgargssize += len(value)
258 else:
258 else:
259 dbg(line % ' %s %s' % (header, value))
259 dbg(line % ' %s %s' % (header, value))
260
260
261 if hgargssize is not None:
261 if hgargssize is not None:
262 dbg(line % ' %d bytes of commands arguments in headers'
262 dbg(line % ' %d bytes of commands arguments in headers'
263 % hgargssize)
263 % hgargssize)
264 data = _reqdata(req)
264 data = _reqdata(req)
265 if data is not None:
265 if data is not None:
266 length = getattr(data, 'length', None)
266 length = getattr(data, 'length', None)
267 if length is None:
267 if length is None:
268 length = len(data)
268 length = len(data)
269 dbg(line % ' %d bytes of data' % length)
269 dbg(line % ' %d bytes of data' % length)
270
270
271 start = util.timer()
271 start = util.timer()
272
272
273 res = None
273 res = None
274 try:
274 try:
275 res = opener.open(req)
275 res = opener.open(req)
276 except urlerr.httperror as inst:
276 except urlerr.httperror as inst:
277 if inst.code == 401:
277 if inst.code == 401:
278 raise error.Abort(_('authorization failed'))
278 raise error.Abort(_('authorization failed'))
279 raise
279 raise
280 except httplib.HTTPException as inst:
280 except httplib.HTTPException as inst:
281 ui.debug('http error requesting %s\n' %
281 ui.debug('http error requesting %s\n' %
282 util.hidepassword(req.get_full_url()))
282 util.hidepassword(req.get_full_url()))
283 ui.traceback()
283 ui.traceback()
284 raise IOError(None, inst)
284 raise IOError(None, inst)
285 finally:
285 finally:
286 if ui.debugflag and ui.configbool('devel', 'debug.peer-request'):
286 if ui.debugflag and ui.configbool('devel', 'debug.peer-request'):
287 code = res.code if res else -1
287 code = res.code if res else -1
288 dbg(line % ' finished in %.4f seconds (%d)'
288 dbg(line % ' finished in %.4f seconds (%d)'
289 % (util.timer() - start, code))
289 % (util.timer() - start, code))
290
290
291 # Insert error handlers for common I/O failures.
291 # Insert error handlers for common I/O failures.
292 urlmod.wrapresponse(res)
292 urlmod.wrapresponse(res)
293
293
294 return res
294 return res
295
295
296 class RedirectedRepoError(error.RepoError):
296 class RedirectedRepoError(error.RepoError):
297 def __init__(self, msg, respurl):
297 def __init__(self, msg, respurl):
298 super(RedirectedRepoError, self).__init__(msg)
298 super(RedirectedRepoError, self).__init__(msg)
299 self.respurl = respurl
299 self.respurl = respurl
300
300
301 def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible,
301 def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible,
302 allowcbor=False):
302 allowcbor=False):
303 # record the url we got redirected to
303 # record the url we got redirected to
304 redirected = False
304 redirected = False
305 respurl = pycompat.bytesurl(resp.geturl())
305 respurl = pycompat.bytesurl(resp.geturl())
306 if respurl.endswith(qs):
306 if respurl.endswith(qs):
307 respurl = respurl[:-len(qs)]
307 respurl = respurl[:-len(qs)]
308 qsdropped = False
308 qsdropped = False
309 else:
309 else:
310 qsdropped = True
310 qsdropped = True
311
311
312 if baseurl.rstrip('/') != respurl.rstrip('/'):
312 if baseurl.rstrip('/') != respurl.rstrip('/'):
313 redirected = True
313 redirected = True
314 if not ui.quiet:
314 if not ui.quiet:
315 ui.warn(_('real URL is %s\n') % respurl)
315 ui.warn(_('real URL is %s\n') % respurl)
316
316
317 try:
317 try:
318 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
318 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
319 except AttributeError:
319 except AttributeError:
320 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
320 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
321
321
322 safeurl = util.hidepassword(baseurl)
322 safeurl = util.hidepassword(baseurl)
323 if proto.startswith('application/hg-error'):
323 if proto.startswith('application/hg-error'):
324 raise error.OutOfBandError(resp.read())
324 raise error.OutOfBandError(resp.read())
325
325
326 # Pre 1.0 versions of Mercurial used text/plain and
326 # Pre 1.0 versions of Mercurial used text/plain and
327 # application/hg-changegroup. We don't support such old servers.
327 # application/hg-changegroup. We don't support such old servers.
328 if not proto.startswith('application/mercurial-'):
328 if not proto.startswith('application/mercurial-'):
329 ui.debug("requested URL: '%s'\n" % util.hidepassword(requrl))
329 ui.debug("requested URL: '%s'\n" % util.hidepassword(requrl))
330 msg = _("'%s' does not appear to be an hg repository:\n"
330 msg = _("'%s' does not appear to be an hg repository:\n"
331 "---%%<--- (%s)\n%s\n---%%<---\n") % (
331 "---%%<--- (%s)\n%s\n---%%<---\n") % (
332 safeurl, proto or 'no content-type', resp.read(1024))
332 safeurl, proto or 'no content-type', resp.read(1024))
333
333
334 # Some servers may strip the query string from the redirect. We
334 # Some servers may strip the query string from the redirect. We
335 # raise a special error type so callers can react to this specially.
335 # raise a special error type so callers can react to this specially.
336 if redirected and qsdropped:
336 if redirected and qsdropped:
337 raise RedirectedRepoError(msg, respurl)
337 raise RedirectedRepoError(msg, respurl)
338 else:
338 else:
339 raise error.RepoError(msg)
339 raise error.RepoError(msg)
340
340
341 try:
341 try:
342 subtype = proto.split('-', 1)[1]
342 subtype = proto.split('-', 1)[1]
343
343
344 # Unless we end up supporting CBOR in the legacy wire protocol,
344 # Unless we end up supporting CBOR in the legacy wire protocol,
345 # this should ONLY be encountered for the initial capabilities
345 # this should ONLY be encountered for the initial capabilities
346 # request during handshake.
346 # request during handshake.
347 if subtype == 'cbor':
347 if subtype == 'cbor':
348 if allowcbor:
348 if allowcbor:
349 return respurl, proto, resp
349 return respurl, proto, resp
350 else:
350 else:
351 raise error.RepoError(_('unexpected CBOR response from '
351 raise error.RepoError(_('unexpected CBOR response from '
352 'server'))
352 'server'))
353
353
354 version_info = tuple([int(n) for n in subtype.split('.')])
354 version_info = tuple([int(n) for n in subtype.split('.')])
355 except ValueError:
355 except ValueError:
356 raise error.RepoError(_("'%s' sent a broken Content-Type "
356 raise error.RepoError(_("'%s' sent a broken Content-Type "
357 "header (%s)") % (safeurl, proto))
357 "header (%s)") % (safeurl, proto))
358
358
359 # TODO consider switching to a decompression reader that uses
359 # TODO consider switching to a decompression reader that uses
360 # generators.
360 # generators.
361 if version_info == (0, 1):
361 if version_info == (0, 1):
362 if compressible:
362 if compressible:
363 resp = util.compengines['zlib'].decompressorreader(resp)
363 resp = util.compengines['zlib'].decompressorreader(resp)
364
364
365 elif version_info == (0, 2):
365 elif version_info == (0, 2):
366 # application/mercurial-0.2 always identifies the compression
366 # application/mercurial-0.2 always identifies the compression
367 # engine in the payload header.
367 # engine in the payload header.
368 elen = struct.unpack('B', util.readexactly(resp, 1))[0]
368 elen = struct.unpack('B', util.readexactly(resp, 1))[0]
369 ename = util.readexactly(resp, elen)
369 ename = util.readexactly(resp, elen)
370 engine = util.compengines.forwiretype(ename)
370 engine = util.compengines.forwiretype(ename)
371
371
372 resp = engine.decompressorreader(resp)
372 resp = engine.decompressorreader(resp)
373 else:
373 else:
374 raise error.RepoError(_("'%s' uses newer protocol %s") %
374 raise error.RepoError(_("'%s' uses newer protocol %s") %
375 (safeurl, subtype))
375 (safeurl, subtype))
376
376
377 return respurl, proto, resp
377 return respurl, proto, resp
378
378
379 class httppeer(wireprotov1peer.wirepeer):
379 class httppeer(wireprotov1peer.wirepeer):
380 def __init__(self, ui, path, url, opener, requestbuilder, caps):
380 def __init__(self, ui, path, url, opener, requestbuilder, caps):
381 self.ui = ui
381 self.ui = ui
382 self._path = path
382 self._path = path
383 self._url = url
383 self._url = url
384 self._caps = caps
384 self._caps = caps
385 self._urlopener = opener
385 self._urlopener = opener
386 self._requestbuilder = requestbuilder
386 self._requestbuilder = requestbuilder
387
387
388 def __del__(self):
388 def __del__(self):
389 for h in self._urlopener.handlers:
389 for h in self._urlopener.handlers:
390 h.close()
390 h.close()
391 getattr(h, "close_all", lambda: None)()
391 getattr(h, "close_all", lambda: None)()
392
392
393 # Begin of ipeerconnection interface.
393 # Begin of ipeerconnection interface.
394
394
395 def url(self):
395 def url(self):
396 return self._path
396 return self._path
397
397
398 def local(self):
398 def local(self):
399 return None
399 return None
400
400
401 def peer(self):
401 def peer(self):
402 return self
402 return self
403
403
404 def canpush(self):
404 def canpush(self):
405 return True
405 return True
406
406
407 def close(self):
407 def close(self):
408 self.ui.note(_('(sent %d HTTP requests and %d bytes; '
408 self.ui.note(_('(sent %d HTTP requests and %d bytes; '
409 'received %d bytes in responses)\n') %
409 'received %d bytes in responses)\n') %
410 (self._urlopener.requestscount,
410 (self._urlopener.requestscount,
411 self._urlopener.sentbytescount,
411 self._urlopener.sentbytescount,
412 self._urlopener.receivedbytescount))
412 self._urlopener.receivedbytescount))
413
413
414 # End of ipeerconnection interface.
414 # End of ipeerconnection interface.
415
415
416 # Begin of ipeercommands interface.
416 # Begin of ipeercommands interface.
417
417
418 def capabilities(self):
418 def capabilities(self):
419 return self._caps
419 return self._caps
420
420
421 # End of ipeercommands interface.
421 # End of ipeercommands interface.
422
422
423 def _callstream(self, cmd, _compressible=False, **args):
423 def _callstream(self, cmd, _compressible=False, **args):
424 args = pycompat.byteskwargs(args)
424 args = pycompat.byteskwargs(args)
425
425
426 req, cu, qs = makev1commandrequest(self.ui, self._requestbuilder,
426 req, cu, qs = makev1commandrequest(self.ui, self._requestbuilder,
427 self._caps, self.capable,
427 self._caps, self.capable,
428 self._url, cmd, args)
428 self._url, cmd, args)
429
429
430 resp = sendrequest(self.ui, self._urlopener, req)
430 resp = sendrequest(self.ui, self._urlopener, req)
431
431
432 self._url, ct, resp = parsev1commandresponse(self.ui, self._url, cu, qs,
432 self._url, ct, resp = parsev1commandresponse(self.ui, self._url, cu, qs,
433 resp, _compressible)
433 resp, _compressible)
434
434
435 return resp
435 return resp
436
436
437 def _call(self, cmd, **args):
437 def _call(self, cmd, **args):
438 fp = self._callstream(cmd, **args)
438 fp = self._callstream(cmd, **args)
439 try:
439 try:
440 return fp.read()
440 return fp.read()
441 finally:
441 finally:
442 # if using keepalive, allow connection to be reused
442 # if using keepalive, allow connection to be reused
443 fp.close()
443 fp.close()
444
444
445 def _callpush(self, cmd, cg, **args):
445 def _callpush(self, cmd, cg, **args):
446 # have to stream bundle to a temp file because we do not have
446 # have to stream bundle to a temp file because we do not have
447 # http 1.1 chunked transfer.
447 # http 1.1 chunked transfer.
448
448
449 types = self.capable('unbundle')
449 types = self.capable('unbundle')
450 try:
450 try:
451 types = types.split(',')
451 types = types.split(',')
452 except AttributeError:
452 except AttributeError:
453 # servers older than d1b16a746db6 will send 'unbundle' as a
453 # servers older than d1b16a746db6 will send 'unbundle' as a
454 # boolean capability. They only support headerless/uncompressed
454 # boolean capability. They only support headerless/uncompressed
455 # bundles.
455 # bundles.
456 types = [""]
456 types = [""]
457 for x in types:
457 for x in types:
458 if x in bundle2.bundletypes:
458 if x in bundle2.bundletypes:
459 type = x
459 type = x
460 break
460 break
461
461
462 tempname = bundle2.writebundle(self.ui, cg, None, type)
462 tempname = bundle2.writebundle(self.ui, cg, None, type)
463 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
463 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
464 headers = {r'Content-Type': r'application/mercurial-0.1'}
464 headers = {r'Content-Type': r'application/mercurial-0.1'}
465
465
466 try:
466 try:
467 r = self._call(cmd, data=fp, headers=headers, **args)
467 r = self._call(cmd, data=fp, headers=headers, **args)
468 vals = r.split('\n', 1)
468 vals = r.split('\n', 1)
469 if len(vals) < 2:
469 if len(vals) < 2:
470 raise error.ResponseError(_("unexpected response:"), r)
470 raise error.ResponseError(_("unexpected response:"), r)
471 return vals
471 return vals
472 except urlerr.httperror:
472 except urlerr.httperror:
473 # Catch and re-raise these so we don't try and treat them
473 # Catch and re-raise these so we don't try and treat them
474 # like generic socket errors. They lack any values in
474 # like generic socket errors. They lack any values in
475 # .args on Python 3 which breaks our socket.error block.
475 # .args on Python 3 which breaks our socket.error block.
476 raise
476 raise
477 except socket.error as err:
477 except socket.error as err:
478 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
478 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
479 raise error.Abort(_('push failed: %s') % err.args[1])
479 raise error.Abort(_('push failed: %s') % err.args[1])
480 raise error.Abort(err.args[1])
480 raise error.Abort(err.args[1])
481 finally:
481 finally:
482 fp.close()
482 fp.close()
483 os.unlink(tempname)
483 os.unlink(tempname)
484
484
485 def _calltwowaystream(self, cmd, fp, **args):
485 def _calltwowaystream(self, cmd, fp, **args):
486 fh = None
486 fh = None
487 fp_ = None
487 fp_ = None
488 filename = None
488 filename = None
489 try:
489 try:
490 # dump bundle to disk
490 # dump bundle to disk
491 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
491 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
492 fh = os.fdopen(fd, r"wb")
492 fh = os.fdopen(fd, r"wb")
493 d = fp.read(4096)
493 d = fp.read(4096)
494 while d:
494 while d:
495 fh.write(d)
495 fh.write(d)
496 d = fp.read(4096)
496 d = fp.read(4096)
497 fh.close()
497 fh.close()
498 # start http push
498 # start http push
499 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
499 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
500 headers = {r'Content-Type': r'application/mercurial-0.1'}
500 headers = {r'Content-Type': r'application/mercurial-0.1'}
501 return self._callstream(cmd, data=fp_, headers=headers, **args)
501 return self._callstream(cmd, data=fp_, headers=headers, **args)
502 finally:
502 finally:
503 if fp_ is not None:
503 if fp_ is not None:
504 fp_.close()
504 fp_.close()
505 if fh is not None:
505 if fh is not None:
506 fh.close()
506 fh.close()
507 os.unlink(filename)
507 os.unlink(filename)
508
508
509 def _callcompressable(self, cmd, **args):
509 def _callcompressable(self, cmd, **args):
510 return self._callstream(cmd, _compressible=True, **args)
510 return self._callstream(cmd, _compressible=True, **args)
511
511
512 def _abort(self, exception):
512 def _abort(self, exception):
513 raise exception
513 raise exception
514
514
515 def sendv2request(ui, opener, requestbuilder, apiurl, permission, requests,
515 def sendv2request(ui, opener, requestbuilder, apiurl, permission, requests,
516 redirect):
516 redirect):
517 wireprotoframing.populatestreamencoders()
518
519 uiencoders = ui.configlist(b'experimental', b'httppeer.v2-encoder-order')
520
521 if uiencoders:
522 encoders = []
523
524 for encoder in uiencoders:
525 if encoder not in wireprotoframing.STREAM_ENCODERS:
526 ui.warn(_(b'wire protocol version 2 encoder referenced in '
527 b'config (%s) is not known; ignoring\n') % encoder)
528 else:
529 encoders.append(encoder)
530
531 else:
532 encoders = wireprotoframing.STREAM_ENCODERS_ORDER
533
517 reactor = wireprotoframing.clientreactor(ui,
534 reactor = wireprotoframing.clientreactor(ui,
518 hasmultiplesend=False,
535 hasmultiplesend=False,
519 buffersends=True)
536 buffersends=True,
537 clientcontentencoders=encoders)
520
538
521 handler = wireprotov2peer.clienthandler(ui, reactor,
539 handler = wireprotov2peer.clienthandler(ui, reactor,
522 opener=opener,
540 opener=opener,
523 requestbuilder=requestbuilder)
541 requestbuilder=requestbuilder)
524
542
525 url = '%s/%s' % (apiurl, permission)
543 url = '%s/%s' % (apiurl, permission)
526
544
527 if len(requests) > 1:
545 if len(requests) > 1:
528 url += '/multirequest'
546 url += '/multirequest'
529 else:
547 else:
530 url += '/%s' % requests[0][0]
548 url += '/%s' % requests[0][0]
531
549
532 ui.debug('sending %d commands\n' % len(requests))
550 ui.debug('sending %d commands\n' % len(requests))
533 for command, args, f in requests:
551 for command, args, f in requests:
534 ui.debug('sending command %s: %s\n' % (
552 ui.debug('sending command %s: %s\n' % (
535 command, stringutil.pprint(args, indent=2)))
553 command, stringutil.pprint(args, indent=2)))
536 assert not list(handler.callcommand(command, args, f,
554 assert not list(handler.callcommand(command, args, f,
537 redirect=redirect))
555 redirect=redirect))
538
556
539 # TODO stream this.
557 # TODO stream this.
540 body = b''.join(map(bytes, handler.flushcommands()))
558 body = b''.join(map(bytes, handler.flushcommands()))
541
559
542 # TODO modify user-agent to reflect v2
560 # TODO modify user-agent to reflect v2
543 headers = {
561 headers = {
544 r'Accept': wireprotov2server.FRAMINGTYPE,
562 r'Accept': wireprotov2server.FRAMINGTYPE,
545 r'Content-Type': wireprotov2server.FRAMINGTYPE,
563 r'Content-Type': wireprotov2server.FRAMINGTYPE,
546 }
564 }
547
565
548 req = requestbuilder(pycompat.strurl(url), body, headers)
566 req = requestbuilder(pycompat.strurl(url), body, headers)
549 req.add_unredirected_header(r'Content-Length', r'%d' % len(body))
567 req.add_unredirected_header(r'Content-Length', r'%d' % len(body))
550
568
551 try:
569 try:
552 res = opener.open(req)
570 res = opener.open(req)
553 except urlerr.httperror as e:
571 except urlerr.httperror as e:
554 if e.code == 401:
572 if e.code == 401:
555 raise error.Abort(_('authorization failed'))
573 raise error.Abort(_('authorization failed'))
556
574
557 raise
575 raise
558 except httplib.HTTPException as e:
576 except httplib.HTTPException as e:
559 ui.traceback()
577 ui.traceback()
560 raise IOError(None, e)
578 raise IOError(None, e)
561
579
562 return handler, res
580 return handler, res
563
581
564 class queuedcommandfuture(pycompat.futures.Future):
582 class queuedcommandfuture(pycompat.futures.Future):
565 """Wraps result() on command futures to trigger submission on call."""
583 """Wraps result() on command futures to trigger submission on call."""
566
584
567 def result(self, timeout=None):
585 def result(self, timeout=None):
568 if self.done():
586 if self.done():
569 return pycompat.futures.Future.result(self, timeout)
587 return pycompat.futures.Future.result(self, timeout)
570
588
571 self._peerexecutor.sendcommands()
589 self._peerexecutor.sendcommands()
572
590
573 # sendcommands() will restore the original __class__ and self.result
591 # sendcommands() will restore the original __class__ and self.result
574 # will resolve to Future.result.
592 # will resolve to Future.result.
575 return self.result(timeout)
593 return self.result(timeout)
576
594
577 @interfaceutil.implementer(repository.ipeercommandexecutor)
595 @interfaceutil.implementer(repository.ipeercommandexecutor)
578 class httpv2executor(object):
596 class httpv2executor(object):
579 def __init__(self, ui, opener, requestbuilder, apiurl, descriptor,
597 def __init__(self, ui, opener, requestbuilder, apiurl, descriptor,
580 redirect):
598 redirect):
581 self._ui = ui
599 self._ui = ui
582 self._opener = opener
600 self._opener = opener
583 self._requestbuilder = requestbuilder
601 self._requestbuilder = requestbuilder
584 self._apiurl = apiurl
602 self._apiurl = apiurl
585 self._descriptor = descriptor
603 self._descriptor = descriptor
586 self._redirect = redirect
604 self._redirect = redirect
587 self._sent = False
605 self._sent = False
588 self._closed = False
606 self._closed = False
589 self._neededpermissions = set()
607 self._neededpermissions = set()
590 self._calls = []
608 self._calls = []
591 self._futures = weakref.WeakSet()
609 self._futures = weakref.WeakSet()
592 self._responseexecutor = None
610 self._responseexecutor = None
593 self._responsef = None
611 self._responsef = None
594
612
595 def __enter__(self):
613 def __enter__(self):
596 return self
614 return self
597
615
598 def __exit__(self, exctype, excvalue, exctb):
616 def __exit__(self, exctype, excvalue, exctb):
599 self.close()
617 self.close()
600
618
601 def callcommand(self, command, args):
619 def callcommand(self, command, args):
602 if self._sent:
620 if self._sent:
603 raise error.ProgrammingError('callcommand() cannot be used after '
621 raise error.ProgrammingError('callcommand() cannot be used after '
604 'commands are sent')
622 'commands are sent')
605
623
606 if self._closed:
624 if self._closed:
607 raise error.ProgrammingError('callcommand() cannot be used after '
625 raise error.ProgrammingError('callcommand() cannot be used after '
608 'close()')
626 'close()')
609
627
610 # The service advertises which commands are available. So if we attempt
628 # The service advertises which commands are available. So if we attempt
611 # to call an unknown command or pass an unknown argument, we can screen
629 # to call an unknown command or pass an unknown argument, we can screen
612 # for this.
630 # for this.
613 if command not in self._descriptor['commands']:
631 if command not in self._descriptor['commands']:
614 raise error.ProgrammingError(
632 raise error.ProgrammingError(
615 'wire protocol command %s is not available' % command)
633 'wire protocol command %s is not available' % command)
616
634
617 cmdinfo = self._descriptor['commands'][command]
635 cmdinfo = self._descriptor['commands'][command]
618 unknownargs = set(args.keys()) - set(cmdinfo.get('args', {}))
636 unknownargs = set(args.keys()) - set(cmdinfo.get('args', {}))
619
637
620 if unknownargs:
638 if unknownargs:
621 raise error.ProgrammingError(
639 raise error.ProgrammingError(
622 'wire protocol command %s does not accept argument: %s' % (
640 'wire protocol command %s does not accept argument: %s' % (
623 command, ', '.join(sorted(unknownargs))))
641 command, ', '.join(sorted(unknownargs))))
624
642
625 self._neededpermissions |= set(cmdinfo['permissions'])
643 self._neededpermissions |= set(cmdinfo['permissions'])
626
644
627 # TODO we /could/ also validate types here, since the API descriptor
645 # TODO we /could/ also validate types here, since the API descriptor
628 # includes types...
646 # includes types...
629
647
630 f = pycompat.futures.Future()
648 f = pycompat.futures.Future()
631
649
632 # Monkeypatch it so result() triggers sendcommands(), otherwise result()
650 # Monkeypatch it so result() triggers sendcommands(), otherwise result()
633 # could deadlock.
651 # could deadlock.
634 f.__class__ = queuedcommandfuture
652 f.__class__ = queuedcommandfuture
635 f._peerexecutor = self
653 f._peerexecutor = self
636
654
637 self._futures.add(f)
655 self._futures.add(f)
638 self._calls.append((command, args, f))
656 self._calls.append((command, args, f))
639
657
640 return f
658 return f
641
659
642 def sendcommands(self):
660 def sendcommands(self):
643 if self._sent:
661 if self._sent:
644 return
662 return
645
663
646 if not self._calls:
664 if not self._calls:
647 return
665 return
648
666
649 self._sent = True
667 self._sent = True
650
668
651 # Unhack any future types so caller sees a clean type and so we
669 # Unhack any future types so caller sees a clean type and so we
652 # break reference cycle.
670 # break reference cycle.
653 for f in self._futures:
671 for f in self._futures:
654 if isinstance(f, queuedcommandfuture):
672 if isinstance(f, queuedcommandfuture):
655 f.__class__ = pycompat.futures.Future
673 f.__class__ = pycompat.futures.Future
656 f._peerexecutor = None
674 f._peerexecutor = None
657
675
658 # Mark the future as running and filter out cancelled futures.
676 # Mark the future as running and filter out cancelled futures.
659 calls = [(command, args, f)
677 calls = [(command, args, f)
660 for command, args, f in self._calls
678 for command, args, f in self._calls
661 if f.set_running_or_notify_cancel()]
679 if f.set_running_or_notify_cancel()]
662
680
663 # Clear out references, prevent improper object usage.
681 # Clear out references, prevent improper object usage.
664 self._calls = None
682 self._calls = None
665
683
666 if not calls:
684 if not calls:
667 return
685 return
668
686
669 permissions = set(self._neededpermissions)
687 permissions = set(self._neededpermissions)
670
688
671 if 'push' in permissions and 'pull' in permissions:
689 if 'push' in permissions and 'pull' in permissions:
672 permissions.remove('pull')
690 permissions.remove('pull')
673
691
674 if len(permissions) > 1:
692 if len(permissions) > 1:
675 raise error.RepoError(_('cannot make request requiring multiple '
693 raise error.RepoError(_('cannot make request requiring multiple '
676 'permissions: %s') %
694 'permissions: %s') %
677 _(', ').join(sorted(permissions)))
695 _(', ').join(sorted(permissions)))
678
696
679 permission = {
697 permission = {
680 'push': 'rw',
698 'push': 'rw',
681 'pull': 'ro',
699 'pull': 'ro',
682 }[permissions.pop()]
700 }[permissions.pop()]
683
701
684 handler, resp = sendv2request(
702 handler, resp = sendv2request(
685 self._ui, self._opener, self._requestbuilder, self._apiurl,
703 self._ui, self._opener, self._requestbuilder, self._apiurl,
686 permission, calls, self._redirect)
704 permission, calls, self._redirect)
687
705
688 # TODO we probably want to validate the HTTP code, media type, etc.
706 # TODO we probably want to validate the HTTP code, media type, etc.
689
707
690 self._responseexecutor = pycompat.futures.ThreadPoolExecutor(1)
708 self._responseexecutor = pycompat.futures.ThreadPoolExecutor(1)
691 self._responsef = self._responseexecutor.submit(self._handleresponse,
709 self._responsef = self._responseexecutor.submit(self._handleresponse,
692 handler, resp)
710 handler, resp)
693
711
694 def close(self):
712 def close(self):
695 if self._closed:
713 if self._closed:
696 return
714 return
697
715
698 self.sendcommands()
716 self.sendcommands()
699
717
700 self._closed = True
718 self._closed = True
701
719
702 if not self._responsef:
720 if not self._responsef:
703 return
721 return
704
722
705 # TODO ^C here may not result in immediate program termination.
723 # TODO ^C here may not result in immediate program termination.
706
724
707 try:
725 try:
708 self._responsef.result()
726 self._responsef.result()
709 finally:
727 finally:
710 self._responseexecutor.shutdown(wait=True)
728 self._responseexecutor.shutdown(wait=True)
711 self._responsef = None
729 self._responsef = None
712 self._responseexecutor = None
730 self._responseexecutor = None
713
731
714 # If any of our futures are still in progress, mark them as
732 # If any of our futures are still in progress, mark them as
715 # errored, otherwise a result() could wait indefinitely.
733 # errored, otherwise a result() could wait indefinitely.
716 for f in self._futures:
734 for f in self._futures:
717 if not f.done():
735 if not f.done():
718 f.set_exception(error.ResponseError(
736 f.set_exception(error.ResponseError(
719 _('unfulfilled command response')))
737 _('unfulfilled command response')))
720
738
721 self._futures = None
739 self._futures = None
722
740
723 def _handleresponse(self, handler, resp):
741 def _handleresponse(self, handler, resp):
724 # Called in a thread to read the response.
742 # Called in a thread to read the response.
725
743
726 while handler.readdata(resp):
744 while handler.readdata(resp):
727 pass
745 pass
728
746
729 # TODO implement interface for version 2 peers
747 # TODO implement interface for version 2 peers
730 @interfaceutil.implementer(repository.ipeerconnection,
748 @interfaceutil.implementer(repository.ipeerconnection,
731 repository.ipeercapabilities,
749 repository.ipeercapabilities,
732 repository.ipeerrequests)
750 repository.ipeerrequests)
733 class httpv2peer(object):
751 class httpv2peer(object):
734 def __init__(self, ui, repourl, apipath, opener, requestbuilder,
752 def __init__(self, ui, repourl, apipath, opener, requestbuilder,
735 apidescriptor):
753 apidescriptor):
736 self.ui = ui
754 self.ui = ui
737
755
738 if repourl.endswith('/'):
756 if repourl.endswith('/'):
739 repourl = repourl[:-1]
757 repourl = repourl[:-1]
740
758
741 self._url = repourl
759 self._url = repourl
742 self._apipath = apipath
760 self._apipath = apipath
743 self._apiurl = '%s/%s' % (repourl, apipath)
761 self._apiurl = '%s/%s' % (repourl, apipath)
744 self._opener = opener
762 self._opener = opener
745 self._requestbuilder = requestbuilder
763 self._requestbuilder = requestbuilder
746 self._descriptor = apidescriptor
764 self._descriptor = apidescriptor
747
765
748 self._redirect = wireprotov2peer.supportedredirects(ui, apidescriptor)
766 self._redirect = wireprotov2peer.supportedredirects(ui, apidescriptor)
749
767
750 # Start of ipeerconnection.
768 # Start of ipeerconnection.
751
769
752 def url(self):
770 def url(self):
753 return self._url
771 return self._url
754
772
755 def local(self):
773 def local(self):
756 return None
774 return None
757
775
758 def peer(self):
776 def peer(self):
759 return self
777 return self
760
778
761 def canpush(self):
779 def canpush(self):
762 # TODO change once implemented.
780 # TODO change once implemented.
763 return False
781 return False
764
782
765 def close(self):
783 def close(self):
766 self.ui.note(_('(sent %d HTTP requests and %d bytes; '
784 self.ui.note(_('(sent %d HTTP requests and %d bytes; '
767 'received %d bytes in responses)\n') %
785 'received %d bytes in responses)\n') %
768 (self._opener.requestscount,
786 (self._opener.requestscount,
769 self._opener.sentbytescount,
787 self._opener.sentbytescount,
770 self._opener.receivedbytescount))
788 self._opener.receivedbytescount))
771
789
772 # End of ipeerconnection.
790 # End of ipeerconnection.
773
791
774 # Start of ipeercapabilities.
792 # Start of ipeercapabilities.
775
793
776 def capable(self, name):
794 def capable(self, name):
777 # The capabilities used internally historically map to capabilities
795 # The capabilities used internally historically map to capabilities
778 # advertised from the "capabilities" wire protocol command. However,
796 # advertised from the "capabilities" wire protocol command. However,
779 # version 2 of that command works differently.
797 # version 2 of that command works differently.
780
798
781 # Maps to commands that are available.
799 # Maps to commands that are available.
782 if name in ('branchmap', 'getbundle', 'known', 'lookup', 'pushkey'):
800 if name in ('branchmap', 'getbundle', 'known', 'lookup', 'pushkey'):
783 return True
801 return True
784
802
785 # Other concepts.
803 # Other concepts.
786 if name in ('bundle2'):
804 if name in ('bundle2'):
787 return True
805 return True
788
806
789 # Alias command-* to presence of command of that name.
807 # Alias command-* to presence of command of that name.
790 if name.startswith('command-'):
808 if name.startswith('command-'):
791 return name[len('command-'):] in self._descriptor['commands']
809 return name[len('command-'):] in self._descriptor['commands']
792
810
793 return False
811 return False
794
812
795 def requirecap(self, name, purpose):
813 def requirecap(self, name, purpose):
796 if self.capable(name):
814 if self.capable(name):
797 return
815 return
798
816
799 raise error.CapabilityError(
817 raise error.CapabilityError(
800 _('cannot %s; client or remote repository does not support the %r '
818 _('cannot %s; client or remote repository does not support the %r '
801 'capability') % (purpose, name))
819 'capability') % (purpose, name))
802
820
803 # End of ipeercapabilities.
821 # End of ipeercapabilities.
804
822
805 def _call(self, name, **args):
823 def _call(self, name, **args):
806 with self.commandexecutor() as e:
824 with self.commandexecutor() as e:
807 return e.callcommand(name, args).result()
825 return e.callcommand(name, args).result()
808
826
809 def commandexecutor(self):
827 def commandexecutor(self):
810 return httpv2executor(self.ui, self._opener, self._requestbuilder,
828 return httpv2executor(self.ui, self._opener, self._requestbuilder,
811 self._apiurl, self._descriptor, self._redirect)
829 self._apiurl, self._descriptor, self._redirect)
812
830
813 # Registry of API service names to metadata about peers that handle it.
831 # Registry of API service names to metadata about peers that handle it.
814 #
832 #
815 # The following keys are meaningful:
833 # The following keys are meaningful:
816 #
834 #
817 # init
835 # init
818 # Callable receiving (ui, repourl, servicepath, opener, requestbuilder,
836 # Callable receiving (ui, repourl, servicepath, opener, requestbuilder,
819 # apidescriptor) to create a peer.
837 # apidescriptor) to create a peer.
820 #
838 #
821 # priority
839 # priority
822 # Integer priority for the service. If we could choose from multiple
840 # Integer priority for the service. If we could choose from multiple
823 # services, we choose the one with the highest priority.
841 # services, we choose the one with the highest priority.
824 API_PEERS = {
842 API_PEERS = {
825 wireprototypes.HTTP_WIREPROTO_V2: {
843 wireprototypes.HTTP_WIREPROTO_V2: {
826 'init': httpv2peer,
844 'init': httpv2peer,
827 'priority': 50,
845 'priority': 50,
828 },
846 },
829 }
847 }
830
848
831 def performhandshake(ui, url, opener, requestbuilder):
849 def performhandshake(ui, url, opener, requestbuilder):
832 # The handshake is a request to the capabilities command.
850 # The handshake is a request to the capabilities command.
833
851
834 caps = None
852 caps = None
835 def capable(x):
853 def capable(x):
836 raise error.ProgrammingError('should not be called')
854 raise error.ProgrammingError('should not be called')
837
855
838 args = {}
856 args = {}
839
857
840 # The client advertises support for newer protocols by adding an
858 # The client advertises support for newer protocols by adding an
841 # X-HgUpgrade-* header with a list of supported APIs and an
859 # X-HgUpgrade-* header with a list of supported APIs and an
842 # X-HgProto-* header advertising which serializing formats it supports.
860 # X-HgProto-* header advertising which serializing formats it supports.
843 # We only support the HTTP version 2 transport and CBOR responses for
861 # We only support the HTTP version 2 transport and CBOR responses for
844 # now.
862 # now.
845 advertisev2 = ui.configbool('experimental', 'httppeer.advertise-v2')
863 advertisev2 = ui.configbool('experimental', 'httppeer.advertise-v2')
846
864
847 if advertisev2:
865 if advertisev2:
848 args['headers'] = {
866 args['headers'] = {
849 r'X-HgProto-1': r'cbor',
867 r'X-HgProto-1': r'cbor',
850 }
868 }
851
869
852 args['headers'].update(
870 args['headers'].update(
853 encodevalueinheaders(' '.join(sorted(API_PEERS)),
871 encodevalueinheaders(' '.join(sorted(API_PEERS)),
854 'X-HgUpgrade',
872 'X-HgUpgrade',
855 # We don't know the header limit this early.
873 # We don't know the header limit this early.
856 # So make it small.
874 # So make it small.
857 1024))
875 1024))
858
876
859 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
877 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
860 capable, url, 'capabilities',
878 capable, url, 'capabilities',
861 args)
879 args)
862 resp = sendrequest(ui, opener, req)
880 resp = sendrequest(ui, opener, req)
863
881
864 # The server may redirect us to the repo root, stripping the
882 # The server may redirect us to the repo root, stripping the
865 # ?cmd=capabilities query string from the URL. The server would likely
883 # ?cmd=capabilities query string from the URL. The server would likely
866 # return HTML in this case and ``parsev1commandresponse()`` would raise.
884 # return HTML in this case and ``parsev1commandresponse()`` would raise.
867 # We catch this special case and re-issue the capabilities request against
885 # We catch this special case and re-issue the capabilities request against
868 # the new URL.
886 # the new URL.
869 #
887 #
870 # We should ideally not do this, as a redirect that drops the query
888 # We should ideally not do this, as a redirect that drops the query
871 # string from the URL is arguably a server bug. (Garbage in, garbage out).
889 # string from the URL is arguably a server bug. (Garbage in, garbage out).
872 # However, Mercurial clients for several years appeared to handle this
890 # However, Mercurial clients for several years appeared to handle this
873 # issue without behavior degradation. And according to issue 5860, it may
891 # issue without behavior degradation. And according to issue 5860, it may
874 # be a longstanding bug in some server implementations. So we allow a
892 # be a longstanding bug in some server implementations. So we allow a
875 # redirect that drops the query string to "just work."
893 # redirect that drops the query string to "just work."
876 try:
894 try:
877 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
895 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
878 compressible=False,
896 compressible=False,
879 allowcbor=advertisev2)
897 allowcbor=advertisev2)
880 except RedirectedRepoError as e:
898 except RedirectedRepoError as e:
881 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
899 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
882 capable, e.respurl,
900 capable, e.respurl,
883 'capabilities', args)
901 'capabilities', args)
884 resp = sendrequest(ui, opener, req)
902 resp = sendrequest(ui, opener, req)
885 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
903 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
886 compressible=False,
904 compressible=False,
887 allowcbor=advertisev2)
905 allowcbor=advertisev2)
888
906
889 try:
907 try:
890 rawdata = resp.read()
908 rawdata = resp.read()
891 finally:
909 finally:
892 resp.close()
910 resp.close()
893
911
894 if not ct.startswith('application/mercurial-'):
912 if not ct.startswith('application/mercurial-'):
895 raise error.ProgrammingError('unexpected content-type: %s' % ct)
913 raise error.ProgrammingError('unexpected content-type: %s' % ct)
896
914
897 if advertisev2:
915 if advertisev2:
898 if ct == 'application/mercurial-cbor':
916 if ct == 'application/mercurial-cbor':
899 try:
917 try:
900 info = cborutil.decodeall(rawdata)[0]
918 info = cborutil.decodeall(rawdata)[0]
901 except cborutil.CBORDecodeError:
919 except cborutil.CBORDecodeError:
902 raise error.Abort(_('error decoding CBOR from remote server'),
920 raise error.Abort(_('error decoding CBOR from remote server'),
903 hint=_('try again and consider contacting '
921 hint=_('try again and consider contacting '
904 'the server operator'))
922 'the server operator'))
905
923
906 # We got a legacy response. That's fine.
924 # We got a legacy response. That's fine.
907 elif ct in ('application/mercurial-0.1', 'application/mercurial-0.2'):
925 elif ct in ('application/mercurial-0.1', 'application/mercurial-0.2'):
908 info = {
926 info = {
909 'v1capabilities': set(rawdata.split())
927 'v1capabilities': set(rawdata.split())
910 }
928 }
911
929
912 else:
930 else:
913 raise error.RepoError(
931 raise error.RepoError(
914 _('unexpected response type from server: %s') % ct)
932 _('unexpected response type from server: %s') % ct)
915 else:
933 else:
916 info = {
934 info = {
917 'v1capabilities': set(rawdata.split())
935 'v1capabilities': set(rawdata.split())
918 }
936 }
919
937
920 return respurl, info
938 return respurl, info
921
939
922 def makepeer(ui, path, opener=None, requestbuilder=urlreq.request):
940 def makepeer(ui, path, opener=None, requestbuilder=urlreq.request):
923 """Construct an appropriate HTTP peer instance.
941 """Construct an appropriate HTTP peer instance.
924
942
925 ``opener`` is an ``url.opener`` that should be used to establish
943 ``opener`` is an ``url.opener`` that should be used to establish
926 connections, perform HTTP requests.
944 connections, perform HTTP requests.
927
945
928 ``requestbuilder`` is the type used for constructing HTTP requests.
946 ``requestbuilder`` is the type used for constructing HTTP requests.
929 It exists as an argument so extensions can override the default.
947 It exists as an argument so extensions can override the default.
930 """
948 """
931 u = util.url(path)
949 u = util.url(path)
932 if u.query or u.fragment:
950 if u.query or u.fragment:
933 raise error.Abort(_('unsupported URL component: "%s"') %
951 raise error.Abort(_('unsupported URL component: "%s"') %
934 (u.query or u.fragment))
952 (u.query or u.fragment))
935
953
936 # urllib cannot handle URLs with embedded user or passwd.
954 # urllib cannot handle URLs with embedded user or passwd.
937 url, authinfo = u.authinfo()
955 url, authinfo = u.authinfo()
938 ui.debug('using %s\n' % url)
956 ui.debug('using %s\n' % url)
939
957
940 opener = opener or urlmod.opener(ui, authinfo)
958 opener = opener or urlmod.opener(ui, authinfo)
941
959
942 respurl, info = performhandshake(ui, url, opener, requestbuilder)
960 respurl, info = performhandshake(ui, url, opener, requestbuilder)
943
961
944 # Given the intersection of APIs that both we and the server support,
962 # Given the intersection of APIs that both we and the server support,
945 # sort by their advertised priority and pick the first one.
963 # sort by their advertised priority and pick the first one.
946 #
964 #
947 # TODO consider making this request-based and interface driven. For
965 # TODO consider making this request-based and interface driven. For
948 # example, the caller could say "I want a peer that does X." It's quite
966 # example, the caller could say "I want a peer that does X." It's quite
949 # possible that not all peers would do that. Since we know the service
967 # possible that not all peers would do that. Since we know the service
950 # capabilities, we could filter out services not meeting the
968 # capabilities, we could filter out services not meeting the
951 # requirements. Possibly by consulting the interfaces defined by the
969 # requirements. Possibly by consulting the interfaces defined by the
952 # peer type.
970 # peer type.
953 apipeerchoices = set(info.get('apis', {}).keys()) & set(API_PEERS.keys())
971 apipeerchoices = set(info.get('apis', {}).keys()) & set(API_PEERS.keys())
954
972
955 preferredchoices = sorted(apipeerchoices,
973 preferredchoices = sorted(apipeerchoices,
956 key=lambda x: API_PEERS[x]['priority'],
974 key=lambda x: API_PEERS[x]['priority'],
957 reverse=True)
975 reverse=True)
958
976
959 for service in preferredchoices:
977 for service in preferredchoices:
960 apipath = '%s/%s' % (info['apibase'].rstrip('/'), service)
978 apipath = '%s/%s' % (info['apibase'].rstrip('/'), service)
961
979
962 return API_PEERS[service]['init'](ui, respurl, apipath, opener,
980 return API_PEERS[service]['init'](ui, respurl, apipath, opener,
963 requestbuilder,
981 requestbuilder,
964 info['apis'][service])
982 info['apis'][service])
965
983
966 # Failed to construct an API peer. Fall back to legacy.
984 # Failed to construct an API peer. Fall back to legacy.
967 return httppeer(ui, path, respurl, opener, requestbuilder,
985 return httppeer(ui, path, respurl, opener, requestbuilder,
968 info['v1capabilities'])
986 info['v1capabilities'])
969
987
970 def instance(ui, path, create, intents=None, createopts=None):
988 def instance(ui, path, create, intents=None, createopts=None):
971 if create:
989 if create:
972 raise error.Abort(_('cannot create new http repository'))
990 raise error.Abort(_('cannot create new http repository'))
973 try:
991 try:
974 if path.startswith('https:') and not urlmod.has_https:
992 if path.startswith('https:') and not urlmod.has_https:
975 raise error.Abort(_('Python support for SSL and HTTPS '
993 raise error.Abort(_('Python support for SSL and HTTPS '
976 'is not installed'))
994 'is not installed'))
977
995
978 inst = makepeer(ui, path)
996 inst = makepeer(ui, path)
979
997
980 return inst
998 return inst
981 except error.RepoError as httpexception:
999 except error.RepoError as httpexception:
982 try:
1000 try:
983 r = statichttprepo.instance(ui, "static-" + path, create)
1001 r = statichttprepo.instance(ui, "static-" + path, create)
984 ui.note(_('(falling back to static-http)\n'))
1002 ui.note(_('(falling back to static-http)\n'))
985 return r
1003 return r
986 except error.RepoError:
1004 except error.RepoError:
987 raise httpexception # use the original http RepoError instead
1005 raise httpexception # use the original http RepoError instead
@@ -1,1806 +1,1827
1 # wireprotoframing.py - unified framing protocol for wire protocol
1 # wireprotoframing.py - unified framing protocol for wire protocol
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # This file contains functionality to support the unified frame-based wire
8 # This file contains functionality to support the unified frame-based wire
9 # protocol. For details about the protocol, see
9 # protocol. For details about the protocol, see
10 # `hg help internals.wireprotocol`.
10 # `hg help internals.wireprotocol`.
11
11
12 from __future__ import absolute_import
12 from __future__ import absolute_import
13
13
14 import collections
14 import collections
15 import struct
15 import struct
16
16
17 from .i18n import _
17 from .i18n import _
18 from .thirdparty import (
18 from .thirdparty import (
19 attr,
19 attr,
20 )
20 )
21 from . import (
21 from . import (
22 encoding,
22 encoding,
23 error,
23 error,
24 pycompat,
24 pycompat,
25 util,
25 util,
26 wireprototypes,
26 wireprototypes,
27 )
27 )
28 from .utils import (
28 from .utils import (
29 cborutil,
29 cborutil,
30 stringutil,
30 stringutil,
31 )
31 )
32
32
33 FRAME_HEADER_SIZE = 8
33 FRAME_HEADER_SIZE = 8
34 DEFAULT_MAX_FRAME_SIZE = 32768
34 DEFAULT_MAX_FRAME_SIZE = 32768
35
35
36 STREAM_FLAG_BEGIN_STREAM = 0x01
36 STREAM_FLAG_BEGIN_STREAM = 0x01
37 STREAM_FLAG_END_STREAM = 0x02
37 STREAM_FLAG_END_STREAM = 0x02
38 STREAM_FLAG_ENCODING_APPLIED = 0x04
38 STREAM_FLAG_ENCODING_APPLIED = 0x04
39
39
40 STREAM_FLAGS = {
40 STREAM_FLAGS = {
41 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
41 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
42 b'stream-end': STREAM_FLAG_END_STREAM,
42 b'stream-end': STREAM_FLAG_END_STREAM,
43 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
43 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
44 }
44 }
45
45
46 FRAME_TYPE_COMMAND_REQUEST = 0x01
46 FRAME_TYPE_COMMAND_REQUEST = 0x01
47 FRAME_TYPE_COMMAND_DATA = 0x02
47 FRAME_TYPE_COMMAND_DATA = 0x02
48 FRAME_TYPE_COMMAND_RESPONSE = 0x03
48 FRAME_TYPE_COMMAND_RESPONSE = 0x03
49 FRAME_TYPE_ERROR_RESPONSE = 0x05
49 FRAME_TYPE_ERROR_RESPONSE = 0x05
50 FRAME_TYPE_TEXT_OUTPUT = 0x06
50 FRAME_TYPE_TEXT_OUTPUT = 0x06
51 FRAME_TYPE_PROGRESS = 0x07
51 FRAME_TYPE_PROGRESS = 0x07
52 FRAME_TYPE_SENDER_PROTOCOL_SETTINGS = 0x08
52 FRAME_TYPE_SENDER_PROTOCOL_SETTINGS = 0x08
53 FRAME_TYPE_STREAM_SETTINGS = 0x09
53 FRAME_TYPE_STREAM_SETTINGS = 0x09
54
54
55 FRAME_TYPES = {
55 FRAME_TYPES = {
56 b'command-request': FRAME_TYPE_COMMAND_REQUEST,
56 b'command-request': FRAME_TYPE_COMMAND_REQUEST,
57 b'command-data': FRAME_TYPE_COMMAND_DATA,
57 b'command-data': FRAME_TYPE_COMMAND_DATA,
58 b'command-response': FRAME_TYPE_COMMAND_RESPONSE,
58 b'command-response': FRAME_TYPE_COMMAND_RESPONSE,
59 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
59 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
60 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
60 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
61 b'progress': FRAME_TYPE_PROGRESS,
61 b'progress': FRAME_TYPE_PROGRESS,
62 b'sender-protocol-settings': FRAME_TYPE_SENDER_PROTOCOL_SETTINGS,
62 b'sender-protocol-settings': FRAME_TYPE_SENDER_PROTOCOL_SETTINGS,
63 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
63 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
64 }
64 }
65
65
66 FLAG_COMMAND_REQUEST_NEW = 0x01
66 FLAG_COMMAND_REQUEST_NEW = 0x01
67 FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
67 FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
68 FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
68 FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
69 FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
69 FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
70
70
71 FLAGS_COMMAND_REQUEST = {
71 FLAGS_COMMAND_REQUEST = {
72 b'new': FLAG_COMMAND_REQUEST_NEW,
72 b'new': FLAG_COMMAND_REQUEST_NEW,
73 b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
73 b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
74 b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
74 b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
75 b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
75 b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
76 }
76 }
77
77
78 FLAG_COMMAND_DATA_CONTINUATION = 0x01
78 FLAG_COMMAND_DATA_CONTINUATION = 0x01
79 FLAG_COMMAND_DATA_EOS = 0x02
79 FLAG_COMMAND_DATA_EOS = 0x02
80
80
81 FLAGS_COMMAND_DATA = {
81 FLAGS_COMMAND_DATA = {
82 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
82 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
83 b'eos': FLAG_COMMAND_DATA_EOS,
83 b'eos': FLAG_COMMAND_DATA_EOS,
84 }
84 }
85
85
86 FLAG_COMMAND_RESPONSE_CONTINUATION = 0x01
86 FLAG_COMMAND_RESPONSE_CONTINUATION = 0x01
87 FLAG_COMMAND_RESPONSE_EOS = 0x02
87 FLAG_COMMAND_RESPONSE_EOS = 0x02
88
88
89 FLAGS_COMMAND_RESPONSE = {
89 FLAGS_COMMAND_RESPONSE = {
90 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION,
90 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION,
91 b'eos': FLAG_COMMAND_RESPONSE_EOS,
91 b'eos': FLAG_COMMAND_RESPONSE_EOS,
92 }
92 }
93
93
94 FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION = 0x01
94 FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION = 0x01
95 FLAG_SENDER_PROTOCOL_SETTINGS_EOS = 0x02
95 FLAG_SENDER_PROTOCOL_SETTINGS_EOS = 0x02
96
96
97 FLAGS_SENDER_PROTOCOL_SETTINGS = {
97 FLAGS_SENDER_PROTOCOL_SETTINGS = {
98 b'continuation': FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION,
98 b'continuation': FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION,
99 b'eos': FLAG_SENDER_PROTOCOL_SETTINGS_EOS,
99 b'eos': FLAG_SENDER_PROTOCOL_SETTINGS_EOS,
100 }
100 }
101
101
102 FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION = 0x01
102 FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION = 0x01
103 FLAG_STREAM_ENCODING_SETTINGS_EOS = 0x02
103 FLAG_STREAM_ENCODING_SETTINGS_EOS = 0x02
104
104
105 FLAGS_STREAM_ENCODING_SETTINGS = {
105 FLAGS_STREAM_ENCODING_SETTINGS = {
106 b'continuation': FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION,
106 b'continuation': FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION,
107 b'eos': FLAG_STREAM_ENCODING_SETTINGS_EOS,
107 b'eos': FLAG_STREAM_ENCODING_SETTINGS_EOS,
108 }
108 }
109
109
110 # Maps frame types to their available flags.
110 # Maps frame types to their available flags.
111 FRAME_TYPE_FLAGS = {
111 FRAME_TYPE_FLAGS = {
112 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
112 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
113 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
113 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
114 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE,
114 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE,
115 FRAME_TYPE_ERROR_RESPONSE: {},
115 FRAME_TYPE_ERROR_RESPONSE: {},
116 FRAME_TYPE_TEXT_OUTPUT: {},
116 FRAME_TYPE_TEXT_OUTPUT: {},
117 FRAME_TYPE_PROGRESS: {},
117 FRAME_TYPE_PROGRESS: {},
118 FRAME_TYPE_SENDER_PROTOCOL_SETTINGS: FLAGS_SENDER_PROTOCOL_SETTINGS,
118 FRAME_TYPE_SENDER_PROTOCOL_SETTINGS: FLAGS_SENDER_PROTOCOL_SETTINGS,
119 FRAME_TYPE_STREAM_SETTINGS: FLAGS_STREAM_ENCODING_SETTINGS,
119 FRAME_TYPE_STREAM_SETTINGS: FLAGS_STREAM_ENCODING_SETTINGS,
120 }
120 }
121
121
122 ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
122 ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
123
123
124 def humanflags(mapping, value):
124 def humanflags(mapping, value):
125 """Convert a numeric flags value to a human value, using a mapping table."""
125 """Convert a numeric flags value to a human value, using a mapping table."""
126 namemap = {v: k for k, v in mapping.iteritems()}
126 namemap = {v: k for k, v in mapping.iteritems()}
127 flags = []
127 flags = []
128 val = 1
128 val = 1
129 while value >= val:
129 while value >= val:
130 if value & val:
130 if value & val:
131 flags.append(namemap.get(val, '<unknown 0x%02x>' % val))
131 flags.append(namemap.get(val, '<unknown 0x%02x>' % val))
132 val <<= 1
132 val <<= 1
133
133
134 return b'|'.join(flags)
134 return b'|'.join(flags)
135
135
136 @attr.s(slots=True)
136 @attr.s(slots=True)
137 class frameheader(object):
137 class frameheader(object):
138 """Represents the data in a frame header."""
138 """Represents the data in a frame header."""
139
139
140 length = attr.ib()
140 length = attr.ib()
141 requestid = attr.ib()
141 requestid = attr.ib()
142 streamid = attr.ib()
142 streamid = attr.ib()
143 streamflags = attr.ib()
143 streamflags = attr.ib()
144 typeid = attr.ib()
144 typeid = attr.ib()
145 flags = attr.ib()
145 flags = attr.ib()
146
146
147 @attr.s(slots=True, repr=False)
147 @attr.s(slots=True, repr=False)
148 class frame(object):
148 class frame(object):
149 """Represents a parsed frame."""
149 """Represents a parsed frame."""
150
150
151 requestid = attr.ib()
151 requestid = attr.ib()
152 streamid = attr.ib()
152 streamid = attr.ib()
153 streamflags = attr.ib()
153 streamflags = attr.ib()
154 typeid = attr.ib()
154 typeid = attr.ib()
155 flags = attr.ib()
155 flags = attr.ib()
156 payload = attr.ib()
156 payload = attr.ib()
157
157
158 @encoding.strmethod
158 @encoding.strmethod
159 def __repr__(self):
159 def __repr__(self):
160 typename = '<unknown 0x%02x>' % self.typeid
160 typename = '<unknown 0x%02x>' % self.typeid
161 for name, value in FRAME_TYPES.iteritems():
161 for name, value in FRAME_TYPES.iteritems():
162 if value == self.typeid:
162 if value == self.typeid:
163 typename = name
163 typename = name
164 break
164 break
165
165
166 return ('frame(size=%d; request=%d; stream=%d; streamflags=%s; '
166 return ('frame(size=%d; request=%d; stream=%d; streamflags=%s; '
167 'type=%s; flags=%s)' % (
167 'type=%s; flags=%s)' % (
168 len(self.payload), self.requestid, self.streamid,
168 len(self.payload), self.requestid, self.streamid,
169 humanflags(STREAM_FLAGS, self.streamflags), typename,
169 humanflags(STREAM_FLAGS, self.streamflags), typename,
170 humanflags(FRAME_TYPE_FLAGS.get(self.typeid, {}), self.flags)))
170 humanflags(FRAME_TYPE_FLAGS.get(self.typeid, {}), self.flags)))
171
171
172 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
172 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
173 """Assemble a frame into a byte array."""
173 """Assemble a frame into a byte array."""
174 # TODO assert size of payload.
174 # TODO assert size of payload.
175 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
175 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
176
176
177 # 24 bits length
177 # 24 bits length
178 # 16 bits request id
178 # 16 bits request id
179 # 8 bits stream id
179 # 8 bits stream id
180 # 8 bits stream flags
180 # 8 bits stream flags
181 # 4 bits type
181 # 4 bits type
182 # 4 bits flags
182 # 4 bits flags
183
183
184 l = struct.pack(r'<I', len(payload))
184 l = struct.pack(r'<I', len(payload))
185 frame[0:3] = l[0:3]
185 frame[0:3] = l[0:3]
186 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
186 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
187 frame[7] = (typeid << 4) | flags
187 frame[7] = (typeid << 4) | flags
188 frame[8:] = payload
188 frame[8:] = payload
189
189
190 return frame
190 return frame
191
191
192 def makeframefromhumanstring(s):
192 def makeframefromhumanstring(s):
193 """Create a frame from a human readable string
193 """Create a frame from a human readable string
194
194
195 Strings have the form:
195 Strings have the form:
196
196
197 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
197 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
198
198
199 This can be used by user-facing applications and tests for creating
199 This can be used by user-facing applications and tests for creating
200 frames easily without having to type out a bunch of constants.
200 frames easily without having to type out a bunch of constants.
201
201
202 Request ID and stream IDs are integers.
202 Request ID and stream IDs are integers.
203
203
204 Stream flags, frame type, and flags can be specified by integer or
204 Stream flags, frame type, and flags can be specified by integer or
205 named constant.
205 named constant.
206
206
207 Flags can be delimited by `|` to bitwise OR them together.
207 Flags can be delimited by `|` to bitwise OR them together.
208
208
209 If the payload begins with ``cbor:``, the following string will be
209 If the payload begins with ``cbor:``, the following string will be
210 evaluated as Python literal and the resulting object will be fed into
210 evaluated as Python literal and the resulting object will be fed into
211 a CBOR encoder. Otherwise, the payload is interpreted as a Python
211 a CBOR encoder. Otherwise, the payload is interpreted as a Python
212 byte string literal.
212 byte string literal.
213 """
213 """
214 fields = s.split(b' ', 5)
214 fields = s.split(b' ', 5)
215 requestid, streamid, streamflags, frametype, frameflags, payload = fields
215 requestid, streamid, streamflags, frametype, frameflags, payload = fields
216
216
217 requestid = int(requestid)
217 requestid = int(requestid)
218 streamid = int(streamid)
218 streamid = int(streamid)
219
219
220 finalstreamflags = 0
220 finalstreamflags = 0
221 for flag in streamflags.split(b'|'):
221 for flag in streamflags.split(b'|'):
222 if flag in STREAM_FLAGS:
222 if flag in STREAM_FLAGS:
223 finalstreamflags |= STREAM_FLAGS[flag]
223 finalstreamflags |= STREAM_FLAGS[flag]
224 else:
224 else:
225 finalstreamflags |= int(flag)
225 finalstreamflags |= int(flag)
226
226
227 if frametype in FRAME_TYPES:
227 if frametype in FRAME_TYPES:
228 frametype = FRAME_TYPES[frametype]
228 frametype = FRAME_TYPES[frametype]
229 else:
229 else:
230 frametype = int(frametype)
230 frametype = int(frametype)
231
231
232 finalflags = 0
232 finalflags = 0
233 validflags = FRAME_TYPE_FLAGS[frametype]
233 validflags = FRAME_TYPE_FLAGS[frametype]
234 for flag in frameflags.split(b'|'):
234 for flag in frameflags.split(b'|'):
235 if flag in validflags:
235 if flag in validflags:
236 finalflags |= validflags[flag]
236 finalflags |= validflags[flag]
237 else:
237 else:
238 finalflags |= int(flag)
238 finalflags |= int(flag)
239
239
240 if payload.startswith(b'cbor:'):
240 if payload.startswith(b'cbor:'):
241 payload = b''.join(cborutil.streamencode(
241 payload = b''.join(cborutil.streamencode(
242 stringutil.evalpythonliteral(payload[5:])))
242 stringutil.evalpythonliteral(payload[5:])))
243
243
244 else:
244 else:
245 payload = stringutil.unescapestr(payload)
245 payload = stringutil.unescapestr(payload)
246
246
247 return makeframe(requestid=requestid, streamid=streamid,
247 return makeframe(requestid=requestid, streamid=streamid,
248 streamflags=finalstreamflags, typeid=frametype,
248 streamflags=finalstreamflags, typeid=frametype,
249 flags=finalflags, payload=payload)
249 flags=finalflags, payload=payload)
250
250
251 def parseheader(data):
251 def parseheader(data):
252 """Parse a unified framing protocol frame header from a buffer.
252 """Parse a unified framing protocol frame header from a buffer.
253
253
254 The header is expected to be in the buffer at offset 0 and the
254 The header is expected to be in the buffer at offset 0 and the
255 buffer is expected to be large enough to hold a full header.
255 buffer is expected to be large enough to hold a full header.
256 """
256 """
257 # 24 bits payload length (little endian)
257 # 24 bits payload length (little endian)
258 # 16 bits request ID
258 # 16 bits request ID
259 # 8 bits stream ID
259 # 8 bits stream ID
260 # 8 bits stream flags
260 # 8 bits stream flags
261 # 4 bits frame type
261 # 4 bits frame type
262 # 4 bits frame flags
262 # 4 bits frame flags
263 # ... payload
263 # ... payload
264 framelength = data[0] + 256 * data[1] + 16384 * data[2]
264 framelength = data[0] + 256 * data[1] + 16384 * data[2]
265 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
265 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
266 typeflags = data[7]
266 typeflags = data[7]
267
267
268 frametype = (typeflags & 0xf0) >> 4
268 frametype = (typeflags & 0xf0) >> 4
269 frameflags = typeflags & 0x0f
269 frameflags = typeflags & 0x0f
270
270
271 return frameheader(framelength, requestid, streamid, streamflags,
271 return frameheader(framelength, requestid, streamid, streamflags,
272 frametype, frameflags)
272 frametype, frameflags)
273
273
274 def readframe(fh):
274 def readframe(fh):
275 """Read a unified framing protocol frame from a file object.
275 """Read a unified framing protocol frame from a file object.
276
276
277 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
277 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
278 None if no frame is available. May raise if a malformed frame is
278 None if no frame is available. May raise if a malformed frame is
279 seen.
279 seen.
280 """
280 """
281 header = bytearray(FRAME_HEADER_SIZE)
281 header = bytearray(FRAME_HEADER_SIZE)
282
282
283 readcount = fh.readinto(header)
283 readcount = fh.readinto(header)
284
284
285 if readcount == 0:
285 if readcount == 0:
286 return None
286 return None
287
287
288 if readcount != FRAME_HEADER_SIZE:
288 if readcount != FRAME_HEADER_SIZE:
289 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
289 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
290 (readcount, header))
290 (readcount, header))
291
291
292 h = parseheader(header)
292 h = parseheader(header)
293
293
294 payload = fh.read(h.length)
294 payload = fh.read(h.length)
295 if len(payload) != h.length:
295 if len(payload) != h.length:
296 raise error.Abort(_('frame length error: expected %d; got %d') %
296 raise error.Abort(_('frame length error: expected %d; got %d') %
297 (h.length, len(payload)))
297 (h.length, len(payload)))
298
298
299 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
299 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
300 payload)
300 payload)
301
301
302 def createcommandframes(stream, requestid, cmd, args, datafh=None,
302 def createcommandframes(stream, requestid, cmd, args, datafh=None,
303 maxframesize=DEFAULT_MAX_FRAME_SIZE,
303 maxframesize=DEFAULT_MAX_FRAME_SIZE,
304 redirect=None):
304 redirect=None):
305 """Create frames necessary to transmit a request to run a command.
305 """Create frames necessary to transmit a request to run a command.
306
306
307 This is a generator of bytearrays. Each item represents a frame
307 This is a generator of bytearrays. Each item represents a frame
308 ready to be sent over the wire to a peer.
308 ready to be sent over the wire to a peer.
309 """
309 """
310 data = {b'name': cmd}
310 data = {b'name': cmd}
311 if args:
311 if args:
312 data[b'args'] = args
312 data[b'args'] = args
313
313
314 if redirect:
314 if redirect:
315 data[b'redirect'] = redirect
315 data[b'redirect'] = redirect
316
316
317 data = b''.join(cborutil.streamencode(data))
317 data = b''.join(cborutil.streamencode(data))
318
318
319 offset = 0
319 offset = 0
320
320
321 while True:
321 while True:
322 flags = 0
322 flags = 0
323
323
324 # Must set new or continuation flag.
324 # Must set new or continuation flag.
325 if not offset:
325 if not offset:
326 flags |= FLAG_COMMAND_REQUEST_NEW
326 flags |= FLAG_COMMAND_REQUEST_NEW
327 else:
327 else:
328 flags |= FLAG_COMMAND_REQUEST_CONTINUATION
328 flags |= FLAG_COMMAND_REQUEST_CONTINUATION
329
329
330 # Data frames is set on all frames.
330 # Data frames is set on all frames.
331 if datafh:
331 if datafh:
332 flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
332 flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
333
333
334 payload = data[offset:offset + maxframesize]
334 payload = data[offset:offset + maxframesize]
335 offset += len(payload)
335 offset += len(payload)
336
336
337 if len(payload) == maxframesize and offset < len(data):
337 if len(payload) == maxframesize and offset < len(data):
338 flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
338 flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
339
339
340 yield stream.makeframe(requestid=requestid,
340 yield stream.makeframe(requestid=requestid,
341 typeid=FRAME_TYPE_COMMAND_REQUEST,
341 typeid=FRAME_TYPE_COMMAND_REQUEST,
342 flags=flags,
342 flags=flags,
343 payload=payload)
343 payload=payload)
344
344
345 if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
345 if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
346 break
346 break
347
347
348 if datafh:
348 if datafh:
349 while True:
349 while True:
350 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
350 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
351
351
352 done = False
352 done = False
353 if len(data) == DEFAULT_MAX_FRAME_SIZE:
353 if len(data) == DEFAULT_MAX_FRAME_SIZE:
354 flags = FLAG_COMMAND_DATA_CONTINUATION
354 flags = FLAG_COMMAND_DATA_CONTINUATION
355 else:
355 else:
356 flags = FLAG_COMMAND_DATA_EOS
356 flags = FLAG_COMMAND_DATA_EOS
357 assert datafh.read(1) == b''
357 assert datafh.read(1) == b''
358 done = True
358 done = True
359
359
360 yield stream.makeframe(requestid=requestid,
360 yield stream.makeframe(requestid=requestid,
361 typeid=FRAME_TYPE_COMMAND_DATA,
361 typeid=FRAME_TYPE_COMMAND_DATA,
362 flags=flags,
362 flags=flags,
363 payload=data)
363 payload=data)
364
364
365 if done:
365 if done:
366 break
366 break
367
367
368 def createcommandresponseframesfrombytes(stream, requestid, data,
368 def createcommandresponseframesfrombytes(stream, requestid, data,
369 maxframesize=DEFAULT_MAX_FRAME_SIZE):
369 maxframesize=DEFAULT_MAX_FRAME_SIZE):
370 """Create a raw frame to send a bytes response from static bytes input.
370 """Create a raw frame to send a bytes response from static bytes input.
371
371
372 Returns a generator of bytearrays.
372 Returns a generator of bytearrays.
373 """
373 """
374 # Automatically send the overall CBOR response map.
374 # Automatically send the overall CBOR response map.
375 overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
375 overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
376 if len(overall) > maxframesize:
376 if len(overall) > maxframesize:
377 raise error.ProgrammingError('not yet implemented')
377 raise error.ProgrammingError('not yet implemented')
378
378
379 # Simple case where we can fit the full response in a single frame.
379 # Simple case where we can fit the full response in a single frame.
380 if len(overall) + len(data) <= maxframesize:
380 if len(overall) + len(data) <= maxframesize:
381 flags = FLAG_COMMAND_RESPONSE_EOS
381 flags = FLAG_COMMAND_RESPONSE_EOS
382 yield stream.makeframe(requestid=requestid,
382 yield stream.makeframe(requestid=requestid,
383 typeid=FRAME_TYPE_COMMAND_RESPONSE,
383 typeid=FRAME_TYPE_COMMAND_RESPONSE,
384 flags=flags,
384 flags=flags,
385 payload=overall + data)
385 payload=overall + data)
386 return
386 return
387
387
388 # It's easier to send the overall CBOR map in its own frame than to track
388 # It's easier to send the overall CBOR map in its own frame than to track
389 # offsets.
389 # offsets.
390 yield stream.makeframe(requestid=requestid,
390 yield stream.makeframe(requestid=requestid,
391 typeid=FRAME_TYPE_COMMAND_RESPONSE,
391 typeid=FRAME_TYPE_COMMAND_RESPONSE,
392 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
392 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
393 payload=overall)
393 payload=overall)
394
394
395 offset = 0
395 offset = 0
396 while True:
396 while True:
397 chunk = data[offset:offset + maxframesize]
397 chunk = data[offset:offset + maxframesize]
398 offset += len(chunk)
398 offset += len(chunk)
399 done = offset == len(data)
399 done = offset == len(data)
400
400
401 if done:
401 if done:
402 flags = FLAG_COMMAND_RESPONSE_EOS
402 flags = FLAG_COMMAND_RESPONSE_EOS
403 else:
403 else:
404 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
404 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
405
405
406 yield stream.makeframe(requestid=requestid,
406 yield stream.makeframe(requestid=requestid,
407 typeid=FRAME_TYPE_COMMAND_RESPONSE,
407 typeid=FRAME_TYPE_COMMAND_RESPONSE,
408 flags=flags,
408 flags=flags,
409 payload=chunk)
409 payload=chunk)
410
410
411 if done:
411 if done:
412 break
412 break
413
413
414 def createbytesresponseframesfromgen(stream, requestid, gen,
414 def createbytesresponseframesfromgen(stream, requestid, gen,
415 maxframesize=DEFAULT_MAX_FRAME_SIZE):
415 maxframesize=DEFAULT_MAX_FRAME_SIZE):
416 """Generator of frames from a generator of byte chunks.
416 """Generator of frames from a generator of byte chunks.
417
417
418 This assumes that another frame will follow whatever this emits. i.e.
418 This assumes that another frame will follow whatever this emits. i.e.
419 this always emits the continuation flag and never emits the end-of-stream
419 this always emits the continuation flag and never emits the end-of-stream
420 flag.
420 flag.
421 """
421 """
422 cb = util.chunkbuffer(gen)
422 cb = util.chunkbuffer(gen)
423 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
423 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
424
424
425 while True:
425 while True:
426 chunk = cb.read(maxframesize)
426 chunk = cb.read(maxframesize)
427 if not chunk:
427 if not chunk:
428 break
428 break
429
429
430 yield stream.makeframe(requestid=requestid,
430 yield stream.makeframe(requestid=requestid,
431 typeid=FRAME_TYPE_COMMAND_RESPONSE,
431 typeid=FRAME_TYPE_COMMAND_RESPONSE,
432 flags=flags,
432 flags=flags,
433 payload=chunk)
433 payload=chunk)
434
434
435 flags |= FLAG_COMMAND_RESPONSE_CONTINUATION
435 flags |= FLAG_COMMAND_RESPONSE_CONTINUATION
436
436
437 def createcommandresponseokframe(stream, requestid):
437 def createcommandresponseokframe(stream, requestid):
438 overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
438 overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
439
439
440 return stream.makeframe(requestid=requestid,
440 return stream.makeframe(requestid=requestid,
441 typeid=FRAME_TYPE_COMMAND_RESPONSE,
441 typeid=FRAME_TYPE_COMMAND_RESPONSE,
442 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
442 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
443 payload=overall)
443 payload=overall)
444
444
445 def createcommandresponseeosframe(stream, requestid):
445 def createcommandresponseeosframe(stream, requestid):
446 """Create an empty payload frame representing command end-of-stream."""
446 """Create an empty payload frame representing command end-of-stream."""
447 return stream.makeframe(requestid=requestid,
447 return stream.makeframe(requestid=requestid,
448 typeid=FRAME_TYPE_COMMAND_RESPONSE,
448 typeid=FRAME_TYPE_COMMAND_RESPONSE,
449 flags=FLAG_COMMAND_RESPONSE_EOS,
449 flags=FLAG_COMMAND_RESPONSE_EOS,
450 payload=b'')
450 payload=b'')
451
451
452 def createalternatelocationresponseframe(stream, requestid, location):
452 def createalternatelocationresponseframe(stream, requestid, location):
453 data = {
453 data = {
454 b'status': b'redirect',
454 b'status': b'redirect',
455 b'location': {
455 b'location': {
456 b'url': location.url,
456 b'url': location.url,
457 b'mediatype': location.mediatype,
457 b'mediatype': location.mediatype,
458 }
458 }
459 }
459 }
460
460
461 for a in (r'size', r'fullhashes', r'fullhashseed', r'serverdercerts',
461 for a in (r'size', r'fullhashes', r'fullhashseed', r'serverdercerts',
462 r'servercadercerts'):
462 r'servercadercerts'):
463 value = getattr(location, a)
463 value = getattr(location, a)
464 if value is not None:
464 if value is not None:
465 data[b'location'][pycompat.bytestr(a)] = value
465 data[b'location'][pycompat.bytestr(a)] = value
466
466
467 return stream.makeframe(requestid=requestid,
467 return stream.makeframe(requestid=requestid,
468 typeid=FRAME_TYPE_COMMAND_RESPONSE,
468 typeid=FRAME_TYPE_COMMAND_RESPONSE,
469 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
469 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
470 payload=b''.join(cborutil.streamencode(data)))
470 payload=b''.join(cborutil.streamencode(data)))
471
471
472 def createcommanderrorresponse(stream, requestid, message, args=None):
472 def createcommanderrorresponse(stream, requestid, message, args=None):
473 # TODO should this be using a list of {'msg': ..., 'args': {}} so atom
473 # TODO should this be using a list of {'msg': ..., 'args': {}} so atom
474 # formatting works consistently?
474 # formatting works consistently?
475 m = {
475 m = {
476 b'status': b'error',
476 b'status': b'error',
477 b'error': {
477 b'error': {
478 b'message': message,
478 b'message': message,
479 }
479 }
480 }
480 }
481
481
482 if args:
482 if args:
483 m[b'error'][b'args'] = args
483 m[b'error'][b'args'] = args
484
484
485 overall = b''.join(cborutil.streamencode(m))
485 overall = b''.join(cborutil.streamencode(m))
486
486
487 yield stream.makeframe(requestid=requestid,
487 yield stream.makeframe(requestid=requestid,
488 typeid=FRAME_TYPE_COMMAND_RESPONSE,
488 typeid=FRAME_TYPE_COMMAND_RESPONSE,
489 flags=FLAG_COMMAND_RESPONSE_EOS,
489 flags=FLAG_COMMAND_RESPONSE_EOS,
490 payload=overall)
490 payload=overall)
491
491
492 def createerrorframe(stream, requestid, msg, errtype):
492 def createerrorframe(stream, requestid, msg, errtype):
493 # TODO properly handle frame size limits.
493 # TODO properly handle frame size limits.
494 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
494 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
495
495
496 payload = b''.join(cborutil.streamencode({
496 payload = b''.join(cborutil.streamencode({
497 b'type': errtype,
497 b'type': errtype,
498 b'message': [{b'msg': msg}],
498 b'message': [{b'msg': msg}],
499 }))
499 }))
500
500
501 yield stream.makeframe(requestid=requestid,
501 yield stream.makeframe(requestid=requestid,
502 typeid=FRAME_TYPE_ERROR_RESPONSE,
502 typeid=FRAME_TYPE_ERROR_RESPONSE,
503 flags=0,
503 flags=0,
504 payload=payload)
504 payload=payload)
505
505
506 def createtextoutputframe(stream, requestid, atoms,
506 def createtextoutputframe(stream, requestid, atoms,
507 maxframesize=DEFAULT_MAX_FRAME_SIZE):
507 maxframesize=DEFAULT_MAX_FRAME_SIZE):
508 """Create a text output frame to render text to people.
508 """Create a text output frame to render text to people.
509
509
510 ``atoms`` is a 3-tuple of (formatting string, args, labels).
510 ``atoms`` is a 3-tuple of (formatting string, args, labels).
511
511
512 The formatting string contains ``%s`` tokens to be replaced by the
512 The formatting string contains ``%s`` tokens to be replaced by the
513 corresponding indexed entry in ``args``. ``labels`` is an iterable of
513 corresponding indexed entry in ``args``. ``labels`` is an iterable of
514 formatters to be applied at rendering time. In terms of the ``ui``
514 formatters to be applied at rendering time. In terms of the ``ui``
515 class, each atom corresponds to a ``ui.write()``.
515 class, each atom corresponds to a ``ui.write()``.
516 """
516 """
517 atomdicts = []
517 atomdicts = []
518
518
519 for (formatting, args, labels) in atoms:
519 for (formatting, args, labels) in atoms:
520 # TODO look for localstr, other types here?
520 # TODO look for localstr, other types here?
521
521
522 if not isinstance(formatting, bytes):
522 if not isinstance(formatting, bytes):
523 raise ValueError('must use bytes formatting strings')
523 raise ValueError('must use bytes formatting strings')
524 for arg in args:
524 for arg in args:
525 if not isinstance(arg, bytes):
525 if not isinstance(arg, bytes):
526 raise ValueError('must use bytes for arguments')
526 raise ValueError('must use bytes for arguments')
527 for label in labels:
527 for label in labels:
528 if not isinstance(label, bytes):
528 if not isinstance(label, bytes):
529 raise ValueError('must use bytes for labels')
529 raise ValueError('must use bytes for labels')
530
530
531 # Formatting string must be ASCII.
531 # Formatting string must be ASCII.
532 formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii')
532 formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii')
533
533
534 # Arguments must be UTF-8.
534 # Arguments must be UTF-8.
535 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
535 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
536
536
537 # Labels must be ASCII.
537 # Labels must be ASCII.
538 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
538 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
539 for l in labels]
539 for l in labels]
540
540
541 atom = {b'msg': formatting}
541 atom = {b'msg': formatting}
542 if args:
542 if args:
543 atom[b'args'] = args
543 atom[b'args'] = args
544 if labels:
544 if labels:
545 atom[b'labels'] = labels
545 atom[b'labels'] = labels
546
546
547 atomdicts.append(atom)
547 atomdicts.append(atom)
548
548
549 payload = b''.join(cborutil.streamencode(atomdicts))
549 payload = b''.join(cborutil.streamencode(atomdicts))
550
550
551 if len(payload) > maxframesize:
551 if len(payload) > maxframesize:
552 raise ValueError('cannot encode data in a single frame')
552 raise ValueError('cannot encode data in a single frame')
553
553
554 yield stream.makeframe(requestid=requestid,
554 yield stream.makeframe(requestid=requestid,
555 typeid=FRAME_TYPE_TEXT_OUTPUT,
555 typeid=FRAME_TYPE_TEXT_OUTPUT,
556 flags=0,
556 flags=0,
557 payload=payload)
557 payload=payload)
558
558
559 class bufferingcommandresponseemitter(object):
559 class bufferingcommandresponseemitter(object):
560 """Helper object to emit command response frames intelligently.
560 """Helper object to emit command response frames intelligently.
561
561
562 Raw command response data is likely emitted in chunks much smaller
562 Raw command response data is likely emitted in chunks much smaller
563 than what can fit in a single frame. This class exists to buffer
563 than what can fit in a single frame. This class exists to buffer
564 chunks until enough data is available to fit in a single frame.
564 chunks until enough data is available to fit in a single frame.
565
565
566 TODO we'll need something like this when compression is supported.
566 TODO we'll need something like this when compression is supported.
567 So it might make sense to implement this functionality at the stream
567 So it might make sense to implement this functionality at the stream
568 level.
568 level.
569 """
569 """
570 def __init__(self, stream, requestid, maxframesize=DEFAULT_MAX_FRAME_SIZE):
570 def __init__(self, stream, requestid, maxframesize=DEFAULT_MAX_FRAME_SIZE):
571 self._stream = stream
571 self._stream = stream
572 self._requestid = requestid
572 self._requestid = requestid
573 self._maxsize = maxframesize
573 self._maxsize = maxframesize
574 self._chunks = []
574 self._chunks = []
575 self._chunkssize = 0
575 self._chunkssize = 0
576
576
577 def send(self, data):
577 def send(self, data):
578 """Send new data for emission.
578 """Send new data for emission.
579
579
580 Is a generator of new frames that were derived from the new input.
580 Is a generator of new frames that were derived from the new input.
581
581
582 If the special input ``None`` is received, flushes all buffered
582 If the special input ``None`` is received, flushes all buffered
583 data to frames.
583 data to frames.
584 """
584 """
585
585
586 if data is None:
586 if data is None:
587 for frame in self._flush():
587 for frame in self._flush():
588 yield frame
588 yield frame
589 return
589 return
590
590
591 # There is a ton of potential to do more complicated things here.
591 # There is a ton of potential to do more complicated things here.
592 # Our immediate goal is to coalesce small chunks into big frames,
592 # Our immediate goal is to coalesce small chunks into big frames,
593 # not achieve the fewest number of frames possible. So we go with
593 # not achieve the fewest number of frames possible. So we go with
594 # a simple implementation:
594 # a simple implementation:
595 #
595 #
596 # * If a chunk is too large for a frame, we flush and emit frames
596 # * If a chunk is too large for a frame, we flush and emit frames
597 # for the new chunk.
597 # for the new chunk.
598 # * If a chunk can be buffered without total buffered size limits
598 # * If a chunk can be buffered without total buffered size limits
599 # being exceeded, we do that.
599 # being exceeded, we do that.
600 # * If a chunk causes us to go over our buffering limit, we flush
600 # * If a chunk causes us to go over our buffering limit, we flush
601 # and then buffer the new chunk.
601 # and then buffer the new chunk.
602
602
603 if len(data) > self._maxsize:
603 if len(data) > self._maxsize:
604 for frame in self._flush():
604 for frame in self._flush():
605 yield frame
605 yield frame
606
606
607 # Now emit frames for the big chunk.
607 # Now emit frames for the big chunk.
608 offset = 0
608 offset = 0
609 while True:
609 while True:
610 chunk = data[offset:offset + self._maxsize]
610 chunk = data[offset:offset + self._maxsize]
611 offset += len(chunk)
611 offset += len(chunk)
612
612
613 yield self._stream.makeframe(
613 yield self._stream.makeframe(
614 self._requestid,
614 self._requestid,
615 typeid=FRAME_TYPE_COMMAND_RESPONSE,
615 typeid=FRAME_TYPE_COMMAND_RESPONSE,
616 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
616 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
617 payload=chunk)
617 payload=chunk)
618
618
619 if offset == len(data):
619 if offset == len(data):
620 return
620 return
621
621
622 # If we don't have enough to constitute a full frame, buffer and
622 # If we don't have enough to constitute a full frame, buffer and
623 # return.
623 # return.
624 if len(data) + self._chunkssize < self._maxsize:
624 if len(data) + self._chunkssize < self._maxsize:
625 self._chunks.append(data)
625 self._chunks.append(data)
626 self._chunkssize += len(data)
626 self._chunkssize += len(data)
627 return
627 return
628
628
629 # Else flush what we have and buffer the new chunk. We could do
629 # Else flush what we have and buffer the new chunk. We could do
630 # something more intelligent here, like break the chunk. Let's
630 # something more intelligent here, like break the chunk. Let's
631 # keep things simple for now.
631 # keep things simple for now.
632 for frame in self._flush():
632 for frame in self._flush():
633 yield frame
633 yield frame
634
634
635 self._chunks.append(data)
635 self._chunks.append(data)
636 self._chunkssize = len(data)
636 self._chunkssize = len(data)
637
637
638 def _flush(self):
638 def _flush(self):
639 payload = b''.join(self._chunks)
639 payload = b''.join(self._chunks)
640 assert len(payload) <= self._maxsize
640 assert len(payload) <= self._maxsize
641
641
642 self._chunks[:] = []
642 self._chunks[:] = []
643 self._chunkssize = 0
643 self._chunkssize = 0
644
644
645 yield self._stream.makeframe(
645 yield self._stream.makeframe(
646 self._requestid,
646 self._requestid,
647 typeid=FRAME_TYPE_COMMAND_RESPONSE,
647 typeid=FRAME_TYPE_COMMAND_RESPONSE,
648 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
648 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
649 payload=payload)
649 payload=payload)
650
650
651 # TODO consider defining encoders/decoders using the util.compressionengine
651 # TODO consider defining encoders/decoders using the util.compressionengine
652 # mechanism.
652 # mechanism.
653
653
654 class identityencoder(object):
654 class identityencoder(object):
655 """Encoder for the "identity" stream encoding profile."""
655 """Encoder for the "identity" stream encoding profile."""
656 def __init__(self, ui):
656 def __init__(self, ui):
657 pass
657 pass
658
658
659 def encode(self, data):
659 def encode(self, data):
660 return data
660 return data
661
661
662 def flush(self):
662 def flush(self):
663 return b''
663 return b''
664
664
665 def finish(self):
665 def finish(self):
666 return b''
666 return b''
667
667
668 class identitydecoder(object):
668 class identitydecoder(object):
669 """Decoder for the "identity" stream encoding profile."""
669 """Decoder for the "identity" stream encoding profile."""
670
670
671 def __init__(self, ui, extraobjs):
671 def __init__(self, ui, extraobjs):
672 if extraobjs:
672 if extraobjs:
673 raise error.Abort(_('identity decoder received unexpected '
673 raise error.Abort(_('identity decoder received unexpected '
674 'additional values'))
674 'additional values'))
675
675
676 def decode(self, data):
676 def decode(self, data):
677 return data
677 return data
678
678
679 class zlibencoder(object):
679 class zlibencoder(object):
680 def __init__(self, ui):
680 def __init__(self, ui):
681 import zlib
681 import zlib
682 self._zlib = zlib
682 self._zlib = zlib
683 self._compressor = zlib.compressobj()
683 self._compressor = zlib.compressobj()
684
684
685 def encode(self, data):
685 def encode(self, data):
686 return self._compressor.compress(data)
686 return self._compressor.compress(data)
687
687
688 def flush(self):
688 def flush(self):
689 # Z_SYNC_FLUSH doesn't reset compression context, which is
689 # Z_SYNC_FLUSH doesn't reset compression context, which is
690 # what we want.
690 # what we want.
691 return self._compressor.flush(self._zlib.Z_SYNC_FLUSH)
691 return self._compressor.flush(self._zlib.Z_SYNC_FLUSH)
692
692
693 def finish(self):
693 def finish(self):
694 res = self._compressor.flush(self._zlib.Z_FINISH)
694 res = self._compressor.flush(self._zlib.Z_FINISH)
695 self._compressor = None
695 self._compressor = None
696 return res
696 return res
697
697
698 class zlibdecoder(object):
698 class zlibdecoder(object):
699 def __init__(self, ui, extraobjs):
699 def __init__(self, ui, extraobjs):
700 import zlib
700 import zlib
701
701
702 if extraobjs:
702 if extraobjs:
703 raise error.Abort(_('zlib decoder received unexpected '
703 raise error.Abort(_('zlib decoder received unexpected '
704 'additional values'))
704 'additional values'))
705
705
706 self._decompressor = zlib.decompressobj()
706 self._decompressor = zlib.decompressobj()
707
707
708 def decode(self, data):
708 def decode(self, data):
709 # Python 2's zlib module doesn't use the buffer protocol and can't
709 # Python 2's zlib module doesn't use the buffer protocol and can't
710 # handle all bytes-like types.
710 # handle all bytes-like types.
711 if not pycompat.ispy3 and isinstance(data, bytearray):
711 if not pycompat.ispy3 and isinstance(data, bytearray):
712 data = bytes(data)
712 data = bytes(data)
713
713
714 return self._decompressor.decompress(data)
714 return self._decompressor.decompress(data)
715
715
716 class zstdbaseencoder(object):
716 class zstdbaseencoder(object):
717 def __init__(self, level):
717 def __init__(self, level):
718 from . import zstd
718 from . import zstd
719
719
720 self._zstd = zstd
720 self._zstd = zstd
721 cctx = zstd.ZstdCompressor(level=level)
721 cctx = zstd.ZstdCompressor(level=level)
722 self._compressor = cctx.compressobj()
722 self._compressor = cctx.compressobj()
723
723
724 def encode(self, data):
724 def encode(self, data):
725 return self._compressor.compress(data)
725 return self._compressor.compress(data)
726
726
727 def flush(self):
727 def flush(self):
728 # COMPRESSOBJ_FLUSH_BLOCK flushes all data previously fed into the
728 # COMPRESSOBJ_FLUSH_BLOCK flushes all data previously fed into the
729 # compressor and allows a decompressor to access all encoded data
729 # compressor and allows a decompressor to access all encoded data
730 # up to this point.
730 # up to this point.
731 return self._compressor.flush(self._zstd.COMPRESSOBJ_FLUSH_BLOCK)
731 return self._compressor.flush(self._zstd.COMPRESSOBJ_FLUSH_BLOCK)
732
732
733 def finish(self):
733 def finish(self):
734 res = self._compressor.flush(self._zstd.COMPRESSOBJ_FLUSH_FINISH)
734 res = self._compressor.flush(self._zstd.COMPRESSOBJ_FLUSH_FINISH)
735 self._compressor = None
735 self._compressor = None
736 return res
736 return res
737
737
738 class zstd8mbencoder(zstdbaseencoder):
738 class zstd8mbencoder(zstdbaseencoder):
739 def __init__(self, ui):
739 def __init__(self, ui):
740 super(zstd8mbencoder, self).__init__(3)
740 super(zstd8mbencoder, self).__init__(3)
741
741
742 class zstdbasedecoder(object):
742 class zstdbasedecoder(object):
743 def __init__(self, maxwindowsize):
743 def __init__(self, maxwindowsize):
744 from . import zstd
744 from . import zstd
745 dctx = zstd.ZstdDecompressor(max_window_size=maxwindowsize)
745 dctx = zstd.ZstdDecompressor(max_window_size=maxwindowsize)
746 self._decompressor = dctx.decompressobj()
746 self._decompressor = dctx.decompressobj()
747
747
748 def decode(self, data):
748 def decode(self, data):
749 return self._decompressor.decompress(data)
749 return self._decompressor.decompress(data)
750
750
751 class zstd8mbdecoder(zstdbasedecoder):
751 class zstd8mbdecoder(zstdbasedecoder):
752 def __init__(self, ui, extraobjs):
752 def __init__(self, ui, extraobjs):
753 if extraobjs:
753 if extraobjs:
754 raise error.Abort(_('zstd8mb decoder received unexpected '
754 raise error.Abort(_('zstd8mb decoder received unexpected '
755 'additional values'))
755 'additional values'))
756
756
757 super(zstd8mbdecoder, self).__init__(maxwindowsize=8 * 1048576)
757 super(zstd8mbdecoder, self).__init__(maxwindowsize=8 * 1048576)
758
758
759 # We lazily populate this to avoid excessive module imports when importing
759 # We lazily populate this to avoid excessive module imports when importing
760 # this module.
760 # this module.
761 STREAM_ENCODERS = {}
761 STREAM_ENCODERS = {}
762 STREAM_ENCODERS_ORDER = []
762 STREAM_ENCODERS_ORDER = []
763
763
764 def populatestreamencoders():
764 def populatestreamencoders():
765 if STREAM_ENCODERS:
765 if STREAM_ENCODERS:
766 return
766 return
767
767
768 try:
768 try:
769 from . import zstd
769 from . import zstd
770 zstd.__version__
770 zstd.__version__
771 except ImportError:
771 except ImportError:
772 zstd = None
772 zstd = None
773
773
774 # zstandard is fastest and is preferred.
774 # zstandard is fastest and is preferred.
775 if zstd:
775 if zstd:
776 STREAM_ENCODERS[b'zstd-8mb'] = (zstd8mbencoder, zstd8mbdecoder)
776 STREAM_ENCODERS[b'zstd-8mb'] = (zstd8mbencoder, zstd8mbdecoder)
777 STREAM_ENCODERS_ORDER.append(b'zstd-8mb')
777 STREAM_ENCODERS_ORDER.append(b'zstd-8mb')
778
778
779 STREAM_ENCODERS[b'zlib'] = (zlibencoder, zlibdecoder)
779 STREAM_ENCODERS[b'zlib'] = (zlibencoder, zlibdecoder)
780 STREAM_ENCODERS_ORDER.append(b'zlib')
780 STREAM_ENCODERS_ORDER.append(b'zlib')
781
781
782 STREAM_ENCODERS[b'identity'] = (identityencoder, identitydecoder)
782 STREAM_ENCODERS[b'identity'] = (identityencoder, identitydecoder)
783 STREAM_ENCODERS_ORDER.append(b'identity')
783 STREAM_ENCODERS_ORDER.append(b'identity')
784
784
785 class stream(object):
785 class stream(object):
786 """Represents a logical unidirectional series of frames."""
786 """Represents a logical unidirectional series of frames."""
787
787
788 def __init__(self, streamid, active=False):
788 def __init__(self, streamid, active=False):
789 self.streamid = streamid
789 self.streamid = streamid
790 self._active = active
790 self._active = active
791
791
792 def makeframe(self, requestid, typeid, flags, payload):
792 def makeframe(self, requestid, typeid, flags, payload):
793 """Create a frame to be sent out over this stream.
793 """Create a frame to be sent out over this stream.
794
794
795 Only returns the frame instance. Does not actually send it.
795 Only returns the frame instance. Does not actually send it.
796 """
796 """
797 streamflags = 0
797 streamflags = 0
798 if not self._active:
798 if not self._active:
799 streamflags |= STREAM_FLAG_BEGIN_STREAM
799 streamflags |= STREAM_FLAG_BEGIN_STREAM
800 self._active = True
800 self._active = True
801
801
802 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
802 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
803 payload)
803 payload)
804
804
805 class inputstream(stream):
805 class inputstream(stream):
806 """Represents a stream used for receiving data."""
806 """Represents a stream used for receiving data."""
807
807
808 def __init__(self, streamid, active=False):
808 def __init__(self, streamid, active=False):
809 super(inputstream, self).__init__(streamid, active=active)
809 super(inputstream, self).__init__(streamid, active=active)
810 self._decoder = None
810 self._decoder = None
811
811
812 def setdecoder(self, ui, name, extraobjs):
812 def setdecoder(self, ui, name, extraobjs):
813 """Set the decoder for this stream.
813 """Set the decoder for this stream.
814
814
815 Receives the stream profile name and any additional CBOR objects
815 Receives the stream profile name and any additional CBOR objects
816 decoded from the stream encoding settings frame payloads.
816 decoded from the stream encoding settings frame payloads.
817 """
817 """
818 if name not in STREAM_ENCODERS:
818 if name not in STREAM_ENCODERS:
819 raise error.Abort(_('unknown stream decoder: %s') % name)
819 raise error.Abort(_('unknown stream decoder: %s') % name)
820
820
821 self._decoder = STREAM_ENCODERS[name][1](ui, extraobjs)
821 self._decoder = STREAM_ENCODERS[name][1](ui, extraobjs)
822
822
823 def decode(self, data):
823 def decode(self, data):
824 # Default is identity decoder. We don't bother instantiating one
824 # Default is identity decoder. We don't bother instantiating one
825 # because it is trivial.
825 # because it is trivial.
826 if not self._decoder:
826 if not self._decoder:
827 return data
827 return data
828
828
829 return self._decoder.decode(data)
829 return self._decoder.decode(data)
830
830
831 def flush(self):
831 def flush(self):
832 if not self._decoder:
832 if not self._decoder:
833 return b''
833 return b''
834
834
835 return self._decoder.flush()
835 return self._decoder.flush()
836
836
837 class outputstream(stream):
837 class outputstream(stream):
838 """Represents a stream used for sending data."""
838 """Represents a stream used for sending data."""
839
839
840 def __init__(self, streamid, active=False):
840 def __init__(self, streamid, active=False):
841 super(outputstream, self).__init__(streamid, active=active)
841 super(outputstream, self).__init__(streamid, active=active)
842 self._encoder = None
842 self._encoder = None
843
843
844 def setencoder(self, ui, name):
844 def setencoder(self, ui, name):
845 """Set the encoder for this stream.
845 """Set the encoder for this stream.
846
846
847 Receives the stream profile name.
847 Receives the stream profile name.
848 """
848 """
849 if name not in STREAM_ENCODERS:
849 if name not in STREAM_ENCODERS:
850 raise error.Abort(_('unknown stream encoder: %s') % name)
850 raise error.Abort(_('unknown stream encoder: %s') % name)
851
851
852 self._encoder = STREAM_ENCODERS[name][0](ui)
852 self._encoder = STREAM_ENCODERS[name][0](ui)
853
853
854 def encode(self, data):
854 def encode(self, data):
855 if not self._encoder:
855 if not self._encoder:
856 return data
856 return data
857
857
858 return self._encoder.encode(data)
858 return self._encoder.encode(data)
859
859
860 def flush(self):
860 def flush(self):
861 if not self._encoder:
861 if not self._encoder:
862 return b''
862 return b''
863
863
864 return self._encoder.flush()
864 return self._encoder.flush()
865
865
866 def finish(self):
866 def finish(self):
867 if not self._encoder:
867 if not self._encoder:
868 return b''
868 return b''
869
869
870 self._encoder.finish()
870 self._encoder.finish()
871
871
872 def ensureserverstream(stream):
872 def ensureserverstream(stream):
873 if stream.streamid % 2:
873 if stream.streamid % 2:
874 raise error.ProgrammingError('server should only write to even '
874 raise error.ProgrammingError('server should only write to even '
875 'numbered streams; %d is not even' %
875 'numbered streams; %d is not even' %
876 stream.streamid)
876 stream.streamid)
877
877
878 DEFAULT_PROTOCOL_SETTINGS = {
878 DEFAULT_PROTOCOL_SETTINGS = {
879 'contentencodings': [b'identity'],
879 'contentencodings': [b'identity'],
880 }
880 }
881
881
882 class serverreactor(object):
882 class serverreactor(object):
883 """Holds state of a server handling frame-based protocol requests.
883 """Holds state of a server handling frame-based protocol requests.
884
884
885 This class is the "brain" of the unified frame-based protocol server
885 This class is the "brain" of the unified frame-based protocol server
886 component. While the protocol is stateless from the perspective of
886 component. While the protocol is stateless from the perspective of
887 requests/commands, something needs to track which frames have been
887 requests/commands, something needs to track which frames have been
888 received, what frames to expect, etc. This class is that thing.
888 received, what frames to expect, etc. This class is that thing.
889
889
890 Instances are modeled as a state machine of sorts. Instances are also
890 Instances are modeled as a state machine of sorts. Instances are also
891 reactionary to external events. The point of this class is to encapsulate
891 reactionary to external events. The point of this class is to encapsulate
892 the state of the connection and the exchange of frames, not to perform
892 the state of the connection and the exchange of frames, not to perform
893 work. Instead, callers tell this class when something occurs, like a
893 work. Instead, callers tell this class when something occurs, like a
894 frame arriving. If that activity is worthy of a follow-up action (say
894 frame arriving. If that activity is worthy of a follow-up action (say
895 *run a command*), the return value of that handler will say so.
895 *run a command*), the return value of that handler will say so.
896
896
897 I/O and CPU intensive operations are purposefully delegated outside of
897 I/O and CPU intensive operations are purposefully delegated outside of
898 this class.
898 this class.
899
899
900 Consumers are expected to tell instances when events occur. They do so by
900 Consumers are expected to tell instances when events occur. They do so by
901 calling the various ``on*`` methods. These methods return a 2-tuple
901 calling the various ``on*`` methods. These methods return a 2-tuple
902 describing any follow-up action(s) to take. The first element is the
902 describing any follow-up action(s) to take. The first element is the
903 name of an action to perform. The second is a data structure (usually
903 name of an action to perform. The second is a data structure (usually
904 a dict) specific to that action that contains more information. e.g.
904 a dict) specific to that action that contains more information. e.g.
905 if the server wants to send frames back to the client, the data structure
905 if the server wants to send frames back to the client, the data structure
906 will contain a reference to those frames.
906 will contain a reference to those frames.
907
907
908 Valid actions that consumers can be instructed to take are:
908 Valid actions that consumers can be instructed to take are:
909
909
910 sendframes
910 sendframes
911 Indicates that frames should be sent to the client. The ``framegen``
911 Indicates that frames should be sent to the client. The ``framegen``
912 key contains a generator of frames that should be sent. The server
912 key contains a generator of frames that should be sent. The server
913 assumes that all frames are sent to the client.
913 assumes that all frames are sent to the client.
914
914
915 error
915 error
916 Indicates that an error occurred. Consumer should probably abort.
916 Indicates that an error occurred. Consumer should probably abort.
917
917
918 runcommand
918 runcommand
919 Indicates that the consumer should run a wire protocol command. Details
919 Indicates that the consumer should run a wire protocol command. Details
920 of the command to run are given in the data structure.
920 of the command to run are given in the data structure.
921
921
922 wantframe
922 wantframe
923 Indicates that nothing of interest happened and the server is waiting on
923 Indicates that nothing of interest happened and the server is waiting on
924 more frames from the client before anything interesting can be done.
924 more frames from the client before anything interesting can be done.
925
925
926 noop
926 noop
927 Indicates no additional action is required.
927 Indicates no additional action is required.
928
928
929 Known Issues
929 Known Issues
930 ------------
930 ------------
931
931
932 There are no limits to the number of partially received commands or their
932 There are no limits to the number of partially received commands or their
933 size. A malicious client could stream command request data and exhaust the
933 size. A malicious client could stream command request data and exhaust the
934 server's memory.
934 server's memory.
935
935
936 Partially received commands are not acted upon when end of input is
936 Partially received commands are not acted upon when end of input is
937 reached. Should the server error if it receives a partial request?
937 reached. Should the server error if it receives a partial request?
938 Should the client send a message to abort a partially transmitted request
938 Should the client send a message to abort a partially transmitted request
939 to facilitate graceful shutdown?
939 to facilitate graceful shutdown?
940
940
941 Active requests that haven't been responded to aren't tracked. This means
941 Active requests that haven't been responded to aren't tracked. This means
942 that if we receive a command and instruct its dispatch, another command
942 that if we receive a command and instruct its dispatch, another command
943 with its request ID can come in over the wire and there will be a race
943 with its request ID can come in over the wire and there will be a race
944 between who responds to what.
944 between who responds to what.
945 """
945 """
946
946
947 def __init__(self, ui, deferoutput=False):
947 def __init__(self, ui, deferoutput=False):
948 """Construct a new server reactor.
948 """Construct a new server reactor.
949
949
950 ``deferoutput`` can be used to indicate that no output frames should be
950 ``deferoutput`` can be used to indicate that no output frames should be
951 instructed to be sent until input has been exhausted. In this mode,
951 instructed to be sent until input has been exhausted. In this mode,
952 events that would normally generate output frames (such as a command
952 events that would normally generate output frames (such as a command
953 response being ready) will instead defer instructing the consumer to
953 response being ready) will instead defer instructing the consumer to
954 send those frames. This is useful for half-duplex transports where the
954 send those frames. This is useful for half-duplex transports where the
955 sender cannot receive until all data has been transmitted.
955 sender cannot receive until all data has been transmitted.
956 """
956 """
957 self._ui = ui
957 self._ui = ui
958 self._deferoutput = deferoutput
958 self._deferoutput = deferoutput
959 self._state = 'initial'
959 self._state = 'initial'
960 self._nextoutgoingstreamid = 2
960 self._nextoutgoingstreamid = 2
961 self._bufferedframegens = []
961 self._bufferedframegens = []
962 # stream id -> stream instance for all active streams from the client.
962 # stream id -> stream instance for all active streams from the client.
963 self._incomingstreams = {}
963 self._incomingstreams = {}
964 self._outgoingstreams = {}
964 self._outgoingstreams = {}
965 # request id -> dict of commands that are actively being received.
965 # request id -> dict of commands that are actively being received.
966 self._receivingcommands = {}
966 self._receivingcommands = {}
967 # Request IDs that have been received and are actively being processed.
967 # Request IDs that have been received and are actively being processed.
968 # Once all output for a request has been sent, it is removed from this
968 # Once all output for a request has been sent, it is removed from this
969 # set.
969 # set.
970 self._activecommands = set()
970 self._activecommands = set()
971
971
972 self._protocolsettingsdecoder = None
972 self._protocolsettingsdecoder = None
973
973
974 # Sender protocol settings are optional. Set implied default values.
974 # Sender protocol settings are optional. Set implied default values.
975 self._sendersettings = dict(DEFAULT_PROTOCOL_SETTINGS)
975 self._sendersettings = dict(DEFAULT_PROTOCOL_SETTINGS)
976
976
977 populatestreamencoders()
977 populatestreamencoders()
978
978
979 def onframerecv(self, frame):
979 def onframerecv(self, frame):
980 """Process a frame that has been received off the wire.
980 """Process a frame that has been received off the wire.
981
981
982 Returns a dict with an ``action`` key that details what action,
982 Returns a dict with an ``action`` key that details what action,
983 if any, the consumer should take next.
983 if any, the consumer should take next.
984 """
984 """
985 if not frame.streamid % 2:
985 if not frame.streamid % 2:
986 self._state = 'errored'
986 self._state = 'errored'
987 return self._makeerrorresult(
987 return self._makeerrorresult(
988 _('received frame with even numbered stream ID: %d') %
988 _('received frame with even numbered stream ID: %d') %
989 frame.streamid)
989 frame.streamid)
990
990
991 if frame.streamid not in self._incomingstreams:
991 if frame.streamid not in self._incomingstreams:
992 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
992 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
993 self._state = 'errored'
993 self._state = 'errored'
994 return self._makeerrorresult(
994 return self._makeerrorresult(
995 _('received frame on unknown inactive stream without '
995 _('received frame on unknown inactive stream without '
996 'beginning of stream flag set'))
996 'beginning of stream flag set'))
997
997
998 self._incomingstreams[frame.streamid] = inputstream(frame.streamid)
998 self._incomingstreams[frame.streamid] = inputstream(frame.streamid)
999
999
1000 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1000 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1001 # TODO handle decoding frames
1001 # TODO handle decoding frames
1002 self._state = 'errored'
1002 self._state = 'errored'
1003 raise error.ProgrammingError('support for decoding stream payloads '
1003 raise error.ProgrammingError('support for decoding stream payloads '
1004 'not yet implemented')
1004 'not yet implemented')
1005
1005
1006 if frame.streamflags & STREAM_FLAG_END_STREAM:
1006 if frame.streamflags & STREAM_FLAG_END_STREAM:
1007 del self._incomingstreams[frame.streamid]
1007 del self._incomingstreams[frame.streamid]
1008
1008
1009 handlers = {
1009 handlers = {
1010 'initial': self._onframeinitial,
1010 'initial': self._onframeinitial,
1011 'protocol-settings-receiving': self._onframeprotocolsettings,
1011 'protocol-settings-receiving': self._onframeprotocolsettings,
1012 'idle': self._onframeidle,
1012 'idle': self._onframeidle,
1013 'command-receiving': self._onframecommandreceiving,
1013 'command-receiving': self._onframecommandreceiving,
1014 'errored': self._onframeerrored,
1014 'errored': self._onframeerrored,
1015 }
1015 }
1016
1016
1017 meth = handlers.get(self._state)
1017 meth = handlers.get(self._state)
1018 if not meth:
1018 if not meth:
1019 raise error.ProgrammingError('unhandled state: %s' % self._state)
1019 raise error.ProgrammingError('unhandled state: %s' % self._state)
1020
1020
1021 return meth(frame)
1021 return meth(frame)
1022
1022
1023 def oncommandresponseready(self, stream, requestid, data):
1023 def oncommandresponseready(self, stream, requestid, data):
1024 """Signal that a bytes response is ready to be sent to the client.
1024 """Signal that a bytes response is ready to be sent to the client.
1025
1025
1026 The raw bytes response is passed as an argument.
1026 The raw bytes response is passed as an argument.
1027 """
1027 """
1028 ensureserverstream(stream)
1028 ensureserverstream(stream)
1029
1029
1030 def sendframes():
1030 def sendframes():
1031 for frame in createcommandresponseframesfrombytes(stream, requestid,
1031 for frame in createcommandresponseframesfrombytes(stream, requestid,
1032 data):
1032 data):
1033 yield frame
1033 yield frame
1034
1034
1035 self._activecommands.remove(requestid)
1035 self._activecommands.remove(requestid)
1036
1036
1037 result = sendframes()
1037 result = sendframes()
1038
1038
1039 if self._deferoutput:
1039 if self._deferoutput:
1040 self._bufferedframegens.append(result)
1040 self._bufferedframegens.append(result)
1041 return 'noop', {}
1041 return 'noop', {}
1042 else:
1042 else:
1043 return 'sendframes', {
1043 return 'sendframes', {
1044 'framegen': result,
1044 'framegen': result,
1045 }
1045 }
1046
1046
1047 def oncommandresponsereadyobjects(self, stream, requestid, objs):
1047 def oncommandresponsereadyobjects(self, stream, requestid, objs):
1048 """Signal that objects are ready to be sent to the client.
1048 """Signal that objects are ready to be sent to the client.
1049
1049
1050 ``objs`` is an iterable of objects (typically a generator) that will
1050 ``objs`` is an iterable of objects (typically a generator) that will
1051 be encoded via CBOR and added to frames, which will be sent to the
1051 be encoded via CBOR and added to frames, which will be sent to the
1052 client.
1052 client.
1053 """
1053 """
1054 ensureserverstream(stream)
1054 ensureserverstream(stream)
1055
1055
1056 # We need to take care over exception handling. Uncaught exceptions
1056 # We need to take care over exception handling. Uncaught exceptions
1057 # when generating frames could lead to premature end of the frame
1057 # when generating frames could lead to premature end of the frame
1058 # stream and the possibility of the server or client process getting
1058 # stream and the possibility of the server or client process getting
1059 # in a bad state.
1059 # in a bad state.
1060 #
1060 #
1061 # Keep in mind that if ``objs`` is a generator, advancing it could
1061 # Keep in mind that if ``objs`` is a generator, advancing it could
1062 # raise exceptions that originated in e.g. wire protocol command
1062 # raise exceptions that originated in e.g. wire protocol command
1063 # functions. That is why we differentiate between exceptions raised
1063 # functions. That is why we differentiate between exceptions raised
1064 # when iterating versus other exceptions that occur.
1064 # when iterating versus other exceptions that occur.
1065 #
1065 #
1066 # In all cases, when the function finishes, the request is fully
1066 # In all cases, when the function finishes, the request is fully
1067 # handled and no new frames for it should be seen.
1067 # handled and no new frames for it should be seen.
1068
1068
1069 def sendframes():
1069 def sendframes():
1070 emitted = False
1070 emitted = False
1071 alternatelocationsent = False
1071 alternatelocationsent = False
1072 emitter = bufferingcommandresponseemitter(stream, requestid)
1072 emitter = bufferingcommandresponseemitter(stream, requestid)
1073 while True:
1073 while True:
1074 try:
1074 try:
1075 o = next(objs)
1075 o = next(objs)
1076 except StopIteration:
1076 except StopIteration:
1077 for frame in emitter.send(None):
1077 for frame in emitter.send(None):
1078 yield frame
1078 yield frame
1079
1079
1080 if emitted:
1080 if emitted:
1081 yield createcommandresponseeosframe(stream, requestid)
1081 yield createcommandresponseeosframe(stream, requestid)
1082 break
1082 break
1083
1083
1084 except error.WireprotoCommandError as e:
1084 except error.WireprotoCommandError as e:
1085 for frame in createcommanderrorresponse(
1085 for frame in createcommanderrorresponse(
1086 stream, requestid, e.message, e.messageargs):
1086 stream, requestid, e.message, e.messageargs):
1087 yield frame
1087 yield frame
1088 break
1088 break
1089
1089
1090 except Exception as e:
1090 except Exception as e:
1091 for frame in createerrorframe(
1091 for frame in createerrorframe(
1092 stream, requestid, '%s' % stringutil.forcebytestr(e),
1092 stream, requestid, '%s' % stringutil.forcebytestr(e),
1093 errtype='server'):
1093 errtype='server'):
1094
1094
1095 yield frame
1095 yield frame
1096
1096
1097 break
1097 break
1098
1098
1099 try:
1099 try:
1100 # Alternate location responses can only be the first and
1100 # Alternate location responses can only be the first and
1101 # only object in the output stream.
1101 # only object in the output stream.
1102 if isinstance(o, wireprototypes.alternatelocationresponse):
1102 if isinstance(o, wireprototypes.alternatelocationresponse):
1103 if emitted:
1103 if emitted:
1104 raise error.ProgrammingError(
1104 raise error.ProgrammingError(
1105 'alternatelocationresponse seen after initial '
1105 'alternatelocationresponse seen after initial '
1106 'output object')
1106 'output object')
1107
1107
1108 yield createalternatelocationresponseframe(
1108 yield createalternatelocationresponseframe(
1109 stream, requestid, o)
1109 stream, requestid, o)
1110
1110
1111 alternatelocationsent = True
1111 alternatelocationsent = True
1112 emitted = True
1112 emitted = True
1113 continue
1113 continue
1114
1114
1115 if alternatelocationsent:
1115 if alternatelocationsent:
1116 raise error.ProgrammingError(
1116 raise error.ProgrammingError(
1117 'object follows alternatelocationresponse')
1117 'object follows alternatelocationresponse')
1118
1118
1119 if not emitted:
1119 if not emitted:
1120 yield createcommandresponseokframe(stream, requestid)
1120 yield createcommandresponseokframe(stream, requestid)
1121 emitted = True
1121 emitted = True
1122
1122
1123 # Objects emitted by command functions can be serializable
1123 # Objects emitted by command functions can be serializable
1124 # data structures or special types.
1124 # data structures or special types.
1125 # TODO consider extracting the content normalization to a
1125 # TODO consider extracting the content normalization to a
1126 # standalone function, as it may be useful for e.g. cachers.
1126 # standalone function, as it may be useful for e.g. cachers.
1127
1127
1128 # A pre-encoded object is sent directly to the emitter.
1128 # A pre-encoded object is sent directly to the emitter.
1129 if isinstance(o, wireprototypes.encodedresponse):
1129 if isinstance(o, wireprototypes.encodedresponse):
1130 for frame in emitter.send(o.data):
1130 for frame in emitter.send(o.data):
1131 yield frame
1131 yield frame
1132
1132
1133 # A regular object is CBOR encoded.
1133 # A regular object is CBOR encoded.
1134 else:
1134 else:
1135 for chunk in cborutil.streamencode(o):
1135 for chunk in cborutil.streamencode(o):
1136 for frame in emitter.send(chunk):
1136 for frame in emitter.send(chunk):
1137 yield frame
1137 yield frame
1138
1138
1139 except Exception as e:
1139 except Exception as e:
1140 for frame in createerrorframe(stream, requestid,
1140 for frame in createerrorframe(stream, requestid,
1141 '%s' % e,
1141 '%s' % e,
1142 errtype='server'):
1142 errtype='server'):
1143 yield frame
1143 yield frame
1144
1144
1145 break
1145 break
1146
1146
1147 self._activecommands.remove(requestid)
1147 self._activecommands.remove(requestid)
1148
1148
1149 return self._handlesendframes(sendframes())
1149 return self._handlesendframes(sendframes())
1150
1150
1151 def oninputeof(self):
1151 def oninputeof(self):
1152 """Signals that end of input has been received.
1152 """Signals that end of input has been received.
1153
1153
1154 No more frames will be received. All pending activity should be
1154 No more frames will be received. All pending activity should be
1155 completed.
1155 completed.
1156 """
1156 """
1157 # TODO should we do anything about in-flight commands?
1157 # TODO should we do anything about in-flight commands?
1158
1158
1159 if not self._deferoutput or not self._bufferedframegens:
1159 if not self._deferoutput or not self._bufferedframegens:
1160 return 'noop', {}
1160 return 'noop', {}
1161
1161
1162 # If we buffered all our responses, emit those.
1162 # If we buffered all our responses, emit those.
1163 def makegen():
1163 def makegen():
1164 for gen in self._bufferedframegens:
1164 for gen in self._bufferedframegens:
1165 for frame in gen:
1165 for frame in gen:
1166 yield frame
1166 yield frame
1167
1167
1168 return 'sendframes', {
1168 return 'sendframes', {
1169 'framegen': makegen(),
1169 'framegen': makegen(),
1170 }
1170 }
1171
1171
1172 def _handlesendframes(self, framegen):
1172 def _handlesendframes(self, framegen):
1173 if self._deferoutput:
1173 if self._deferoutput:
1174 self._bufferedframegens.append(framegen)
1174 self._bufferedframegens.append(framegen)
1175 return 'noop', {}
1175 return 'noop', {}
1176 else:
1176 else:
1177 return 'sendframes', {
1177 return 'sendframes', {
1178 'framegen': framegen,
1178 'framegen': framegen,
1179 }
1179 }
1180
1180
1181 def onservererror(self, stream, requestid, msg):
1181 def onservererror(self, stream, requestid, msg):
1182 ensureserverstream(stream)
1182 ensureserverstream(stream)
1183
1183
1184 def sendframes():
1184 def sendframes():
1185 for frame in createerrorframe(stream, requestid, msg,
1185 for frame in createerrorframe(stream, requestid, msg,
1186 errtype='server'):
1186 errtype='server'):
1187 yield frame
1187 yield frame
1188
1188
1189 self._activecommands.remove(requestid)
1189 self._activecommands.remove(requestid)
1190
1190
1191 return self._handlesendframes(sendframes())
1191 return self._handlesendframes(sendframes())
1192
1192
1193 def oncommanderror(self, stream, requestid, message, args=None):
1193 def oncommanderror(self, stream, requestid, message, args=None):
1194 """Called when a command encountered an error before sending output."""
1194 """Called when a command encountered an error before sending output."""
1195 ensureserverstream(stream)
1195 ensureserverstream(stream)
1196
1196
1197 def sendframes():
1197 def sendframes():
1198 for frame in createcommanderrorresponse(stream, requestid, message,
1198 for frame in createcommanderrorresponse(stream, requestid, message,
1199 args):
1199 args):
1200 yield frame
1200 yield frame
1201
1201
1202 self._activecommands.remove(requestid)
1202 self._activecommands.remove(requestid)
1203
1203
1204 return self._handlesendframes(sendframes())
1204 return self._handlesendframes(sendframes())
1205
1205
1206 def makeoutputstream(self):
1206 def makeoutputstream(self):
1207 """Create a stream to be used for sending data to the client."""
1207 """Create a stream to be used for sending data to the client."""
1208 streamid = self._nextoutgoingstreamid
1208 streamid = self._nextoutgoingstreamid
1209 self._nextoutgoingstreamid += 2
1209 self._nextoutgoingstreamid += 2
1210
1210
1211 s = outputstream(streamid)
1211 s = outputstream(streamid)
1212 self._outgoingstreams[streamid] = s
1212 self._outgoingstreams[streamid] = s
1213
1213
1214 return s
1214 return s
1215
1215
1216 def _makeerrorresult(self, msg):
1216 def _makeerrorresult(self, msg):
1217 return 'error', {
1217 return 'error', {
1218 'message': msg,
1218 'message': msg,
1219 }
1219 }
1220
1220
1221 def _makeruncommandresult(self, requestid):
1221 def _makeruncommandresult(self, requestid):
1222 entry = self._receivingcommands[requestid]
1222 entry = self._receivingcommands[requestid]
1223
1223
1224 if not entry['requestdone']:
1224 if not entry['requestdone']:
1225 self._state = 'errored'
1225 self._state = 'errored'
1226 raise error.ProgrammingError('should not be called without '
1226 raise error.ProgrammingError('should not be called without '
1227 'requestdone set')
1227 'requestdone set')
1228
1228
1229 del self._receivingcommands[requestid]
1229 del self._receivingcommands[requestid]
1230
1230
1231 if self._receivingcommands:
1231 if self._receivingcommands:
1232 self._state = 'command-receiving'
1232 self._state = 'command-receiving'
1233 else:
1233 else:
1234 self._state = 'idle'
1234 self._state = 'idle'
1235
1235
1236 # Decode the payloads as CBOR.
1236 # Decode the payloads as CBOR.
1237 entry['payload'].seek(0)
1237 entry['payload'].seek(0)
1238 request = cborutil.decodeall(entry['payload'].getvalue())[0]
1238 request = cborutil.decodeall(entry['payload'].getvalue())[0]
1239
1239
1240 if b'name' not in request:
1240 if b'name' not in request:
1241 self._state = 'errored'
1241 self._state = 'errored'
1242 return self._makeerrorresult(
1242 return self._makeerrorresult(
1243 _('command request missing "name" field'))
1243 _('command request missing "name" field'))
1244
1244
1245 if b'args' not in request:
1245 if b'args' not in request:
1246 request[b'args'] = {}
1246 request[b'args'] = {}
1247
1247
1248 assert requestid not in self._activecommands
1248 assert requestid not in self._activecommands
1249 self._activecommands.add(requestid)
1249 self._activecommands.add(requestid)
1250
1250
1251 return 'runcommand', {
1251 return 'runcommand', {
1252 'requestid': requestid,
1252 'requestid': requestid,
1253 'command': request[b'name'],
1253 'command': request[b'name'],
1254 'args': request[b'args'],
1254 'args': request[b'args'],
1255 'redirect': request.get(b'redirect'),
1255 'redirect': request.get(b'redirect'),
1256 'data': entry['data'].getvalue() if entry['data'] else None,
1256 'data': entry['data'].getvalue() if entry['data'] else None,
1257 }
1257 }
1258
1258
1259 def _makewantframeresult(self):
1259 def _makewantframeresult(self):
1260 return 'wantframe', {
1260 return 'wantframe', {
1261 'state': self._state,
1261 'state': self._state,
1262 }
1262 }
1263
1263
1264 def _validatecommandrequestframe(self, frame):
1264 def _validatecommandrequestframe(self, frame):
1265 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
1265 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
1266 continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
1266 continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
1267
1267
1268 if new and continuation:
1268 if new and continuation:
1269 self._state = 'errored'
1269 self._state = 'errored'
1270 return self._makeerrorresult(
1270 return self._makeerrorresult(
1271 _('received command request frame with both new and '
1271 _('received command request frame with both new and '
1272 'continuation flags set'))
1272 'continuation flags set'))
1273
1273
1274 if not new and not continuation:
1274 if not new and not continuation:
1275 self._state = 'errored'
1275 self._state = 'errored'
1276 return self._makeerrorresult(
1276 return self._makeerrorresult(
1277 _('received command request frame with neither new nor '
1277 _('received command request frame with neither new nor '
1278 'continuation flags set'))
1278 'continuation flags set'))
1279
1279
1280 def _onframeinitial(self, frame):
1280 def _onframeinitial(self, frame):
1281 # Called when we receive a frame when in the "initial" state.
1281 # Called when we receive a frame when in the "initial" state.
1282 if frame.typeid == FRAME_TYPE_SENDER_PROTOCOL_SETTINGS:
1282 if frame.typeid == FRAME_TYPE_SENDER_PROTOCOL_SETTINGS:
1283 self._state = 'protocol-settings-receiving'
1283 self._state = 'protocol-settings-receiving'
1284 self._protocolsettingsdecoder = cborutil.bufferingdecoder()
1284 self._protocolsettingsdecoder = cborutil.bufferingdecoder()
1285 return self._onframeprotocolsettings(frame)
1285 return self._onframeprotocolsettings(frame)
1286
1286
1287 elif frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1287 elif frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1288 self._state = 'idle'
1288 self._state = 'idle'
1289 return self._onframeidle(frame)
1289 return self._onframeidle(frame)
1290
1290
1291 else:
1291 else:
1292 self._state = 'errored'
1292 self._state = 'errored'
1293 return self._makeerrorresult(
1293 return self._makeerrorresult(
1294 _('expected sender protocol settings or command request '
1294 _('expected sender protocol settings or command request '
1295 'frame; got %d') % frame.typeid)
1295 'frame; got %d') % frame.typeid)
1296
1296
1297 def _onframeprotocolsettings(self, frame):
1297 def _onframeprotocolsettings(self, frame):
1298 assert self._state == 'protocol-settings-receiving'
1298 assert self._state == 'protocol-settings-receiving'
1299 assert self._protocolsettingsdecoder is not None
1299 assert self._protocolsettingsdecoder is not None
1300
1300
1301 if frame.typeid != FRAME_TYPE_SENDER_PROTOCOL_SETTINGS:
1301 if frame.typeid != FRAME_TYPE_SENDER_PROTOCOL_SETTINGS:
1302 self._state = 'errored'
1302 self._state = 'errored'
1303 return self._makeerrorresult(
1303 return self._makeerrorresult(
1304 _('expected sender protocol settings frame; got %d') %
1304 _('expected sender protocol settings frame; got %d') %
1305 frame.typeid)
1305 frame.typeid)
1306
1306
1307 more = frame.flags & FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION
1307 more = frame.flags & FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION
1308 eos = frame.flags & FLAG_SENDER_PROTOCOL_SETTINGS_EOS
1308 eos = frame.flags & FLAG_SENDER_PROTOCOL_SETTINGS_EOS
1309
1309
1310 if more and eos:
1310 if more and eos:
1311 self._state = 'errored'
1311 self._state = 'errored'
1312 return self._makeerrorresult(
1312 return self._makeerrorresult(
1313 _('sender protocol settings frame cannot have both '
1313 _('sender protocol settings frame cannot have both '
1314 'continuation and end of stream flags set'))
1314 'continuation and end of stream flags set'))
1315
1315
1316 if not more and not eos:
1316 if not more and not eos:
1317 self._state = 'errored'
1317 self._state = 'errored'
1318 return self._makeerrorresult(
1318 return self._makeerrorresult(
1319 _('sender protocol settings frame must have continuation or '
1319 _('sender protocol settings frame must have continuation or '
1320 'end of stream flag set'))
1320 'end of stream flag set'))
1321
1321
1322 # TODO establish limits for maximum amount of data that can be
1322 # TODO establish limits for maximum amount of data that can be
1323 # buffered.
1323 # buffered.
1324 try:
1324 try:
1325 self._protocolsettingsdecoder.decode(frame.payload)
1325 self._protocolsettingsdecoder.decode(frame.payload)
1326 except Exception as e:
1326 except Exception as e:
1327 self._state = 'errored'
1327 self._state = 'errored'
1328 return self._makeerrorresult(
1328 return self._makeerrorresult(
1329 _('error decoding CBOR from sender protocol settings frame: %s')
1329 _('error decoding CBOR from sender protocol settings frame: %s')
1330 % stringutil.forcebytestr(e))
1330 % stringutil.forcebytestr(e))
1331
1331
1332 if more:
1332 if more:
1333 return self._makewantframeresult()
1333 return self._makewantframeresult()
1334
1334
1335 assert eos
1335 assert eos
1336
1336
1337 decoded = self._protocolsettingsdecoder.getavailable()
1337 decoded = self._protocolsettingsdecoder.getavailable()
1338 self._protocolsettingsdecoder = None
1338 self._protocolsettingsdecoder = None
1339
1339
1340 if not decoded:
1340 if not decoded:
1341 self._state = 'errored'
1341 self._state = 'errored'
1342 return self._makeerrorresult(
1342 return self._makeerrorresult(
1343 _('sender protocol settings frame did not contain CBOR data'))
1343 _('sender protocol settings frame did not contain CBOR data'))
1344 elif len(decoded) > 1:
1344 elif len(decoded) > 1:
1345 self._state = 'errored'
1345 self._state = 'errored'
1346 return self._makeerrorresult(
1346 return self._makeerrorresult(
1347 _('sender protocol settings frame contained multiple CBOR '
1347 _('sender protocol settings frame contained multiple CBOR '
1348 'values'))
1348 'values'))
1349
1349
1350 d = decoded[0]
1350 d = decoded[0]
1351
1351
1352 if b'contentencodings' in d:
1352 if b'contentencodings' in d:
1353 self._sendersettings['contentencodings'] = d[b'contentencodings']
1353 self._sendersettings['contentencodings'] = d[b'contentencodings']
1354
1354
1355 self._state = 'idle'
1355 self._state = 'idle'
1356
1356
1357 return self._makewantframeresult()
1357 return self._makewantframeresult()
1358
1358
1359 def _onframeidle(self, frame):
1359 def _onframeidle(self, frame):
1360 # The only frame type that should be received in this state is a
1360 # The only frame type that should be received in this state is a
1361 # command request.
1361 # command request.
1362 if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
1362 if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
1363 self._state = 'errored'
1363 self._state = 'errored'
1364 return self._makeerrorresult(
1364 return self._makeerrorresult(
1365 _('expected command request frame; got %d') % frame.typeid)
1365 _('expected command request frame; got %d') % frame.typeid)
1366
1366
1367 res = self._validatecommandrequestframe(frame)
1367 res = self._validatecommandrequestframe(frame)
1368 if res:
1368 if res:
1369 return res
1369 return res
1370
1370
1371 if frame.requestid in self._receivingcommands:
1371 if frame.requestid in self._receivingcommands:
1372 self._state = 'errored'
1372 self._state = 'errored'
1373 return self._makeerrorresult(
1373 return self._makeerrorresult(
1374 _('request with ID %d already received') % frame.requestid)
1374 _('request with ID %d already received') % frame.requestid)
1375
1375
1376 if frame.requestid in self._activecommands:
1376 if frame.requestid in self._activecommands:
1377 self._state = 'errored'
1377 self._state = 'errored'
1378 return self._makeerrorresult(
1378 return self._makeerrorresult(
1379 _('request with ID %d is already active') % frame.requestid)
1379 _('request with ID %d is already active') % frame.requestid)
1380
1380
1381 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
1381 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
1382 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
1382 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
1383 expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
1383 expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
1384
1384
1385 if not new:
1385 if not new:
1386 self._state = 'errored'
1386 self._state = 'errored'
1387 return self._makeerrorresult(
1387 return self._makeerrorresult(
1388 _('received command request frame without new flag set'))
1388 _('received command request frame without new flag set'))
1389
1389
1390 payload = util.bytesio()
1390 payload = util.bytesio()
1391 payload.write(frame.payload)
1391 payload.write(frame.payload)
1392
1392
1393 self._receivingcommands[frame.requestid] = {
1393 self._receivingcommands[frame.requestid] = {
1394 'payload': payload,
1394 'payload': payload,
1395 'data': None,
1395 'data': None,
1396 'requestdone': not moreframes,
1396 'requestdone': not moreframes,
1397 'expectingdata': bool(expectingdata),
1397 'expectingdata': bool(expectingdata),
1398 }
1398 }
1399
1399
1400 # This is the final frame for this request. Dispatch it.
1400 # This is the final frame for this request. Dispatch it.
1401 if not moreframes and not expectingdata:
1401 if not moreframes and not expectingdata:
1402 return self._makeruncommandresult(frame.requestid)
1402 return self._makeruncommandresult(frame.requestid)
1403
1403
1404 assert moreframes or expectingdata
1404 assert moreframes or expectingdata
1405 self._state = 'command-receiving'
1405 self._state = 'command-receiving'
1406 return self._makewantframeresult()
1406 return self._makewantframeresult()
1407
1407
1408 def _onframecommandreceiving(self, frame):
1408 def _onframecommandreceiving(self, frame):
1409 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1409 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1410 # Process new command requests as such.
1410 # Process new command requests as such.
1411 if frame.flags & FLAG_COMMAND_REQUEST_NEW:
1411 if frame.flags & FLAG_COMMAND_REQUEST_NEW:
1412 return self._onframeidle(frame)
1412 return self._onframeidle(frame)
1413
1413
1414 res = self._validatecommandrequestframe(frame)
1414 res = self._validatecommandrequestframe(frame)
1415 if res:
1415 if res:
1416 return res
1416 return res
1417
1417
1418 # All other frames should be related to a command that is currently
1418 # All other frames should be related to a command that is currently
1419 # receiving but is not active.
1419 # receiving but is not active.
1420 if frame.requestid in self._activecommands:
1420 if frame.requestid in self._activecommands:
1421 self._state = 'errored'
1421 self._state = 'errored'
1422 return self._makeerrorresult(
1422 return self._makeerrorresult(
1423 _('received frame for request that is still active: %d') %
1423 _('received frame for request that is still active: %d') %
1424 frame.requestid)
1424 frame.requestid)
1425
1425
1426 if frame.requestid not in self._receivingcommands:
1426 if frame.requestid not in self._receivingcommands:
1427 self._state = 'errored'
1427 self._state = 'errored'
1428 return self._makeerrorresult(
1428 return self._makeerrorresult(
1429 _('received frame for request that is not receiving: %d') %
1429 _('received frame for request that is not receiving: %d') %
1430 frame.requestid)
1430 frame.requestid)
1431
1431
1432 entry = self._receivingcommands[frame.requestid]
1432 entry = self._receivingcommands[frame.requestid]
1433
1433
1434 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1434 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1435 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
1435 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
1436 expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
1436 expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
1437
1437
1438 if entry['requestdone']:
1438 if entry['requestdone']:
1439 self._state = 'errored'
1439 self._state = 'errored'
1440 return self._makeerrorresult(
1440 return self._makeerrorresult(
1441 _('received command request frame when request frames '
1441 _('received command request frame when request frames '
1442 'were supposedly done'))
1442 'were supposedly done'))
1443
1443
1444 if expectingdata != entry['expectingdata']:
1444 if expectingdata != entry['expectingdata']:
1445 self._state = 'errored'
1445 self._state = 'errored'
1446 return self._makeerrorresult(
1446 return self._makeerrorresult(
1447 _('mismatch between expect data flag and previous frame'))
1447 _('mismatch between expect data flag and previous frame'))
1448
1448
1449 entry['payload'].write(frame.payload)
1449 entry['payload'].write(frame.payload)
1450
1450
1451 if not moreframes:
1451 if not moreframes:
1452 entry['requestdone'] = True
1452 entry['requestdone'] = True
1453
1453
1454 if not moreframes and not expectingdata:
1454 if not moreframes and not expectingdata:
1455 return self._makeruncommandresult(frame.requestid)
1455 return self._makeruncommandresult(frame.requestid)
1456
1456
1457 return self._makewantframeresult()
1457 return self._makewantframeresult()
1458
1458
1459 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
1459 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
1460 if not entry['expectingdata']:
1460 if not entry['expectingdata']:
1461 self._state = 'errored'
1461 self._state = 'errored'
1462 return self._makeerrorresult(_(
1462 return self._makeerrorresult(_(
1463 'received command data frame for request that is not '
1463 'received command data frame for request that is not '
1464 'expecting data: %d') % frame.requestid)
1464 'expecting data: %d') % frame.requestid)
1465
1465
1466 if entry['data'] is None:
1466 if entry['data'] is None:
1467 entry['data'] = util.bytesio()
1467 entry['data'] = util.bytesio()
1468
1468
1469 return self._handlecommanddataframe(frame, entry)
1469 return self._handlecommanddataframe(frame, entry)
1470 else:
1470 else:
1471 self._state = 'errored'
1471 self._state = 'errored'
1472 return self._makeerrorresult(_(
1472 return self._makeerrorresult(_(
1473 'received unexpected frame type: %d') % frame.typeid)
1473 'received unexpected frame type: %d') % frame.typeid)
1474
1474
1475 def _handlecommanddataframe(self, frame, entry):
1475 def _handlecommanddataframe(self, frame, entry):
1476 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
1476 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
1477
1477
1478 # TODO support streaming data instead of buffering it.
1478 # TODO support streaming data instead of buffering it.
1479 entry['data'].write(frame.payload)
1479 entry['data'].write(frame.payload)
1480
1480
1481 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
1481 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
1482 return self._makewantframeresult()
1482 return self._makewantframeresult()
1483 elif frame.flags & FLAG_COMMAND_DATA_EOS:
1483 elif frame.flags & FLAG_COMMAND_DATA_EOS:
1484 entry['data'].seek(0)
1484 entry['data'].seek(0)
1485 return self._makeruncommandresult(frame.requestid)
1485 return self._makeruncommandresult(frame.requestid)
1486 else:
1486 else:
1487 self._state = 'errored'
1487 self._state = 'errored'
1488 return self._makeerrorresult(_('command data frame without '
1488 return self._makeerrorresult(_('command data frame without '
1489 'flags'))
1489 'flags'))
1490
1490
1491 def _onframeerrored(self, frame):
1491 def _onframeerrored(self, frame):
1492 return self._makeerrorresult(_('server already errored'))
1492 return self._makeerrorresult(_('server already errored'))
1493
1493
1494 class commandrequest(object):
1494 class commandrequest(object):
1495 """Represents a request to run a command."""
1495 """Represents a request to run a command."""
1496
1496
1497 def __init__(self, requestid, name, args, datafh=None, redirect=None):
1497 def __init__(self, requestid, name, args, datafh=None, redirect=None):
1498 self.requestid = requestid
1498 self.requestid = requestid
1499 self.name = name
1499 self.name = name
1500 self.args = args
1500 self.args = args
1501 self.datafh = datafh
1501 self.datafh = datafh
1502 self.redirect = redirect
1502 self.redirect = redirect
1503 self.state = 'pending'
1503 self.state = 'pending'
1504
1504
1505 class clientreactor(object):
1505 class clientreactor(object):
1506 """Holds state of a client issuing frame-based protocol requests.
1506 """Holds state of a client issuing frame-based protocol requests.
1507
1507
1508 This is like ``serverreactor`` but for client-side state.
1508 This is like ``serverreactor`` but for client-side state.
1509
1509
1510 Each instance is bound to the lifetime of a connection. For persistent
1510 Each instance is bound to the lifetime of a connection. For persistent
1511 connection transports using e.g. TCP sockets and speaking the raw
1511 connection transports using e.g. TCP sockets and speaking the raw
1512 framing protocol, there will be a single instance for the lifetime of
1512 framing protocol, there will be a single instance for the lifetime of
1513 the TCP socket. For transports where there are multiple discrete
1513 the TCP socket. For transports where there are multiple discrete
1514 interactions (say tunneled within in HTTP request), there will be a
1514 interactions (say tunneled within in HTTP request), there will be a
1515 separate instance for each distinct interaction.
1515 separate instance for each distinct interaction.
1516
1516
1517 Consumers are expected to tell instances when events occur by calling
1517 Consumers are expected to tell instances when events occur by calling
1518 various methods. These methods return a 2-tuple describing any follow-up
1518 various methods. These methods return a 2-tuple describing any follow-up
1519 action(s) to take. The first element is the name of an action to
1519 action(s) to take. The first element is the name of an action to
1520 perform. The second is a data structure (usually a dict) specific to
1520 perform. The second is a data structure (usually a dict) specific to
1521 that action that contains more information. e.g. if the reactor wants
1521 that action that contains more information. e.g. if the reactor wants
1522 to send frames to the server, the data structure will contain a reference
1522 to send frames to the server, the data structure will contain a reference
1523 to those frames.
1523 to those frames.
1524
1524
1525 Valid actions that consumers can be instructed to take are:
1525 Valid actions that consumers can be instructed to take are:
1526
1526
1527 noop
1527 noop
1528 Indicates no additional action is required.
1528 Indicates no additional action is required.
1529
1529
1530 sendframes
1530 sendframes
1531 Indicates that frames should be sent to the server. The ``framegen``
1531 Indicates that frames should be sent to the server. The ``framegen``
1532 key contains a generator of frames that should be sent. The reactor
1532 key contains a generator of frames that should be sent. The reactor
1533 assumes that all frames in this generator are sent to the server.
1533 assumes that all frames in this generator are sent to the server.
1534
1534
1535 error
1535 error
1536 Indicates that an error occurred. The ``message`` key contains an
1536 Indicates that an error occurred. The ``message`` key contains an
1537 error message describing the failure.
1537 error message describing the failure.
1538
1538
1539 responsedata
1539 responsedata
1540 Indicates a response to a previously-issued command was received.
1540 Indicates a response to a previously-issued command was received.
1541
1541
1542 The ``request`` key contains the ``commandrequest`` instance that
1542 The ``request`` key contains the ``commandrequest`` instance that
1543 represents the request this data is for.
1543 represents the request this data is for.
1544
1544
1545 The ``data`` key contains the decoded data from the server.
1545 The ``data`` key contains the decoded data from the server.
1546
1546
1547 ``expectmore`` and ``eos`` evaluate to True when more response data
1547 ``expectmore`` and ``eos`` evaluate to True when more response data
1548 is expected to follow or we're at the end of the response stream,
1548 is expected to follow or we're at the end of the response stream,
1549 respectively.
1549 respectively.
1550 """
1550 """
1551 def __init__(self, ui, hasmultiplesend=False, buffersends=True):
1551 def __init__(self, ui, hasmultiplesend=False, buffersends=True,
1552 clientcontentencoders=None):
1552 """Create a new instance.
1553 """Create a new instance.
1553
1554
1554 ``hasmultiplesend`` indicates whether multiple sends are supported
1555 ``hasmultiplesend`` indicates whether multiple sends are supported
1555 by the transport. When True, it is possible to send commands immediately
1556 by the transport. When True, it is possible to send commands immediately
1556 instead of buffering until the caller signals an intent to finish a
1557 instead of buffering until the caller signals an intent to finish a
1557 send operation.
1558 send operation.
1558
1559
1559 ``buffercommands`` indicates whether sends should be buffered until the
1560 ``buffercommands`` indicates whether sends should be buffered until the
1560 last request has been issued.
1561 last request has been issued.
1562
1563 ``clientcontentencoders`` is an iterable of content encoders the client
1564 will advertise to the server and that the server can use for encoding
1565 data. If not defined, the client will not advertise content encoders
1566 to the server.
1561 """
1567 """
1562 self._ui = ui
1568 self._ui = ui
1563 self._hasmultiplesend = hasmultiplesend
1569 self._hasmultiplesend = hasmultiplesend
1564 self._buffersends = buffersends
1570 self._buffersends = buffersends
1571 self._clientcontentencoders = clientcontentencoders
1565
1572
1566 self._canissuecommands = True
1573 self._canissuecommands = True
1567 self._cansend = True
1574 self._cansend = True
1575 self._protocolsettingssent = False
1568
1576
1569 self._nextrequestid = 1
1577 self._nextrequestid = 1
1570 # We only support a single outgoing stream for now.
1578 # We only support a single outgoing stream for now.
1571 self._outgoingstream = outputstream(1)
1579 self._outgoingstream = outputstream(1)
1572 self._pendingrequests = collections.deque()
1580 self._pendingrequests = collections.deque()
1573 self._activerequests = {}
1581 self._activerequests = {}
1574 self._incomingstreams = {}
1582 self._incomingstreams = {}
1575 self._streamsettingsdecoders = {}
1583 self._streamsettingsdecoders = {}
1576
1584
1577 populatestreamencoders()
1585 populatestreamencoders()
1578
1586
1579 def callcommand(self, name, args, datafh=None, redirect=None):
1587 def callcommand(self, name, args, datafh=None, redirect=None):
1580 """Request that a command be executed.
1588 """Request that a command be executed.
1581
1589
1582 Receives the command name, a dict of arguments to pass to the command,
1590 Receives the command name, a dict of arguments to pass to the command,
1583 and an optional file object containing the raw data for the command.
1591 and an optional file object containing the raw data for the command.
1584
1592
1585 Returns a 3-tuple of (request, action, action data).
1593 Returns a 3-tuple of (request, action, action data).
1586 """
1594 """
1587 if not self._canissuecommands:
1595 if not self._canissuecommands:
1588 raise error.ProgrammingError('cannot issue new commands')
1596 raise error.ProgrammingError('cannot issue new commands')
1589
1597
1590 requestid = self._nextrequestid
1598 requestid = self._nextrequestid
1591 self._nextrequestid += 2
1599 self._nextrequestid += 2
1592
1600
1593 request = commandrequest(requestid, name, args, datafh=datafh,
1601 request = commandrequest(requestid, name, args, datafh=datafh,
1594 redirect=redirect)
1602 redirect=redirect)
1595
1603
1596 if self._buffersends:
1604 if self._buffersends:
1597 self._pendingrequests.append(request)
1605 self._pendingrequests.append(request)
1598 return request, 'noop', {}
1606 return request, 'noop', {}
1599 else:
1607 else:
1600 if not self._cansend:
1608 if not self._cansend:
1601 raise error.ProgrammingError('sends cannot be performed on '
1609 raise error.ProgrammingError('sends cannot be performed on '
1602 'this instance')
1610 'this instance')
1603
1611
1604 if not self._hasmultiplesend:
1612 if not self._hasmultiplesend:
1605 self._cansend = False
1613 self._cansend = False
1606 self._canissuecommands = False
1614 self._canissuecommands = False
1607
1615
1608 return request, 'sendframes', {
1616 return request, 'sendframes', {
1609 'framegen': self._makecommandframes(request),
1617 'framegen': self._makecommandframes(request),
1610 }
1618 }
1611
1619
1612 def flushcommands(self):
1620 def flushcommands(self):
1613 """Request that all queued commands be sent.
1621 """Request that all queued commands be sent.
1614
1622
1615 If any commands are buffered, this will instruct the caller to send
1623 If any commands are buffered, this will instruct the caller to send
1616 them over the wire. If no commands are buffered it instructs the client
1624 them over the wire. If no commands are buffered it instructs the client
1617 to no-op.
1625 to no-op.
1618
1626
1619 If instances aren't configured for multiple sends, no new command
1627 If instances aren't configured for multiple sends, no new command
1620 requests are allowed after this is called.
1628 requests are allowed after this is called.
1621 """
1629 """
1622 if not self._pendingrequests:
1630 if not self._pendingrequests:
1623 return 'noop', {}
1631 return 'noop', {}
1624
1632
1625 if not self._cansend:
1633 if not self._cansend:
1626 raise error.ProgrammingError('sends cannot be performed on this '
1634 raise error.ProgrammingError('sends cannot be performed on this '
1627 'instance')
1635 'instance')
1628
1636
1629 # If the instance only allows sending once, mark that we have fired
1637 # If the instance only allows sending once, mark that we have fired
1630 # our one shot.
1638 # our one shot.
1631 if not self._hasmultiplesend:
1639 if not self._hasmultiplesend:
1632 self._canissuecommands = False
1640 self._canissuecommands = False
1633 self._cansend = False
1641 self._cansend = False
1634
1642
1635 def makeframes():
1643 def makeframes():
1636 while self._pendingrequests:
1644 while self._pendingrequests:
1637 request = self._pendingrequests.popleft()
1645 request = self._pendingrequests.popleft()
1638 for frame in self._makecommandframes(request):
1646 for frame in self._makecommandframes(request):
1639 yield frame
1647 yield frame
1640
1648
1641 return 'sendframes', {
1649 return 'sendframes', {
1642 'framegen': makeframes(),
1650 'framegen': makeframes(),
1643 }
1651 }
1644
1652
1645 def _makecommandframes(self, request):
1653 def _makecommandframes(self, request):
1646 """Emit frames to issue a command request.
1654 """Emit frames to issue a command request.
1647
1655
1648 As a side-effect, update request accounting to reflect its changed
1656 As a side-effect, update request accounting to reflect its changed
1649 state.
1657 state.
1650 """
1658 """
1651 self._activerequests[request.requestid] = request
1659 self._activerequests[request.requestid] = request
1652 request.state = 'sending'
1660 request.state = 'sending'
1653
1661
1662 if not self._protocolsettingssent and self._clientcontentencoders:
1663 self._protocolsettingssent = True
1664
1665 payload = b''.join(cborutil.streamencode({
1666 b'contentencodings': self._clientcontentencoders,
1667 }))
1668
1669 yield self._outgoingstream.makeframe(
1670 requestid=request.requestid,
1671 typeid=FRAME_TYPE_SENDER_PROTOCOL_SETTINGS,
1672 flags=FLAG_SENDER_PROTOCOL_SETTINGS_EOS,
1673 payload=payload)
1674
1654 res = createcommandframes(self._outgoingstream,
1675 res = createcommandframes(self._outgoingstream,
1655 request.requestid,
1676 request.requestid,
1656 request.name,
1677 request.name,
1657 request.args,
1678 request.args,
1658 datafh=request.datafh,
1679 datafh=request.datafh,
1659 redirect=request.redirect)
1680 redirect=request.redirect)
1660
1681
1661 for frame in res:
1682 for frame in res:
1662 yield frame
1683 yield frame
1663
1684
1664 request.state = 'sent'
1685 request.state = 'sent'
1665
1686
1666 def onframerecv(self, frame):
1687 def onframerecv(self, frame):
1667 """Process a frame that has been received off the wire.
1688 """Process a frame that has been received off the wire.
1668
1689
1669 Returns a 2-tuple of (action, meta) describing further action the
1690 Returns a 2-tuple of (action, meta) describing further action the
1670 caller needs to take as a result of receiving this frame.
1691 caller needs to take as a result of receiving this frame.
1671 """
1692 """
1672 if frame.streamid % 2:
1693 if frame.streamid % 2:
1673 return 'error', {
1694 return 'error', {
1674 'message': (
1695 'message': (
1675 _('received frame with odd numbered stream ID: %d') %
1696 _('received frame with odd numbered stream ID: %d') %
1676 frame.streamid),
1697 frame.streamid),
1677 }
1698 }
1678
1699
1679 if frame.streamid not in self._incomingstreams:
1700 if frame.streamid not in self._incomingstreams:
1680 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
1701 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
1681 return 'error', {
1702 return 'error', {
1682 'message': _('received frame on unknown stream '
1703 'message': _('received frame on unknown stream '
1683 'without beginning of stream flag set'),
1704 'without beginning of stream flag set'),
1684 }
1705 }
1685
1706
1686 self._incomingstreams[frame.streamid] = inputstream(
1707 self._incomingstreams[frame.streamid] = inputstream(
1687 frame.streamid)
1708 frame.streamid)
1688
1709
1689 stream = self._incomingstreams[frame.streamid]
1710 stream = self._incomingstreams[frame.streamid]
1690
1711
1691 # If the payload is encoded, ask the stream to decode it. We
1712 # If the payload is encoded, ask the stream to decode it. We
1692 # merely substitute the decoded result into the frame payload as
1713 # merely substitute the decoded result into the frame payload as
1693 # if it had been transferred all along.
1714 # if it had been transferred all along.
1694 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1715 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1695 frame.payload = stream.decode(frame.payload)
1716 frame.payload = stream.decode(frame.payload)
1696
1717
1697 if frame.streamflags & STREAM_FLAG_END_STREAM:
1718 if frame.streamflags & STREAM_FLAG_END_STREAM:
1698 del self._incomingstreams[frame.streamid]
1719 del self._incomingstreams[frame.streamid]
1699
1720
1700 if frame.typeid == FRAME_TYPE_STREAM_SETTINGS:
1721 if frame.typeid == FRAME_TYPE_STREAM_SETTINGS:
1701 return self._onstreamsettingsframe(frame)
1722 return self._onstreamsettingsframe(frame)
1702
1723
1703 if frame.requestid not in self._activerequests:
1724 if frame.requestid not in self._activerequests:
1704 return 'error', {
1725 return 'error', {
1705 'message': (_('received frame for inactive request ID: %d') %
1726 'message': (_('received frame for inactive request ID: %d') %
1706 frame.requestid),
1727 frame.requestid),
1707 }
1728 }
1708
1729
1709 request = self._activerequests[frame.requestid]
1730 request = self._activerequests[frame.requestid]
1710 request.state = 'receiving'
1731 request.state = 'receiving'
1711
1732
1712 handlers = {
1733 handlers = {
1713 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe,
1734 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe,
1714 FRAME_TYPE_ERROR_RESPONSE: self._onerrorresponseframe,
1735 FRAME_TYPE_ERROR_RESPONSE: self._onerrorresponseframe,
1715 }
1736 }
1716
1737
1717 meth = handlers.get(frame.typeid)
1738 meth = handlers.get(frame.typeid)
1718 if not meth:
1739 if not meth:
1719 raise error.ProgrammingError('unhandled frame type: %d' %
1740 raise error.ProgrammingError('unhandled frame type: %d' %
1720 frame.typeid)
1741 frame.typeid)
1721
1742
1722 return meth(request, frame)
1743 return meth(request, frame)
1723
1744
1724 def _onstreamsettingsframe(self, frame):
1745 def _onstreamsettingsframe(self, frame):
1725 assert frame.typeid == FRAME_TYPE_STREAM_SETTINGS
1746 assert frame.typeid == FRAME_TYPE_STREAM_SETTINGS
1726
1747
1727 more = frame.flags & FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION
1748 more = frame.flags & FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION
1728 eos = frame.flags & FLAG_STREAM_ENCODING_SETTINGS_EOS
1749 eos = frame.flags & FLAG_STREAM_ENCODING_SETTINGS_EOS
1729
1750
1730 if more and eos:
1751 if more and eos:
1731 return 'error', {
1752 return 'error', {
1732 'message': (_('stream encoding settings frame cannot have both '
1753 'message': (_('stream encoding settings frame cannot have both '
1733 'continuation and end of stream flags set')),
1754 'continuation and end of stream flags set')),
1734 }
1755 }
1735
1756
1736 if not more and not eos:
1757 if not more and not eos:
1737 return 'error', {
1758 return 'error', {
1738 'message': _('stream encoding settings frame must have '
1759 'message': _('stream encoding settings frame must have '
1739 'continuation or end of stream flag set'),
1760 'continuation or end of stream flag set'),
1740 }
1761 }
1741
1762
1742 if frame.streamid not in self._streamsettingsdecoders:
1763 if frame.streamid not in self._streamsettingsdecoders:
1743 decoder = cborutil.bufferingdecoder()
1764 decoder = cborutil.bufferingdecoder()
1744 self._streamsettingsdecoders[frame.streamid] = decoder
1765 self._streamsettingsdecoders[frame.streamid] = decoder
1745
1766
1746 decoder = self._streamsettingsdecoders[frame.streamid]
1767 decoder = self._streamsettingsdecoders[frame.streamid]
1747
1768
1748 try:
1769 try:
1749 decoder.decode(frame.payload)
1770 decoder.decode(frame.payload)
1750 except Exception as e:
1771 except Exception as e:
1751 return 'error', {
1772 return 'error', {
1752 'message': (_('error decoding CBOR from stream encoding '
1773 'message': (_('error decoding CBOR from stream encoding '
1753 'settings frame: %s') %
1774 'settings frame: %s') %
1754 stringutil.forcebytestr(e)),
1775 stringutil.forcebytestr(e)),
1755 }
1776 }
1756
1777
1757 if more:
1778 if more:
1758 return 'noop', {}
1779 return 'noop', {}
1759
1780
1760 assert eos
1781 assert eos
1761
1782
1762 decoded = decoder.getavailable()
1783 decoded = decoder.getavailable()
1763 del self._streamsettingsdecoders[frame.streamid]
1784 del self._streamsettingsdecoders[frame.streamid]
1764
1785
1765 if not decoded:
1786 if not decoded:
1766 return 'error', {
1787 return 'error', {
1767 'message': _('stream encoding settings frame did not contain '
1788 'message': _('stream encoding settings frame did not contain '
1768 'CBOR data'),
1789 'CBOR data'),
1769 }
1790 }
1770
1791
1771 try:
1792 try:
1772 self._incomingstreams[frame.streamid].setdecoder(self._ui,
1793 self._incomingstreams[frame.streamid].setdecoder(self._ui,
1773 decoded[0],
1794 decoded[0],
1774 decoded[1:])
1795 decoded[1:])
1775 except Exception as e:
1796 except Exception as e:
1776 return 'error', {
1797 return 'error', {
1777 'message': (_('error setting stream decoder: %s') %
1798 'message': (_('error setting stream decoder: %s') %
1778 stringutil.forcebytestr(e)),
1799 stringutil.forcebytestr(e)),
1779 }
1800 }
1780
1801
1781 return 'noop', {}
1802 return 'noop', {}
1782
1803
1783 def _oncommandresponseframe(self, request, frame):
1804 def _oncommandresponseframe(self, request, frame):
1784 if frame.flags & FLAG_COMMAND_RESPONSE_EOS:
1805 if frame.flags & FLAG_COMMAND_RESPONSE_EOS:
1785 request.state = 'received'
1806 request.state = 'received'
1786 del self._activerequests[request.requestid]
1807 del self._activerequests[request.requestid]
1787
1808
1788 return 'responsedata', {
1809 return 'responsedata', {
1789 'request': request,
1810 'request': request,
1790 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION,
1811 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION,
1791 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS,
1812 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS,
1792 'data': frame.payload,
1813 'data': frame.payload,
1793 }
1814 }
1794
1815
1795 def _onerrorresponseframe(self, request, frame):
1816 def _onerrorresponseframe(self, request, frame):
1796 request.state = 'errored'
1817 request.state = 'errored'
1797 del self._activerequests[request.requestid]
1818 del self._activerequests[request.requestid]
1798
1819
1799 # The payload should be a CBOR map.
1820 # The payload should be a CBOR map.
1800 m = cborutil.decodeall(frame.payload)[0]
1821 m = cborutil.decodeall(frame.payload)[0]
1801
1822
1802 return 'error', {
1823 return 'error', {
1803 'request': request,
1824 'request': request,
1804 'type': m['type'],
1825 'type': m['type'],
1805 'message': m['message'],
1826 'message': m['message'],
1806 }
1827 }
@@ -1,1187 +1,1187
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10 import hashlib
10 import hashlib
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import (
13 from .node import (
14 hex,
14 hex,
15 nullid,
15 nullid,
16 )
16 )
17 from . import (
17 from . import (
18 discovery,
18 discovery,
19 encoding,
19 encoding,
20 error,
20 error,
21 narrowspec,
21 narrowspec,
22 pycompat,
22 pycompat,
23 wireprotoframing,
23 wireprotoframing,
24 wireprototypes,
24 wireprototypes,
25 )
25 )
26 from .utils import (
26 from .utils import (
27 cborutil,
27 cborutil,
28 interfaceutil,
28 interfaceutil,
29 stringutil,
29 stringutil,
30 )
30 )
31
31
32 FRAMINGTYPE = b'application/mercurial-exp-framing-0005'
32 FRAMINGTYPE = b'application/mercurial-exp-framing-0006'
33
33
34 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
34 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
35
35
36 COMMANDS = wireprototypes.commanddict()
36 COMMANDS = wireprototypes.commanddict()
37
37
38 # Value inserted into cache key computation function. Change the value to
38 # Value inserted into cache key computation function. Change the value to
39 # force new cache keys for every command request. This should be done when
39 # force new cache keys for every command request. This should be done when
40 # there is a change to how caching works, etc.
40 # there is a change to how caching works, etc.
41 GLOBAL_CACHE_VERSION = 1
41 GLOBAL_CACHE_VERSION = 1
42
42
43 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
43 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
44 from .hgweb import common as hgwebcommon
44 from .hgweb import common as hgwebcommon
45
45
46 # URL space looks like: <permissions>/<command>, where <permission> can
46 # URL space looks like: <permissions>/<command>, where <permission> can
47 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
47 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
48
48
49 # Root URL does nothing meaningful... yet.
49 # Root URL does nothing meaningful... yet.
50 if not urlparts:
50 if not urlparts:
51 res.status = b'200 OK'
51 res.status = b'200 OK'
52 res.headers[b'Content-Type'] = b'text/plain'
52 res.headers[b'Content-Type'] = b'text/plain'
53 res.setbodybytes(_('HTTP version 2 API handler'))
53 res.setbodybytes(_('HTTP version 2 API handler'))
54 return
54 return
55
55
56 if len(urlparts) == 1:
56 if len(urlparts) == 1:
57 res.status = b'404 Not Found'
57 res.status = b'404 Not Found'
58 res.headers[b'Content-Type'] = b'text/plain'
58 res.headers[b'Content-Type'] = b'text/plain'
59 res.setbodybytes(_('do not know how to process %s\n') %
59 res.setbodybytes(_('do not know how to process %s\n') %
60 req.dispatchpath)
60 req.dispatchpath)
61 return
61 return
62
62
63 permission, command = urlparts[0:2]
63 permission, command = urlparts[0:2]
64
64
65 if permission not in (b'ro', b'rw'):
65 if permission not in (b'ro', b'rw'):
66 res.status = b'404 Not Found'
66 res.status = b'404 Not Found'
67 res.headers[b'Content-Type'] = b'text/plain'
67 res.headers[b'Content-Type'] = b'text/plain'
68 res.setbodybytes(_('unknown permission: %s') % permission)
68 res.setbodybytes(_('unknown permission: %s') % permission)
69 return
69 return
70
70
71 if req.method != 'POST':
71 if req.method != 'POST':
72 res.status = b'405 Method Not Allowed'
72 res.status = b'405 Method Not Allowed'
73 res.headers[b'Allow'] = b'POST'
73 res.headers[b'Allow'] = b'POST'
74 res.setbodybytes(_('commands require POST requests'))
74 res.setbodybytes(_('commands require POST requests'))
75 return
75 return
76
76
77 # At some point we'll want to use our own API instead of recycling the
77 # At some point we'll want to use our own API instead of recycling the
78 # behavior of version 1 of the wire protocol...
78 # behavior of version 1 of the wire protocol...
79 # TODO return reasonable responses - not responses that overload the
79 # TODO return reasonable responses - not responses that overload the
80 # HTTP status line message for error reporting.
80 # HTTP status line message for error reporting.
81 try:
81 try:
82 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
82 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
83 except hgwebcommon.ErrorResponse as e:
83 except hgwebcommon.ErrorResponse as e:
84 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
84 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
85 for k, v in e.headers:
85 for k, v in e.headers:
86 res.headers[k] = v
86 res.headers[k] = v
87 res.setbodybytes('permission denied')
87 res.setbodybytes('permission denied')
88 return
88 return
89
89
90 # We have a special endpoint to reflect the request back at the client.
90 # We have a special endpoint to reflect the request back at the client.
91 if command == b'debugreflect':
91 if command == b'debugreflect':
92 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
92 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
93 return
93 return
94
94
95 # Extra commands that we handle that aren't really wire protocol
95 # Extra commands that we handle that aren't really wire protocol
96 # commands. Think extra hard before making this hackery available to
96 # commands. Think extra hard before making this hackery available to
97 # extension.
97 # extension.
98 extracommands = {'multirequest'}
98 extracommands = {'multirequest'}
99
99
100 if command not in COMMANDS and command not in extracommands:
100 if command not in COMMANDS and command not in extracommands:
101 res.status = b'404 Not Found'
101 res.status = b'404 Not Found'
102 res.headers[b'Content-Type'] = b'text/plain'
102 res.headers[b'Content-Type'] = b'text/plain'
103 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
103 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
104 return
104 return
105
105
106 repo = rctx.repo
106 repo = rctx.repo
107 ui = repo.ui
107 ui = repo.ui
108
108
109 proto = httpv2protocolhandler(req, ui)
109 proto = httpv2protocolhandler(req, ui)
110
110
111 if (not COMMANDS.commandavailable(command, proto)
111 if (not COMMANDS.commandavailable(command, proto)
112 and command not in extracommands):
112 and command not in extracommands):
113 res.status = b'404 Not Found'
113 res.status = b'404 Not Found'
114 res.headers[b'Content-Type'] = b'text/plain'
114 res.headers[b'Content-Type'] = b'text/plain'
115 res.setbodybytes(_('invalid wire protocol command: %s') % command)
115 res.setbodybytes(_('invalid wire protocol command: %s') % command)
116 return
116 return
117
117
118 # TODO consider cases where proxies may add additional Accept headers.
118 # TODO consider cases where proxies may add additional Accept headers.
119 if req.headers.get(b'Accept') != FRAMINGTYPE:
119 if req.headers.get(b'Accept') != FRAMINGTYPE:
120 res.status = b'406 Not Acceptable'
120 res.status = b'406 Not Acceptable'
121 res.headers[b'Content-Type'] = b'text/plain'
121 res.headers[b'Content-Type'] = b'text/plain'
122 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
122 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
123 % FRAMINGTYPE)
123 % FRAMINGTYPE)
124 return
124 return
125
125
126 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
126 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
127 res.status = b'415 Unsupported Media Type'
127 res.status = b'415 Unsupported Media Type'
128 # TODO we should send a response with appropriate media type,
128 # TODO we should send a response with appropriate media type,
129 # since client does Accept it.
129 # since client does Accept it.
130 res.headers[b'Content-Type'] = b'text/plain'
130 res.headers[b'Content-Type'] = b'text/plain'
131 res.setbodybytes(_('client MUST send Content-Type header with '
131 res.setbodybytes(_('client MUST send Content-Type header with '
132 'value: %s\n') % FRAMINGTYPE)
132 'value: %s\n') % FRAMINGTYPE)
133 return
133 return
134
134
135 _processhttpv2request(ui, repo, req, res, permission, command, proto)
135 _processhttpv2request(ui, repo, req, res, permission, command, proto)
136
136
137 def _processhttpv2reflectrequest(ui, repo, req, res):
137 def _processhttpv2reflectrequest(ui, repo, req, res):
138 """Reads unified frame protocol request and dumps out state to client.
138 """Reads unified frame protocol request and dumps out state to client.
139
139
140 This special endpoint can be used to help debug the wire protocol.
140 This special endpoint can be used to help debug the wire protocol.
141
141
142 Instead of routing the request through the normal dispatch mechanism,
142 Instead of routing the request through the normal dispatch mechanism,
143 we instead read all frames, decode them, and feed them into our state
143 we instead read all frames, decode them, and feed them into our state
144 tracker. We then dump the log of all that activity back out to the
144 tracker. We then dump the log of all that activity back out to the
145 client.
145 client.
146 """
146 """
147 import json
147 import json
148
148
149 # Reflection APIs have a history of being abused, accidentally disclosing
149 # Reflection APIs have a history of being abused, accidentally disclosing
150 # sensitive data, etc. So we have a config knob.
150 # sensitive data, etc. So we have a config knob.
151 if not ui.configbool('experimental', 'web.api.debugreflect'):
151 if not ui.configbool('experimental', 'web.api.debugreflect'):
152 res.status = b'404 Not Found'
152 res.status = b'404 Not Found'
153 res.headers[b'Content-Type'] = b'text/plain'
153 res.headers[b'Content-Type'] = b'text/plain'
154 res.setbodybytes(_('debugreflect service not available'))
154 res.setbodybytes(_('debugreflect service not available'))
155 return
155 return
156
156
157 # We assume we have a unified framing protocol request body.
157 # We assume we have a unified framing protocol request body.
158
158
159 reactor = wireprotoframing.serverreactor(ui)
159 reactor = wireprotoframing.serverreactor(ui)
160 states = []
160 states = []
161
161
162 while True:
162 while True:
163 frame = wireprotoframing.readframe(req.bodyfh)
163 frame = wireprotoframing.readframe(req.bodyfh)
164
164
165 if not frame:
165 if not frame:
166 states.append(b'received: <no frame>')
166 states.append(b'received: <no frame>')
167 break
167 break
168
168
169 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
169 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
170 frame.requestid,
170 frame.requestid,
171 frame.payload))
171 frame.payload))
172
172
173 action, meta = reactor.onframerecv(frame)
173 action, meta = reactor.onframerecv(frame)
174 states.append(json.dumps((action, meta), sort_keys=True,
174 states.append(json.dumps((action, meta), sort_keys=True,
175 separators=(', ', ': ')))
175 separators=(', ', ': ')))
176
176
177 action, meta = reactor.oninputeof()
177 action, meta = reactor.oninputeof()
178 meta['action'] = action
178 meta['action'] = action
179 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
179 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
180
180
181 res.status = b'200 OK'
181 res.status = b'200 OK'
182 res.headers[b'Content-Type'] = b'text/plain'
182 res.headers[b'Content-Type'] = b'text/plain'
183 res.setbodybytes(b'\n'.join(states))
183 res.setbodybytes(b'\n'.join(states))
184
184
185 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
185 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
186 """Post-validation handler for HTTPv2 requests.
186 """Post-validation handler for HTTPv2 requests.
187
187
188 Called when the HTTP request contains unified frame-based protocol
188 Called when the HTTP request contains unified frame-based protocol
189 frames for evaluation.
189 frames for evaluation.
190 """
190 """
191 # TODO Some HTTP clients are full duplex and can receive data before
191 # TODO Some HTTP clients are full duplex and can receive data before
192 # the entire request is transmitted. Figure out a way to indicate support
192 # the entire request is transmitted. Figure out a way to indicate support
193 # for that so we can opt into full duplex mode.
193 # for that so we can opt into full duplex mode.
194 reactor = wireprotoframing.serverreactor(ui, deferoutput=True)
194 reactor = wireprotoframing.serverreactor(ui, deferoutput=True)
195 seencommand = False
195 seencommand = False
196
196
197 outstream = reactor.makeoutputstream()
197 outstream = reactor.makeoutputstream()
198
198
199 while True:
199 while True:
200 frame = wireprotoframing.readframe(req.bodyfh)
200 frame = wireprotoframing.readframe(req.bodyfh)
201 if not frame:
201 if not frame:
202 break
202 break
203
203
204 action, meta = reactor.onframerecv(frame)
204 action, meta = reactor.onframerecv(frame)
205
205
206 if action == 'wantframe':
206 if action == 'wantframe':
207 # Need more data before we can do anything.
207 # Need more data before we can do anything.
208 continue
208 continue
209 elif action == 'runcommand':
209 elif action == 'runcommand':
210 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
210 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
211 reqcommand, reactor, outstream,
211 reqcommand, reactor, outstream,
212 meta, issubsequent=seencommand)
212 meta, issubsequent=seencommand)
213
213
214 if sentoutput:
214 if sentoutput:
215 return
215 return
216
216
217 seencommand = True
217 seencommand = True
218
218
219 elif action == 'error':
219 elif action == 'error':
220 # TODO define proper error mechanism.
220 # TODO define proper error mechanism.
221 res.status = b'200 OK'
221 res.status = b'200 OK'
222 res.headers[b'Content-Type'] = b'text/plain'
222 res.headers[b'Content-Type'] = b'text/plain'
223 res.setbodybytes(meta['message'] + b'\n')
223 res.setbodybytes(meta['message'] + b'\n')
224 return
224 return
225 else:
225 else:
226 raise error.ProgrammingError(
226 raise error.ProgrammingError(
227 'unhandled action from frame processor: %s' % action)
227 'unhandled action from frame processor: %s' % action)
228
228
229 action, meta = reactor.oninputeof()
229 action, meta = reactor.oninputeof()
230 if action == 'sendframes':
230 if action == 'sendframes':
231 # We assume we haven't started sending the response yet. If we're
231 # We assume we haven't started sending the response yet. If we're
232 # wrong, the response type will raise an exception.
232 # wrong, the response type will raise an exception.
233 res.status = b'200 OK'
233 res.status = b'200 OK'
234 res.headers[b'Content-Type'] = FRAMINGTYPE
234 res.headers[b'Content-Type'] = FRAMINGTYPE
235 res.setbodygen(meta['framegen'])
235 res.setbodygen(meta['framegen'])
236 elif action == 'noop':
236 elif action == 'noop':
237 pass
237 pass
238 else:
238 else:
239 raise error.ProgrammingError('unhandled action from frame processor: %s'
239 raise error.ProgrammingError('unhandled action from frame processor: %s'
240 % action)
240 % action)
241
241
242 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
242 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
243 outstream, command, issubsequent):
243 outstream, command, issubsequent):
244 """Dispatch a wire protocol command made from HTTPv2 requests.
244 """Dispatch a wire protocol command made from HTTPv2 requests.
245
245
246 The authenticated permission (``authedperm``) along with the original
246 The authenticated permission (``authedperm``) along with the original
247 command from the URL (``reqcommand``) are passed in.
247 command from the URL (``reqcommand``) are passed in.
248 """
248 """
249 # We already validated that the session has permissions to perform the
249 # We already validated that the session has permissions to perform the
250 # actions in ``authedperm``. In the unified frame protocol, the canonical
250 # actions in ``authedperm``. In the unified frame protocol, the canonical
251 # command to run is expressed in a frame. However, the URL also requested
251 # command to run is expressed in a frame. However, the URL also requested
252 # to run a specific command. We need to be careful that the command we
252 # to run a specific command. We need to be careful that the command we
253 # run doesn't have permissions requirements greater than what was granted
253 # run doesn't have permissions requirements greater than what was granted
254 # by ``authedperm``.
254 # by ``authedperm``.
255 #
255 #
256 # Our rule for this is we only allow one command per HTTP request and
256 # Our rule for this is we only allow one command per HTTP request and
257 # that command must match the command in the URL. However, we make
257 # that command must match the command in the URL. However, we make
258 # an exception for the ``multirequest`` URL. This URL is allowed to
258 # an exception for the ``multirequest`` URL. This URL is allowed to
259 # execute multiple commands. We double check permissions of each command
259 # execute multiple commands. We double check permissions of each command
260 # as it is invoked to ensure there is no privilege escalation.
260 # as it is invoked to ensure there is no privilege escalation.
261 # TODO consider allowing multiple commands to regular command URLs
261 # TODO consider allowing multiple commands to regular command URLs
262 # iff each command is the same.
262 # iff each command is the same.
263
263
264 proto = httpv2protocolhandler(req, ui, args=command['args'])
264 proto = httpv2protocolhandler(req, ui, args=command['args'])
265
265
266 if reqcommand == b'multirequest':
266 if reqcommand == b'multirequest':
267 if not COMMANDS.commandavailable(command['command'], proto):
267 if not COMMANDS.commandavailable(command['command'], proto):
268 # TODO proper error mechanism
268 # TODO proper error mechanism
269 res.status = b'200 OK'
269 res.status = b'200 OK'
270 res.headers[b'Content-Type'] = b'text/plain'
270 res.headers[b'Content-Type'] = b'text/plain'
271 res.setbodybytes(_('wire protocol command not available: %s') %
271 res.setbodybytes(_('wire protocol command not available: %s') %
272 command['command'])
272 command['command'])
273 return True
273 return True
274
274
275 # TODO don't use assert here, since it may be elided by -O.
275 # TODO don't use assert here, since it may be elided by -O.
276 assert authedperm in (b'ro', b'rw')
276 assert authedperm in (b'ro', b'rw')
277 wirecommand = COMMANDS[command['command']]
277 wirecommand = COMMANDS[command['command']]
278 assert wirecommand.permission in ('push', 'pull')
278 assert wirecommand.permission in ('push', 'pull')
279
279
280 if authedperm == b'ro' and wirecommand.permission != 'pull':
280 if authedperm == b'ro' and wirecommand.permission != 'pull':
281 # TODO proper error mechanism
281 # TODO proper error mechanism
282 res.status = b'403 Forbidden'
282 res.status = b'403 Forbidden'
283 res.headers[b'Content-Type'] = b'text/plain'
283 res.headers[b'Content-Type'] = b'text/plain'
284 res.setbodybytes(_('insufficient permissions to execute '
284 res.setbodybytes(_('insufficient permissions to execute '
285 'command: %s') % command['command'])
285 'command: %s') % command['command'])
286 return True
286 return True
287
287
288 # TODO should we also call checkperm() here? Maybe not if we're going
288 # TODO should we also call checkperm() here? Maybe not if we're going
289 # to overhaul that API. The granted scope from the URL check should
289 # to overhaul that API. The granted scope from the URL check should
290 # be good enough.
290 # be good enough.
291
291
292 else:
292 else:
293 # Don't allow multiple commands outside of ``multirequest`` URL.
293 # Don't allow multiple commands outside of ``multirequest`` URL.
294 if issubsequent:
294 if issubsequent:
295 # TODO proper error mechanism
295 # TODO proper error mechanism
296 res.status = b'200 OK'
296 res.status = b'200 OK'
297 res.headers[b'Content-Type'] = b'text/plain'
297 res.headers[b'Content-Type'] = b'text/plain'
298 res.setbodybytes(_('multiple commands cannot be issued to this '
298 res.setbodybytes(_('multiple commands cannot be issued to this '
299 'URL'))
299 'URL'))
300 return True
300 return True
301
301
302 if reqcommand != command['command']:
302 if reqcommand != command['command']:
303 # TODO define proper error mechanism
303 # TODO define proper error mechanism
304 res.status = b'200 OK'
304 res.status = b'200 OK'
305 res.headers[b'Content-Type'] = b'text/plain'
305 res.headers[b'Content-Type'] = b'text/plain'
306 res.setbodybytes(_('command in frame must match command in URL'))
306 res.setbodybytes(_('command in frame must match command in URL'))
307 return True
307 return True
308
308
309 res.status = b'200 OK'
309 res.status = b'200 OK'
310 res.headers[b'Content-Type'] = FRAMINGTYPE
310 res.headers[b'Content-Type'] = FRAMINGTYPE
311
311
312 try:
312 try:
313 objs = dispatch(repo, proto, command['command'], command['redirect'])
313 objs = dispatch(repo, proto, command['command'], command['redirect'])
314
314
315 action, meta = reactor.oncommandresponsereadyobjects(
315 action, meta = reactor.oncommandresponsereadyobjects(
316 outstream, command['requestid'], objs)
316 outstream, command['requestid'], objs)
317
317
318 except error.WireprotoCommandError as e:
318 except error.WireprotoCommandError as e:
319 action, meta = reactor.oncommanderror(
319 action, meta = reactor.oncommanderror(
320 outstream, command['requestid'], e.message, e.messageargs)
320 outstream, command['requestid'], e.message, e.messageargs)
321
321
322 except Exception as e:
322 except Exception as e:
323 action, meta = reactor.onservererror(
323 action, meta = reactor.onservererror(
324 outstream, command['requestid'],
324 outstream, command['requestid'],
325 _('exception when invoking command: %s') %
325 _('exception when invoking command: %s') %
326 stringutil.forcebytestr(e))
326 stringutil.forcebytestr(e))
327
327
328 if action == 'sendframes':
328 if action == 'sendframes':
329 res.setbodygen(meta['framegen'])
329 res.setbodygen(meta['framegen'])
330 return True
330 return True
331 elif action == 'noop':
331 elif action == 'noop':
332 return False
332 return False
333 else:
333 else:
334 raise error.ProgrammingError('unhandled event from reactor: %s' %
334 raise error.ProgrammingError('unhandled event from reactor: %s' %
335 action)
335 action)
336
336
337 def getdispatchrepo(repo, proto, command):
337 def getdispatchrepo(repo, proto, command):
338 return repo.filtered('served')
338 return repo.filtered('served')
339
339
340 def dispatch(repo, proto, command, redirect):
340 def dispatch(repo, proto, command, redirect):
341 """Run a wire protocol command.
341 """Run a wire protocol command.
342
342
343 Returns an iterable of objects that will be sent to the client.
343 Returns an iterable of objects that will be sent to the client.
344 """
344 """
345 repo = getdispatchrepo(repo, proto, command)
345 repo = getdispatchrepo(repo, proto, command)
346
346
347 entry = COMMANDS[command]
347 entry = COMMANDS[command]
348 func = entry.func
348 func = entry.func
349 spec = entry.args
349 spec = entry.args
350
350
351 args = proto.getargs(spec)
351 args = proto.getargs(spec)
352
352
353 # There is some duplicate boilerplate code here for calling the command and
353 # There is some duplicate boilerplate code here for calling the command and
354 # emitting objects. It is either that or a lot of indented code that looks
354 # emitting objects. It is either that or a lot of indented code that looks
355 # like a pyramid (since there are a lot of code paths that result in not
355 # like a pyramid (since there are a lot of code paths that result in not
356 # using the cacher).
356 # using the cacher).
357 callcommand = lambda: func(repo, proto, **pycompat.strkwargs(args))
357 callcommand = lambda: func(repo, proto, **pycompat.strkwargs(args))
358
358
359 # Request is not cacheable. Don't bother instantiating a cacher.
359 # Request is not cacheable. Don't bother instantiating a cacher.
360 if not entry.cachekeyfn:
360 if not entry.cachekeyfn:
361 for o in callcommand():
361 for o in callcommand():
362 yield o
362 yield o
363 return
363 return
364
364
365 if redirect:
365 if redirect:
366 redirecttargets = redirect[b'targets']
366 redirecttargets = redirect[b'targets']
367 redirecthashes = redirect[b'hashes']
367 redirecthashes = redirect[b'hashes']
368 else:
368 else:
369 redirecttargets = []
369 redirecttargets = []
370 redirecthashes = []
370 redirecthashes = []
371
371
372 cacher = makeresponsecacher(repo, proto, command, args,
372 cacher = makeresponsecacher(repo, proto, command, args,
373 cborutil.streamencode,
373 cborutil.streamencode,
374 redirecttargets=redirecttargets,
374 redirecttargets=redirecttargets,
375 redirecthashes=redirecthashes)
375 redirecthashes=redirecthashes)
376
376
377 # But we have no cacher. Do default handling.
377 # But we have no cacher. Do default handling.
378 if not cacher:
378 if not cacher:
379 for o in callcommand():
379 for o in callcommand():
380 yield o
380 yield o
381 return
381 return
382
382
383 with cacher:
383 with cacher:
384 cachekey = entry.cachekeyfn(repo, proto, cacher, **args)
384 cachekey = entry.cachekeyfn(repo, proto, cacher, **args)
385
385
386 # No cache key or the cacher doesn't like it. Do default handling.
386 # No cache key or the cacher doesn't like it. Do default handling.
387 if cachekey is None or not cacher.setcachekey(cachekey):
387 if cachekey is None or not cacher.setcachekey(cachekey):
388 for o in callcommand():
388 for o in callcommand():
389 yield o
389 yield o
390 return
390 return
391
391
392 # Serve it from the cache, if possible.
392 # Serve it from the cache, if possible.
393 cached = cacher.lookup()
393 cached = cacher.lookup()
394
394
395 if cached:
395 if cached:
396 for o in cached['objs']:
396 for o in cached['objs']:
397 yield o
397 yield o
398 return
398 return
399
399
400 # Else call the command and feed its output into the cacher, allowing
400 # Else call the command and feed its output into the cacher, allowing
401 # the cacher to buffer/mutate objects as it desires.
401 # the cacher to buffer/mutate objects as it desires.
402 for o in callcommand():
402 for o in callcommand():
403 for o in cacher.onobject(o):
403 for o in cacher.onobject(o):
404 yield o
404 yield o
405
405
406 for o in cacher.onfinished():
406 for o in cacher.onfinished():
407 yield o
407 yield o
408
408
409 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
409 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
410 class httpv2protocolhandler(object):
410 class httpv2protocolhandler(object):
411 def __init__(self, req, ui, args=None):
411 def __init__(self, req, ui, args=None):
412 self._req = req
412 self._req = req
413 self._ui = ui
413 self._ui = ui
414 self._args = args
414 self._args = args
415
415
416 @property
416 @property
417 def name(self):
417 def name(self):
418 return HTTP_WIREPROTO_V2
418 return HTTP_WIREPROTO_V2
419
419
420 def getargs(self, args):
420 def getargs(self, args):
421 # First look for args that were passed but aren't registered on this
421 # First look for args that were passed but aren't registered on this
422 # command.
422 # command.
423 extra = set(self._args) - set(args)
423 extra = set(self._args) - set(args)
424 if extra:
424 if extra:
425 raise error.WireprotoCommandError(
425 raise error.WireprotoCommandError(
426 'unsupported argument to command: %s' %
426 'unsupported argument to command: %s' %
427 ', '.join(sorted(extra)))
427 ', '.join(sorted(extra)))
428
428
429 # And look for required arguments that are missing.
429 # And look for required arguments that are missing.
430 missing = {a for a in args if args[a]['required']} - set(self._args)
430 missing = {a for a in args if args[a]['required']} - set(self._args)
431
431
432 if missing:
432 if missing:
433 raise error.WireprotoCommandError(
433 raise error.WireprotoCommandError(
434 'missing required arguments: %s' % ', '.join(sorted(missing)))
434 'missing required arguments: %s' % ', '.join(sorted(missing)))
435
435
436 # Now derive the arguments to pass to the command, taking into
436 # Now derive the arguments to pass to the command, taking into
437 # account the arguments specified by the client.
437 # account the arguments specified by the client.
438 data = {}
438 data = {}
439 for k, meta in sorted(args.items()):
439 for k, meta in sorted(args.items()):
440 # This argument wasn't passed by the client.
440 # This argument wasn't passed by the client.
441 if k not in self._args:
441 if k not in self._args:
442 data[k] = meta['default']()
442 data[k] = meta['default']()
443 continue
443 continue
444
444
445 v = self._args[k]
445 v = self._args[k]
446
446
447 # Sets may be expressed as lists. Silently normalize.
447 # Sets may be expressed as lists. Silently normalize.
448 if meta['type'] == 'set' and isinstance(v, list):
448 if meta['type'] == 'set' and isinstance(v, list):
449 v = set(v)
449 v = set(v)
450
450
451 # TODO consider more/stronger type validation.
451 # TODO consider more/stronger type validation.
452
452
453 data[k] = v
453 data[k] = v
454
454
455 return data
455 return data
456
456
457 def getprotocaps(self):
457 def getprotocaps(self):
458 # Protocol capabilities are currently not implemented for HTTP V2.
458 # Protocol capabilities are currently not implemented for HTTP V2.
459 return set()
459 return set()
460
460
461 def getpayload(self):
461 def getpayload(self):
462 raise NotImplementedError
462 raise NotImplementedError
463
463
464 @contextlib.contextmanager
464 @contextlib.contextmanager
465 def mayberedirectstdio(self):
465 def mayberedirectstdio(self):
466 raise NotImplementedError
466 raise NotImplementedError
467
467
468 def client(self):
468 def client(self):
469 raise NotImplementedError
469 raise NotImplementedError
470
470
471 def addcapabilities(self, repo, caps):
471 def addcapabilities(self, repo, caps):
472 return caps
472 return caps
473
473
474 def checkperm(self, perm):
474 def checkperm(self, perm):
475 raise NotImplementedError
475 raise NotImplementedError
476
476
477 def httpv2apidescriptor(req, repo):
477 def httpv2apidescriptor(req, repo):
478 proto = httpv2protocolhandler(req, repo.ui)
478 proto = httpv2protocolhandler(req, repo.ui)
479
479
480 return _capabilitiesv2(repo, proto)
480 return _capabilitiesv2(repo, proto)
481
481
482 def _capabilitiesv2(repo, proto):
482 def _capabilitiesv2(repo, proto):
483 """Obtain the set of capabilities for version 2 transports.
483 """Obtain the set of capabilities for version 2 transports.
484
484
485 These capabilities are distinct from the capabilities for version 1
485 These capabilities are distinct from the capabilities for version 1
486 transports.
486 transports.
487 """
487 """
488 caps = {
488 caps = {
489 'commands': {},
489 'commands': {},
490 'framingmediatypes': [FRAMINGTYPE],
490 'framingmediatypes': [FRAMINGTYPE],
491 'pathfilterprefixes': set(narrowspec.VALID_PREFIXES),
491 'pathfilterprefixes': set(narrowspec.VALID_PREFIXES),
492 }
492 }
493
493
494 for command, entry in COMMANDS.items():
494 for command, entry in COMMANDS.items():
495 args = {}
495 args = {}
496
496
497 for arg, meta in entry.args.items():
497 for arg, meta in entry.args.items():
498 args[arg] = {
498 args[arg] = {
499 # TODO should this be a normalized type using CBOR's
499 # TODO should this be a normalized type using CBOR's
500 # terminology?
500 # terminology?
501 b'type': meta['type'],
501 b'type': meta['type'],
502 b'required': meta['required'],
502 b'required': meta['required'],
503 }
503 }
504
504
505 if not meta['required']:
505 if not meta['required']:
506 args[arg][b'default'] = meta['default']()
506 args[arg][b'default'] = meta['default']()
507
507
508 if meta['validvalues']:
508 if meta['validvalues']:
509 args[arg][b'validvalues'] = meta['validvalues']
509 args[arg][b'validvalues'] = meta['validvalues']
510
510
511 caps['commands'][command] = {
511 caps['commands'][command] = {
512 'args': args,
512 'args': args,
513 'permissions': [entry.permission],
513 'permissions': [entry.permission],
514 }
514 }
515
515
516 caps['rawrepoformats'] = sorted(repo.requirements &
516 caps['rawrepoformats'] = sorted(repo.requirements &
517 repo.supportedformats)
517 repo.supportedformats)
518
518
519 targets = getadvertisedredirecttargets(repo, proto)
519 targets = getadvertisedredirecttargets(repo, proto)
520 if targets:
520 if targets:
521 caps[b'redirect'] = {
521 caps[b'redirect'] = {
522 b'targets': [],
522 b'targets': [],
523 b'hashes': [b'sha256', b'sha1'],
523 b'hashes': [b'sha256', b'sha1'],
524 }
524 }
525
525
526 for target in targets:
526 for target in targets:
527 entry = {
527 entry = {
528 b'name': target['name'],
528 b'name': target['name'],
529 b'protocol': target['protocol'],
529 b'protocol': target['protocol'],
530 b'uris': target['uris'],
530 b'uris': target['uris'],
531 }
531 }
532
532
533 for key in ('snirequired', 'tlsversions'):
533 for key in ('snirequired', 'tlsversions'):
534 if key in target:
534 if key in target:
535 entry[key] = target[key]
535 entry[key] = target[key]
536
536
537 caps[b'redirect'][b'targets'].append(entry)
537 caps[b'redirect'][b'targets'].append(entry)
538
538
539 return proto.addcapabilities(repo, caps)
539 return proto.addcapabilities(repo, caps)
540
540
541 def getadvertisedredirecttargets(repo, proto):
541 def getadvertisedredirecttargets(repo, proto):
542 """Obtain a list of content redirect targets.
542 """Obtain a list of content redirect targets.
543
543
544 Returns a list containing potential redirect targets that will be
544 Returns a list containing potential redirect targets that will be
545 advertised in capabilities data. Each dict MUST have the following
545 advertised in capabilities data. Each dict MUST have the following
546 keys:
546 keys:
547
547
548 name
548 name
549 The name of this redirect target. This is the identifier clients use
549 The name of this redirect target. This is the identifier clients use
550 to refer to a target. It is transferred as part of every command
550 to refer to a target. It is transferred as part of every command
551 request.
551 request.
552
552
553 protocol
553 protocol
554 Network protocol used by this target. Typically this is the string
554 Network protocol used by this target. Typically this is the string
555 in front of the ``://`` in a URL. e.g. ``https``.
555 in front of the ``://`` in a URL. e.g. ``https``.
556
556
557 uris
557 uris
558 List of representative URIs for this target. Clients can use the
558 List of representative URIs for this target. Clients can use the
559 URIs to test parsing for compatibility or for ordering preference
559 URIs to test parsing for compatibility or for ordering preference
560 for which target to use.
560 for which target to use.
561
561
562 The following optional keys are recognized:
562 The following optional keys are recognized:
563
563
564 snirequired
564 snirequired
565 Bool indicating if Server Name Indication (SNI) is required to
565 Bool indicating if Server Name Indication (SNI) is required to
566 connect to this target.
566 connect to this target.
567
567
568 tlsversions
568 tlsversions
569 List of bytes indicating which TLS versions are supported by this
569 List of bytes indicating which TLS versions are supported by this
570 target.
570 target.
571
571
572 By default, clients reflect the target order advertised by servers
572 By default, clients reflect the target order advertised by servers
573 and servers will use the first client-advertised target when picking
573 and servers will use the first client-advertised target when picking
574 a redirect target. So targets should be advertised in the order the
574 a redirect target. So targets should be advertised in the order the
575 server prefers they be used.
575 server prefers they be used.
576 """
576 """
577 return []
577 return []
578
578
579 def wireprotocommand(name, args=None, permission='push', cachekeyfn=None):
579 def wireprotocommand(name, args=None, permission='push', cachekeyfn=None):
580 """Decorator to declare a wire protocol command.
580 """Decorator to declare a wire protocol command.
581
581
582 ``name`` is the name of the wire protocol command being provided.
582 ``name`` is the name of the wire protocol command being provided.
583
583
584 ``args`` is a dict defining arguments accepted by the command. Keys are
584 ``args`` is a dict defining arguments accepted by the command. Keys are
585 the argument name. Values are dicts with the following keys:
585 the argument name. Values are dicts with the following keys:
586
586
587 ``type``
587 ``type``
588 The argument data type. Must be one of the following string
588 The argument data type. Must be one of the following string
589 literals: ``bytes``, ``int``, ``list``, ``dict``, ``set``,
589 literals: ``bytes``, ``int``, ``list``, ``dict``, ``set``,
590 or ``bool``.
590 or ``bool``.
591
591
592 ``default``
592 ``default``
593 A callable returning the default value for this argument. If not
593 A callable returning the default value for this argument. If not
594 specified, ``None`` will be the default value.
594 specified, ``None`` will be the default value.
595
595
596 ``example``
596 ``example``
597 An example value for this argument.
597 An example value for this argument.
598
598
599 ``validvalues``
599 ``validvalues``
600 Set of recognized values for this argument.
600 Set of recognized values for this argument.
601
601
602 ``permission`` defines the permission type needed to run this command.
602 ``permission`` defines the permission type needed to run this command.
603 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
603 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
604 respectively. Default is to assume command requires ``push`` permissions
604 respectively. Default is to assume command requires ``push`` permissions
605 because otherwise commands not declaring their permissions could modify
605 because otherwise commands not declaring their permissions could modify
606 a repository that is supposed to be read-only.
606 a repository that is supposed to be read-only.
607
607
608 ``cachekeyfn`` defines an optional callable that can derive the
608 ``cachekeyfn`` defines an optional callable that can derive the
609 cache key for this request.
609 cache key for this request.
610
610
611 Wire protocol commands are generators of objects to be serialized and
611 Wire protocol commands are generators of objects to be serialized and
612 sent to the client.
612 sent to the client.
613
613
614 If a command raises an uncaught exception, this will be translated into
614 If a command raises an uncaught exception, this will be translated into
615 a command error.
615 a command error.
616
616
617 All commands can opt in to being cacheable by defining a function
617 All commands can opt in to being cacheable by defining a function
618 (``cachekeyfn``) that is called to derive a cache key. This function
618 (``cachekeyfn``) that is called to derive a cache key. This function
619 receives the same arguments as the command itself plus a ``cacher``
619 receives the same arguments as the command itself plus a ``cacher``
620 argument containing the active cacher for the request and returns a bytes
620 argument containing the active cacher for the request and returns a bytes
621 containing the key in a cache the response to this command may be cached
621 containing the key in a cache the response to this command may be cached
622 under.
622 under.
623 """
623 """
624 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
624 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
625 if v['version'] == 2}
625 if v['version'] == 2}
626
626
627 if permission not in ('push', 'pull'):
627 if permission not in ('push', 'pull'):
628 raise error.ProgrammingError('invalid wire protocol permission; '
628 raise error.ProgrammingError('invalid wire protocol permission; '
629 'got %s; expected "push" or "pull"' %
629 'got %s; expected "push" or "pull"' %
630 permission)
630 permission)
631
631
632 if args is None:
632 if args is None:
633 args = {}
633 args = {}
634
634
635 if not isinstance(args, dict):
635 if not isinstance(args, dict):
636 raise error.ProgrammingError('arguments for version 2 commands '
636 raise error.ProgrammingError('arguments for version 2 commands '
637 'must be declared as dicts')
637 'must be declared as dicts')
638
638
639 for arg, meta in args.items():
639 for arg, meta in args.items():
640 if arg == '*':
640 if arg == '*':
641 raise error.ProgrammingError('* argument name not allowed on '
641 raise error.ProgrammingError('* argument name not allowed on '
642 'version 2 commands')
642 'version 2 commands')
643
643
644 if not isinstance(meta, dict):
644 if not isinstance(meta, dict):
645 raise error.ProgrammingError('arguments for version 2 commands '
645 raise error.ProgrammingError('arguments for version 2 commands '
646 'must declare metadata as a dict')
646 'must declare metadata as a dict')
647
647
648 if 'type' not in meta:
648 if 'type' not in meta:
649 raise error.ProgrammingError('%s argument for command %s does not '
649 raise error.ProgrammingError('%s argument for command %s does not '
650 'declare type field' % (arg, name))
650 'declare type field' % (arg, name))
651
651
652 if meta['type'] not in ('bytes', 'int', 'list', 'dict', 'set', 'bool'):
652 if meta['type'] not in ('bytes', 'int', 'list', 'dict', 'set', 'bool'):
653 raise error.ProgrammingError('%s argument for command %s has '
653 raise error.ProgrammingError('%s argument for command %s has '
654 'illegal type: %s' % (arg, name,
654 'illegal type: %s' % (arg, name,
655 meta['type']))
655 meta['type']))
656
656
657 if 'example' not in meta:
657 if 'example' not in meta:
658 raise error.ProgrammingError('%s argument for command %s does not '
658 raise error.ProgrammingError('%s argument for command %s does not '
659 'declare example field' % (arg, name))
659 'declare example field' % (arg, name))
660
660
661 meta['required'] = 'default' not in meta
661 meta['required'] = 'default' not in meta
662
662
663 meta.setdefault('default', lambda: None)
663 meta.setdefault('default', lambda: None)
664 meta.setdefault('validvalues', None)
664 meta.setdefault('validvalues', None)
665
665
666 def register(func):
666 def register(func):
667 if name in COMMANDS:
667 if name in COMMANDS:
668 raise error.ProgrammingError('%s command already registered '
668 raise error.ProgrammingError('%s command already registered '
669 'for version 2' % name)
669 'for version 2' % name)
670
670
671 COMMANDS[name] = wireprototypes.commandentry(
671 COMMANDS[name] = wireprototypes.commandentry(
672 func, args=args, transports=transports, permission=permission,
672 func, args=args, transports=transports, permission=permission,
673 cachekeyfn=cachekeyfn)
673 cachekeyfn=cachekeyfn)
674
674
675 return func
675 return func
676
676
677 return register
677 return register
678
678
679 def makecommandcachekeyfn(command, localversion=None, allargs=False):
679 def makecommandcachekeyfn(command, localversion=None, allargs=False):
680 """Construct a cache key derivation function with common features.
680 """Construct a cache key derivation function with common features.
681
681
682 By default, the cache key is a hash of:
682 By default, the cache key is a hash of:
683
683
684 * The command name.
684 * The command name.
685 * A global cache version number.
685 * A global cache version number.
686 * A local cache version number (passed via ``localversion``).
686 * A local cache version number (passed via ``localversion``).
687 * All the arguments passed to the command.
687 * All the arguments passed to the command.
688 * The media type used.
688 * The media type used.
689 * Wire protocol version string.
689 * Wire protocol version string.
690 * The repository path.
690 * The repository path.
691 """
691 """
692 if not allargs:
692 if not allargs:
693 raise error.ProgrammingError('only allargs=True is currently supported')
693 raise error.ProgrammingError('only allargs=True is currently supported')
694
694
695 if localversion is None:
695 if localversion is None:
696 raise error.ProgrammingError('must set localversion argument value')
696 raise error.ProgrammingError('must set localversion argument value')
697
697
698 def cachekeyfn(repo, proto, cacher, **args):
698 def cachekeyfn(repo, proto, cacher, **args):
699 spec = COMMANDS[command]
699 spec = COMMANDS[command]
700
700
701 # Commands that mutate the repo can not be cached.
701 # Commands that mutate the repo can not be cached.
702 if spec.permission == 'push':
702 if spec.permission == 'push':
703 return None
703 return None
704
704
705 # TODO config option to disable caching.
705 # TODO config option to disable caching.
706
706
707 # Our key derivation strategy is to construct a data structure
707 # Our key derivation strategy is to construct a data structure
708 # holding everything that could influence cacheability and to hash
708 # holding everything that could influence cacheability and to hash
709 # the CBOR representation of that. Using CBOR seems like it might
709 # the CBOR representation of that. Using CBOR seems like it might
710 # be overkill. However, simpler hashing mechanisms are prone to
710 # be overkill. However, simpler hashing mechanisms are prone to
711 # duplicate input issues. e.g. if you just concatenate two values,
711 # duplicate input issues. e.g. if you just concatenate two values,
712 # "foo"+"bar" is identical to "fo"+"obar". Using CBOR provides
712 # "foo"+"bar" is identical to "fo"+"obar". Using CBOR provides
713 # "padding" between values and prevents these problems.
713 # "padding" between values and prevents these problems.
714
714
715 # Seed the hash with various data.
715 # Seed the hash with various data.
716 state = {
716 state = {
717 # To invalidate all cache keys.
717 # To invalidate all cache keys.
718 b'globalversion': GLOBAL_CACHE_VERSION,
718 b'globalversion': GLOBAL_CACHE_VERSION,
719 # More granular cache key invalidation.
719 # More granular cache key invalidation.
720 b'localversion': localversion,
720 b'localversion': localversion,
721 # Cache keys are segmented by command.
721 # Cache keys are segmented by command.
722 b'command': pycompat.sysbytes(command),
722 b'command': pycompat.sysbytes(command),
723 # Throw in the media type and API version strings so changes
723 # Throw in the media type and API version strings so changes
724 # to exchange semantics invalid cache.
724 # to exchange semantics invalid cache.
725 b'mediatype': FRAMINGTYPE,
725 b'mediatype': FRAMINGTYPE,
726 b'version': HTTP_WIREPROTO_V2,
726 b'version': HTTP_WIREPROTO_V2,
727 # So same requests for different repos don't share cache keys.
727 # So same requests for different repos don't share cache keys.
728 b'repo': repo.root,
728 b'repo': repo.root,
729 }
729 }
730
730
731 # The arguments passed to us will have already been normalized.
731 # The arguments passed to us will have already been normalized.
732 # Default values will be set, etc. This is important because it
732 # Default values will be set, etc. This is important because it
733 # means that it doesn't matter if clients send an explicit argument
733 # means that it doesn't matter if clients send an explicit argument
734 # or rely on the default value: it will all normalize to the same
734 # or rely on the default value: it will all normalize to the same
735 # set of arguments on the server and therefore the same cache key.
735 # set of arguments on the server and therefore the same cache key.
736 #
736 #
737 # Arguments by their very nature must support being encoded to CBOR.
737 # Arguments by their very nature must support being encoded to CBOR.
738 # And the CBOR encoder is deterministic. So we hash the arguments
738 # And the CBOR encoder is deterministic. So we hash the arguments
739 # by feeding the CBOR of their representation into the hasher.
739 # by feeding the CBOR of their representation into the hasher.
740 if allargs:
740 if allargs:
741 state[b'args'] = pycompat.byteskwargs(args)
741 state[b'args'] = pycompat.byteskwargs(args)
742
742
743 cacher.adjustcachekeystate(state)
743 cacher.adjustcachekeystate(state)
744
744
745 hasher = hashlib.sha1()
745 hasher = hashlib.sha1()
746 for chunk in cborutil.streamencode(state):
746 for chunk in cborutil.streamencode(state):
747 hasher.update(chunk)
747 hasher.update(chunk)
748
748
749 return pycompat.sysbytes(hasher.hexdigest())
749 return pycompat.sysbytes(hasher.hexdigest())
750
750
751 return cachekeyfn
751 return cachekeyfn
752
752
753 def makeresponsecacher(repo, proto, command, args, objencoderfn,
753 def makeresponsecacher(repo, proto, command, args, objencoderfn,
754 redirecttargets, redirecthashes):
754 redirecttargets, redirecthashes):
755 """Construct a cacher for a cacheable command.
755 """Construct a cacher for a cacheable command.
756
756
757 Returns an ``iwireprotocolcommandcacher`` instance.
757 Returns an ``iwireprotocolcommandcacher`` instance.
758
758
759 Extensions can monkeypatch this function to provide custom caching
759 Extensions can monkeypatch this function to provide custom caching
760 backends.
760 backends.
761 """
761 """
762 return None
762 return None
763
763
764 @wireprotocommand('branchmap', permission='pull')
764 @wireprotocommand('branchmap', permission='pull')
765 def branchmapv2(repo, proto):
765 def branchmapv2(repo, proto):
766 yield {encoding.fromlocal(k): v
766 yield {encoding.fromlocal(k): v
767 for k, v in repo.branchmap().iteritems()}
767 for k, v in repo.branchmap().iteritems()}
768
768
769 @wireprotocommand('capabilities', permission='pull')
769 @wireprotocommand('capabilities', permission='pull')
770 def capabilitiesv2(repo, proto):
770 def capabilitiesv2(repo, proto):
771 yield _capabilitiesv2(repo, proto)
771 yield _capabilitiesv2(repo, proto)
772
772
773 @wireprotocommand(
773 @wireprotocommand(
774 'changesetdata',
774 'changesetdata',
775 args={
775 args={
776 'noderange': {
776 'noderange': {
777 'type': 'list',
777 'type': 'list',
778 'default': lambda: None,
778 'default': lambda: None,
779 'example': [[b'0123456...'], [b'abcdef...']],
779 'example': [[b'0123456...'], [b'abcdef...']],
780 },
780 },
781 'nodes': {
781 'nodes': {
782 'type': 'list',
782 'type': 'list',
783 'default': lambda: None,
783 'default': lambda: None,
784 'example': [b'0123456...'],
784 'example': [b'0123456...'],
785 },
785 },
786 'nodesdepth': {
786 'nodesdepth': {
787 'type': 'int',
787 'type': 'int',
788 'default': lambda: None,
788 'default': lambda: None,
789 'example': 10,
789 'example': 10,
790 },
790 },
791 'fields': {
791 'fields': {
792 'type': 'set',
792 'type': 'set',
793 'default': set,
793 'default': set,
794 'example': {b'parents', b'revision'},
794 'example': {b'parents', b'revision'},
795 'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'},
795 'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'},
796 },
796 },
797 },
797 },
798 permission='pull')
798 permission='pull')
799 def changesetdata(repo, proto, noderange, nodes, nodesdepth, fields):
799 def changesetdata(repo, proto, noderange, nodes, nodesdepth, fields):
800 # TODO look for unknown fields and abort when they can't be serviced.
800 # TODO look for unknown fields and abort when they can't be serviced.
801 # This could probably be validated by dispatcher using validvalues.
801 # This could probably be validated by dispatcher using validvalues.
802
802
803 if noderange is None and nodes is None:
803 if noderange is None and nodes is None:
804 raise error.WireprotoCommandError(
804 raise error.WireprotoCommandError(
805 'noderange or nodes must be defined')
805 'noderange or nodes must be defined')
806
806
807 if nodesdepth is not None and nodes is None:
807 if nodesdepth is not None and nodes is None:
808 raise error.WireprotoCommandError(
808 raise error.WireprotoCommandError(
809 'nodesdepth requires the nodes argument')
809 'nodesdepth requires the nodes argument')
810
810
811 if noderange is not None:
811 if noderange is not None:
812 if len(noderange) != 2:
812 if len(noderange) != 2:
813 raise error.WireprotoCommandError(
813 raise error.WireprotoCommandError(
814 'noderange must consist of 2 elements')
814 'noderange must consist of 2 elements')
815
815
816 if not noderange[1]:
816 if not noderange[1]:
817 raise error.WireprotoCommandError(
817 raise error.WireprotoCommandError(
818 'heads in noderange request cannot be empty')
818 'heads in noderange request cannot be empty')
819
819
820 cl = repo.changelog
820 cl = repo.changelog
821 hasnode = cl.hasnode
821 hasnode = cl.hasnode
822
822
823 seen = set()
823 seen = set()
824 outgoing = []
824 outgoing = []
825
825
826 if nodes is not None:
826 if nodes is not None:
827 outgoing = [n for n in nodes if hasnode(n)]
827 outgoing = [n for n in nodes if hasnode(n)]
828
828
829 if nodesdepth:
829 if nodesdepth:
830 outgoing = [cl.node(r) for r in
830 outgoing = [cl.node(r) for r in
831 repo.revs(b'ancestors(%ln, %d)', outgoing,
831 repo.revs(b'ancestors(%ln, %d)', outgoing,
832 nodesdepth - 1)]
832 nodesdepth - 1)]
833
833
834 seen |= set(outgoing)
834 seen |= set(outgoing)
835
835
836 if noderange is not None:
836 if noderange is not None:
837 if noderange[0]:
837 if noderange[0]:
838 common = [n for n in noderange[0] if hasnode(n)]
838 common = [n for n in noderange[0] if hasnode(n)]
839 else:
839 else:
840 common = [nullid]
840 common = [nullid]
841
841
842 for n in discovery.outgoing(repo, common, noderange[1]).missing:
842 for n in discovery.outgoing(repo, common, noderange[1]).missing:
843 if n not in seen:
843 if n not in seen:
844 outgoing.append(n)
844 outgoing.append(n)
845 # Don't need to add to seen here because this is the final
845 # Don't need to add to seen here because this is the final
846 # source of nodes and there should be no duplicates in this
846 # source of nodes and there should be no duplicates in this
847 # list.
847 # list.
848
848
849 seen.clear()
849 seen.clear()
850 publishing = repo.publishing()
850 publishing = repo.publishing()
851
851
852 if outgoing:
852 if outgoing:
853 repo.hook('preoutgoing', throw=True, source='serve')
853 repo.hook('preoutgoing', throw=True, source='serve')
854
854
855 yield {
855 yield {
856 b'totalitems': len(outgoing),
856 b'totalitems': len(outgoing),
857 }
857 }
858
858
859 # The phases of nodes already transferred to the client may have changed
859 # The phases of nodes already transferred to the client may have changed
860 # since the client last requested data. We send phase-only records
860 # since the client last requested data. We send phase-only records
861 # for these revisions, if requested.
861 # for these revisions, if requested.
862 if b'phase' in fields and noderange is not None:
862 if b'phase' in fields and noderange is not None:
863 # TODO skip nodes whose phase will be reflected by a node in the
863 # TODO skip nodes whose phase will be reflected by a node in the
864 # outgoing set. This is purely an optimization to reduce data
864 # outgoing set. This is purely an optimization to reduce data
865 # size.
865 # size.
866 for node in noderange[0]:
866 for node in noderange[0]:
867 yield {
867 yield {
868 b'node': node,
868 b'node': node,
869 b'phase': b'public' if publishing else repo[node].phasestr()
869 b'phase': b'public' if publishing else repo[node].phasestr()
870 }
870 }
871
871
872 nodebookmarks = {}
872 nodebookmarks = {}
873 for mark, node in repo._bookmarks.items():
873 for mark, node in repo._bookmarks.items():
874 nodebookmarks.setdefault(node, set()).add(mark)
874 nodebookmarks.setdefault(node, set()).add(mark)
875
875
876 # It is already topologically sorted by revision number.
876 # It is already topologically sorted by revision number.
877 for node in outgoing:
877 for node in outgoing:
878 d = {
878 d = {
879 b'node': node,
879 b'node': node,
880 }
880 }
881
881
882 if b'parents' in fields:
882 if b'parents' in fields:
883 d[b'parents'] = cl.parents(node)
883 d[b'parents'] = cl.parents(node)
884
884
885 if b'phase' in fields:
885 if b'phase' in fields:
886 if publishing:
886 if publishing:
887 d[b'phase'] = b'public'
887 d[b'phase'] = b'public'
888 else:
888 else:
889 ctx = repo[node]
889 ctx = repo[node]
890 d[b'phase'] = ctx.phasestr()
890 d[b'phase'] = ctx.phasestr()
891
891
892 if b'bookmarks' in fields and node in nodebookmarks:
892 if b'bookmarks' in fields and node in nodebookmarks:
893 d[b'bookmarks'] = sorted(nodebookmarks[node])
893 d[b'bookmarks'] = sorted(nodebookmarks[node])
894 del nodebookmarks[node]
894 del nodebookmarks[node]
895
895
896 followingmeta = []
896 followingmeta = []
897 followingdata = []
897 followingdata = []
898
898
899 if b'revision' in fields:
899 if b'revision' in fields:
900 revisiondata = cl.revision(node, raw=True)
900 revisiondata = cl.revision(node, raw=True)
901 followingmeta.append((b'revision', len(revisiondata)))
901 followingmeta.append((b'revision', len(revisiondata)))
902 followingdata.append(revisiondata)
902 followingdata.append(revisiondata)
903
903
904 # TODO make it possible for extensions to wrap a function or register
904 # TODO make it possible for extensions to wrap a function or register
905 # a handler to service custom fields.
905 # a handler to service custom fields.
906
906
907 if followingmeta:
907 if followingmeta:
908 d[b'fieldsfollowing'] = followingmeta
908 d[b'fieldsfollowing'] = followingmeta
909
909
910 yield d
910 yield d
911
911
912 for extra in followingdata:
912 for extra in followingdata:
913 yield extra
913 yield extra
914
914
915 # If requested, send bookmarks from nodes that didn't have revision
915 # If requested, send bookmarks from nodes that didn't have revision
916 # data sent so receiver is aware of any bookmark updates.
916 # data sent so receiver is aware of any bookmark updates.
917 if b'bookmarks' in fields:
917 if b'bookmarks' in fields:
918 for node, marks in sorted(nodebookmarks.iteritems()):
918 for node, marks in sorted(nodebookmarks.iteritems()):
919 yield {
919 yield {
920 b'node': node,
920 b'node': node,
921 b'bookmarks': sorted(marks),
921 b'bookmarks': sorted(marks),
922 }
922 }
923
923
924 class FileAccessError(Exception):
924 class FileAccessError(Exception):
925 """Represents an error accessing a specific file."""
925 """Represents an error accessing a specific file."""
926
926
927 def __init__(self, path, msg, args):
927 def __init__(self, path, msg, args):
928 self.path = path
928 self.path = path
929 self.msg = msg
929 self.msg = msg
930 self.args = args
930 self.args = args
931
931
932 def getfilestore(repo, proto, path):
932 def getfilestore(repo, proto, path):
933 """Obtain a file storage object for use with wire protocol.
933 """Obtain a file storage object for use with wire protocol.
934
934
935 Exists as a standalone function so extensions can monkeypatch to add
935 Exists as a standalone function so extensions can monkeypatch to add
936 access control.
936 access control.
937 """
937 """
938 # This seems to work even if the file doesn't exist. So catch
938 # This seems to work even if the file doesn't exist. So catch
939 # "empty" files and return an error.
939 # "empty" files and return an error.
940 fl = repo.file(path)
940 fl = repo.file(path)
941
941
942 if not len(fl):
942 if not len(fl):
943 raise FileAccessError(path, 'unknown file: %s', (path,))
943 raise FileAccessError(path, 'unknown file: %s', (path,))
944
944
945 return fl
945 return fl
946
946
947 @wireprotocommand(
947 @wireprotocommand(
948 'filedata',
948 'filedata',
949 args={
949 args={
950 'haveparents': {
950 'haveparents': {
951 'type': 'bool',
951 'type': 'bool',
952 'default': lambda: False,
952 'default': lambda: False,
953 'example': True,
953 'example': True,
954 },
954 },
955 'nodes': {
955 'nodes': {
956 'type': 'list',
956 'type': 'list',
957 'example': [b'0123456...'],
957 'example': [b'0123456...'],
958 },
958 },
959 'fields': {
959 'fields': {
960 'type': 'set',
960 'type': 'set',
961 'default': set,
961 'default': set,
962 'example': {b'parents', b'revision'},
962 'example': {b'parents', b'revision'},
963 'validvalues': {b'parents', b'revision'},
963 'validvalues': {b'parents', b'revision'},
964 },
964 },
965 'path': {
965 'path': {
966 'type': 'bytes',
966 'type': 'bytes',
967 'example': b'foo.txt',
967 'example': b'foo.txt',
968 }
968 }
969 },
969 },
970 permission='pull',
970 permission='pull',
971 # TODO censoring a file revision won't invalidate the cache.
971 # TODO censoring a file revision won't invalidate the cache.
972 # Figure out a way to take censoring into account when deriving
972 # Figure out a way to take censoring into account when deriving
973 # the cache key.
973 # the cache key.
974 cachekeyfn=makecommandcachekeyfn('filedata', 1, allargs=True))
974 cachekeyfn=makecommandcachekeyfn('filedata', 1, allargs=True))
975 def filedata(repo, proto, haveparents, nodes, fields, path):
975 def filedata(repo, proto, haveparents, nodes, fields, path):
976 try:
976 try:
977 # Extensions may wish to access the protocol handler.
977 # Extensions may wish to access the protocol handler.
978 store = getfilestore(repo, proto, path)
978 store = getfilestore(repo, proto, path)
979 except FileAccessError as e:
979 except FileAccessError as e:
980 raise error.WireprotoCommandError(e.msg, e.args)
980 raise error.WireprotoCommandError(e.msg, e.args)
981
981
982 # Validate requested nodes.
982 # Validate requested nodes.
983 for node in nodes:
983 for node in nodes:
984 try:
984 try:
985 store.rev(node)
985 store.rev(node)
986 except error.LookupError:
986 except error.LookupError:
987 raise error.WireprotoCommandError('unknown file node: %s',
987 raise error.WireprotoCommandError('unknown file node: %s',
988 (hex(node),))
988 (hex(node),))
989
989
990 revisions = store.emitrevisions(nodes,
990 revisions = store.emitrevisions(nodes,
991 revisiondata=b'revision' in fields,
991 revisiondata=b'revision' in fields,
992 assumehaveparentrevisions=haveparents)
992 assumehaveparentrevisions=haveparents)
993
993
994 yield {
994 yield {
995 b'totalitems': len(nodes),
995 b'totalitems': len(nodes),
996 }
996 }
997
997
998 for revision in revisions:
998 for revision in revisions:
999 d = {
999 d = {
1000 b'node': revision.node,
1000 b'node': revision.node,
1001 }
1001 }
1002
1002
1003 if b'parents' in fields:
1003 if b'parents' in fields:
1004 d[b'parents'] = [revision.p1node, revision.p2node]
1004 d[b'parents'] = [revision.p1node, revision.p2node]
1005
1005
1006 followingmeta = []
1006 followingmeta = []
1007 followingdata = []
1007 followingdata = []
1008
1008
1009 if b'revision' in fields:
1009 if b'revision' in fields:
1010 if revision.revision is not None:
1010 if revision.revision is not None:
1011 followingmeta.append((b'revision', len(revision.revision)))
1011 followingmeta.append((b'revision', len(revision.revision)))
1012 followingdata.append(revision.revision)
1012 followingdata.append(revision.revision)
1013 else:
1013 else:
1014 d[b'deltabasenode'] = revision.basenode
1014 d[b'deltabasenode'] = revision.basenode
1015 followingmeta.append((b'delta', len(revision.delta)))
1015 followingmeta.append((b'delta', len(revision.delta)))
1016 followingdata.append(revision.delta)
1016 followingdata.append(revision.delta)
1017
1017
1018 if followingmeta:
1018 if followingmeta:
1019 d[b'fieldsfollowing'] = followingmeta
1019 d[b'fieldsfollowing'] = followingmeta
1020
1020
1021 yield d
1021 yield d
1022
1022
1023 for extra in followingdata:
1023 for extra in followingdata:
1024 yield extra
1024 yield extra
1025
1025
1026 @wireprotocommand(
1026 @wireprotocommand(
1027 'heads',
1027 'heads',
1028 args={
1028 args={
1029 'publiconly': {
1029 'publiconly': {
1030 'type': 'bool',
1030 'type': 'bool',
1031 'default': lambda: False,
1031 'default': lambda: False,
1032 'example': False,
1032 'example': False,
1033 },
1033 },
1034 },
1034 },
1035 permission='pull')
1035 permission='pull')
1036 def headsv2(repo, proto, publiconly):
1036 def headsv2(repo, proto, publiconly):
1037 if publiconly:
1037 if publiconly:
1038 repo = repo.filtered('immutable')
1038 repo = repo.filtered('immutable')
1039
1039
1040 yield repo.heads()
1040 yield repo.heads()
1041
1041
1042 @wireprotocommand(
1042 @wireprotocommand(
1043 'known',
1043 'known',
1044 args={
1044 args={
1045 'nodes': {
1045 'nodes': {
1046 'type': 'list',
1046 'type': 'list',
1047 'default': list,
1047 'default': list,
1048 'example': [b'deadbeef'],
1048 'example': [b'deadbeef'],
1049 },
1049 },
1050 },
1050 },
1051 permission='pull')
1051 permission='pull')
1052 def knownv2(repo, proto, nodes):
1052 def knownv2(repo, proto, nodes):
1053 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
1053 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
1054 yield result
1054 yield result
1055
1055
1056 @wireprotocommand(
1056 @wireprotocommand(
1057 'listkeys',
1057 'listkeys',
1058 args={
1058 args={
1059 'namespace': {
1059 'namespace': {
1060 'type': 'bytes',
1060 'type': 'bytes',
1061 'example': b'ns',
1061 'example': b'ns',
1062 },
1062 },
1063 },
1063 },
1064 permission='pull')
1064 permission='pull')
1065 def listkeysv2(repo, proto, namespace):
1065 def listkeysv2(repo, proto, namespace):
1066 keys = repo.listkeys(encoding.tolocal(namespace))
1066 keys = repo.listkeys(encoding.tolocal(namespace))
1067 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
1067 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
1068 for k, v in keys.iteritems()}
1068 for k, v in keys.iteritems()}
1069
1069
1070 yield keys
1070 yield keys
1071
1071
1072 @wireprotocommand(
1072 @wireprotocommand(
1073 'lookup',
1073 'lookup',
1074 args={
1074 args={
1075 'key': {
1075 'key': {
1076 'type': 'bytes',
1076 'type': 'bytes',
1077 'example': b'foo',
1077 'example': b'foo',
1078 },
1078 },
1079 },
1079 },
1080 permission='pull')
1080 permission='pull')
1081 def lookupv2(repo, proto, key):
1081 def lookupv2(repo, proto, key):
1082 key = encoding.tolocal(key)
1082 key = encoding.tolocal(key)
1083
1083
1084 # TODO handle exception.
1084 # TODO handle exception.
1085 node = repo.lookup(key)
1085 node = repo.lookup(key)
1086
1086
1087 yield node
1087 yield node
1088
1088
1089 @wireprotocommand(
1089 @wireprotocommand(
1090 'manifestdata',
1090 'manifestdata',
1091 args={
1091 args={
1092 'nodes': {
1092 'nodes': {
1093 'type': 'list',
1093 'type': 'list',
1094 'example': [b'0123456...'],
1094 'example': [b'0123456...'],
1095 },
1095 },
1096 'haveparents': {
1096 'haveparents': {
1097 'type': 'bool',
1097 'type': 'bool',
1098 'default': lambda: False,
1098 'default': lambda: False,
1099 'example': True,
1099 'example': True,
1100 },
1100 },
1101 'fields': {
1101 'fields': {
1102 'type': 'set',
1102 'type': 'set',
1103 'default': set,
1103 'default': set,
1104 'example': {b'parents', b'revision'},
1104 'example': {b'parents', b'revision'},
1105 'validvalues': {b'parents', b'revision'},
1105 'validvalues': {b'parents', b'revision'},
1106 },
1106 },
1107 'tree': {
1107 'tree': {
1108 'type': 'bytes',
1108 'type': 'bytes',
1109 'example': b'',
1109 'example': b'',
1110 },
1110 },
1111 },
1111 },
1112 permission='pull',
1112 permission='pull',
1113 cachekeyfn=makecommandcachekeyfn('manifestdata', 1, allargs=True))
1113 cachekeyfn=makecommandcachekeyfn('manifestdata', 1, allargs=True))
1114 def manifestdata(repo, proto, haveparents, nodes, fields, tree):
1114 def manifestdata(repo, proto, haveparents, nodes, fields, tree):
1115 store = repo.manifestlog.getstorage(tree)
1115 store = repo.manifestlog.getstorage(tree)
1116
1116
1117 # Validate the node is known and abort on unknown revisions.
1117 # Validate the node is known and abort on unknown revisions.
1118 for node in nodes:
1118 for node in nodes:
1119 try:
1119 try:
1120 store.rev(node)
1120 store.rev(node)
1121 except error.LookupError:
1121 except error.LookupError:
1122 raise error.WireprotoCommandError(
1122 raise error.WireprotoCommandError(
1123 'unknown node: %s', (node,))
1123 'unknown node: %s', (node,))
1124
1124
1125 revisions = store.emitrevisions(nodes,
1125 revisions = store.emitrevisions(nodes,
1126 revisiondata=b'revision' in fields,
1126 revisiondata=b'revision' in fields,
1127 assumehaveparentrevisions=haveparents)
1127 assumehaveparentrevisions=haveparents)
1128
1128
1129 yield {
1129 yield {
1130 b'totalitems': len(nodes),
1130 b'totalitems': len(nodes),
1131 }
1131 }
1132
1132
1133 for revision in revisions:
1133 for revision in revisions:
1134 d = {
1134 d = {
1135 b'node': revision.node,
1135 b'node': revision.node,
1136 }
1136 }
1137
1137
1138 if b'parents' in fields:
1138 if b'parents' in fields:
1139 d[b'parents'] = [revision.p1node, revision.p2node]
1139 d[b'parents'] = [revision.p1node, revision.p2node]
1140
1140
1141 followingmeta = []
1141 followingmeta = []
1142 followingdata = []
1142 followingdata = []
1143
1143
1144 if b'revision' in fields:
1144 if b'revision' in fields:
1145 if revision.revision is not None:
1145 if revision.revision is not None:
1146 followingmeta.append((b'revision', len(revision.revision)))
1146 followingmeta.append((b'revision', len(revision.revision)))
1147 followingdata.append(revision.revision)
1147 followingdata.append(revision.revision)
1148 else:
1148 else:
1149 d[b'deltabasenode'] = revision.basenode
1149 d[b'deltabasenode'] = revision.basenode
1150 followingmeta.append((b'delta', len(revision.delta)))
1150 followingmeta.append((b'delta', len(revision.delta)))
1151 followingdata.append(revision.delta)
1151 followingdata.append(revision.delta)
1152
1152
1153 if followingmeta:
1153 if followingmeta:
1154 d[b'fieldsfollowing'] = followingmeta
1154 d[b'fieldsfollowing'] = followingmeta
1155
1155
1156 yield d
1156 yield d
1157
1157
1158 for extra in followingdata:
1158 for extra in followingdata:
1159 yield extra
1159 yield extra
1160
1160
1161 @wireprotocommand(
1161 @wireprotocommand(
1162 'pushkey',
1162 'pushkey',
1163 args={
1163 args={
1164 'namespace': {
1164 'namespace': {
1165 'type': 'bytes',
1165 'type': 'bytes',
1166 'example': b'ns',
1166 'example': b'ns',
1167 },
1167 },
1168 'key': {
1168 'key': {
1169 'type': 'bytes',
1169 'type': 'bytes',
1170 'example': b'key',
1170 'example': b'key',
1171 },
1171 },
1172 'old': {
1172 'old': {
1173 'type': 'bytes',
1173 'type': 'bytes',
1174 'example': b'old',
1174 'example': b'old',
1175 },
1175 },
1176 'new': {
1176 'new': {
1177 'type': 'bytes',
1177 'type': 'bytes',
1178 'example': 'new',
1178 'example': 'new',
1179 },
1179 },
1180 },
1180 },
1181 permission='push')
1181 permission='push')
1182 def pushkeyv2(repo, proto, namespace, key, old, new):
1182 def pushkeyv2(repo, proto, namespace, key, old, new):
1183 # TODO handle ui output redirection
1183 # TODO handle ui output redirection
1184 yield repo.pushkey(encoding.tolocal(namespace),
1184 yield repo.pushkey(encoding.tolocal(namespace),
1185 encoding.tolocal(key),
1185 encoding.tolocal(key),
1186 encoding.tolocal(old),
1186 encoding.tolocal(old),
1187 encoding.tolocal(new))
1187 encoding.tolocal(new))
@@ -1,620 +1,711
1 #require no-chg
1 #require no-chg
2
2
3 $ . $TESTDIR/wireprotohelpers.sh
3 $ . $TESTDIR/wireprotohelpers.sh
4 $ enabledummycommands
4 $ enabledummycommands
5
5
6 $ hg init server
6 $ hg init server
7 $ cat > server/.hg/hgrc << EOF
7 $ cat > server/.hg/hgrc << EOF
8 > [experimental]
8 > [experimental]
9 > web.apiserver = true
9 > web.apiserver = true
10 > EOF
10 > EOF
11 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
11 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
12 $ cat hg.pid > $DAEMON_PIDS
12 $ cat hg.pid > $DAEMON_PIDS
13
13
14 HTTP v2 protocol not enabled by default
14 HTTP v2 protocol not enabled by default
15
15
16 $ sendhttpraw << EOF
16 $ sendhttpraw << EOF
17 > httprequest GET api/$HTTPV2
17 > httprequest GET api/$HTTPV2
18 > user-agent: test
18 > user-agent: test
19 > EOF
19 > EOF
20 using raw connection to peer
20 using raw connection to peer
21 s> GET /api/exp-http-v2-0002 HTTP/1.1\r\n
21 s> GET /api/exp-http-v2-0002 HTTP/1.1\r\n
22 s> Accept-Encoding: identity\r\n
22 s> Accept-Encoding: identity\r\n
23 s> user-agent: test\r\n
23 s> user-agent: test\r\n
24 s> host: $LOCALIP:$HGPORT\r\n (glob)
24 s> host: $LOCALIP:$HGPORT\r\n (glob)
25 s> \r\n
25 s> \r\n
26 s> makefile('rb', None)
26 s> makefile('rb', None)
27 s> HTTP/1.1 404 Not Found\r\n
27 s> HTTP/1.1 404 Not Found\r\n
28 s> Server: testing stub value\r\n
28 s> Server: testing stub value\r\n
29 s> Date: $HTTP_DATE$\r\n
29 s> Date: $HTTP_DATE$\r\n
30 s> Content-Type: text/plain\r\n
30 s> Content-Type: text/plain\r\n
31 s> Content-Length: 33\r\n
31 s> Content-Length: 33\r\n
32 s> \r\n
32 s> \r\n
33 s> API exp-http-v2-0002 not enabled\n
33 s> API exp-http-v2-0002 not enabled\n
34
34
35 Restart server with support for HTTP v2 API
35 Restart server with support for HTTP v2 API
36
36
37 $ killdaemons.py
37 $ killdaemons.py
38 $ enablehttpv2 server
38 $ enablehttpv2 server
39 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
39 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
40 $ cat hg.pid > $DAEMON_PIDS
40 $ cat hg.pid > $DAEMON_PIDS
41
41
42 Request to unknown command yields 404
42 Request to unknown command yields 404
43
43
44 $ sendhttpraw << EOF
44 $ sendhttpraw << EOF
45 > httprequest POST api/$HTTPV2/ro/badcommand
45 > httprequest POST api/$HTTPV2/ro/badcommand
46 > user-agent: test
46 > user-agent: test
47 > EOF
47 > EOF
48 using raw connection to peer
48 using raw connection to peer
49 s> POST /api/exp-http-v2-0002/ro/badcommand HTTP/1.1\r\n
49 s> POST /api/exp-http-v2-0002/ro/badcommand HTTP/1.1\r\n
50 s> Accept-Encoding: identity\r\n
50 s> Accept-Encoding: identity\r\n
51 s> user-agent: test\r\n
51 s> user-agent: test\r\n
52 s> host: $LOCALIP:$HGPORT\r\n (glob)
52 s> host: $LOCALIP:$HGPORT\r\n (glob)
53 s> \r\n
53 s> \r\n
54 s> makefile('rb', None)
54 s> makefile('rb', None)
55 s> HTTP/1.1 404 Not Found\r\n
55 s> HTTP/1.1 404 Not Found\r\n
56 s> Server: testing stub value\r\n
56 s> Server: testing stub value\r\n
57 s> Date: $HTTP_DATE$\r\n
57 s> Date: $HTTP_DATE$\r\n
58 s> Content-Type: text/plain\r\n
58 s> Content-Type: text/plain\r\n
59 s> Content-Length: 42\r\n
59 s> Content-Length: 42\r\n
60 s> \r\n
60 s> \r\n
61 s> unknown wire protocol command: badcommand\n
61 s> unknown wire protocol command: badcommand\n
62
62
63 GET to read-only command yields a 405
63 GET to read-only command yields a 405
64
64
65 $ sendhttpraw << EOF
65 $ sendhttpraw << EOF
66 > httprequest GET api/$HTTPV2/ro/customreadonly
66 > httprequest GET api/$HTTPV2/ro/customreadonly
67 > user-agent: test
67 > user-agent: test
68 > EOF
68 > EOF
69 using raw connection to peer
69 using raw connection to peer
70 s> GET /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
70 s> GET /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
71 s> Accept-Encoding: identity\r\n
71 s> Accept-Encoding: identity\r\n
72 s> user-agent: test\r\n
72 s> user-agent: test\r\n
73 s> host: $LOCALIP:$HGPORT\r\n (glob)
73 s> host: $LOCALIP:$HGPORT\r\n (glob)
74 s> \r\n
74 s> \r\n
75 s> makefile('rb', None)
75 s> makefile('rb', None)
76 s> HTTP/1.1 405 Method Not Allowed\r\n
76 s> HTTP/1.1 405 Method Not Allowed\r\n
77 s> Server: testing stub value\r\n
77 s> Server: testing stub value\r\n
78 s> Date: $HTTP_DATE$\r\n
78 s> Date: $HTTP_DATE$\r\n
79 s> Allow: POST\r\n
79 s> Allow: POST\r\n
80 s> Content-Length: 30\r\n
80 s> Content-Length: 30\r\n
81 s> \r\n
81 s> \r\n
82 s> commands require POST requests
82 s> commands require POST requests
83
83
84 Missing Accept header results in 406
84 Missing Accept header results in 406
85
85
86 $ sendhttpraw << EOF
86 $ sendhttpraw << EOF
87 > httprequest POST api/$HTTPV2/ro/customreadonly
87 > httprequest POST api/$HTTPV2/ro/customreadonly
88 > user-agent: test
88 > user-agent: test
89 > EOF
89 > EOF
90 using raw connection to peer
90 using raw connection to peer
91 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
91 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
92 s> Accept-Encoding: identity\r\n
92 s> Accept-Encoding: identity\r\n
93 s> user-agent: test\r\n
93 s> user-agent: test\r\n
94 s> host: $LOCALIP:$HGPORT\r\n (glob)
94 s> host: $LOCALIP:$HGPORT\r\n (glob)
95 s> \r\n
95 s> \r\n
96 s> makefile('rb', None)
96 s> makefile('rb', None)
97 s> HTTP/1.1 406 Not Acceptable\r\n
97 s> HTTP/1.1 406 Not Acceptable\r\n
98 s> Server: testing stub value\r\n
98 s> Server: testing stub value\r\n
99 s> Date: $HTTP_DATE$\r\n
99 s> Date: $HTTP_DATE$\r\n
100 s> Content-Type: text/plain\r\n
100 s> Content-Type: text/plain\r\n
101 s> Content-Length: 85\r\n
101 s> Content-Length: 85\r\n
102 s> \r\n
102 s> \r\n
103 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
103 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0006\n
104
104
105 Bad Accept header results in 406
105 Bad Accept header results in 406
106
106
107 $ sendhttpraw << EOF
107 $ sendhttpraw << EOF
108 > httprequest POST api/$HTTPV2/ro/customreadonly
108 > httprequest POST api/$HTTPV2/ro/customreadonly
109 > accept: invalid
109 > accept: invalid
110 > user-agent: test
110 > user-agent: test
111 > EOF
111 > EOF
112 using raw connection to peer
112 using raw connection to peer
113 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
113 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
114 s> Accept-Encoding: identity\r\n
114 s> Accept-Encoding: identity\r\n
115 s> accept: invalid\r\n
115 s> accept: invalid\r\n
116 s> user-agent: test\r\n
116 s> user-agent: test\r\n
117 s> host: $LOCALIP:$HGPORT\r\n (glob)
117 s> host: $LOCALIP:$HGPORT\r\n (glob)
118 s> \r\n
118 s> \r\n
119 s> makefile('rb', None)
119 s> makefile('rb', None)
120 s> HTTP/1.1 406 Not Acceptable\r\n
120 s> HTTP/1.1 406 Not Acceptable\r\n
121 s> Server: testing stub value\r\n
121 s> Server: testing stub value\r\n
122 s> Date: $HTTP_DATE$\r\n
122 s> Date: $HTTP_DATE$\r\n
123 s> Content-Type: text/plain\r\n
123 s> Content-Type: text/plain\r\n
124 s> Content-Length: 85\r\n
124 s> Content-Length: 85\r\n
125 s> \r\n
125 s> \r\n
126 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
126 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0006\n
127
127
128 Bad Content-Type header results in 415
128 Bad Content-Type header results in 415
129
129
130 $ sendhttpraw << EOF
130 $ sendhttpraw << EOF
131 > httprequest POST api/$HTTPV2/ro/customreadonly
131 > httprequest POST api/$HTTPV2/ro/customreadonly
132 > accept: $MEDIATYPE
132 > accept: $MEDIATYPE
133 > user-agent: test
133 > user-agent: test
134 > content-type: badmedia
134 > content-type: badmedia
135 > EOF
135 > EOF
136 using raw connection to peer
136 using raw connection to peer
137 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
137 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
138 s> Accept-Encoding: identity\r\n
138 s> Accept-Encoding: identity\r\n
139 s> accept: application/mercurial-exp-framing-0005\r\n
139 s> accept: application/mercurial-exp-framing-0006\r\n
140 s> content-type: badmedia\r\n
140 s> content-type: badmedia\r\n
141 s> user-agent: test\r\n
141 s> user-agent: test\r\n
142 s> host: $LOCALIP:$HGPORT\r\n (glob)
142 s> host: $LOCALIP:$HGPORT\r\n (glob)
143 s> \r\n
143 s> \r\n
144 s> makefile('rb', None)
144 s> makefile('rb', None)
145 s> HTTP/1.1 415 Unsupported Media Type\r\n
145 s> HTTP/1.1 415 Unsupported Media Type\r\n
146 s> Server: testing stub value\r\n
146 s> Server: testing stub value\r\n
147 s> Date: $HTTP_DATE$\r\n
147 s> Date: $HTTP_DATE$\r\n
148 s> Content-Type: text/plain\r\n
148 s> Content-Type: text/plain\r\n
149 s> Content-Length: 88\r\n
149 s> Content-Length: 88\r\n
150 s> \r\n
150 s> \r\n
151 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0005\n
151 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0006\n
152
152
153 Request to read-only command works out of the box
153 Request to read-only command works out of the box
154
154
155 $ sendhttpraw << EOF
155 $ sendhttpraw << EOF
156 > httprequest POST api/$HTTPV2/ro/customreadonly
156 > httprequest POST api/$HTTPV2/ro/customreadonly
157 > accept: $MEDIATYPE
157 > accept: $MEDIATYPE
158 > content-type: $MEDIATYPE
158 > content-type: $MEDIATYPE
159 > user-agent: test
159 > user-agent: test
160 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
160 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
161 > EOF
161 > EOF
162 using raw connection to peer
162 using raw connection to peer
163 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
163 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
164 s> Accept-Encoding: identity\r\n
164 s> Accept-Encoding: identity\r\n
165 s> *\r\n (glob)
165 s> *\r\n (glob)
166 s> content-type: application/mercurial-exp-framing-0005\r\n
166 s> content-type: application/mercurial-exp-framing-0006\r\n
167 s> user-agent: test\r\n
167 s> user-agent: test\r\n
168 s> content-length: 29\r\n
168 s> content-length: 29\r\n
169 s> host: $LOCALIP:$HGPORT\r\n (glob)
169 s> host: $LOCALIP:$HGPORT\r\n (glob)
170 s> \r\n
170 s> \r\n
171 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
171 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
172 s> makefile('rb', None)
172 s> makefile('rb', None)
173 s> HTTP/1.1 200 OK\r\n
173 s> HTTP/1.1 200 OK\r\n
174 s> Server: testing stub value\r\n
174 s> Server: testing stub value\r\n
175 s> Date: $HTTP_DATE$\r\n
175 s> Date: $HTTP_DATE$\r\n
176 s> Content-Type: application/mercurial-exp-framing-0005\r\n
176 s> Content-Type: application/mercurial-exp-framing-0006\r\n
177 s> Transfer-Encoding: chunked\r\n
177 s> Transfer-Encoding: chunked\r\n
178 s> \r\n
178 s> \r\n
179 s> 13\r\n
179 s> 13\r\n
180 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
180 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
181 s> \r\n
181 s> \r\n
182 s> 27\r\n
182 s> 27\r\n
183 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
183 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
184 s> \r\n
184 s> \r\n
185 s> 8\r\n
185 s> 8\r\n
186 s> \x00\x00\x00\x01\x00\x02\x002
186 s> \x00\x00\x00\x01\x00\x02\x002
187 s> \r\n
187 s> \r\n
188 s> 0\r\n
188 s> 0\r\n
189 s> \r\n
189 s> \r\n
190
190
191 $ sendhttpv2peerverbose << EOF
191 $ sendhttpv2peerverbose << EOF
192 > command customreadonly
192 > command customreadonly
193 > EOF
193 > EOF
194 creating http peer for wire protocol version 2
194 creating http peer for wire protocol version 2
195 sending customreadonly command
195 sending customreadonly command
196 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
196 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
197 s> Accept-Encoding: identity\r\n
197 s> Accept-Encoding: identity\r\n
198 s> accept: application/mercurial-exp-framing-0005\r\n
198 s> accept: application/mercurial-exp-framing-0006\r\n
199 s> content-type: application/mercurial-exp-framing-0005\r\n
199 s> content-type: application/mercurial-exp-framing-0006\r\n
200 s> content-length: 29\r\n
200 s> content-length: 65\r\n
201 s> host: $LOCALIP:$HGPORT\r\n (glob)
201 s> host: $LOCALIP:$HGPORT\r\n (glob)
202 s> user-agent: Mercurial debugwireproto\r\n
202 s> user-agent: Mercurial debugwireproto\r\n
203 s> \r\n
203 s> \r\n
204 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
204 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x15\x00\x00\x01\x00\x01\x00\x11\xa1DnameNcustomreadonly
205 s> makefile('rb', None)
205 s> makefile('rb', None)
206 s> HTTP/1.1 200 OK\r\n
206 s> HTTP/1.1 200 OK\r\n
207 s> Server: testing stub value\r\n
207 s> Server: testing stub value\r\n
208 s> Date: $HTTP_DATE$\r\n
208 s> Date: $HTTP_DATE$\r\n
209 s> Content-Type: application/mercurial-exp-framing-0005\r\n
209 s> Content-Type: application/mercurial-exp-framing-0006\r\n
210 s> Transfer-Encoding: chunked\r\n
210 s> Transfer-Encoding: chunked\r\n
211 s> \r\n
211 s> \r\n
212 s> 13\r\n
212 s> 13\r\n
213 s> \x0b\x00\x00\x01\x00\x02\x011
213 s> \x0b\x00\x00\x01\x00\x02\x011
214 s> \xa1FstatusBok
214 s> \xa1FstatusBok
215 s> \r\n
215 s> \r\n
216 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
216 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
217 s> 27\r\n
217 s> 27\r\n
218 s> \x1f\x00\x00\x01\x00\x02\x001
218 s> \x1f\x00\x00\x01\x00\x02\x001
219 s> X\x1dcustomreadonly bytes response
219 s> X\x1dcustomreadonly bytes response
220 s> \r\n
220 s> \r\n
221 received frame(size=31; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
221 received frame(size=31; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
222 s> 8\r\n
222 s> 8\r\n
223 s> \x00\x00\x00\x01\x00\x02\x002
223 s> \x00\x00\x00\x01\x00\x02\x002
224 s> \r\n
224 s> \r\n
225 s> 0\r\n
225 s> 0\r\n
226 s> \r\n
226 s> \r\n
227 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
227 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
228 response: gen[
228 response: gen[
229 b'customreadonly bytes response'
229 b'customreadonly bytes response'
230 ]
230 ]
231 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
231 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
232
232
233 Request to read-write command fails because server is read-only by default
233 Request to read-write command fails because server is read-only by default
234
234
235 GET to read-write request yields 405
235 GET to read-write request yields 405
236
236
237 $ sendhttpraw << EOF
237 $ sendhttpraw << EOF
238 > httprequest GET api/$HTTPV2/rw/customreadonly
238 > httprequest GET api/$HTTPV2/rw/customreadonly
239 > user-agent: test
239 > user-agent: test
240 > EOF
240 > EOF
241 using raw connection to peer
241 using raw connection to peer
242 s> GET /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
242 s> GET /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
243 s> Accept-Encoding: identity\r\n
243 s> Accept-Encoding: identity\r\n
244 s> user-agent: test\r\n
244 s> user-agent: test\r\n
245 s> host: $LOCALIP:$HGPORT\r\n (glob)
245 s> host: $LOCALIP:$HGPORT\r\n (glob)
246 s> \r\n
246 s> \r\n
247 s> makefile('rb', None)
247 s> makefile('rb', None)
248 s> HTTP/1.1 405 Method Not Allowed\r\n
248 s> HTTP/1.1 405 Method Not Allowed\r\n
249 s> Server: testing stub value\r\n
249 s> Server: testing stub value\r\n
250 s> Date: $HTTP_DATE$\r\n
250 s> Date: $HTTP_DATE$\r\n
251 s> Allow: POST\r\n
251 s> Allow: POST\r\n
252 s> Content-Length: 30\r\n
252 s> Content-Length: 30\r\n
253 s> \r\n
253 s> \r\n
254 s> commands require POST requests
254 s> commands require POST requests
255
255
256 Even for unknown commands
256 Even for unknown commands
257
257
258 $ sendhttpraw << EOF
258 $ sendhttpraw << EOF
259 > httprequest GET api/$HTTPV2/rw/badcommand
259 > httprequest GET api/$HTTPV2/rw/badcommand
260 > user-agent: test
260 > user-agent: test
261 > EOF
261 > EOF
262 using raw connection to peer
262 using raw connection to peer
263 s> GET /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
263 s> GET /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
264 s> Accept-Encoding: identity\r\n
264 s> Accept-Encoding: identity\r\n
265 s> user-agent: test\r\n
265 s> user-agent: test\r\n
266 s> host: $LOCALIP:$HGPORT\r\n (glob)
266 s> host: $LOCALIP:$HGPORT\r\n (glob)
267 s> \r\n
267 s> \r\n
268 s> makefile('rb', None)
268 s> makefile('rb', None)
269 s> HTTP/1.1 405 Method Not Allowed\r\n
269 s> HTTP/1.1 405 Method Not Allowed\r\n
270 s> Server: testing stub value\r\n
270 s> Server: testing stub value\r\n
271 s> Date: $HTTP_DATE$\r\n
271 s> Date: $HTTP_DATE$\r\n
272 s> Allow: POST\r\n
272 s> Allow: POST\r\n
273 s> Content-Length: 30\r\n
273 s> Content-Length: 30\r\n
274 s> \r\n
274 s> \r\n
275 s> commands require POST requests
275 s> commands require POST requests
276
276
277 SSL required by default
277 SSL required by default
278
278
279 $ sendhttpraw << EOF
279 $ sendhttpraw << EOF
280 > httprequest POST api/$HTTPV2/rw/customreadonly
280 > httprequest POST api/$HTTPV2/rw/customreadonly
281 > user-agent: test
281 > user-agent: test
282 > EOF
282 > EOF
283 using raw connection to peer
283 using raw connection to peer
284 s> POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
284 s> POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
285 s> Accept-Encoding: identity\r\n
285 s> Accept-Encoding: identity\r\n
286 s> user-agent: test\r\n
286 s> user-agent: test\r\n
287 s> host: $LOCALIP:$HGPORT\r\n (glob)
287 s> host: $LOCALIP:$HGPORT\r\n (glob)
288 s> \r\n
288 s> \r\n
289 s> makefile('rb', None)
289 s> makefile('rb', None)
290 s> HTTP/1.1 403 ssl required\r\n
290 s> HTTP/1.1 403 ssl required\r\n
291 s> Server: testing stub value\r\n
291 s> Server: testing stub value\r\n
292 s> Date: $HTTP_DATE$\r\n
292 s> Date: $HTTP_DATE$\r\n
293 s> Content-Length: 17\r\n
293 s> Content-Length: 17\r\n
294 s> \r\n
294 s> \r\n
295 s> permission denied
295 s> permission denied
296
296
297 Restart server to allow non-ssl read-write operations
297 Restart server to allow non-ssl read-write operations
298
298
299 $ killdaemons.py
299 $ killdaemons.py
300 $ cat > server/.hg/hgrc << EOF
300 $ cat > server/.hg/hgrc << EOF
301 > [experimental]
301 > [experimental]
302 > web.apiserver = true
302 > web.apiserver = true
303 > web.api.http-v2 = true
303 > web.api.http-v2 = true
304 > [web]
304 > [web]
305 > push_ssl = false
305 > push_ssl = false
306 > allow-push = *
306 > allow-push = *
307 > EOF
307 > EOF
308
308
309 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
309 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
310 $ cat hg.pid > $DAEMON_PIDS
310 $ cat hg.pid > $DAEMON_PIDS
311
311
312 Authorized request for valid read-write command works
312 Authorized request for valid read-write command works
313
313
314 $ sendhttpraw << EOF
314 $ sendhttpraw << EOF
315 > httprequest POST api/$HTTPV2/rw/customreadonly
315 > httprequest POST api/$HTTPV2/rw/customreadonly
316 > user-agent: test
316 > user-agent: test
317 > accept: $MEDIATYPE
317 > accept: $MEDIATYPE
318 > content-type: $MEDIATYPE
318 > content-type: $MEDIATYPE
319 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
319 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
320 > EOF
320 > EOF
321 using raw connection to peer
321 using raw connection to peer
322 s> POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
322 s> POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
323 s> Accept-Encoding: identity\r\n
323 s> Accept-Encoding: identity\r\n
324 s> accept: application/mercurial-exp-framing-0005\r\n
324 s> accept: application/mercurial-exp-framing-0006\r\n
325 s> content-type: application/mercurial-exp-framing-0005\r\n
325 s> content-type: application/mercurial-exp-framing-0006\r\n
326 s> user-agent: test\r\n
326 s> user-agent: test\r\n
327 s> content-length: 29\r\n
327 s> content-length: 29\r\n
328 s> host: $LOCALIP:$HGPORT\r\n (glob)
328 s> host: $LOCALIP:$HGPORT\r\n (glob)
329 s> \r\n
329 s> \r\n
330 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
330 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
331 s> makefile('rb', None)
331 s> makefile('rb', None)
332 s> HTTP/1.1 200 OK\r\n
332 s> HTTP/1.1 200 OK\r\n
333 s> Server: testing stub value\r\n
333 s> Server: testing stub value\r\n
334 s> Date: $HTTP_DATE$\r\n
334 s> Date: $HTTP_DATE$\r\n
335 s> Content-Type: application/mercurial-exp-framing-0005\r\n
335 s> Content-Type: application/mercurial-exp-framing-0006\r\n
336 s> Transfer-Encoding: chunked\r\n
336 s> Transfer-Encoding: chunked\r\n
337 s> \r\n
337 s> \r\n
338 s> 13\r\n
338 s> 13\r\n
339 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
339 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
340 s> \r\n
340 s> \r\n
341 s> 27\r\n
341 s> 27\r\n
342 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
342 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
343 s> \r\n
343 s> \r\n
344 s> 8\r\n
344 s> 8\r\n
345 s> \x00\x00\x00\x01\x00\x02\x002
345 s> \x00\x00\x00\x01\x00\x02\x002
346 s> \r\n
346 s> \r\n
347 s> 0\r\n
347 s> 0\r\n
348 s> \r\n
348 s> \r\n
349
349
350 Authorized request for unknown command is rejected
350 Authorized request for unknown command is rejected
351
351
352 $ sendhttpraw << EOF
352 $ sendhttpraw << EOF
353 > httprequest POST api/$HTTPV2/rw/badcommand
353 > httprequest POST api/$HTTPV2/rw/badcommand
354 > user-agent: test
354 > user-agent: test
355 > accept: $MEDIATYPE
355 > accept: $MEDIATYPE
356 > EOF
356 > EOF
357 using raw connection to peer
357 using raw connection to peer
358 s> POST /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
358 s> POST /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
359 s> Accept-Encoding: identity\r\n
359 s> Accept-Encoding: identity\r\n
360 s> accept: application/mercurial-exp-framing-0005\r\n
360 s> accept: application/mercurial-exp-framing-0006\r\n
361 s> user-agent: test\r\n
361 s> user-agent: test\r\n
362 s> host: $LOCALIP:$HGPORT\r\n (glob)
362 s> host: $LOCALIP:$HGPORT\r\n (glob)
363 s> \r\n
363 s> \r\n
364 s> makefile('rb', None)
364 s> makefile('rb', None)
365 s> HTTP/1.1 404 Not Found\r\n
365 s> HTTP/1.1 404 Not Found\r\n
366 s> Server: testing stub value\r\n
366 s> Server: testing stub value\r\n
367 s> Date: $HTTP_DATE$\r\n
367 s> Date: $HTTP_DATE$\r\n
368 s> Content-Type: text/plain\r\n
368 s> Content-Type: text/plain\r\n
369 s> Content-Length: 42\r\n
369 s> Content-Length: 42\r\n
370 s> \r\n
370 s> \r\n
371 s> unknown wire protocol command: badcommand\n
371 s> unknown wire protocol command: badcommand\n
372
372
373 debugreflect isn't enabled by default
373 debugreflect isn't enabled by default
374
374
375 $ sendhttpraw << EOF
375 $ sendhttpraw << EOF
376 > httprequest POST api/$HTTPV2/ro/debugreflect
376 > httprequest POST api/$HTTPV2/ro/debugreflect
377 > user-agent: test
377 > user-agent: test
378 > EOF
378 > EOF
379 using raw connection to peer
379 using raw connection to peer
380 s> POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
380 s> POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
381 s> Accept-Encoding: identity\r\n
381 s> Accept-Encoding: identity\r\n
382 s> user-agent: test\r\n
382 s> user-agent: test\r\n
383 s> host: $LOCALIP:$HGPORT\r\n (glob)
383 s> host: $LOCALIP:$HGPORT\r\n (glob)
384 s> \r\n
384 s> \r\n
385 s> makefile('rb', None)
385 s> makefile('rb', None)
386 s> HTTP/1.1 404 Not Found\r\n
386 s> HTTP/1.1 404 Not Found\r\n
387 s> Server: testing stub value\r\n
387 s> Server: testing stub value\r\n
388 s> Date: $HTTP_DATE$\r\n
388 s> Date: $HTTP_DATE$\r\n
389 s> Content-Type: text/plain\r\n
389 s> Content-Type: text/plain\r\n
390 s> Content-Length: 34\r\n
390 s> Content-Length: 34\r\n
391 s> \r\n
391 s> \r\n
392 s> debugreflect service not available
392 s> debugreflect service not available
393
393
394 Restart server to get debugreflect endpoint
394 Restart server to get debugreflect endpoint
395
395
396 $ killdaemons.py
396 $ killdaemons.py
397 $ cat > server/.hg/hgrc << EOF
397 $ cat > server/.hg/hgrc << EOF
398 > [experimental]
398 > [experimental]
399 > web.apiserver = true
399 > web.apiserver = true
400 > web.api.debugreflect = true
400 > web.api.debugreflect = true
401 > web.api.http-v2 = true
401 > web.api.http-v2 = true
402 > [web]
402 > [web]
403 > push_ssl = false
403 > push_ssl = false
404 > allow-push = *
404 > allow-push = *
405 > EOF
405 > EOF
406
406
407 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
407 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
408 $ cat hg.pid > $DAEMON_PIDS
408 $ cat hg.pid > $DAEMON_PIDS
409
409
410 Command frames can be reflected via debugreflect
410 Command frames can be reflected via debugreflect
411
411
412 $ sendhttpraw << EOF
412 $ sendhttpraw << EOF
413 > httprequest POST api/$HTTPV2/ro/debugreflect
413 > httprequest POST api/$HTTPV2/ro/debugreflect
414 > accept: $MEDIATYPE
414 > accept: $MEDIATYPE
415 > content-type: $MEDIATYPE
415 > content-type: $MEDIATYPE
416 > user-agent: test
416 > user-agent: test
417 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
417 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
418 > EOF
418 > EOF
419 using raw connection to peer
419 using raw connection to peer
420 s> POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
420 s> POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
421 s> Accept-Encoding: identity\r\n
421 s> Accept-Encoding: identity\r\n
422 s> accept: application/mercurial-exp-framing-0005\r\n
422 s> accept: application/mercurial-exp-framing-0006\r\n
423 s> content-type: application/mercurial-exp-framing-0005\r\n
423 s> content-type: application/mercurial-exp-framing-0006\r\n
424 s> user-agent: test\r\n
424 s> user-agent: test\r\n
425 s> content-length: 47\r\n
425 s> content-length: 47\r\n
426 s> host: $LOCALIP:$HGPORT\r\n (glob)
426 s> host: $LOCALIP:$HGPORT\r\n (glob)
427 s> \r\n
427 s> \r\n
428 s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1
428 s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1
429 s> makefile('rb', None)
429 s> makefile('rb', None)
430 s> HTTP/1.1 200 OK\r\n
430 s> HTTP/1.1 200 OK\r\n
431 s> Server: testing stub value\r\n
431 s> Server: testing stub value\r\n
432 s> Date: $HTTP_DATE$\r\n
432 s> Date: $HTTP_DATE$\r\n
433 s> Content-Type: text/plain\r\n
433 s> Content-Type: text/plain\r\n
434 s> Content-Length: 223\r\n
434 s> Content-Length: 223\r\n
435 s> \r\n
435 s> \r\n
436 s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
436 s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
437 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "redirect": null, "requestid": 1}]\n
437 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "redirect": null, "requestid": 1}]\n
438 s> received: <no frame>\n
438 s> received: <no frame>\n
439 s> {"action": "noop"}
439 s> {"action": "noop"}
440
440
441 Multiple requests to regular command URL are not allowed
441 Multiple requests to regular command URL are not allowed
442
442
443 $ sendhttpraw << EOF
443 $ sendhttpraw << EOF
444 > httprequest POST api/$HTTPV2/ro/customreadonly
444 > httprequest POST api/$HTTPV2/ro/customreadonly
445 > accept: $MEDIATYPE
445 > accept: $MEDIATYPE
446 > content-type: $MEDIATYPE
446 > content-type: $MEDIATYPE
447 > user-agent: test
447 > user-agent: test
448 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
448 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
449 > EOF
449 > EOF
450 using raw connection to peer
450 using raw connection to peer
451 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
451 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
452 s> Accept-Encoding: identity\r\n
452 s> Accept-Encoding: identity\r\n
453 s> accept: application/mercurial-exp-framing-0005\r\n
453 s> accept: application/mercurial-exp-framing-0006\r\n
454 s> content-type: application/mercurial-exp-framing-0005\r\n
454 s> content-type: application/mercurial-exp-framing-0006\r\n
455 s> user-agent: test\r\n
455 s> user-agent: test\r\n
456 s> content-length: 29\r\n
456 s> content-length: 29\r\n
457 s> host: $LOCALIP:$HGPORT\r\n (glob)
457 s> host: $LOCALIP:$HGPORT\r\n (glob)
458 s> \r\n
458 s> \r\n
459 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
459 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
460 s> makefile('rb', None)
460 s> makefile('rb', None)
461 s> HTTP/1.1 200 OK\r\n
461 s> HTTP/1.1 200 OK\r\n
462 s> Server: testing stub value\r\n
462 s> Server: testing stub value\r\n
463 s> Date: $HTTP_DATE$\r\n
463 s> Date: $HTTP_DATE$\r\n
464 s> Content-Type: application/mercurial-exp-framing-0005\r\n
464 s> Content-Type: application/mercurial-exp-framing-0006\r\n
465 s> Transfer-Encoding: chunked\r\n
465 s> Transfer-Encoding: chunked\r\n
466 s> \r\n
466 s> \r\n
467 s> 13\r\n
467 s> 13\r\n
468 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
468 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
469 s> \r\n
469 s> \r\n
470 s> 27\r\n
470 s> 27\r\n
471 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
471 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
472 s> \r\n
472 s> \r\n
473 s> 8\r\n
473 s> 8\r\n
474 s> \x00\x00\x00\x01\x00\x02\x002
474 s> \x00\x00\x00\x01\x00\x02\x002
475 s> \r\n
475 s> \r\n
476 s> 0\r\n
476 s> 0\r\n
477 s> \r\n
477 s> \r\n
478
478
479 Multiple requests to "multirequest" URL are allowed
479 Multiple requests to "multirequest" URL are allowed
480
480
481 $ sendhttpraw << EOF
481 $ sendhttpraw << EOF
482 > httprequest POST api/$HTTPV2/ro/multirequest
482 > httprequest POST api/$HTTPV2/ro/multirequest
483 > accept: $MEDIATYPE
483 > accept: $MEDIATYPE
484 > content-type: $MEDIATYPE
484 > content-type: $MEDIATYPE
485 > user-agent: test
485 > user-agent: test
486 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
486 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
487 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
487 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
488 > EOF
488 > EOF
489 using raw connection to peer
489 using raw connection to peer
490 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
490 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
491 s> Accept-Encoding: identity\r\n
491 s> Accept-Encoding: identity\r\n
492 s> *\r\n (glob)
492 s> *\r\n (glob)
493 s> *\r\n (glob)
493 s> *\r\n (glob)
494 s> user-agent: test\r\n
494 s> user-agent: test\r\n
495 s> content-length: 58\r\n
495 s> content-length: 58\r\n
496 s> host: $LOCALIP:$HGPORT\r\n (glob)
496 s> host: $LOCALIP:$HGPORT\r\n (glob)
497 s> \r\n
497 s> \r\n
498 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
498 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
499 s> makefile('rb', None)
499 s> makefile('rb', None)
500 s> HTTP/1.1 200 OK\r\n
500 s> HTTP/1.1 200 OK\r\n
501 s> Server: testing stub value\r\n
501 s> Server: testing stub value\r\n
502 s> Date: $HTTP_DATE$\r\n
502 s> Date: $HTTP_DATE$\r\n
503 s> Content-Type: application/mercurial-exp-framing-0005\r\n
503 s> Content-Type: application/mercurial-exp-framing-0006\r\n
504 s> Transfer-Encoding: chunked\r\n
504 s> Transfer-Encoding: chunked\r\n
505 s> \r\n
505 s> \r\n
506 s> 13\r\n
506 s> 13\r\n
507 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
507 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
508 s> \r\n
508 s> \r\n
509 s> 27\r\n
509 s> 27\r\n
510 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
510 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
511 s> \r\n
511 s> \r\n
512 s> 8\r\n
512 s> 8\r\n
513 s> \x00\x00\x00\x01\x00\x02\x002
513 s> \x00\x00\x00\x01\x00\x02\x002
514 s> \r\n
514 s> \r\n
515 s> 13\r\n
515 s> 13\r\n
516 s> \x0b\x00\x00\x03\x00\x02\x001\xa1FstatusBok
516 s> \x0b\x00\x00\x03\x00\x02\x001\xa1FstatusBok
517 s> \r\n
517 s> \r\n
518 s> 27\r\n
518 s> 27\r\n
519 s> \x1f\x00\x00\x03\x00\x02\x001X\x1dcustomreadonly bytes response
519 s> \x1f\x00\x00\x03\x00\x02\x001X\x1dcustomreadonly bytes response
520 s> \r\n
520 s> \r\n
521 s> 8\r\n
521 s> 8\r\n
522 s> \x00\x00\x00\x03\x00\x02\x002
522 s> \x00\x00\x00\x03\x00\x02\x002
523 s> \r\n
523 s> \r\n
524 s> 0\r\n
524 s> 0\r\n
525 s> \r\n
525 s> \r\n
526
526
527 Interleaved requests to "multirequest" are processed
527 Interleaved requests to "multirequest" are processed
528
528
529 $ sendhttpraw << EOF
529 $ sendhttpraw << EOF
530 > httprequest POST api/$HTTPV2/ro/multirequest
530 > httprequest POST api/$HTTPV2/ro/multirequest
531 > accept: $MEDIATYPE
531 > accept: $MEDIATYPE
532 > content-type: $MEDIATYPE
532 > content-type: $MEDIATYPE
533 > user-agent: test
533 > user-agent: test
534 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
534 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
535 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
535 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
536 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
536 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
537 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
537 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
538 > EOF
538 > EOF
539 using raw connection to peer
539 using raw connection to peer
540 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
540 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
541 s> Accept-Encoding: identity\r\n
541 s> Accept-Encoding: identity\r\n
542 s> accept: application/mercurial-exp-framing-0005\r\n
542 s> accept: application/mercurial-exp-framing-0006\r\n
543 s> content-type: application/mercurial-exp-framing-0005\r\n
543 s> content-type: application/mercurial-exp-framing-0006\r\n
544 s> user-agent: test\r\n
544 s> user-agent: test\r\n
545 s> content-length: 115\r\n
545 s> content-length: 115\r\n
546 s> host: $LOCALIP:$HGPORT\r\n (glob)
546 s> host: $LOCALIP:$HGPORT\r\n (glob)
547 s> \r\n
547 s> \r\n
548 s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys
548 s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys
549 s> makefile('rb', None)
549 s> makefile('rb', None)
550 s> HTTP/1.1 200 OK\r\n
550 s> HTTP/1.1 200 OK\r\n
551 s> Server: testing stub value\r\n
551 s> Server: testing stub value\r\n
552 s> Date: $HTTP_DATE$\r\n
552 s> Date: $HTTP_DATE$\r\n
553 s> Content-Type: application/mercurial-exp-framing-0005\r\n
553 s> Content-Type: application/mercurial-exp-framing-0006\r\n
554 s> Transfer-Encoding: chunked\r\n
554 s> Transfer-Encoding: chunked\r\n
555 s> \r\n
555 s> \r\n
556 s> 13\r\n
556 s> 13\r\n
557 s> \x0b\x00\x00\x03\x00\x02\x011\xa1FstatusBok
557 s> \x0b\x00\x00\x03\x00\x02\x011\xa1FstatusBok
558 s> \r\n
558 s> \r\n
559 s> 28\r\n
559 s> 28\r\n
560 s> \x00\x00\x03\x00\x02\x001\xa3Ibookmarks@Jnamespaces@Fphases@
560 s> \x00\x00\x03\x00\x02\x001\xa3Ibookmarks@Jnamespaces@Fphases@
561 s> \r\n
561 s> \r\n
562 s> 8\r\n
562 s> 8\r\n
563 s> \x00\x00\x00\x03\x00\x02\x002
563 s> \x00\x00\x00\x03\x00\x02\x002
564 s> \r\n
564 s> \r\n
565 s> 13\r\n
565 s> 13\r\n
566 s> \x0b\x00\x00\x01\x00\x02\x001\xa1FstatusBok
566 s> \x0b\x00\x00\x01\x00\x02\x001\xa1FstatusBok
567 s> \r\n
567 s> \r\n
568 s> 9\r\n
568 s> 9\r\n
569 s> \x01\x00\x00\x01\x00\x02\x001\xa0
569 s> \x01\x00\x00\x01\x00\x02\x001\xa0
570 s> \r\n
570 s> \r\n
571 s> 8\r\n
571 s> 8\r\n
572 s> \x00\x00\x00\x01\x00\x02\x002
572 s> \x00\x00\x00\x01\x00\x02\x002
573 s> \r\n
573 s> \r\n
574 s> 0\r\n
574 s> 0\r\n
575 s> \r\n
575 s> \r\n
576
576
577 Restart server to disable read-write access
577 Restart server to disable read-write access
578
578
579 $ killdaemons.py
579 $ killdaemons.py
580 $ cat > server/.hg/hgrc << EOF
580 $ cat > server/.hg/hgrc << EOF
581 > [experimental]
581 > [experimental]
582 > web.apiserver = true
582 > web.apiserver = true
583 > web.api.debugreflect = true
583 > web.api.debugreflect = true
584 > web.api.http-v2 = true
584 > web.api.http-v2 = true
585 > [web]
585 > [web]
586 > push_ssl = false
586 > push_ssl = false
587 > EOF
587 > EOF
588
588
589 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
589 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
590 $ cat hg.pid > $DAEMON_PIDS
590 $ cat hg.pid > $DAEMON_PIDS
591
591
592 Attempting to run a read-write command via multirequest on read-only URL is not allowed
592 Attempting to run a read-write command via multirequest on read-only URL is not allowed
593
593
594 $ sendhttpraw << EOF
594 $ sendhttpraw << EOF
595 > httprequest POST api/$HTTPV2/ro/multirequest
595 > httprequest POST api/$HTTPV2/ro/multirequest
596 > accept: $MEDIATYPE
596 > accept: $MEDIATYPE
597 > content-type: $MEDIATYPE
597 > content-type: $MEDIATYPE
598 > user-agent: test
598 > user-agent: test
599 > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
599 > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
600 > EOF
600 > EOF
601 using raw connection to peer
601 using raw connection to peer
602 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
602 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
603 s> Accept-Encoding: identity\r\n
603 s> Accept-Encoding: identity\r\n
604 s> accept: application/mercurial-exp-framing-0005\r\n
604 s> accept: application/mercurial-exp-framing-0006\r\n
605 s> content-type: application/mercurial-exp-framing-0005\r\n
605 s> content-type: application/mercurial-exp-framing-0006\r\n
606 s> user-agent: test\r\n
606 s> user-agent: test\r\n
607 s> content-length: 22\r\n
607 s> content-length: 22\r\n
608 s> host: $LOCALIP:$HGPORT\r\n (glob)
608 s> host: $LOCALIP:$HGPORT\r\n (glob)
609 s> \r\n
609 s> \r\n
610 s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
610 s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
611 s> makefile('rb', None)
611 s> makefile('rb', None)
612 s> HTTP/1.1 403 Forbidden\r\n
612 s> HTTP/1.1 403 Forbidden\r\n
613 s> Server: testing stub value\r\n
613 s> Server: testing stub value\r\n
614 s> Date: $HTTP_DATE$\r\n
614 s> Date: $HTTP_DATE$\r\n
615 s> Content-Type: text/plain\r\n
615 s> Content-Type: text/plain\r\n
616 s> Content-Length: 52\r\n
616 s> Content-Length: 52\r\n
617 s> \r\n
617 s> \r\n
618 s> insufficient permissions to execute command: pushkey
618 s> insufficient permissions to execute command: pushkey
619
619
620 Defining an invalid content encoding results in warning
621
622 $ hg --config experimental.httppeer.v2-encoder-order=identity,badencoder --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ << EOF
623 > command heads
624 > EOF
625 creating http peer for wire protocol version 2
626 sending heads command
627 wire protocol version 2 encoder referenced in config (badencoder) is not known; ignoring
628 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
629 s> Accept-Encoding: identity\r\n
630 s> accept: application/mercurial-exp-framing-0006\r\n
631 s> content-type: application/mercurial-exp-framing-0006\r\n
632 s> content-length: 56\r\n
633 s> host: $LOCALIP:$HGPORT\r\n (glob)
634 s> user-agent: Mercurial debugwireproto\r\n
635 s> \r\n
636 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
637 s> makefile('rb', None)
638 s> HTTP/1.1 200 OK\r\n
639 s> Server: testing stub value\r\n
640 s> Date: $HTTP_DATE$\r\n
641 s> Content-Type: application/mercurial-exp-framing-0006\r\n
642 s> Transfer-Encoding: chunked\r\n
643 s> \r\n
644 s> 13\r\n
645 s> \x0b\x00\x00\x01\x00\x02\x011
646 s> \xa1FstatusBok
647 s> \r\n
648 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
649 s> 1e\r\n
650 s> \x16\x00\x00\x01\x00\x02\x001
651 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
652 s> \r\n
653 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
654 s> 8\r\n
655 s> \x00\x00\x00\x01\x00\x02\x002
656 s> \r\n
657 s> 0\r\n
658 s> \r\n
659 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
660 response: [
661 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
662 ]
663 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
664
665 #if zstd
666
667 $ hg --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ << EOF
668 > command heads
669 > EOF
670 creating http peer for wire protocol version 2
671 sending heads command
672 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
673 s> Accept-Encoding: identity\r\n
674 s> accept: application/mercurial-exp-framing-0006\r\n
675 s> content-type: application/mercurial-exp-framing-0006\r\n
676 s> content-length: 70\r\n
677 s> host: $LOCALIP:$HGPORT\r\n (glob)
678 s> user-agent: Mercurial debugwireproto\r\n
679 s> \r\n
680 s> *\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x83Hzstd-8mbDzlibHidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
681 s> makefile('rb', None)
682 s> HTTP/1.1 200 OK\r\n
683 s> Server: testing stub value\r\n
684 s> Date: $HTTP_DATE$\r\n
685 s> Content-Type: application/mercurial-exp-framing-0006\r\n
686 s> Transfer-Encoding: chunked\r\n
687 s> \r\n
688 s> 13\r\n
689 s> \x0b\x00\x00\x01\x00\x02\x011
690 s> \xa1FstatusBok
691 s> \r\n
692 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
693 s> 1e\r\n
694 s> \x16\x00\x00\x01\x00\x02\x001
695 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
696 s> \r\n
697 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
698 s> 8\r\n
699 s> \x00\x00\x00\x01\x00\x02\x002
700 s> \r\n
701 s> 0\r\n
702 s> \r\n
703 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
704 response: [
705 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
706 ]
707 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
708
709 #endif
710
620 $ cat error.log
711 $ cat error.log
@@ -1,754 +1,754
1 #require no-chg
1 #require no-chg
2
2
3 $ . $TESTDIR/wireprotohelpers.sh
3 $ . $TESTDIR/wireprotohelpers.sh
4
4
5 $ cat >> $HGRCPATH << EOF
5 $ cat >> $HGRCPATH << EOF
6 > [web]
6 > [web]
7 > push_ssl = false
7 > push_ssl = false
8 > allow_push = *
8 > allow_push = *
9 > EOF
9 > EOF
10
10
11 $ hg init server
11 $ hg init server
12 $ cd server
12 $ cd server
13 $ touch a
13 $ touch a
14 $ hg -q commit -A -m initial
14 $ hg -q commit -A -m initial
15 $ cd ..
15 $ cd ..
16
16
17 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
17 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
18 $ cat hg.pid >> $DAEMON_PIDS
18 $ cat hg.pid >> $DAEMON_PIDS
19
19
20 compression formats are advertised in compression capability
20 compression formats are advertised in compression capability
21
21
22 #if zstd
22 #if zstd
23 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zstd,zlib$' > /dev/null
23 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zstd,zlib$' > /dev/null
24 #else
24 #else
25 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zlib$' > /dev/null
25 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zlib$' > /dev/null
26 #endif
26 #endif
27
27
28 $ killdaemons.py
28 $ killdaemons.py
29
29
30 server.compressionengines can replace engines list wholesale
30 server.compressionengines can replace engines list wholesale
31
31
32 $ hg serve --config server.compressionengines=none -R server -p $HGPORT -d --pid-file hg.pid
32 $ hg serve --config server.compressionengines=none -R server -p $HGPORT -d --pid-file hg.pid
33 $ cat hg.pid > $DAEMON_PIDS
33 $ cat hg.pid > $DAEMON_PIDS
34 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none$' > /dev/null
34 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none$' > /dev/null
35
35
36 $ killdaemons.py
36 $ killdaemons.py
37
37
38 Order of engines can also change
38 Order of engines can also change
39
39
40 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
40 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
41 $ cat hg.pid > $DAEMON_PIDS
41 $ cat hg.pid > $DAEMON_PIDS
42 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none,zlib$' > /dev/null
42 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none,zlib$' > /dev/null
43
43
44 $ killdaemons.py
44 $ killdaemons.py
45
45
46 Start a default server again
46 Start a default server again
47
47
48 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
48 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
49 $ cat hg.pid > $DAEMON_PIDS
49 $ cat hg.pid > $DAEMON_PIDS
50
50
51 Server should send application/mercurial-0.1 to clients if no Accept is used
51 Server should send application/mercurial-0.1 to clients if no Accept is used
52
52
53 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
53 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
54 200 Script output follows
54 200 Script output follows
55 content-type: application/mercurial-0.1
55 content-type: application/mercurial-0.1
56 date: $HTTP_DATE$
56 date: $HTTP_DATE$
57 server: testing stub value
57 server: testing stub value
58 transfer-encoding: chunked
58 transfer-encoding: chunked
59
59
60 Server should send application/mercurial-0.1 when client says it wants it
60 Server should send application/mercurial-0.1 when client says it wants it
61
61
62 $ get-with-headers.py --hgproto '0.1' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
62 $ get-with-headers.py --hgproto '0.1' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
63 200 Script output follows
63 200 Script output follows
64 content-type: application/mercurial-0.1
64 content-type: application/mercurial-0.1
65 date: $HTTP_DATE$
65 date: $HTTP_DATE$
66 server: testing stub value
66 server: testing stub value
67 transfer-encoding: chunked
67 transfer-encoding: chunked
68
68
69 Server should send application/mercurial-0.2 when client says it wants it
69 Server should send application/mercurial-0.2 when client says it wants it
70
70
71 $ get-with-headers.py --hgproto '0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
71 $ get-with-headers.py --hgproto '0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
72 200 Script output follows
72 200 Script output follows
73 content-type: application/mercurial-0.2
73 content-type: application/mercurial-0.2
74 date: $HTTP_DATE$
74 date: $HTTP_DATE$
75 server: testing stub value
75 server: testing stub value
76 transfer-encoding: chunked
76 transfer-encoding: chunked
77
77
78 $ get-with-headers.py --hgproto '0.1 0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
78 $ get-with-headers.py --hgproto '0.1 0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
79 200 Script output follows
79 200 Script output follows
80 content-type: application/mercurial-0.2
80 content-type: application/mercurial-0.2
81 date: $HTTP_DATE$
81 date: $HTTP_DATE$
82 server: testing stub value
82 server: testing stub value
83 transfer-encoding: chunked
83 transfer-encoding: chunked
84
84
85 Requesting a compression format that server doesn't support results will fall back to 0.1
85 Requesting a compression format that server doesn't support results will fall back to 0.1
86
86
87 $ get-with-headers.py --hgproto '0.2 comp=aa' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
87 $ get-with-headers.py --hgproto '0.2 comp=aa' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
88 200 Script output follows
88 200 Script output follows
89 content-type: application/mercurial-0.1
89 content-type: application/mercurial-0.1
90 date: $HTTP_DATE$
90 date: $HTTP_DATE$
91 server: testing stub value
91 server: testing stub value
92 transfer-encoding: chunked
92 transfer-encoding: chunked
93
93
94 #if zstd
94 #if zstd
95 zstd is used if available
95 zstd is used if available
96
96
97 $ get-with-headers.py --hgproto '0.2 comp=zstd' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
97 $ get-with-headers.py --hgproto '0.2 comp=zstd' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
98 $ f --size --hexdump --bytes 36 --sha1 resp
98 $ f --size --hexdump --bytes 36 --sha1 resp
99 resp: size=248, sha1=4d8d8f87fb82bd542ce52881fdc94f850748
99 resp: size=248, sha1=4d8d8f87fb82bd542ce52881fdc94f850748
100 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
100 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
101 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 73 74 64 |t follows...zstd|
101 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 73 74 64 |t follows...zstd|
102 0020: 28 b5 2f fd |(./.|
102 0020: 28 b5 2f fd |(./.|
103
103
104 #endif
104 #endif
105
105
106 application/mercurial-0.2 is not yet used on non-streaming responses
106 application/mercurial-0.2 is not yet used on non-streaming responses
107
107
108 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=heads' -
108 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=heads' -
109 200 Script output follows
109 200 Script output follows
110 content-length: 41
110 content-length: 41
111 content-type: application/mercurial-0.1
111 content-type: application/mercurial-0.1
112 date: $HTTP_DATE$
112 date: $HTTP_DATE$
113 server: testing stub value
113 server: testing stub value
114
114
115 e93700bd72895c5addab234c56d4024b487a362f
115 e93700bd72895c5addab234c56d4024b487a362f
116
116
117 Now test protocol preference usage
117 Now test protocol preference usage
118
118
119 $ killdaemons.py
119 $ killdaemons.py
120 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
120 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
121 $ cat hg.pid > $DAEMON_PIDS
121 $ cat hg.pid > $DAEMON_PIDS
122
122
123 No Accept will send 0.1+zlib, even though "none" is preferred b/c "none" isn't supported on 0.1
123 No Accept will send 0.1+zlib, even though "none" is preferred b/c "none" isn't supported on 0.1
124
124
125 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' Content-Type
125 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' Content-Type
126 200 Script output follows
126 200 Script output follows
127 content-type: application/mercurial-0.1
127 content-type: application/mercurial-0.1
128
128
129 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
129 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
130 $ f --size --hexdump --bytes 28 --sha1 resp
130 $ f --size --hexdump --bytes 28 --sha1 resp
131 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
131 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
132 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
132 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
133 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
133 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
134
134
135 Explicit 0.1 will send zlib because "none" isn't supported on 0.1
135 Explicit 0.1 will send zlib because "none" isn't supported on 0.1
136
136
137 $ get-with-headers.py --hgproto '0.1' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
137 $ get-with-headers.py --hgproto '0.1' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
138 $ f --size --hexdump --bytes 28 --sha1 resp
138 $ f --size --hexdump --bytes 28 --sha1 resp
139 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
139 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
140 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
140 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
141 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
141 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
142
142
143 0.2 with no compression will get "none" because that is server's preference
143 0.2 with no compression will get "none" because that is server's preference
144 (spec says ZL and UN are implicitly supported)
144 (spec says ZL and UN are implicitly supported)
145
145
146 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
146 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
147 $ f --size --hexdump --bytes 32 --sha1 resp
147 $ f --size --hexdump --bytes 32 --sha1 resp
148 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
148 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
149 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
149 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
150 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
150 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
151
151
152 Client receives server preference even if local order doesn't match
152 Client receives server preference even if local order doesn't match
153
153
154 $ get-with-headers.py --hgproto '0.2 comp=zlib,none' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
154 $ get-with-headers.py --hgproto '0.2 comp=zlib,none' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
155 $ f --size --hexdump --bytes 32 --sha1 resp
155 $ f --size --hexdump --bytes 32 --sha1 resp
156 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
156 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
157 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
157 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
158 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
158 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
159
159
160 Client receives only supported format even if not server preferred format
160 Client receives only supported format even if not server preferred format
161
161
162 $ get-with-headers.py --hgproto '0.2 comp=zlib' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
162 $ get-with-headers.py --hgproto '0.2 comp=zlib' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
163 $ f --size --hexdump --bytes 33 --sha1 resp
163 $ f --size --hexdump --bytes 33 --sha1 resp
164 resp: size=232, sha1=a1c727f0c9693ca15742a75c30419bc36
164 resp: size=232, sha1=a1c727f0c9693ca15742a75c30419bc36
165 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
165 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
166 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 6c 69 62 |t follows...zlib|
166 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 6c 69 62 |t follows...zlib|
167 0020: 78 |x|
167 0020: 78 |x|
168
168
169 $ killdaemons.py
169 $ killdaemons.py
170 $ cd ..
170 $ cd ..
171
171
172 Test listkeys for listing namespaces
172 Test listkeys for listing namespaces
173
173
174 $ hg init empty
174 $ hg init empty
175 $ hg -R empty serve -p $HGPORT -d --pid-file hg.pid
175 $ hg -R empty serve -p $HGPORT -d --pid-file hg.pid
176 $ cat hg.pid > $DAEMON_PIDS
176 $ cat hg.pid > $DAEMON_PIDS
177
177
178 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
178 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
179 > command listkeys
179 > command listkeys
180 > namespace namespaces
180 > namespace namespaces
181 > EOF
181 > EOF
182 s> GET /?cmd=capabilities HTTP/1.1\r\n
182 s> GET /?cmd=capabilities HTTP/1.1\r\n
183 s> Accept-Encoding: identity\r\n
183 s> Accept-Encoding: identity\r\n
184 s> accept: application/mercurial-0.1\r\n
184 s> accept: application/mercurial-0.1\r\n
185 s> host: $LOCALIP:$HGPORT\r\n (glob)
185 s> host: $LOCALIP:$HGPORT\r\n (glob)
186 s> user-agent: Mercurial debugwireproto\r\n
186 s> user-agent: Mercurial debugwireproto\r\n
187 s> \r\n
187 s> \r\n
188 s> makefile('rb', None)
188 s> makefile('rb', None)
189 s> HTTP/1.1 200 Script output follows\r\n
189 s> HTTP/1.1 200 Script output follows\r\n
190 s> Server: testing stub value\r\n
190 s> Server: testing stub value\r\n
191 s> Date: $HTTP_DATE$\r\n
191 s> Date: $HTTP_DATE$\r\n
192 s> Content-Type: application/mercurial-0.1\r\n
192 s> Content-Type: application/mercurial-0.1\r\n
193 s> Content-Length: *\r\n (glob)
193 s> Content-Length: *\r\n (glob)
194 s> \r\n
194 s> \r\n
195 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
195 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
196 sending listkeys command
196 sending listkeys command
197 s> GET /?cmd=listkeys HTTP/1.1\r\n
197 s> GET /?cmd=listkeys HTTP/1.1\r\n
198 s> Accept-Encoding: identity\r\n
198 s> Accept-Encoding: identity\r\n
199 s> vary: X-HgArg-1,X-HgProto-1\r\n
199 s> vary: X-HgArg-1,X-HgProto-1\r\n
200 s> x-hgarg-1: namespace=namespaces\r\n
200 s> x-hgarg-1: namespace=namespaces\r\n
201 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
201 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
202 s> accept: application/mercurial-0.1\r\n
202 s> accept: application/mercurial-0.1\r\n
203 s> host: $LOCALIP:$HGPORT\r\n (glob)
203 s> host: $LOCALIP:$HGPORT\r\n (glob)
204 s> user-agent: Mercurial debugwireproto\r\n
204 s> user-agent: Mercurial debugwireproto\r\n
205 s> \r\n
205 s> \r\n
206 s> makefile('rb', None)
206 s> makefile('rb', None)
207 s> HTTP/1.1 200 Script output follows\r\n
207 s> HTTP/1.1 200 Script output follows\r\n
208 s> Server: testing stub value\r\n
208 s> Server: testing stub value\r\n
209 s> Date: $HTTP_DATE$\r\n
209 s> Date: $HTTP_DATE$\r\n
210 s> Content-Type: application/mercurial-0.1\r\n
210 s> Content-Type: application/mercurial-0.1\r\n
211 s> Content-Length: 30\r\n
211 s> Content-Length: 30\r\n
212 s> \r\n
212 s> \r\n
213 s> bookmarks\t\n
213 s> bookmarks\t\n
214 s> namespaces\t\n
214 s> namespaces\t\n
215 s> phases\t
215 s> phases\t
216 response: {
216 response: {
217 b'bookmarks': b'',
217 b'bookmarks': b'',
218 b'namespaces': b'',
218 b'namespaces': b'',
219 b'phases': b''
219 b'phases': b''
220 }
220 }
221 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
221 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
222
222
223 Same thing, but with "httprequest" command
223 Same thing, but with "httprequest" command
224
224
225 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
225 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
226 > httprequest GET ?cmd=listkeys
226 > httprequest GET ?cmd=listkeys
227 > user-agent: test
227 > user-agent: test
228 > x-hgarg-1: namespace=namespaces
228 > x-hgarg-1: namespace=namespaces
229 > EOF
229 > EOF
230 using raw connection to peer
230 using raw connection to peer
231 s> GET /?cmd=listkeys HTTP/1.1\r\n
231 s> GET /?cmd=listkeys HTTP/1.1\r\n
232 s> Accept-Encoding: identity\r\n
232 s> Accept-Encoding: identity\r\n
233 s> user-agent: test\r\n
233 s> user-agent: test\r\n
234 s> x-hgarg-1: namespace=namespaces\r\n
234 s> x-hgarg-1: namespace=namespaces\r\n
235 s> host: $LOCALIP:$HGPORT\r\n (glob)
235 s> host: $LOCALIP:$HGPORT\r\n (glob)
236 s> \r\n
236 s> \r\n
237 s> makefile('rb', None)
237 s> makefile('rb', None)
238 s> HTTP/1.1 200 Script output follows\r\n
238 s> HTTP/1.1 200 Script output follows\r\n
239 s> Server: testing stub value\r\n
239 s> Server: testing stub value\r\n
240 s> Date: $HTTP_DATE$\r\n
240 s> Date: $HTTP_DATE$\r\n
241 s> Content-Type: application/mercurial-0.1\r\n
241 s> Content-Type: application/mercurial-0.1\r\n
242 s> Content-Length: 30\r\n
242 s> Content-Length: 30\r\n
243 s> \r\n
243 s> \r\n
244 s> bookmarks\t\n
244 s> bookmarks\t\n
245 s> namespaces\t\n
245 s> namespaces\t\n
246 s> phases\t
246 s> phases\t
247
247
248 Client with HTTPv2 enabled advertises that and gets old capabilities response from old server
248 Client with HTTPv2 enabled advertises that and gets old capabilities response from old server
249
249
250 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
250 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
251 > command heads
251 > command heads
252 > EOF
252 > EOF
253 s> GET /?cmd=capabilities HTTP/1.1\r\n
253 s> GET /?cmd=capabilities HTTP/1.1\r\n
254 s> Accept-Encoding: identity\r\n
254 s> Accept-Encoding: identity\r\n
255 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
255 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
256 s> x-hgproto-1: cbor\r\n
256 s> x-hgproto-1: cbor\r\n
257 s> x-hgupgrade-1: exp-http-v2-0002\r\n
257 s> x-hgupgrade-1: exp-http-v2-0002\r\n
258 s> accept: application/mercurial-0.1\r\n
258 s> accept: application/mercurial-0.1\r\n
259 s> host: $LOCALIP:$HGPORT\r\n (glob)
259 s> host: $LOCALIP:$HGPORT\r\n (glob)
260 s> user-agent: Mercurial debugwireproto\r\n
260 s> user-agent: Mercurial debugwireproto\r\n
261 s> \r\n
261 s> \r\n
262 s> makefile('rb', None)
262 s> makefile('rb', None)
263 s> HTTP/1.1 200 Script output follows\r\n
263 s> HTTP/1.1 200 Script output follows\r\n
264 s> Server: testing stub value\r\n
264 s> Server: testing stub value\r\n
265 s> Date: $HTTP_DATE$\r\n
265 s> Date: $HTTP_DATE$\r\n
266 s> Content-Type: application/mercurial-0.1\r\n
266 s> Content-Type: application/mercurial-0.1\r\n
267 s> Content-Length: *\r\n (glob)
267 s> Content-Length: *\r\n (glob)
268 s> \r\n
268 s> \r\n
269 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
269 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
270 sending heads command
270 sending heads command
271 s> GET /?cmd=heads HTTP/1.1\r\n
271 s> GET /?cmd=heads HTTP/1.1\r\n
272 s> Accept-Encoding: identity\r\n
272 s> Accept-Encoding: identity\r\n
273 s> vary: X-HgProto-1\r\n
273 s> vary: X-HgProto-1\r\n
274 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
274 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
275 s> accept: application/mercurial-0.1\r\n
275 s> accept: application/mercurial-0.1\r\n
276 s> host: $LOCALIP:$HGPORT\r\n (glob)
276 s> host: $LOCALIP:$HGPORT\r\n (glob)
277 s> user-agent: Mercurial debugwireproto\r\n
277 s> user-agent: Mercurial debugwireproto\r\n
278 s> \r\n
278 s> \r\n
279 s> makefile('rb', None)
279 s> makefile('rb', None)
280 s> HTTP/1.1 200 Script output follows\r\n
280 s> HTTP/1.1 200 Script output follows\r\n
281 s> Server: testing stub value\r\n
281 s> Server: testing stub value\r\n
282 s> Date: $HTTP_DATE$\r\n
282 s> Date: $HTTP_DATE$\r\n
283 s> Content-Type: application/mercurial-0.1\r\n
283 s> Content-Type: application/mercurial-0.1\r\n
284 s> Content-Length: 41\r\n
284 s> Content-Length: 41\r\n
285 s> \r\n
285 s> \r\n
286 s> 0000000000000000000000000000000000000000\n
286 s> 0000000000000000000000000000000000000000\n
287 response: [
287 response: [
288 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
288 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
289 ]
289 ]
290 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
290 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
291
291
292 $ killdaemons.py
292 $ killdaemons.py
293 $ enablehttpv2 empty
293 $ enablehttpv2 empty
294 $ hg --config server.compressionengines=zlib -R empty serve -p $HGPORT -d --pid-file hg.pid
294 $ hg --config server.compressionengines=zlib -R empty serve -p $HGPORT -d --pid-file hg.pid
295 $ cat hg.pid > $DAEMON_PIDS
295 $ cat hg.pid > $DAEMON_PIDS
296
296
297 Client with HTTPv2 enabled automatically upgrades if the server supports it
297 Client with HTTPv2 enabled automatically upgrades if the server supports it
298
298
299 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
299 $ hg --config experimental.httppeer.advertise-v2=true --config experimental.httppeer.v2-encoder-order=identity --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
300 > command heads
300 > command heads
301 > EOF
301 > EOF
302 s> GET /?cmd=capabilities HTTP/1.1\r\n
302 s> GET /?cmd=capabilities HTTP/1.1\r\n
303 s> Accept-Encoding: identity\r\n
303 s> Accept-Encoding: identity\r\n
304 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
304 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
305 s> x-hgproto-1: cbor\r\n
305 s> x-hgproto-1: cbor\r\n
306 s> x-hgupgrade-1: exp-http-v2-0002\r\n
306 s> x-hgupgrade-1: exp-http-v2-0002\r\n
307 s> accept: application/mercurial-0.1\r\n
307 s> accept: application/mercurial-0.1\r\n
308 s> host: $LOCALIP:$HGPORT\r\n (glob)
308 s> host: $LOCALIP:$HGPORT\r\n (glob)
309 s> user-agent: Mercurial debugwireproto\r\n
309 s> user-agent: Mercurial debugwireproto\r\n
310 s> \r\n
310 s> \r\n
311 s> makefile('rb', None)
311 s> makefile('rb', None)
312 s> HTTP/1.1 200 OK\r\n
312 s> HTTP/1.1 200 OK\r\n
313 s> Server: testing stub value\r\n
313 s> Server: testing stub value\r\n
314 s> Date: $HTTP_DATE$\r\n
314 s> Date: $HTTP_DATE$\r\n
315 s> Content-Type: application/mercurial-cbor\r\n
315 s> Content-Type: application/mercurial-cbor\r\n
316 s> Content-Length: *\r\n (glob)
316 s> Content-Length: *\r\n (glob)
317 s> \r\n
317 s> \r\n
318 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
318 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
319 sending heads command
319 sending heads command
320 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
320 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
321 s> Accept-Encoding: identity\r\n
321 s> Accept-Encoding: identity\r\n
322 s> accept: application/mercurial-exp-framing-0005\r\n
322 s> accept: application/mercurial-exp-framing-0006\r\n
323 s> content-type: application/mercurial-exp-framing-0005\r\n
323 s> content-type: application/mercurial-exp-framing-0006\r\n
324 s> content-length: 20\r\n
324 s> content-length: 56\r\n
325 s> host: $LOCALIP:$HGPORT\r\n (glob)
325 s> host: $LOCALIP:$HGPORT\r\n (glob)
326 s> user-agent: Mercurial debugwireproto\r\n
326 s> user-agent: Mercurial debugwireproto\r\n
327 s> \r\n
327 s> \r\n
328 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEheads
328 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
329 s> makefile('rb', None)
329 s> makefile('rb', None)
330 s> HTTP/1.1 200 OK\r\n
330 s> HTTP/1.1 200 OK\r\n
331 s> Server: testing stub value\r\n
331 s> Server: testing stub value\r\n
332 s> Date: $HTTP_DATE$\r\n
332 s> Date: $HTTP_DATE$\r\n
333 s> Content-Type: application/mercurial-exp-framing-0005\r\n
333 s> Content-Type: application/mercurial-exp-framing-0006\r\n
334 s> Transfer-Encoding: chunked\r\n
334 s> Transfer-Encoding: chunked\r\n
335 s> \r\n
335 s> \r\n
336 s> 13\r\n
336 s> 13\r\n
337 s> \x0b\x00\x00\x01\x00\x02\x011
337 s> \x0b\x00\x00\x01\x00\x02\x011
338 s> \xa1FstatusBok
338 s> \xa1FstatusBok
339 s> \r\n
339 s> \r\n
340 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
340 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
341 s> 1e\r\n
341 s> 1e\r\n
342 s> \x16\x00\x00\x01\x00\x02\x001
342 s> \x16\x00\x00\x01\x00\x02\x001
343 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
343 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
344 s> \r\n
344 s> \r\n
345 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
345 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
346 s> 8\r\n
346 s> 8\r\n
347 s> \x00\x00\x00\x01\x00\x02\x002
347 s> \x00\x00\x00\x01\x00\x02\x002
348 s> \r\n
348 s> \r\n
349 s> 0\r\n
349 s> 0\r\n
350 s> \r\n
350 s> \r\n
351 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
351 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
352 response: [
352 response: [
353 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
353 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
354 ]
354 ]
355 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
355 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
356
356
357 $ killdaemons.py
357 $ killdaemons.py
358
358
359 HTTP client follows HTTP redirect on handshake to new repo
359 HTTP client follows HTTP redirect on handshake to new repo
360
360
361 $ cd $TESTTMP
361 $ cd $TESTTMP
362
362
363 $ hg init redirector
363 $ hg init redirector
364 $ hg init redirected
364 $ hg init redirected
365 $ cd redirected
365 $ cd redirected
366 $ touch foo
366 $ touch foo
367 $ hg -q commit -A -m initial
367 $ hg -q commit -A -m initial
368 $ cd ..
368 $ cd ..
369
369
370 $ cat > paths.conf << EOF
370 $ cat > paths.conf << EOF
371 > [paths]
371 > [paths]
372 > / = $TESTTMP/*
372 > / = $TESTTMP/*
373 > EOF
373 > EOF
374
374
375 $ cat > redirectext.py << EOF
375 $ cat > redirectext.py << EOF
376 > from mercurial import extensions, wireprotoserver
376 > from mercurial import extensions, wireprotoserver
377 > def wrappedcallhttp(orig, repo, req, res, proto, cmd):
377 > def wrappedcallhttp(orig, repo, req, res, proto, cmd):
378 > path = req.advertisedurl[len(req.advertisedbaseurl):]
378 > path = req.advertisedurl[len(req.advertisedbaseurl):]
379 > if not path.startswith(b'/redirector'):
379 > if not path.startswith(b'/redirector'):
380 > return orig(repo, req, res, proto, cmd)
380 > return orig(repo, req, res, proto, cmd)
381 > relpath = path[len(b'/redirector'):]
381 > relpath = path[len(b'/redirector'):]
382 > res.status = b'301 Redirect'
382 > res.status = b'301 Redirect'
383 > newurl = b'%s/redirected%s' % (req.baseurl, relpath)
383 > newurl = b'%s/redirected%s' % (req.baseurl, relpath)
384 > if not repo.ui.configbool('testing', 'redirectqs', True) and b'?' in newurl:
384 > if not repo.ui.configbool('testing', 'redirectqs', True) and b'?' in newurl:
385 > newurl = newurl[0:newurl.index(b'?')]
385 > newurl = newurl[0:newurl.index(b'?')]
386 > res.headers[b'Location'] = newurl
386 > res.headers[b'Location'] = newurl
387 > res.headers[b'Content-Type'] = b'text/plain'
387 > res.headers[b'Content-Type'] = b'text/plain'
388 > res.setbodybytes(b'redirected')
388 > res.setbodybytes(b'redirected')
389 > return True
389 > return True
390 >
390 >
391 > extensions.wrapfunction(wireprotoserver, '_callhttp', wrappedcallhttp)
391 > extensions.wrapfunction(wireprotoserver, '_callhttp', wrappedcallhttp)
392 > EOF
392 > EOF
393
393
394 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
394 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
395 > --config server.compressionengines=zlib \
395 > --config server.compressionengines=zlib \
396 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
396 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
397 $ cat hg.pid > $DAEMON_PIDS
397 $ cat hg.pid > $DAEMON_PIDS
398
398
399 Verify our HTTP 301 is served properly
399 Verify our HTTP 301 is served properly
400
400
401 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
401 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
402 > httprequest GET /redirector?cmd=capabilities
402 > httprequest GET /redirector?cmd=capabilities
403 > user-agent: test
403 > user-agent: test
404 > EOF
404 > EOF
405 using raw connection to peer
405 using raw connection to peer
406 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
406 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
407 s> Accept-Encoding: identity\r\n
407 s> Accept-Encoding: identity\r\n
408 s> user-agent: test\r\n
408 s> user-agent: test\r\n
409 s> host: $LOCALIP:$HGPORT\r\n (glob)
409 s> host: $LOCALIP:$HGPORT\r\n (glob)
410 s> \r\n
410 s> \r\n
411 s> makefile('rb', None)
411 s> makefile('rb', None)
412 s> HTTP/1.1 301 Redirect\r\n
412 s> HTTP/1.1 301 Redirect\r\n
413 s> Server: testing stub value\r\n
413 s> Server: testing stub value\r\n
414 s> Date: $HTTP_DATE$\r\n
414 s> Date: $HTTP_DATE$\r\n
415 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
415 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
416 s> Content-Type: text/plain\r\n
416 s> Content-Type: text/plain\r\n
417 s> Content-Length: 10\r\n
417 s> Content-Length: 10\r\n
418 s> \r\n
418 s> \r\n
419 s> redirected
419 s> redirected
420 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
420 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
421 s> Accept-Encoding: identity\r\n
421 s> Accept-Encoding: identity\r\n
422 s> user-agent: test\r\n
422 s> user-agent: test\r\n
423 s> host: $LOCALIP:$HGPORT\r\n (glob)
423 s> host: $LOCALIP:$HGPORT\r\n (glob)
424 s> \r\n
424 s> \r\n
425 s> makefile('rb', None)
425 s> makefile('rb', None)
426 s> HTTP/1.1 200 Script output follows\r\n
426 s> HTTP/1.1 200 Script output follows\r\n
427 s> Server: testing stub value\r\n
427 s> Server: testing stub value\r\n
428 s> Date: $HTTP_DATE$\r\n
428 s> Date: $HTTP_DATE$\r\n
429 s> Content-Type: application/mercurial-0.1\r\n
429 s> Content-Type: application/mercurial-0.1\r\n
430 s> Content-Length: 467\r\n
430 s> Content-Length: 467\r\n
431 s> \r\n
431 s> \r\n
432 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
432 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
433
433
434 Test with the HTTP peer
434 Test with the HTTP peer
435
435
436 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
436 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
437 > command heads
437 > command heads
438 > EOF
438 > EOF
439 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
439 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
440 s> Accept-Encoding: identity\r\n
440 s> Accept-Encoding: identity\r\n
441 s> accept: application/mercurial-0.1\r\n
441 s> accept: application/mercurial-0.1\r\n
442 s> host: $LOCALIP:$HGPORT\r\n (glob)
442 s> host: $LOCALIP:$HGPORT\r\n (glob)
443 s> user-agent: Mercurial debugwireproto\r\n
443 s> user-agent: Mercurial debugwireproto\r\n
444 s> \r\n
444 s> \r\n
445 s> makefile('rb', None)
445 s> makefile('rb', None)
446 s> HTTP/1.1 301 Redirect\r\n
446 s> HTTP/1.1 301 Redirect\r\n
447 s> Server: testing stub value\r\n
447 s> Server: testing stub value\r\n
448 s> Date: $HTTP_DATE$\r\n
448 s> Date: $HTTP_DATE$\r\n
449 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
449 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
450 s> Content-Type: text/plain\r\n
450 s> Content-Type: text/plain\r\n
451 s> Content-Length: 10\r\n
451 s> Content-Length: 10\r\n
452 s> \r\n
452 s> \r\n
453 s> redirected
453 s> redirected
454 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
454 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
455 s> Accept-Encoding: identity\r\n
455 s> Accept-Encoding: identity\r\n
456 s> accept: application/mercurial-0.1\r\n
456 s> accept: application/mercurial-0.1\r\n
457 s> host: $LOCALIP:$HGPORT\r\n (glob)
457 s> host: $LOCALIP:$HGPORT\r\n (glob)
458 s> user-agent: Mercurial debugwireproto\r\n
458 s> user-agent: Mercurial debugwireproto\r\n
459 s> \r\n
459 s> \r\n
460 s> makefile('rb', None)
460 s> makefile('rb', None)
461 s> HTTP/1.1 200 Script output follows\r\n
461 s> HTTP/1.1 200 Script output follows\r\n
462 s> Server: testing stub value\r\n
462 s> Server: testing stub value\r\n
463 s> Date: $HTTP_DATE$\r\n
463 s> Date: $HTTP_DATE$\r\n
464 s> Content-Type: application/mercurial-0.1\r\n
464 s> Content-Type: application/mercurial-0.1\r\n
465 s> Content-Length: 467\r\n
465 s> Content-Length: 467\r\n
466 s> \r\n
466 s> \r\n
467 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
467 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
468 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
468 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
469 sending heads command
469 sending heads command
470 s> GET /redirected?cmd=heads HTTP/1.1\r\n
470 s> GET /redirected?cmd=heads HTTP/1.1\r\n
471 s> Accept-Encoding: identity\r\n
471 s> Accept-Encoding: identity\r\n
472 s> vary: X-HgProto-1\r\n
472 s> vary: X-HgProto-1\r\n
473 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
473 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
474 s> accept: application/mercurial-0.1\r\n
474 s> accept: application/mercurial-0.1\r\n
475 s> host: $LOCALIP:$HGPORT\r\n (glob)
475 s> host: $LOCALIP:$HGPORT\r\n (glob)
476 s> user-agent: Mercurial debugwireproto\r\n
476 s> user-agent: Mercurial debugwireproto\r\n
477 s> \r\n
477 s> \r\n
478 s> makefile('rb', None)
478 s> makefile('rb', None)
479 s> HTTP/1.1 200 Script output follows\r\n
479 s> HTTP/1.1 200 Script output follows\r\n
480 s> Server: testing stub value\r\n
480 s> Server: testing stub value\r\n
481 s> Date: $HTTP_DATE$\r\n
481 s> Date: $HTTP_DATE$\r\n
482 s> Content-Type: application/mercurial-0.1\r\n
482 s> Content-Type: application/mercurial-0.1\r\n
483 s> Content-Length: 41\r\n
483 s> Content-Length: 41\r\n
484 s> \r\n
484 s> \r\n
485 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
485 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
486 response: [
486 response: [
487 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
487 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
488 ]
488 ]
489 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
489 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
490
490
491 $ killdaemons.py
491 $ killdaemons.py
492
492
493 Now test a variation where we strip the query string from the redirect URL.
493 Now test a variation where we strip the query string from the redirect URL.
494 (SCM Manager apparently did this and clients would recover from it)
494 (SCM Manager apparently did this and clients would recover from it)
495
495
496 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
496 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
497 > --config server.compressionengines=zlib \
497 > --config server.compressionengines=zlib \
498 > --config testing.redirectqs=false \
498 > --config testing.redirectqs=false \
499 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
499 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
500 $ cat hg.pid > $DAEMON_PIDS
500 $ cat hg.pid > $DAEMON_PIDS
501
501
502 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
502 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
503 > httprequest GET /redirector?cmd=capabilities
503 > httprequest GET /redirector?cmd=capabilities
504 > user-agent: test
504 > user-agent: test
505 > EOF
505 > EOF
506 using raw connection to peer
506 using raw connection to peer
507 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
507 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
508 s> Accept-Encoding: identity\r\n
508 s> Accept-Encoding: identity\r\n
509 s> user-agent: test\r\n
509 s> user-agent: test\r\n
510 s> host: $LOCALIP:$HGPORT\r\n (glob)
510 s> host: $LOCALIP:$HGPORT\r\n (glob)
511 s> \r\n
511 s> \r\n
512 s> makefile('rb', None)
512 s> makefile('rb', None)
513 s> HTTP/1.1 301 Redirect\r\n
513 s> HTTP/1.1 301 Redirect\r\n
514 s> Server: testing stub value\r\n
514 s> Server: testing stub value\r\n
515 s> Date: $HTTP_DATE$\r\n
515 s> Date: $HTTP_DATE$\r\n
516 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
516 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
517 s> Content-Type: text/plain\r\n
517 s> Content-Type: text/plain\r\n
518 s> Content-Length: 10\r\n
518 s> Content-Length: 10\r\n
519 s> \r\n
519 s> \r\n
520 s> redirected
520 s> redirected
521 s> GET /redirected HTTP/1.1\r\n
521 s> GET /redirected HTTP/1.1\r\n
522 s> Accept-Encoding: identity\r\n
522 s> Accept-Encoding: identity\r\n
523 s> user-agent: test\r\n
523 s> user-agent: test\r\n
524 s> host: $LOCALIP:$HGPORT\r\n (glob)
524 s> host: $LOCALIP:$HGPORT\r\n (glob)
525 s> \r\n
525 s> \r\n
526 s> makefile('rb', None)
526 s> makefile('rb', None)
527 s> HTTP/1.1 200 Script output follows\r\n
527 s> HTTP/1.1 200 Script output follows\r\n
528 s> Server: testing stub value\r\n
528 s> Server: testing stub value\r\n
529 s> Date: $HTTP_DATE$\r\n
529 s> Date: $HTTP_DATE$\r\n
530 s> ETag: W/"*"\r\n (glob)
530 s> ETag: W/"*"\r\n (glob)
531 s> Content-Type: text/html; charset=ascii\r\n
531 s> Content-Type: text/html; charset=ascii\r\n
532 s> Transfer-Encoding: chunked\r\n
532 s> Transfer-Encoding: chunked\r\n
533 s> \r\n
533 s> \r\n
534 s> 414\r\n
534 s> 414\r\n
535 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
535 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
536 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
536 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
537 s> <head>\n
537 s> <head>\n
538 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
538 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
539 s> <meta name="robots" content="index, nofollow" />\n
539 s> <meta name="robots" content="index, nofollow" />\n
540 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
540 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
541 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
541 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
542 s> \n
542 s> \n
543 s> <title>redirected: log</title>\n
543 s> <title>redirected: log</title>\n
544 s> <link rel="alternate" type="application/atom+xml"\n
544 s> <link rel="alternate" type="application/atom+xml"\n
545 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
545 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
546 s> <link rel="alternate" type="application/rss+xml"\n
546 s> <link rel="alternate" type="application/rss+xml"\n
547 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
547 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
548 s> </head>\n
548 s> </head>\n
549 s> <body>\n
549 s> <body>\n
550 s> \n
550 s> \n
551 s> <div class="container">\n
551 s> <div class="container">\n
552 s> <div class="menu">\n
552 s> <div class="menu">\n
553 s> <div class="logo">\n
553 s> <div class="logo">\n
554 s> <a href="https://mercurial-scm.org/">\n
554 s> <a href="https://mercurial-scm.org/">\n
555 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
555 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
556 s> </div>\n
556 s> </div>\n
557 s> <ul>\n
557 s> <ul>\n
558 s> <li class="active">log</li>\n
558 s> <li class="active">log</li>\n
559 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
559 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
560 s> <li><a href="/redirected/tags">tags</a></li>\n
560 s> <li><a href="/redirected/tags">tags</a></li>\n
561 s> <li><a href="
561 s> <li><a href="
562 s> \r\n
562 s> \r\n
563 s> 810\r\n
563 s> 810\r\n
564 s> /redirected/bookmarks">bookmarks</a></li>\n
564 s> /redirected/bookmarks">bookmarks</a></li>\n
565 s> <li><a href="/redirected/branches">branches</a></li>\n
565 s> <li><a href="/redirected/branches">branches</a></li>\n
566 s> </ul>\n
566 s> </ul>\n
567 s> <ul>\n
567 s> <ul>\n
568 s> <li><a href="/redirected/rev/tip">changeset</a></li>\n
568 s> <li><a href="/redirected/rev/tip">changeset</a></li>\n
569 s> <li><a href="/redirected/file/tip">browse</a></li>\n
569 s> <li><a href="/redirected/file/tip">browse</a></li>\n
570 s> </ul>\n
570 s> </ul>\n
571 s> <ul>\n
571 s> <ul>\n
572 s> \n
572 s> \n
573 s> </ul>\n
573 s> </ul>\n
574 s> <ul>\n
574 s> <ul>\n
575 s> <li><a href="/redirected/help">help</a></li>\n
575 s> <li><a href="/redirected/help">help</a></li>\n
576 s> </ul>\n
576 s> </ul>\n
577 s> <div class="atom-logo">\n
577 s> <div class="atom-logo">\n
578 s> <a href="/redirected/atom-log" title="subscribe to atom feed">\n
578 s> <a href="/redirected/atom-log" title="subscribe to atom feed">\n
579 s> <img class="atom-logo" src="/redirected/static/feed-icon-14x14.png" alt="atom feed" />\n
579 s> <img class="atom-logo" src="/redirected/static/feed-icon-14x14.png" alt="atom feed" />\n
580 s> </a>\n
580 s> </a>\n
581 s> </div>\n
581 s> </div>\n
582 s> </div>\n
582 s> </div>\n
583 s> \n
583 s> \n
584 s> <div class="main">\n
584 s> <div class="main">\n
585 s> <h2 class="breadcrumb"><a href="/">Mercurial</a> &gt; <a href="/redirected">redirected</a> </h2>\n
585 s> <h2 class="breadcrumb"><a href="/">Mercurial</a> &gt; <a href="/redirected">redirected</a> </h2>\n
586 s> <h3>log</h3>\n
586 s> <h3>log</h3>\n
587 s> \n
587 s> \n
588 s> \n
588 s> \n
589 s> <form class="search" action="/redirected/log">\n
589 s> <form class="search" action="/redirected/log">\n
590 s> \n
590 s> \n
591 s> <p><input name="rev" id="search1" type="text" size="30" value="" /></p>\n
591 s> <p><input name="rev" id="search1" type="text" size="30" value="" /></p>\n
592 s> <div id="hint">Find changesets by keywords (author, files, the commit message), revision\n
592 s> <div id="hint">Find changesets by keywords (author, files, the commit message), revision\n
593 s> number or hash, or <a href="/redirected/help/revsets">revset expression</a>.</div>\n
593 s> number or hash, or <a href="/redirected/help/revsets">revset expression</a>.</div>\n
594 s> </form>\n
594 s> </form>\n
595 s> \n
595 s> \n
596 s> <div class="navigate">\n
596 s> <div class="navigate">\n
597 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
597 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
598 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
598 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
599 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
599 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
600 s> </div>\n
600 s> </div>\n
601 s> \n
601 s> \n
602 s> <table class="bigtable">\n
602 s> <table class="bigtable">\n
603 s> <thead>\n
603 s> <thead>\n
604 s> <tr>\n
604 s> <tr>\n
605 s> <th class="age">age</th>\n
605 s> <th class="age">age</th>\n
606 s> <th class="author">author</th>\n
606 s> <th class="author">author</th>\n
607 s> <th class="description">description</th>\n
607 s> <th class="description">description</th>\n
608 s> </tr>\n
608 s> </tr>\n
609 s> </thead>\n
609 s> </thead>\n
610 s> <tbody class="stripes2">\n
610 s> <tbody class="stripes2">\n
611 s> <tr>\n
611 s> <tr>\n
612 s> <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>\n
612 s> <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>\n
613 s> <td class="author">test</td>\n
613 s> <td class="author">test</td>\n
614 s> <td class="description">\n
614 s> <td class="description">\n
615 s> <a href="/redirected/rev/96ee1d7354c4">initial</a>\n
615 s> <a href="/redirected/rev/96ee1d7354c4">initial</a>\n
616 s> <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> \n
616 s> <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> \n
617 s> </td>\n
617 s> </td>\n
618 s> </tr>\n
618 s> </tr>\n
619 s> \n
619 s> \n
620 s> </tbody>\n
620 s> </tbody>\n
621 s> </table>\n
621 s> </table>\n
622 s> \n
622 s> \n
623 s> <div class="navigate">\n
623 s> <div class="navigate">\n
624 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
624 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
625 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
625 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
626 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
626 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
627 s> </div>\n
627 s> </div>\n
628 s> \n
628 s> \n
629 s> <script type="text/javascript">\n
629 s> <script type="text/javascript">\n
630 s> ajaxScrollInit(\n
630 s> ajaxScrollInit(\n
631 s> \'/redirected/shortlog/%next%\',\n
631 s> \'/redirected/shortlog/%next%\',\n
632 s> \'\', <!-- NEXTHASH\n
632 s> \'\', <!-- NEXTHASH\n
633 s> function (htmlText) {
633 s> function (htmlText) {
634 s> \r\n
634 s> \r\n
635 s> 14a\r\n
635 s> 14a\r\n
636 s> \n
636 s> \n
637 s> var m = htmlText.match(/\'(\\w+)\', <!-- NEXTHASH/);\n
637 s> var m = htmlText.match(/\'(\\w+)\', <!-- NEXTHASH/);\n
638 s> return m ? m[1] : null;\n
638 s> return m ? m[1] : null;\n
639 s> },\n
639 s> },\n
640 s> \'.bigtable > tbody\',\n
640 s> \'.bigtable > tbody\',\n
641 s> \'<tr class="%class%">\\\n
641 s> \'<tr class="%class%">\\\n
642 s> <td colspan="3" style="text-align: center;">%text%</td>\\\n
642 s> <td colspan="3" style="text-align: center;">%text%</td>\\\n
643 s> </tr>\'\n
643 s> </tr>\'\n
644 s> );\n
644 s> );\n
645 s> </script>\n
645 s> </script>\n
646 s> \n
646 s> \n
647 s> </div>\n
647 s> </div>\n
648 s> </div>\n
648 s> </div>\n
649 s> \n
649 s> \n
650 s> \n
650 s> \n
651 s> \n
651 s> \n
652 s> </body>\n
652 s> </body>\n
653 s> </html>\n
653 s> </html>\n
654 s> \n
654 s> \n
655 s> \r\n
655 s> \r\n
656 s> 0\r\n
656 s> 0\r\n
657 s> \r\n
657 s> \r\n
658
658
659 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
659 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
660 > command heads
660 > command heads
661 > EOF
661 > EOF
662 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
662 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
663 s> Accept-Encoding: identity\r\n
663 s> Accept-Encoding: identity\r\n
664 s> accept: application/mercurial-0.1\r\n
664 s> accept: application/mercurial-0.1\r\n
665 s> host: $LOCALIP:$HGPORT\r\n (glob)
665 s> host: $LOCALIP:$HGPORT\r\n (glob)
666 s> user-agent: Mercurial debugwireproto\r\n
666 s> user-agent: Mercurial debugwireproto\r\n
667 s> \r\n
667 s> \r\n
668 s> makefile('rb', None)
668 s> makefile('rb', None)
669 s> HTTP/1.1 301 Redirect\r\n
669 s> HTTP/1.1 301 Redirect\r\n
670 s> Server: testing stub value\r\n
670 s> Server: testing stub value\r\n
671 s> Date: $HTTP_DATE$\r\n
671 s> Date: $HTTP_DATE$\r\n
672 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
672 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
673 s> Content-Type: text/plain\r\n
673 s> Content-Type: text/plain\r\n
674 s> Content-Length: 10\r\n
674 s> Content-Length: 10\r\n
675 s> \r\n
675 s> \r\n
676 s> redirected
676 s> redirected
677 s> GET /redirected HTTP/1.1\r\n
677 s> GET /redirected HTTP/1.1\r\n
678 s> Accept-Encoding: identity\r\n
678 s> Accept-Encoding: identity\r\n
679 s> accept: application/mercurial-0.1\r\n
679 s> accept: application/mercurial-0.1\r\n
680 s> host: $LOCALIP:$HGPORT\r\n (glob)
680 s> host: $LOCALIP:$HGPORT\r\n (glob)
681 s> user-agent: Mercurial debugwireproto\r\n
681 s> user-agent: Mercurial debugwireproto\r\n
682 s> \r\n
682 s> \r\n
683 s> makefile('rb', None)
683 s> makefile('rb', None)
684 s> HTTP/1.1 200 Script output follows\r\n
684 s> HTTP/1.1 200 Script output follows\r\n
685 s> Server: testing stub value\r\n
685 s> Server: testing stub value\r\n
686 s> Date: $HTTP_DATE$\r\n
686 s> Date: $HTTP_DATE$\r\n
687 s> ETag: W/"*"\r\n (glob)
687 s> ETag: W/"*"\r\n (glob)
688 s> Content-Type: text/html; charset=ascii\r\n
688 s> Content-Type: text/html; charset=ascii\r\n
689 s> Transfer-Encoding: chunked\r\n
689 s> Transfer-Encoding: chunked\r\n
690 s> \r\n
690 s> \r\n
691 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
691 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
692 s> 414\r\n
692 s> 414\r\n
693 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
693 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
694 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
694 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
695 s> <head>\n
695 s> <head>\n
696 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
696 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
697 s> <meta name="robots" content="index, nofollow" />\n
697 s> <meta name="robots" content="index, nofollow" />\n
698 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
698 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
699 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
699 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
700 s> \n
700 s> \n
701 s> <title>redirected: log</title>\n
701 s> <title>redirected: log</title>\n
702 s> <link rel="alternate" type="application/atom+xml"\n
702 s> <link rel="alternate" type="application/atom+xml"\n
703 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
703 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
704 s> <link rel="alternate" type="application/rss+xml"\n
704 s> <link rel="alternate" type="application/rss+xml"\n
705 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
705 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
706 s> </head>\n
706 s> </head>\n
707 s> <body>\n
707 s> <body>\n
708 s> \n
708 s> \n
709 s> <div class="container">\n
709 s> <div class="container">\n
710 s> <div class="menu">\n
710 s> <div class="menu">\n
711 s> <div class="logo">\n
711 s> <div class="logo">\n
712 s> <a href="https://mercurial-scm.org/">\n
712 s> <a href="https://mercurial-scm.org/">\n
713 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
713 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
714 s> </div>\n
714 s> </div>\n
715 s> <ul>\n
715 s> <ul>\n
716 s> <li class="active">log</li>\n
716 s> <li class="active">log</li>\n
717 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
717 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
718 s> <li><a href="/redirected/tags">tags</a
718 s> <li><a href="/redirected/tags">tags</a
719 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
719 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
720 s> Accept-Encoding: identity\r\n
720 s> Accept-Encoding: identity\r\n
721 s> accept: application/mercurial-0.1\r\n
721 s> accept: application/mercurial-0.1\r\n
722 s> host: $LOCALIP:$HGPORT\r\n (glob)
722 s> host: $LOCALIP:$HGPORT\r\n (glob)
723 s> user-agent: Mercurial debugwireproto\r\n
723 s> user-agent: Mercurial debugwireproto\r\n
724 s> \r\n
724 s> \r\n
725 s> makefile('rb', None)
725 s> makefile('rb', None)
726 s> HTTP/1.1 200 Script output follows\r\n
726 s> HTTP/1.1 200 Script output follows\r\n
727 s> Server: testing stub value\r\n
727 s> Server: testing stub value\r\n
728 s> Date: $HTTP_DATE$\r\n
728 s> Date: $HTTP_DATE$\r\n
729 s> Content-Type: application/mercurial-0.1\r\n
729 s> Content-Type: application/mercurial-0.1\r\n
730 s> Content-Length: 467\r\n
730 s> Content-Length: 467\r\n
731 s> \r\n
731 s> \r\n
732 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
732 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
733 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
733 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
734 sending heads command
734 sending heads command
735 s> GET /redirected?cmd=heads HTTP/1.1\r\n
735 s> GET /redirected?cmd=heads HTTP/1.1\r\n
736 s> Accept-Encoding: identity\r\n
736 s> Accept-Encoding: identity\r\n
737 s> vary: X-HgProto-1\r\n
737 s> vary: X-HgProto-1\r\n
738 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
738 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
739 s> accept: application/mercurial-0.1\r\n
739 s> accept: application/mercurial-0.1\r\n
740 s> host: $LOCALIP:$HGPORT\r\n (glob)
740 s> host: $LOCALIP:$HGPORT\r\n (glob)
741 s> user-agent: Mercurial debugwireproto\r\n
741 s> user-agent: Mercurial debugwireproto\r\n
742 s> \r\n
742 s> \r\n
743 s> makefile('rb', None)
743 s> makefile('rb', None)
744 s> HTTP/1.1 200 Script output follows\r\n
744 s> HTTP/1.1 200 Script output follows\r\n
745 s> Server: testing stub value\r\n
745 s> Server: testing stub value\r\n
746 s> Date: $HTTP_DATE$\r\n
746 s> Date: $HTTP_DATE$\r\n
747 s> Content-Type: application/mercurial-0.1\r\n
747 s> Content-Type: application/mercurial-0.1\r\n
748 s> Content-Length: 41\r\n
748 s> Content-Length: 41\r\n
749 s> \r\n
749 s> \r\n
750 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
750 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
751 response: [
751 response: [
752 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
752 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
753 ]
753 ]
754 (sent 4 HTTP requests and * bytes; received * bytes in responses) (glob)
754 (sent 4 HTTP requests and * bytes; received * bytes in responses) (glob)
@@ -1,422 +1,422
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2 $ cat >> $HGRCPATH << EOF
2 $ cat >> $HGRCPATH << EOF
3 > [extensions]
3 > [extensions]
4 > blackbox =
4 > blackbox =
5 > [blackbox]
5 > [blackbox]
6 > track = simplecache
6 > track = simplecache
7 > EOF
7 > EOF
8 $ hg init server
8 $ hg init server
9 $ enablehttpv2 server
9 $ enablehttpv2 server
10 $ cd server
10 $ cd server
11 $ cat >> .hg/hgrc << EOF
11 $ cat >> .hg/hgrc << EOF
12 > [extensions]
12 > [extensions]
13 > simplecache = $TESTDIR/wireprotosimplecache.py
13 > simplecache = $TESTDIR/wireprotosimplecache.py
14 > EOF
14 > EOF
15
15
16 $ echo a0 > a
16 $ echo a0 > a
17 $ echo b0 > b
17 $ echo b0 > b
18 $ hg -q commit -A -m 'commit 0'
18 $ hg -q commit -A -m 'commit 0'
19 $ echo a1 > a
19 $ echo a1 > a
20 $ hg commit -m 'commit 1'
20 $ hg commit -m 'commit 1'
21 $ echo b1 > b
21 $ echo b1 > b
22 $ hg commit -m 'commit 2'
22 $ hg commit -m 'commit 2'
23 $ echo a2 > a
23 $ echo a2 > a
24 $ echo b2 > b
24 $ echo b2 > b
25 $ hg commit -m 'commit 3'
25 $ hg commit -m 'commit 3'
26
26
27 $ hg log -G -T '{rev}:{node} {desc}'
27 $ hg log -G -T '{rev}:{node} {desc}'
28 @ 3:50590a86f3ff5d1e9a1624a7a6957884565cc8e8 commit 3
28 @ 3:50590a86f3ff5d1e9a1624a7a6957884565cc8e8 commit 3
29 |
29 |
30 o 2:4d01eda50c6ac5f7e89cbe1880143a32f559c302 commit 2
30 o 2:4d01eda50c6ac5f7e89cbe1880143a32f559c302 commit 2
31 |
31 |
32 o 1:4432d83626e8a98655f062ec1f2a43b07f7fbbb0 commit 1
32 o 1:4432d83626e8a98655f062ec1f2a43b07f7fbbb0 commit 1
33 |
33 |
34 o 0:3390ef850073fbc2f0dfff2244342c8e9229013a commit 0
34 o 0:3390ef850073fbc2f0dfff2244342c8e9229013a commit 0
35
35
36
36
37 $ hg --debug debugindex -m
37 $ hg --debug debugindex -m
38 rev linkrev nodeid p1 p2
38 rev linkrev nodeid p1 p2
39 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
39 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
40 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000
40 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000
41 2 2 a8853dafacfca6fc807055a660d8b835141a3bb4 a988fb43583e871d1ed5750ee074c6d840bbbfc8 0000000000000000000000000000000000000000
41 2 2 a8853dafacfca6fc807055a660d8b835141a3bb4 a988fb43583e871d1ed5750ee074c6d840bbbfc8 0000000000000000000000000000000000000000
42 3 3 3fe11dfbb13645782b0addafbe75a87c210ffddc a8853dafacfca6fc807055a660d8b835141a3bb4 0000000000000000000000000000000000000000
42 3 3 3fe11dfbb13645782b0addafbe75a87c210ffddc a8853dafacfca6fc807055a660d8b835141a3bb4 0000000000000000000000000000000000000000
43
43
44 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
44 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
45 $ cat hg.pid > $DAEMON_PIDS
45 $ cat hg.pid > $DAEMON_PIDS
46
46
47 Performing the same request should result in same result, with 2nd response
47 Performing the same request should result in same result, with 2nd response
48 coming from cache.
48 coming from cache.
49
49
50 $ sendhttpv2peer << EOF
50 $ sendhttpv2peer << EOF
51 > command manifestdata
51 > command manifestdata
52 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
52 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
53 > tree eval:b''
53 > tree eval:b''
54 > fields eval:[b'parents']
54 > fields eval:[b'parents']
55 > EOF
55 > EOF
56 creating http peer for wire protocol version 2
56 creating http peer for wire protocol version 2
57 sending manifestdata command
57 sending manifestdata command
58 response: gen[
58 response: gen[
59 {
59 {
60 b'totalitems': 1
60 b'totalitems': 1
61 },
61 },
62 {
62 {
63 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
63 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
64 b'parents': [
64 b'parents': [
65 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
65 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
66 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
66 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
67 ]
67 ]
68 }
68 }
69 ]
69 ]
70
70
71 $ sendhttpv2peer << EOF
71 $ sendhttpv2peer << EOF
72 > command manifestdata
72 > command manifestdata
73 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
73 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
74 > tree eval:b''
74 > tree eval:b''
75 > fields eval:[b'parents']
75 > fields eval:[b'parents']
76 > EOF
76 > EOF
77 creating http peer for wire protocol version 2
77 creating http peer for wire protocol version 2
78 sending manifestdata command
78 sending manifestdata command
79 response: gen[
79 response: gen[
80 {
80 {
81 b'totalitems': 1
81 b'totalitems': 1
82 },
82 },
83 {
83 {
84 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
84 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
85 b'parents': [
85 b'parents': [
86 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
86 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
87 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
87 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
88 ]
88 ]
89 }
89 }
90 ]
90 ]
91
91
92 Sending different request doesn't yield cache hit.
92 Sending different request doesn't yield cache hit.
93
93
94 $ sendhttpv2peer << EOF
94 $ sendhttpv2peer << EOF
95 > command manifestdata
95 > command manifestdata
96 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41', b'\xa9\x88\xfb\x43\x58\x3e\x87\x1d\x1e\xd5\x75\x0e\xe0\x74\xc6\xd8\x40\xbb\xbf\xc8']
96 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41', b'\xa9\x88\xfb\x43\x58\x3e\x87\x1d\x1e\xd5\x75\x0e\xe0\x74\xc6\xd8\x40\xbb\xbf\xc8']
97 > tree eval:b''
97 > tree eval:b''
98 > fields eval:[b'parents']
98 > fields eval:[b'parents']
99 > EOF
99 > EOF
100 creating http peer for wire protocol version 2
100 creating http peer for wire protocol version 2
101 sending manifestdata command
101 sending manifestdata command
102 response: gen[
102 response: gen[
103 {
103 {
104 b'totalitems': 2
104 b'totalitems': 2
105 },
105 },
106 {
106 {
107 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
107 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
108 b'parents': [
108 b'parents': [
109 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
109 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
110 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
110 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
111 ]
111 ]
112 },
112 },
113 {
113 {
114 b'node': b'\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8',
114 b'node': b'\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8',
115 b'parents': [
115 b'parents': [
116 b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
116 b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
117 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
117 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
118 ]
118 ]
119 }
119 }
120 ]
120 ]
121
121
122 $ cat .hg/blackbox.log
122 $ cat .hg/blackbox.log
123 *> cacher constructed for manifestdata (glob)
123 *> cacher constructed for manifestdata (glob)
124 *> cache miss for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
124 *> cache miss for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
125 *> storing cache entry for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
125 *> storing cache entry for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
126 *> cacher constructed for manifestdata (glob)
126 *> cacher constructed for manifestdata (glob)
127 *> cache hit for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
127 *> cache hit for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
128 *> cacher constructed for manifestdata (glob)
128 *> cacher constructed for manifestdata (glob)
129 *> cache miss for 6ed2f740a1cdd12c9e99c4f27695543143c26a11 (glob)
129 *> cache miss for 1cf89363ec234c6b92d5961281eaa5713e7493f9 (glob)
130 *> storing cache entry for 6ed2f740a1cdd12c9e99c4f27695543143c26a11 (glob)
130 *> storing cache entry for 1cf89363ec234c6b92d5961281eaa5713e7493f9 (glob)
131
131
132 $ cat error.log
132 $ cat error.log
133
133
134 $ killdaemons.py
134 $ killdaemons.py
135 $ rm .hg/blackbox.log
135 $ rm .hg/blackbox.log
136
136
137 Try with object caching mode
137 Try with object caching mode
138
138
139 $ cat >> .hg/hgrc << EOF
139 $ cat >> .hg/hgrc << EOF
140 > [simplecache]
140 > [simplecache]
141 > cacheobjects = true
141 > cacheobjects = true
142 > EOF
142 > EOF
143
143
144 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
144 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
145 $ cat hg.pid > $DAEMON_PIDS
145 $ cat hg.pid > $DAEMON_PIDS
146
146
147 $ sendhttpv2peer << EOF
147 $ sendhttpv2peer << EOF
148 > command manifestdata
148 > command manifestdata
149 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
149 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
150 > tree eval:b''
150 > tree eval:b''
151 > fields eval:[b'parents']
151 > fields eval:[b'parents']
152 > EOF
152 > EOF
153 creating http peer for wire protocol version 2
153 creating http peer for wire protocol version 2
154 sending manifestdata command
154 sending manifestdata command
155 response: gen[
155 response: gen[
156 {
156 {
157 b'totalitems': 1
157 b'totalitems': 1
158 },
158 },
159 {
159 {
160 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
160 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
161 b'parents': [
161 b'parents': [
162 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
162 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
163 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
163 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
164 ]
164 ]
165 }
165 }
166 ]
166 ]
167
167
168 $ sendhttpv2peer << EOF
168 $ sendhttpv2peer << EOF
169 > command manifestdata
169 > command manifestdata
170 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
170 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
171 > tree eval:b''
171 > tree eval:b''
172 > fields eval:[b'parents']
172 > fields eval:[b'parents']
173 > EOF
173 > EOF
174 creating http peer for wire protocol version 2
174 creating http peer for wire protocol version 2
175 sending manifestdata command
175 sending manifestdata command
176 response: gen[
176 response: gen[
177 {
177 {
178 b'totalitems': 1
178 b'totalitems': 1
179 },
179 },
180 {
180 {
181 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
181 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
182 b'parents': [
182 b'parents': [
183 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
183 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
184 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
184 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
185 ]
185 ]
186 }
186 }
187 ]
187 ]
188
188
189 $ cat .hg/blackbox.log
189 $ cat .hg/blackbox.log
190 *> cacher constructed for manifestdata (glob)
190 *> cacher constructed for manifestdata (glob)
191 *> cache miss for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
191 *> cache miss for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
192 *> storing cache entry for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
192 *> storing cache entry for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
193 *> cacher constructed for manifestdata (glob)
193 *> cacher constructed for manifestdata (glob)
194 *> cache hit for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
194 *> cache hit for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
195
195
196 $ cat error.log
196 $ cat error.log
197
197
198 $ killdaemons.py
198 $ killdaemons.py
199 $ rm .hg/blackbox.log
199 $ rm .hg/blackbox.log
200
200
201 A non-cacheable command does not instantiate cacher
201 A non-cacheable command does not instantiate cacher
202
202
203 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
203 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
204 $ cat hg.pid > $DAEMON_PIDS
204 $ cat hg.pid > $DAEMON_PIDS
205 $ sendhttpv2peer << EOF
205 $ sendhttpv2peer << EOF
206 > command capabilities
206 > command capabilities
207 > EOF
207 > EOF
208 creating http peer for wire protocol version 2
208 creating http peer for wire protocol version 2
209 sending capabilities command
209 sending capabilities command
210 response: gen[
210 response: gen[
211 {
211 {
212 b'commands': {
212 b'commands': {
213 b'branchmap': {
213 b'branchmap': {
214 b'args': {},
214 b'args': {},
215 b'permissions': [
215 b'permissions': [
216 b'pull'
216 b'pull'
217 ]
217 ]
218 },
218 },
219 b'capabilities': {
219 b'capabilities': {
220 b'args': {},
220 b'args': {},
221 b'permissions': [
221 b'permissions': [
222 b'pull'
222 b'pull'
223 ]
223 ]
224 },
224 },
225 b'changesetdata': {
225 b'changesetdata': {
226 b'args': {
226 b'args': {
227 b'fields': {
227 b'fields': {
228 b'default': set([]),
228 b'default': set([]),
229 b'required': False,
229 b'required': False,
230 b'type': b'set',
230 b'type': b'set',
231 b'validvalues': set([
231 b'validvalues': set([
232 b'bookmarks',
232 b'bookmarks',
233 b'parents',
233 b'parents',
234 b'phase',
234 b'phase',
235 b'revision'
235 b'revision'
236 ])
236 ])
237 },
237 },
238 b'noderange': {
238 b'noderange': {
239 b'default': None,
239 b'default': None,
240 b'required': False,
240 b'required': False,
241 b'type': b'list'
241 b'type': b'list'
242 },
242 },
243 b'nodes': {
243 b'nodes': {
244 b'default': None,
244 b'default': None,
245 b'required': False,
245 b'required': False,
246 b'type': b'list'
246 b'type': b'list'
247 },
247 },
248 b'nodesdepth': {
248 b'nodesdepth': {
249 b'default': None,
249 b'default': None,
250 b'required': False,
250 b'required': False,
251 b'type': b'int'
251 b'type': b'int'
252 }
252 }
253 },
253 },
254 b'permissions': [
254 b'permissions': [
255 b'pull'
255 b'pull'
256 ]
256 ]
257 },
257 },
258 b'filedata': {
258 b'filedata': {
259 b'args': {
259 b'args': {
260 b'fields': {
260 b'fields': {
261 b'default': set([]),
261 b'default': set([]),
262 b'required': False,
262 b'required': False,
263 b'type': b'set',
263 b'type': b'set',
264 b'validvalues': set([
264 b'validvalues': set([
265 b'parents',
265 b'parents',
266 b'revision'
266 b'revision'
267 ])
267 ])
268 },
268 },
269 b'haveparents': {
269 b'haveparents': {
270 b'default': False,
270 b'default': False,
271 b'required': False,
271 b'required': False,
272 b'type': b'bool'
272 b'type': b'bool'
273 },
273 },
274 b'nodes': {
274 b'nodes': {
275 b'required': True,
275 b'required': True,
276 b'type': b'list'
276 b'type': b'list'
277 },
277 },
278 b'path': {
278 b'path': {
279 b'required': True,
279 b'required': True,
280 b'type': b'bytes'
280 b'type': b'bytes'
281 }
281 }
282 },
282 },
283 b'permissions': [
283 b'permissions': [
284 b'pull'
284 b'pull'
285 ]
285 ]
286 },
286 },
287 b'heads': {
287 b'heads': {
288 b'args': {
288 b'args': {
289 b'publiconly': {
289 b'publiconly': {
290 b'default': False,
290 b'default': False,
291 b'required': False,
291 b'required': False,
292 b'type': b'bool'
292 b'type': b'bool'
293 }
293 }
294 },
294 },
295 b'permissions': [
295 b'permissions': [
296 b'pull'
296 b'pull'
297 ]
297 ]
298 },
298 },
299 b'known': {
299 b'known': {
300 b'args': {
300 b'args': {
301 b'nodes': {
301 b'nodes': {
302 b'default': [],
302 b'default': [],
303 b'required': False,
303 b'required': False,
304 b'type': b'list'
304 b'type': b'list'
305 }
305 }
306 },
306 },
307 b'permissions': [
307 b'permissions': [
308 b'pull'
308 b'pull'
309 ]
309 ]
310 },
310 },
311 b'listkeys': {
311 b'listkeys': {
312 b'args': {
312 b'args': {
313 b'namespace': {
313 b'namespace': {
314 b'required': True,
314 b'required': True,
315 b'type': b'bytes'
315 b'type': b'bytes'
316 }
316 }
317 },
317 },
318 b'permissions': [
318 b'permissions': [
319 b'pull'
319 b'pull'
320 ]
320 ]
321 },
321 },
322 b'lookup': {
322 b'lookup': {
323 b'args': {
323 b'args': {
324 b'key': {
324 b'key': {
325 b'required': True,
325 b'required': True,
326 b'type': b'bytes'
326 b'type': b'bytes'
327 }
327 }
328 },
328 },
329 b'permissions': [
329 b'permissions': [
330 b'pull'
330 b'pull'
331 ]
331 ]
332 },
332 },
333 b'manifestdata': {
333 b'manifestdata': {
334 b'args': {
334 b'args': {
335 b'fields': {
335 b'fields': {
336 b'default': set([]),
336 b'default': set([]),
337 b'required': False,
337 b'required': False,
338 b'type': b'set',
338 b'type': b'set',
339 b'validvalues': set([
339 b'validvalues': set([
340 b'parents',
340 b'parents',
341 b'revision'
341 b'revision'
342 ])
342 ])
343 },
343 },
344 b'haveparents': {
344 b'haveparents': {
345 b'default': False,
345 b'default': False,
346 b'required': False,
346 b'required': False,
347 b'type': b'bool'
347 b'type': b'bool'
348 },
348 },
349 b'nodes': {
349 b'nodes': {
350 b'required': True,
350 b'required': True,
351 b'type': b'list'
351 b'type': b'list'
352 },
352 },
353 b'tree': {
353 b'tree': {
354 b'required': True,
354 b'required': True,
355 b'type': b'bytes'
355 b'type': b'bytes'
356 }
356 }
357 },
357 },
358 b'permissions': [
358 b'permissions': [
359 b'pull'
359 b'pull'
360 ]
360 ]
361 },
361 },
362 b'pushkey': {
362 b'pushkey': {
363 b'args': {
363 b'args': {
364 b'key': {
364 b'key': {
365 b'required': True,
365 b'required': True,
366 b'type': b'bytes'
366 b'type': b'bytes'
367 },
367 },
368 b'namespace': {
368 b'namespace': {
369 b'required': True,
369 b'required': True,
370 b'type': b'bytes'
370 b'type': b'bytes'
371 },
371 },
372 b'new': {
372 b'new': {
373 b'required': True,
373 b'required': True,
374 b'type': b'bytes'
374 b'type': b'bytes'
375 },
375 },
376 b'old': {
376 b'old': {
377 b'required': True,
377 b'required': True,
378 b'type': b'bytes'
378 b'type': b'bytes'
379 }
379 }
380 },
380 },
381 b'permissions': [
381 b'permissions': [
382 b'push'
382 b'push'
383 ]
383 ]
384 }
384 }
385 },
385 },
386 b'framingmediatypes': [
386 b'framingmediatypes': [
387 b'application/mercurial-exp-framing-0005'
387 b'application/mercurial-exp-framing-0006'
388 ],
388 ],
389 b'pathfilterprefixes': set([
389 b'pathfilterprefixes': set([
390 b'path:',
390 b'path:',
391 b'rootfilesin:'
391 b'rootfilesin:'
392 ]),
392 ]),
393 b'rawrepoformats': [
393 b'rawrepoformats': [
394 b'generaldelta',
394 b'generaldelta',
395 b'revlogv1'
395 b'revlogv1'
396 ]
396 ]
397 }
397 }
398 ]
398 ]
399
399
400 $ test -f .hg/blackbox.log
400 $ test -f .hg/blackbox.log
401 [1]
401 [1]
402
402
403 An error is not cached
403 An error is not cached
404
404
405 $ sendhttpv2peer << EOF
405 $ sendhttpv2peer << EOF
406 > command manifestdata
406 > command manifestdata
407 > nodes eval:[b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa']
407 > nodes eval:[b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa']
408 > tree eval:b''
408 > tree eval:b''
409 > fields eval:[b'parents']
409 > fields eval:[b'parents']
410 > EOF
410 > EOF
411 creating http peer for wire protocol version 2
411 creating http peer for wire protocol version 2
412 sending manifestdata command
412 sending manifestdata command
413 abort: unknown node: \xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa! (esc)
413 abort: unknown node: \xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa! (esc)
414 [255]
414 [255]
415
415
416 $ cat .hg/blackbox.log
416 $ cat .hg/blackbox.log
417 *> cacher constructed for manifestdata (glob)
417 *> cacher constructed for manifestdata (glob)
418 *> cache miss for 9d1bb421d99e913d45f2d099aa49728514292dd2 (glob)
418 *> cache miss for 904560928eb95650358f0829d9399b256822eb26 (glob)
419 *> cacher exiting due to error (glob)
419 *> cacher exiting due to error (glob)
420
420
421 $ killdaemons.py
421 $ killdaemons.py
422 $ rm .hg/blackbox.log
422 $ rm .hg/blackbox.log
@@ -1,663 +1,663
1 #require no-chg
1 #require no-chg
2
2
3 $ . $TESTDIR/wireprotohelpers.sh
3 $ . $TESTDIR/wireprotohelpers.sh
4
4
5 $ hg init server
5 $ hg init server
6
6
7 zstd isn't present in plain builds. Make tests easier by removing
7 zstd isn't present in plain builds. Make tests easier by removing
8 zstd from the equation.
8 zstd from the equation.
9
9
10 $ cat >> server/.hg/hgrc << EOF
10 $ cat >> server/.hg/hgrc << EOF
11 > [server]
11 > [server]
12 > compressionengines = zlib
12 > compressionengines = zlib
13 > EOF
13 > EOF
14
14
15 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
15 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
16 $ cat hg.pid > $DAEMON_PIDS
16 $ cat hg.pid > $DAEMON_PIDS
17
17
18 A normal capabilities request is serviced for version 1
18 A normal capabilities request is serviced for version 1
19
19
20 $ sendhttpraw << EOF
20 $ sendhttpraw << EOF
21 > httprequest GET ?cmd=capabilities
21 > httprequest GET ?cmd=capabilities
22 > user-agent: test
22 > user-agent: test
23 > EOF
23 > EOF
24 using raw connection to peer
24 using raw connection to peer
25 s> GET /?cmd=capabilities HTTP/1.1\r\n
25 s> GET /?cmd=capabilities HTTP/1.1\r\n
26 s> Accept-Encoding: identity\r\n
26 s> Accept-Encoding: identity\r\n
27 s> user-agent: test\r\n
27 s> user-agent: test\r\n
28 s> host: $LOCALIP:$HGPORT\r\n (glob)
28 s> host: $LOCALIP:$HGPORT\r\n (glob)
29 s> \r\n
29 s> \r\n
30 s> makefile('rb', None)
30 s> makefile('rb', None)
31 s> HTTP/1.1 200 Script output follows\r\n
31 s> HTTP/1.1 200 Script output follows\r\n
32 s> Server: testing stub value\r\n
32 s> Server: testing stub value\r\n
33 s> Date: $HTTP_DATE$\r\n
33 s> Date: $HTTP_DATE$\r\n
34 s> Content-Type: application/mercurial-0.1\r\n
34 s> Content-Type: application/mercurial-0.1\r\n
35 s> Content-Length: *\r\n (glob)
35 s> Content-Length: *\r\n (glob)
36 s> \r\n
36 s> \r\n
37 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
37 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
38
38
39 A proper request without the API server enabled returns the legacy response
39 A proper request without the API server enabled returns the legacy response
40
40
41 $ sendhttpraw << EOF
41 $ sendhttpraw << EOF
42 > httprequest GET ?cmd=capabilities
42 > httprequest GET ?cmd=capabilities
43 > user-agent: test
43 > user-agent: test
44 > x-hgupgrade-1: foo
44 > x-hgupgrade-1: foo
45 > x-hgproto-1: cbor
45 > x-hgproto-1: cbor
46 > EOF
46 > EOF
47 using raw connection to peer
47 using raw connection to peer
48 s> GET /?cmd=capabilities HTTP/1.1\r\n
48 s> GET /?cmd=capabilities HTTP/1.1\r\n
49 s> Accept-Encoding: identity\r\n
49 s> Accept-Encoding: identity\r\n
50 s> user-agent: test\r\n
50 s> user-agent: test\r\n
51 s> x-hgproto-1: cbor\r\n
51 s> x-hgproto-1: cbor\r\n
52 s> x-hgupgrade-1: foo\r\n
52 s> x-hgupgrade-1: foo\r\n
53 s> host: $LOCALIP:$HGPORT\r\n (glob)
53 s> host: $LOCALIP:$HGPORT\r\n (glob)
54 s> \r\n
54 s> \r\n
55 s> makefile('rb', None)
55 s> makefile('rb', None)
56 s> HTTP/1.1 200 Script output follows\r\n
56 s> HTTP/1.1 200 Script output follows\r\n
57 s> Server: testing stub value\r\n
57 s> Server: testing stub value\r\n
58 s> Date: $HTTP_DATE$\r\n
58 s> Date: $HTTP_DATE$\r\n
59 s> Content-Type: application/mercurial-0.1\r\n
59 s> Content-Type: application/mercurial-0.1\r\n
60 s> Content-Length: *\r\n (glob)
60 s> Content-Length: *\r\n (glob)
61 s> \r\n
61 s> \r\n
62 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
62 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
63
63
64 Restart with just API server enabled. This enables serving the new format.
64 Restart with just API server enabled. This enables serving the new format.
65
65
66 $ killdaemons.py
66 $ killdaemons.py
67 $ cat error.log
67 $ cat error.log
68
68
69 $ cat >> server/.hg/hgrc << EOF
69 $ cat >> server/.hg/hgrc << EOF
70 > [experimental]
70 > [experimental]
71 > web.apiserver = true
71 > web.apiserver = true
72 > EOF
72 > EOF
73
73
74 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
74 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
75 $ cat hg.pid > $DAEMON_PIDS
75 $ cat hg.pid > $DAEMON_PIDS
76
76
77 X-HgUpgrade-<N> without CBOR advertisement uses legacy response
77 X-HgUpgrade-<N> without CBOR advertisement uses legacy response
78
78
79 $ sendhttpraw << EOF
79 $ sendhttpraw << EOF
80 > httprequest GET ?cmd=capabilities
80 > httprequest GET ?cmd=capabilities
81 > user-agent: test
81 > user-agent: test
82 > x-hgupgrade-1: foo bar
82 > x-hgupgrade-1: foo bar
83 > EOF
83 > EOF
84 using raw connection to peer
84 using raw connection to peer
85 s> GET /?cmd=capabilities HTTP/1.1\r\n
85 s> GET /?cmd=capabilities HTTP/1.1\r\n
86 s> Accept-Encoding: identity\r\n
86 s> Accept-Encoding: identity\r\n
87 s> user-agent: test\r\n
87 s> user-agent: test\r\n
88 s> x-hgupgrade-1: foo bar\r\n
88 s> x-hgupgrade-1: foo bar\r\n
89 s> host: $LOCALIP:$HGPORT\r\n (glob)
89 s> host: $LOCALIP:$HGPORT\r\n (glob)
90 s> \r\n
90 s> \r\n
91 s> makefile('rb', None)
91 s> makefile('rb', None)
92 s> HTTP/1.1 200 Script output follows\r\n
92 s> HTTP/1.1 200 Script output follows\r\n
93 s> Server: testing stub value\r\n
93 s> Server: testing stub value\r\n
94 s> Date: $HTTP_DATE$\r\n
94 s> Date: $HTTP_DATE$\r\n
95 s> Content-Type: application/mercurial-0.1\r\n
95 s> Content-Type: application/mercurial-0.1\r\n
96 s> Content-Length: *\r\n (glob)
96 s> Content-Length: *\r\n (glob)
97 s> \r\n
97 s> \r\n
98 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
98 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
99
99
100 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
100 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
101
101
102 $ sendhttpraw << EOF
102 $ sendhttpraw << EOF
103 > httprequest GET ?cmd=capabilities
103 > httprequest GET ?cmd=capabilities
104 > user-agent: test
104 > user-agent: test
105 > x-hgupgrade-1: foo bar
105 > x-hgupgrade-1: foo bar
106 > x-hgproto-1: some value
106 > x-hgproto-1: some value
107 > EOF
107 > EOF
108 using raw connection to peer
108 using raw connection to peer
109 s> GET /?cmd=capabilities HTTP/1.1\r\n
109 s> GET /?cmd=capabilities HTTP/1.1\r\n
110 s> Accept-Encoding: identity\r\n
110 s> Accept-Encoding: identity\r\n
111 s> user-agent: test\r\n
111 s> user-agent: test\r\n
112 s> x-hgproto-1: some value\r\n
112 s> x-hgproto-1: some value\r\n
113 s> x-hgupgrade-1: foo bar\r\n
113 s> x-hgupgrade-1: foo bar\r\n
114 s> host: $LOCALIP:$HGPORT\r\n (glob)
114 s> host: $LOCALIP:$HGPORT\r\n (glob)
115 s> \r\n
115 s> \r\n
116 s> makefile('rb', None)
116 s> makefile('rb', None)
117 s> HTTP/1.1 200 Script output follows\r\n
117 s> HTTP/1.1 200 Script output follows\r\n
118 s> Server: testing stub value\r\n
118 s> Server: testing stub value\r\n
119 s> Date: $HTTP_DATE$\r\n
119 s> Date: $HTTP_DATE$\r\n
120 s> Content-Type: application/mercurial-0.1\r\n
120 s> Content-Type: application/mercurial-0.1\r\n
121 s> Content-Length: *\r\n (glob)
121 s> Content-Length: *\r\n (glob)
122 s> \r\n
122 s> \r\n
123 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
123 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
124
124
125 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
125 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
126
126
127 $ sendhttpraw << EOF
127 $ sendhttpraw << EOF
128 > httprequest GET ?cmd=capabilities
128 > httprequest GET ?cmd=capabilities
129 > user-agent: test
129 > user-agent: test
130 > x-hgupgrade-1: foo bar
130 > x-hgupgrade-1: foo bar
131 > x-hgproto-1: cbor
131 > x-hgproto-1: cbor
132 > EOF
132 > EOF
133 using raw connection to peer
133 using raw connection to peer
134 s> GET /?cmd=capabilities HTTP/1.1\r\n
134 s> GET /?cmd=capabilities HTTP/1.1\r\n
135 s> Accept-Encoding: identity\r\n
135 s> Accept-Encoding: identity\r\n
136 s> user-agent: test\r\n
136 s> user-agent: test\r\n
137 s> x-hgproto-1: cbor\r\n
137 s> x-hgproto-1: cbor\r\n
138 s> x-hgupgrade-1: foo bar\r\n
138 s> x-hgupgrade-1: foo bar\r\n
139 s> host: $LOCALIP:$HGPORT\r\n (glob)
139 s> host: $LOCALIP:$HGPORT\r\n (glob)
140 s> \r\n
140 s> \r\n
141 s> makefile('rb', None)
141 s> makefile('rb', None)
142 s> HTTP/1.1 200 OK\r\n
142 s> HTTP/1.1 200 OK\r\n
143 s> Server: testing stub value\r\n
143 s> Server: testing stub value\r\n
144 s> Date: $HTTP_DATE$\r\n
144 s> Date: $HTTP_DATE$\r\n
145 s> Content-Type: application/mercurial-cbor\r\n
145 s> Content-Type: application/mercurial-cbor\r\n
146 s> Content-Length: *\r\n (glob)
146 s> Content-Length: *\r\n (glob)
147 s> \r\n
147 s> \r\n
148 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
148 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
149 cbor> [
149 cbor> [
150 {
150 {
151 b'apibase': b'api/',
151 b'apibase': b'api/',
152 b'apis': {},
152 b'apis': {},
153 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
153 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
154 }
154 }
155 ]
155 ]
156
156
157 Restart server to enable HTTPv2
157 Restart server to enable HTTPv2
158
158
159 $ killdaemons.py
159 $ killdaemons.py
160 $ enablehttpv2 server
160 $ enablehttpv2 server
161 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
161 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
162 $ cat hg.pid > $DAEMON_PIDS
162 $ cat hg.pid > $DAEMON_PIDS
163
163
164 Only requested API services are returned
164 Only requested API services are returned
165
165
166 $ sendhttpraw << EOF
166 $ sendhttpraw << EOF
167 > httprequest GET ?cmd=capabilities
167 > httprequest GET ?cmd=capabilities
168 > user-agent: test
168 > user-agent: test
169 > x-hgupgrade-1: foo bar
169 > x-hgupgrade-1: foo bar
170 > x-hgproto-1: cbor
170 > x-hgproto-1: cbor
171 > EOF
171 > EOF
172 using raw connection to peer
172 using raw connection to peer
173 s> GET /?cmd=capabilities HTTP/1.1\r\n
173 s> GET /?cmd=capabilities HTTP/1.1\r\n
174 s> Accept-Encoding: identity\r\n
174 s> Accept-Encoding: identity\r\n
175 s> user-agent: test\r\n
175 s> user-agent: test\r\n
176 s> x-hgproto-1: cbor\r\n
176 s> x-hgproto-1: cbor\r\n
177 s> x-hgupgrade-1: foo bar\r\n
177 s> x-hgupgrade-1: foo bar\r\n
178 s> host: $LOCALIP:$HGPORT\r\n (glob)
178 s> host: $LOCALIP:$HGPORT\r\n (glob)
179 s> \r\n
179 s> \r\n
180 s> makefile('rb', None)
180 s> makefile('rb', None)
181 s> HTTP/1.1 200 OK\r\n
181 s> HTTP/1.1 200 OK\r\n
182 s> Server: testing stub value\r\n
182 s> Server: testing stub value\r\n
183 s> Date: $HTTP_DATE$\r\n
183 s> Date: $HTTP_DATE$\r\n
184 s> Content-Type: application/mercurial-cbor\r\n
184 s> Content-Type: application/mercurial-cbor\r\n
185 s> Content-Length: *\r\n (glob)
185 s> Content-Length: *\r\n (glob)
186 s> \r\n
186 s> \r\n
187 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
187 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
188 cbor> [
188 cbor> [
189 {
189 {
190 b'apibase': b'api/',
190 b'apibase': b'api/',
191 b'apis': {},
191 b'apis': {},
192 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
192 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
193 }
193 }
194 ]
194 ]
195
195
196 Request for HTTPv2 service returns information about it
196 Request for HTTPv2 service returns information about it
197
197
198 $ sendhttpraw << EOF
198 $ sendhttpraw << EOF
199 > httprequest GET ?cmd=capabilities
199 > httprequest GET ?cmd=capabilities
200 > user-agent: test
200 > user-agent: test
201 > x-hgupgrade-1: exp-http-v2-0002 foo bar
201 > x-hgupgrade-1: exp-http-v2-0002 foo bar
202 > x-hgproto-1: cbor
202 > x-hgproto-1: cbor
203 > EOF
203 > EOF
204 using raw connection to peer
204 using raw connection to peer
205 s> GET /?cmd=capabilities HTTP/1.1\r\n
205 s> GET /?cmd=capabilities HTTP/1.1\r\n
206 s> Accept-Encoding: identity\r\n
206 s> Accept-Encoding: identity\r\n
207 s> user-agent: test\r\n
207 s> user-agent: test\r\n
208 s> x-hgproto-1: cbor\r\n
208 s> x-hgproto-1: cbor\r\n
209 s> x-hgupgrade-1: exp-http-v2-0002 foo bar\r\n
209 s> x-hgupgrade-1: exp-http-v2-0002 foo bar\r\n
210 s> host: $LOCALIP:$HGPORT\r\n (glob)
210 s> host: $LOCALIP:$HGPORT\r\n (glob)
211 s> \r\n
211 s> \r\n
212 s> makefile('rb', None)
212 s> makefile('rb', None)
213 s> HTTP/1.1 200 OK\r\n
213 s> HTTP/1.1 200 OK\r\n
214 s> Server: testing stub value\r\n
214 s> Server: testing stub value\r\n
215 s> Date: $HTTP_DATE$\r\n
215 s> Date: $HTTP_DATE$\r\n
216 s> Content-Type: application/mercurial-cbor\r\n
216 s> Content-Type: application/mercurial-cbor\r\n
217 s> Content-Length: *\r\n (glob)
217 s> Content-Length: *\r\n (glob)
218 s> \r\n
218 s> \r\n
219 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
219 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
220 cbor> [
220 cbor> [
221 {
221 {
222 b'apibase': b'api/',
222 b'apibase': b'api/',
223 b'apis': {
223 b'apis': {
224 b'exp-http-v2-0002': {
224 b'exp-http-v2-0002': {
225 b'commands': {
225 b'commands': {
226 b'branchmap': {
226 b'branchmap': {
227 b'args': {},
227 b'args': {},
228 b'permissions': [
228 b'permissions': [
229 b'pull'
229 b'pull'
230 ]
230 ]
231 },
231 },
232 b'capabilities': {
232 b'capabilities': {
233 b'args': {},
233 b'args': {},
234 b'permissions': [
234 b'permissions': [
235 b'pull'
235 b'pull'
236 ]
236 ]
237 },
237 },
238 b'changesetdata': {
238 b'changesetdata': {
239 b'args': {
239 b'args': {
240 b'fields': {
240 b'fields': {
241 b'default': set([]),
241 b'default': set([]),
242 b'required': False,
242 b'required': False,
243 b'type': b'set',
243 b'type': b'set',
244 b'validvalues': set([
244 b'validvalues': set([
245 b'bookmarks',
245 b'bookmarks',
246 b'parents',
246 b'parents',
247 b'phase',
247 b'phase',
248 b'revision'
248 b'revision'
249 ])
249 ])
250 },
250 },
251 b'noderange': {
251 b'noderange': {
252 b'default': None,
252 b'default': None,
253 b'required': False,
253 b'required': False,
254 b'type': b'list'
254 b'type': b'list'
255 },
255 },
256 b'nodes': {
256 b'nodes': {
257 b'default': None,
257 b'default': None,
258 b'required': False,
258 b'required': False,
259 b'type': b'list'
259 b'type': b'list'
260 },
260 },
261 b'nodesdepth': {
261 b'nodesdepth': {
262 b'default': None,
262 b'default': None,
263 b'required': False,
263 b'required': False,
264 b'type': b'int'
264 b'type': b'int'
265 }
265 }
266 },
266 },
267 b'permissions': [
267 b'permissions': [
268 b'pull'
268 b'pull'
269 ]
269 ]
270 },
270 },
271 b'filedata': {
271 b'filedata': {
272 b'args': {
272 b'args': {
273 b'fields': {
273 b'fields': {
274 b'default': set([]),
274 b'default': set([]),
275 b'required': False,
275 b'required': False,
276 b'type': b'set',
276 b'type': b'set',
277 b'validvalues': set([
277 b'validvalues': set([
278 b'parents',
278 b'parents',
279 b'revision'
279 b'revision'
280 ])
280 ])
281 },
281 },
282 b'haveparents': {
282 b'haveparents': {
283 b'default': False,
283 b'default': False,
284 b'required': False,
284 b'required': False,
285 b'type': b'bool'
285 b'type': b'bool'
286 },
286 },
287 b'nodes': {
287 b'nodes': {
288 b'required': True,
288 b'required': True,
289 b'type': b'list'
289 b'type': b'list'
290 },
290 },
291 b'path': {
291 b'path': {
292 b'required': True,
292 b'required': True,
293 b'type': b'bytes'
293 b'type': b'bytes'
294 }
294 }
295 },
295 },
296 b'permissions': [
296 b'permissions': [
297 b'pull'
297 b'pull'
298 ]
298 ]
299 },
299 },
300 b'heads': {
300 b'heads': {
301 b'args': {
301 b'args': {
302 b'publiconly': {
302 b'publiconly': {
303 b'default': False,
303 b'default': False,
304 b'required': False,
304 b'required': False,
305 b'type': b'bool'
305 b'type': b'bool'
306 }
306 }
307 },
307 },
308 b'permissions': [
308 b'permissions': [
309 b'pull'
309 b'pull'
310 ]
310 ]
311 },
311 },
312 b'known': {
312 b'known': {
313 b'args': {
313 b'args': {
314 b'nodes': {
314 b'nodes': {
315 b'default': [],
315 b'default': [],
316 b'required': False,
316 b'required': False,
317 b'type': b'list'
317 b'type': b'list'
318 }
318 }
319 },
319 },
320 b'permissions': [
320 b'permissions': [
321 b'pull'
321 b'pull'
322 ]
322 ]
323 },
323 },
324 b'listkeys': {
324 b'listkeys': {
325 b'args': {
325 b'args': {
326 b'namespace': {
326 b'namespace': {
327 b'required': True,
327 b'required': True,
328 b'type': b'bytes'
328 b'type': b'bytes'
329 }
329 }
330 },
330 },
331 b'permissions': [
331 b'permissions': [
332 b'pull'
332 b'pull'
333 ]
333 ]
334 },
334 },
335 b'lookup': {
335 b'lookup': {
336 b'args': {
336 b'args': {
337 b'key': {
337 b'key': {
338 b'required': True,
338 b'required': True,
339 b'type': b'bytes'
339 b'type': b'bytes'
340 }
340 }
341 },
341 },
342 b'permissions': [
342 b'permissions': [
343 b'pull'
343 b'pull'
344 ]
344 ]
345 },
345 },
346 b'manifestdata': {
346 b'manifestdata': {
347 b'args': {
347 b'args': {
348 b'fields': {
348 b'fields': {
349 b'default': set([]),
349 b'default': set([]),
350 b'required': False,
350 b'required': False,
351 b'type': b'set',
351 b'type': b'set',
352 b'validvalues': set([
352 b'validvalues': set([
353 b'parents',
353 b'parents',
354 b'revision'
354 b'revision'
355 ])
355 ])
356 },
356 },
357 b'haveparents': {
357 b'haveparents': {
358 b'default': False,
358 b'default': False,
359 b'required': False,
359 b'required': False,
360 b'type': b'bool'
360 b'type': b'bool'
361 },
361 },
362 b'nodes': {
362 b'nodes': {
363 b'required': True,
363 b'required': True,
364 b'type': b'list'
364 b'type': b'list'
365 },
365 },
366 b'tree': {
366 b'tree': {
367 b'required': True,
367 b'required': True,
368 b'type': b'bytes'
368 b'type': b'bytes'
369 }
369 }
370 },
370 },
371 b'permissions': [
371 b'permissions': [
372 b'pull'
372 b'pull'
373 ]
373 ]
374 },
374 },
375 b'pushkey': {
375 b'pushkey': {
376 b'args': {
376 b'args': {
377 b'key': {
377 b'key': {
378 b'required': True,
378 b'required': True,
379 b'type': b'bytes'
379 b'type': b'bytes'
380 },
380 },
381 b'namespace': {
381 b'namespace': {
382 b'required': True,
382 b'required': True,
383 b'type': b'bytes'
383 b'type': b'bytes'
384 },
384 },
385 b'new': {
385 b'new': {
386 b'required': True,
386 b'required': True,
387 b'type': b'bytes'
387 b'type': b'bytes'
388 },
388 },
389 b'old': {
389 b'old': {
390 b'required': True,
390 b'required': True,
391 b'type': b'bytes'
391 b'type': b'bytes'
392 }
392 }
393 },
393 },
394 b'permissions': [
394 b'permissions': [
395 b'push'
395 b'push'
396 ]
396 ]
397 }
397 }
398 },
398 },
399 b'framingmediatypes': [
399 b'framingmediatypes': [
400 b'application/mercurial-exp-framing-0005'
400 b'application/mercurial-exp-framing-0006'
401 ],
401 ],
402 b'pathfilterprefixes': set([
402 b'pathfilterprefixes': set([
403 b'path:',
403 b'path:',
404 b'rootfilesin:'
404 b'rootfilesin:'
405 ]),
405 ]),
406 b'rawrepoformats': [
406 b'rawrepoformats': [
407 b'generaldelta',
407 b'generaldelta',
408 b'revlogv1'
408 b'revlogv1'
409 ]
409 ]
410 }
410 }
411 },
411 },
412 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
412 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
413 }
413 }
414 ]
414 ]
415
415
416 capabilities command returns expected info
416 capabilities command returns expected info
417
417
418 $ sendhttpv2peerhandshake << EOF
418 $ sendhttpv2peerhandshake << EOF
419 > command capabilities
419 > command capabilities
420 > EOF
420 > EOF
421 creating http peer for wire protocol version 2
421 creating http peer for wire protocol version 2
422 s> GET /?cmd=capabilities HTTP/1.1\r\n
422 s> GET /?cmd=capabilities HTTP/1.1\r\n
423 s> Accept-Encoding: identity\r\n
423 s> Accept-Encoding: identity\r\n
424 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
424 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
425 s> x-hgproto-1: cbor\r\n
425 s> x-hgproto-1: cbor\r\n
426 s> x-hgupgrade-1: exp-http-v2-0002\r\n
426 s> x-hgupgrade-1: exp-http-v2-0002\r\n
427 s> accept: application/mercurial-0.1\r\n
427 s> accept: application/mercurial-0.1\r\n
428 s> host: $LOCALIP:$HGPORT\r\n (glob)
428 s> host: $LOCALIP:$HGPORT\r\n (glob)
429 s> user-agent: Mercurial debugwireproto\r\n
429 s> user-agent: Mercurial debugwireproto\r\n
430 s> \r\n
430 s> \r\n
431 s> makefile('rb', None)
431 s> makefile('rb', None)
432 s> HTTP/1.1 200 OK\r\n
432 s> HTTP/1.1 200 OK\r\n
433 s> Server: testing stub value\r\n
433 s> Server: testing stub value\r\n
434 s> Date: $HTTP_DATE$\r\n
434 s> Date: $HTTP_DATE$\r\n
435 s> Content-Type: application/mercurial-cbor\r\n
435 s> Content-Type: application/mercurial-cbor\r\n
436 s> Content-Length: *\r\n (glob)
436 s> Content-Length: *\r\n (glob)
437 s> \r\n
437 s> \r\n
438 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
438 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
439 sending capabilities command
439 sending capabilities command
440 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
440 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
441 s> Accept-Encoding: identity\r\n
441 s> Accept-Encoding: identity\r\n
442 s> accept: application/mercurial-exp-framing-0005\r\n
442 s> accept: application/mercurial-exp-framing-0006\r\n
443 s> content-type: application/mercurial-exp-framing-0005\r\n
443 s> content-type: application/mercurial-exp-framing-0006\r\n
444 s> content-length: 27\r\n
444 s> content-length: 63\r\n
445 s> host: $LOCALIP:$HGPORT\r\n (glob)
445 s> host: $LOCALIP:$HGPORT\r\n (glob)
446 s> user-agent: Mercurial debugwireproto\r\n
446 s> user-agent: Mercurial debugwireproto\r\n
447 s> \r\n
447 s> \r\n
448 s> \x13\x00\x00\x01\x00\x01\x01\x11\xa1DnameLcapabilities
448 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x13\x00\x00\x01\x00\x01\x00\x11\xa1DnameLcapabilities
449 s> makefile('rb', None)
449 s> makefile('rb', None)
450 s> HTTP/1.1 200 OK\r\n
450 s> HTTP/1.1 200 OK\r\n
451 s> Server: testing stub value\r\n
451 s> Server: testing stub value\r\n
452 s> Date: $HTTP_DATE$\r\n
452 s> Date: $HTTP_DATE$\r\n
453 s> Content-Type: application/mercurial-exp-framing-0005\r\n
453 s> Content-Type: application/mercurial-exp-framing-0006\r\n
454 s> Transfer-Encoding: chunked\r\n
454 s> Transfer-Encoding: chunked\r\n
455 s> \r\n
455 s> \r\n
456 s> 13\r\n
456 s> 13\r\n
457 s> \x0b\x00\x00\x01\x00\x02\x011
457 s> \x0b\x00\x00\x01\x00\x02\x011
458 s> \xa1FstatusBok
458 s> \xa1FstatusBok
459 s> \r\n
459 s> \r\n
460 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
460 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
461 s> 508\r\n
461 s> 508\r\n
462 s> \x00\x05\x00\x01\x00\x02\x001
462 s> \x00\x05\x00\x01\x00\x02\x001
463 s> \xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1
463 s> \xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1
464 s> \r\n
464 s> \r\n
465 received frame(size=1280; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
465 received frame(size=1280; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
466 s> 8\r\n
466 s> 8\r\n
467 s> \x00\x00\x00\x01\x00\x02\x002
467 s> \x00\x00\x00\x01\x00\x02\x002
468 s> \r\n
468 s> \r\n
469 s> 0\r\n
469 s> 0\r\n
470 s> \r\n
470 s> \r\n
471 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
471 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
472 response: gen[
472 response: gen[
473 {
473 {
474 b'commands': {
474 b'commands': {
475 b'branchmap': {
475 b'branchmap': {
476 b'args': {},
476 b'args': {},
477 b'permissions': [
477 b'permissions': [
478 b'pull'
478 b'pull'
479 ]
479 ]
480 },
480 },
481 b'capabilities': {
481 b'capabilities': {
482 b'args': {},
482 b'args': {},
483 b'permissions': [
483 b'permissions': [
484 b'pull'
484 b'pull'
485 ]
485 ]
486 },
486 },
487 b'changesetdata': {
487 b'changesetdata': {
488 b'args': {
488 b'args': {
489 b'fields': {
489 b'fields': {
490 b'default': set([]),
490 b'default': set([]),
491 b'required': False,
491 b'required': False,
492 b'type': b'set',
492 b'type': b'set',
493 b'validvalues': set([
493 b'validvalues': set([
494 b'bookmarks',
494 b'bookmarks',
495 b'parents',
495 b'parents',
496 b'phase',
496 b'phase',
497 b'revision'
497 b'revision'
498 ])
498 ])
499 },
499 },
500 b'noderange': {
500 b'noderange': {
501 b'default': None,
501 b'default': None,
502 b'required': False,
502 b'required': False,
503 b'type': b'list'
503 b'type': b'list'
504 },
504 },
505 b'nodes': {
505 b'nodes': {
506 b'default': None,
506 b'default': None,
507 b'required': False,
507 b'required': False,
508 b'type': b'list'
508 b'type': b'list'
509 },
509 },
510 b'nodesdepth': {
510 b'nodesdepth': {
511 b'default': None,
511 b'default': None,
512 b'required': False,
512 b'required': False,
513 b'type': b'int'
513 b'type': b'int'
514 }
514 }
515 },
515 },
516 b'permissions': [
516 b'permissions': [
517 b'pull'
517 b'pull'
518 ]
518 ]
519 },
519 },
520 b'filedata': {
520 b'filedata': {
521 b'args': {
521 b'args': {
522 b'fields': {
522 b'fields': {
523 b'default': set([]),
523 b'default': set([]),
524 b'required': False,
524 b'required': False,
525 b'type': b'set',
525 b'type': b'set',
526 b'validvalues': set([
526 b'validvalues': set([
527 b'parents',
527 b'parents',
528 b'revision'
528 b'revision'
529 ])
529 ])
530 },
530 },
531 b'haveparents': {
531 b'haveparents': {
532 b'default': False,
532 b'default': False,
533 b'required': False,
533 b'required': False,
534 b'type': b'bool'
534 b'type': b'bool'
535 },
535 },
536 b'nodes': {
536 b'nodes': {
537 b'required': True,
537 b'required': True,
538 b'type': b'list'
538 b'type': b'list'
539 },
539 },
540 b'path': {
540 b'path': {
541 b'required': True,
541 b'required': True,
542 b'type': b'bytes'
542 b'type': b'bytes'
543 }
543 }
544 },
544 },
545 b'permissions': [
545 b'permissions': [
546 b'pull'
546 b'pull'
547 ]
547 ]
548 },
548 },
549 b'heads': {
549 b'heads': {
550 b'args': {
550 b'args': {
551 b'publiconly': {
551 b'publiconly': {
552 b'default': False,
552 b'default': False,
553 b'required': False,
553 b'required': False,
554 b'type': b'bool'
554 b'type': b'bool'
555 }
555 }
556 },
556 },
557 b'permissions': [
557 b'permissions': [
558 b'pull'
558 b'pull'
559 ]
559 ]
560 },
560 },
561 b'known': {
561 b'known': {
562 b'args': {
562 b'args': {
563 b'nodes': {
563 b'nodes': {
564 b'default': [],
564 b'default': [],
565 b'required': False,
565 b'required': False,
566 b'type': b'list'
566 b'type': b'list'
567 }
567 }
568 },
568 },
569 b'permissions': [
569 b'permissions': [
570 b'pull'
570 b'pull'
571 ]
571 ]
572 },
572 },
573 b'listkeys': {
573 b'listkeys': {
574 b'args': {
574 b'args': {
575 b'namespace': {
575 b'namespace': {
576 b'required': True,
576 b'required': True,
577 b'type': b'bytes'
577 b'type': b'bytes'
578 }
578 }
579 },
579 },
580 b'permissions': [
580 b'permissions': [
581 b'pull'
581 b'pull'
582 ]
582 ]
583 },
583 },
584 b'lookup': {
584 b'lookup': {
585 b'args': {
585 b'args': {
586 b'key': {
586 b'key': {
587 b'required': True,
587 b'required': True,
588 b'type': b'bytes'
588 b'type': b'bytes'
589 }
589 }
590 },
590 },
591 b'permissions': [
591 b'permissions': [
592 b'pull'
592 b'pull'
593 ]
593 ]
594 },
594 },
595 b'manifestdata': {
595 b'manifestdata': {
596 b'args': {
596 b'args': {
597 b'fields': {
597 b'fields': {
598 b'default': set([]),
598 b'default': set([]),
599 b'required': False,
599 b'required': False,
600 b'type': b'set',
600 b'type': b'set',
601 b'validvalues': set([
601 b'validvalues': set([
602 b'parents',
602 b'parents',
603 b'revision'
603 b'revision'
604 ])
604 ])
605 },
605 },
606 b'haveparents': {
606 b'haveparents': {
607 b'default': False,
607 b'default': False,
608 b'required': False,
608 b'required': False,
609 b'type': b'bool'
609 b'type': b'bool'
610 },
610 },
611 b'nodes': {
611 b'nodes': {
612 b'required': True,
612 b'required': True,
613 b'type': b'list'
613 b'type': b'list'
614 },
614 },
615 b'tree': {
615 b'tree': {
616 b'required': True,
616 b'required': True,
617 b'type': b'bytes'
617 b'type': b'bytes'
618 }
618 }
619 },
619 },
620 b'permissions': [
620 b'permissions': [
621 b'pull'
621 b'pull'
622 ]
622 ]
623 },
623 },
624 b'pushkey': {
624 b'pushkey': {
625 b'args': {
625 b'args': {
626 b'key': {
626 b'key': {
627 b'required': True,
627 b'required': True,
628 b'type': b'bytes'
628 b'type': b'bytes'
629 },
629 },
630 b'namespace': {
630 b'namespace': {
631 b'required': True,
631 b'required': True,
632 b'type': b'bytes'
632 b'type': b'bytes'
633 },
633 },
634 b'new': {
634 b'new': {
635 b'required': True,
635 b'required': True,
636 b'type': b'bytes'
636 b'type': b'bytes'
637 },
637 },
638 b'old': {
638 b'old': {
639 b'required': True,
639 b'required': True,
640 b'type': b'bytes'
640 b'type': b'bytes'
641 }
641 }
642 },
642 },
643 b'permissions': [
643 b'permissions': [
644 b'push'
644 b'push'
645 ]
645 ]
646 }
646 }
647 },
647 },
648 b'framingmediatypes': [
648 b'framingmediatypes': [
649 b'application/mercurial-exp-framing-0005'
649 b'application/mercurial-exp-framing-0006'
650 ],
650 ],
651 b'pathfilterprefixes': set([
651 b'pathfilterprefixes': set([
652 b'path:',
652 b'path:',
653 b'rootfilesin:'
653 b'rootfilesin:'
654 ]),
654 ]),
655 b'rawrepoformats': [
655 b'rawrepoformats': [
656 b'generaldelta',
656 b'generaldelta',
657 b'revlogv1'
657 b'revlogv1'
658 ]
658 ]
659 }
659 }
660 ]
660 ]
661 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
661 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
662
662
663 $ cat error.log
663 $ cat error.log
@@ -1,1290 +1,1290
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ cat >> $HGRCPATH << EOF
3 $ cat >> $HGRCPATH << EOF
4 > [extensions]
4 > [extensions]
5 > blackbox =
5 > blackbox =
6 > [blackbox]
6 > [blackbox]
7 > track = simplecache
7 > track = simplecache
8 > EOF
8 > EOF
9
9
10 $ hg init server
10 $ hg init server
11 $ enablehttpv2 server
11 $ enablehttpv2 server
12 $ cd server
12 $ cd server
13 $ cat >> .hg/hgrc << EOF
13 $ cat >> .hg/hgrc << EOF
14 > [server]
14 > [server]
15 > compressionengines = zlib
15 > compressionengines = zlib
16 > [extensions]
16 > [extensions]
17 > simplecache = $TESTDIR/wireprotosimplecache.py
17 > simplecache = $TESTDIR/wireprotosimplecache.py
18 > [simplecache]
18 > [simplecache]
19 > cacheapi = true
19 > cacheapi = true
20 > EOF
20 > EOF
21
21
22 $ echo a0 > a
22 $ echo a0 > a
23 $ echo b0 > b
23 $ echo b0 > b
24 $ hg -q commit -A -m 'commit 0'
24 $ hg -q commit -A -m 'commit 0'
25 $ echo a1 > a
25 $ echo a1 > a
26 $ hg commit -m 'commit 1'
26 $ hg commit -m 'commit 1'
27
27
28 $ hg --debug debugindex -m
28 $ hg --debug debugindex -m
29 rev linkrev nodeid p1 p2
29 rev linkrev nodeid p1 p2
30 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
30 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
31 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000
31 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000
32
32
33 $ hg --config simplecache.redirectsfile=redirects.py serve -p $HGPORT -d --pid-file hg.pid -E error.log
33 $ hg --config simplecache.redirectsfile=redirects.py serve -p $HGPORT -d --pid-file hg.pid -E error.log
34 $ cat hg.pid > $DAEMON_PIDS
34 $ cat hg.pid > $DAEMON_PIDS
35
35
36 $ cat > redirects.py << EOF
36 $ cat > redirects.py << EOF
37 > [
37 > [
38 > {
38 > {
39 > b'name': b'target-a',
39 > b'name': b'target-a',
40 > b'protocol': b'http',
40 > b'protocol': b'http',
41 > b'snirequired': False,
41 > b'snirequired': False,
42 > b'tlsversions': [b'1.2', b'1.3'],
42 > b'tlsversions': [b'1.2', b'1.3'],
43 > b'uris': [b'http://example.com/'],
43 > b'uris': [b'http://example.com/'],
44 > },
44 > },
45 > ]
45 > ]
46 > EOF
46 > EOF
47
47
48 Redirect targets advertised when configured
48 Redirect targets advertised when configured
49
49
50 $ sendhttpv2peerhandshake << EOF
50 $ sendhttpv2peerhandshake << EOF
51 > command capabilities
51 > command capabilities
52 > EOF
52 > EOF
53 creating http peer for wire protocol version 2
53 creating http peer for wire protocol version 2
54 s> GET /?cmd=capabilities HTTP/1.1\r\n
54 s> GET /?cmd=capabilities HTTP/1.1\r\n
55 s> Accept-Encoding: identity\r\n
55 s> Accept-Encoding: identity\r\n
56 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
56 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
57 s> x-hgproto-1: cbor\r\n
57 s> x-hgproto-1: cbor\r\n
58 s> x-hgupgrade-1: exp-http-v2-0002\r\n
58 s> x-hgupgrade-1: exp-http-v2-0002\r\n
59 s> accept: application/mercurial-0.1\r\n
59 s> accept: application/mercurial-0.1\r\n
60 s> host: $LOCALIP:$HGPORT\r\n (glob)
60 s> host: $LOCALIP:$HGPORT\r\n (glob)
61 s> user-agent: Mercurial debugwireproto\r\n
61 s> user-agent: Mercurial debugwireproto\r\n
62 s> \r\n
62 s> \r\n
63 s> makefile('rb', None)
63 s> makefile('rb', None)
64 s> HTTP/1.1 200 OK\r\n
64 s> HTTP/1.1 200 OK\r\n
65 s> Server: testing stub value\r\n
65 s> Server: testing stub value\r\n
66 s> Date: $HTTP_DATE$\r\n
66 s> Date: $HTTP_DATE$\r\n
67 s> Content-Type: application/mercurial-cbor\r\n
67 s> Content-Type: application/mercurial-cbor\r\n
68 s> Content-Length: 1930\r\n
68 s> Content-Length: 1930\r\n
69 s> \r\n
69 s> \r\n
70 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
70 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
71 (remote redirect target target-a is compatible)
71 (remote redirect target target-a is compatible)
72 sending capabilities command
72 sending capabilities command
73 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
73 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
74 s> Accept-Encoding: identity\r\n
74 s> Accept-Encoding: identity\r\n
75 s> accept: application/mercurial-exp-framing-0005\r\n
75 s> accept: application/mercurial-exp-framing-0006\r\n
76 s> content-type: application/mercurial-exp-framing-0005\r\n
76 s> content-type: application/mercurial-exp-framing-0006\r\n
77 s> content-length: 75\r\n
77 s> content-length: 111\r\n
78 s> host: $LOCALIP:$HGPORT\r\n (glob)
78 s> host: $LOCALIP:$HGPORT\r\n (glob)
79 s> user-agent: Mercurial debugwireproto\r\n
79 s> user-agent: Mercurial debugwireproto\r\n
80 s> \r\n
80 s> \r\n
81 s> C\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
81 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81HidentityC\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
82 s> makefile('rb', None)
82 s> makefile('rb', None)
83 s> HTTP/1.1 200 OK\r\n
83 s> HTTP/1.1 200 OK\r\n
84 s> Server: testing stub value\r\n
84 s> Server: testing stub value\r\n
85 s> Date: $HTTP_DATE$\r\n
85 s> Date: $HTTP_DATE$\r\n
86 s> Content-Type: application/mercurial-exp-framing-0005\r\n
86 s> Content-Type: application/mercurial-exp-framing-0006\r\n
87 s> Transfer-Encoding: chunked\r\n
87 s> Transfer-Encoding: chunked\r\n
88 s> \r\n
88 s> \r\n
89 s> 13\r\n
89 s> 13\r\n
90 s> \x0b\x00\x00\x01\x00\x02\x011
90 s> \x0b\x00\x00\x01\x00\x02\x011
91 s> \xa1FstatusBok
91 s> \xa1FstatusBok
92 s> \r\n
92 s> \r\n
93 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
93 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
94 s> 588\r\n
94 s> 588\r\n
95 s> \x80\x05\x00\x01\x00\x02\x001
95 s> \x80\x05\x00\x01\x00\x02\x001
96 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/
96 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/
97 s> \r\n
97 s> \r\n
98 received frame(size=1408; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
98 received frame(size=1408; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
99 s> 8\r\n
99 s> 8\r\n
100 s> \x00\x00\x00\x01\x00\x02\x002
100 s> \x00\x00\x00\x01\x00\x02\x002
101 s> \r\n
101 s> \r\n
102 s> 0\r\n
102 s> 0\r\n
103 s> \r\n
103 s> \r\n
104 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
104 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
105 response: gen[
105 response: gen[
106 {
106 {
107 b'commands': {
107 b'commands': {
108 b'branchmap': {
108 b'branchmap': {
109 b'args': {},
109 b'args': {},
110 b'permissions': [
110 b'permissions': [
111 b'pull'
111 b'pull'
112 ]
112 ]
113 },
113 },
114 b'capabilities': {
114 b'capabilities': {
115 b'args': {},
115 b'args': {},
116 b'permissions': [
116 b'permissions': [
117 b'pull'
117 b'pull'
118 ]
118 ]
119 },
119 },
120 b'changesetdata': {
120 b'changesetdata': {
121 b'args': {
121 b'args': {
122 b'fields': {
122 b'fields': {
123 b'default': set([]),
123 b'default': set([]),
124 b'required': False,
124 b'required': False,
125 b'type': b'set',
125 b'type': b'set',
126 b'validvalues': set([
126 b'validvalues': set([
127 b'bookmarks',
127 b'bookmarks',
128 b'parents',
128 b'parents',
129 b'phase',
129 b'phase',
130 b'revision'
130 b'revision'
131 ])
131 ])
132 },
132 },
133 b'noderange': {
133 b'noderange': {
134 b'default': None,
134 b'default': None,
135 b'required': False,
135 b'required': False,
136 b'type': b'list'
136 b'type': b'list'
137 },
137 },
138 b'nodes': {
138 b'nodes': {
139 b'default': None,
139 b'default': None,
140 b'required': False,
140 b'required': False,
141 b'type': b'list'
141 b'type': b'list'
142 },
142 },
143 b'nodesdepth': {
143 b'nodesdepth': {
144 b'default': None,
144 b'default': None,
145 b'required': False,
145 b'required': False,
146 b'type': b'int'
146 b'type': b'int'
147 }
147 }
148 },
148 },
149 b'permissions': [
149 b'permissions': [
150 b'pull'
150 b'pull'
151 ]
151 ]
152 },
152 },
153 b'filedata': {
153 b'filedata': {
154 b'args': {
154 b'args': {
155 b'fields': {
155 b'fields': {
156 b'default': set([]),
156 b'default': set([]),
157 b'required': False,
157 b'required': False,
158 b'type': b'set',
158 b'type': b'set',
159 b'validvalues': set([
159 b'validvalues': set([
160 b'parents',
160 b'parents',
161 b'revision'
161 b'revision'
162 ])
162 ])
163 },
163 },
164 b'haveparents': {
164 b'haveparents': {
165 b'default': False,
165 b'default': False,
166 b'required': False,
166 b'required': False,
167 b'type': b'bool'
167 b'type': b'bool'
168 },
168 },
169 b'nodes': {
169 b'nodes': {
170 b'required': True,
170 b'required': True,
171 b'type': b'list'
171 b'type': b'list'
172 },
172 },
173 b'path': {
173 b'path': {
174 b'required': True,
174 b'required': True,
175 b'type': b'bytes'
175 b'type': b'bytes'
176 }
176 }
177 },
177 },
178 b'permissions': [
178 b'permissions': [
179 b'pull'
179 b'pull'
180 ]
180 ]
181 },
181 },
182 b'heads': {
182 b'heads': {
183 b'args': {
183 b'args': {
184 b'publiconly': {
184 b'publiconly': {
185 b'default': False,
185 b'default': False,
186 b'required': False,
186 b'required': False,
187 b'type': b'bool'
187 b'type': b'bool'
188 }
188 }
189 },
189 },
190 b'permissions': [
190 b'permissions': [
191 b'pull'
191 b'pull'
192 ]
192 ]
193 },
193 },
194 b'known': {
194 b'known': {
195 b'args': {
195 b'args': {
196 b'nodes': {
196 b'nodes': {
197 b'default': [],
197 b'default': [],
198 b'required': False,
198 b'required': False,
199 b'type': b'list'
199 b'type': b'list'
200 }
200 }
201 },
201 },
202 b'permissions': [
202 b'permissions': [
203 b'pull'
203 b'pull'
204 ]
204 ]
205 },
205 },
206 b'listkeys': {
206 b'listkeys': {
207 b'args': {
207 b'args': {
208 b'namespace': {
208 b'namespace': {
209 b'required': True,
209 b'required': True,
210 b'type': b'bytes'
210 b'type': b'bytes'
211 }
211 }
212 },
212 },
213 b'permissions': [
213 b'permissions': [
214 b'pull'
214 b'pull'
215 ]
215 ]
216 },
216 },
217 b'lookup': {
217 b'lookup': {
218 b'args': {
218 b'args': {
219 b'key': {
219 b'key': {
220 b'required': True,
220 b'required': True,
221 b'type': b'bytes'
221 b'type': b'bytes'
222 }
222 }
223 },
223 },
224 b'permissions': [
224 b'permissions': [
225 b'pull'
225 b'pull'
226 ]
226 ]
227 },
227 },
228 b'manifestdata': {
228 b'manifestdata': {
229 b'args': {
229 b'args': {
230 b'fields': {
230 b'fields': {
231 b'default': set([]),
231 b'default': set([]),
232 b'required': False,
232 b'required': False,
233 b'type': b'set',
233 b'type': b'set',
234 b'validvalues': set([
234 b'validvalues': set([
235 b'parents',
235 b'parents',
236 b'revision'
236 b'revision'
237 ])
237 ])
238 },
238 },
239 b'haveparents': {
239 b'haveparents': {
240 b'default': False,
240 b'default': False,
241 b'required': False,
241 b'required': False,
242 b'type': b'bool'
242 b'type': b'bool'
243 },
243 },
244 b'nodes': {
244 b'nodes': {
245 b'required': True,
245 b'required': True,
246 b'type': b'list'
246 b'type': b'list'
247 },
247 },
248 b'tree': {
248 b'tree': {
249 b'required': True,
249 b'required': True,
250 b'type': b'bytes'
250 b'type': b'bytes'
251 }
251 }
252 },
252 },
253 b'permissions': [
253 b'permissions': [
254 b'pull'
254 b'pull'
255 ]
255 ]
256 },
256 },
257 b'pushkey': {
257 b'pushkey': {
258 b'args': {
258 b'args': {
259 b'key': {
259 b'key': {
260 b'required': True,
260 b'required': True,
261 b'type': b'bytes'
261 b'type': b'bytes'
262 },
262 },
263 b'namespace': {
263 b'namespace': {
264 b'required': True,
264 b'required': True,
265 b'type': b'bytes'
265 b'type': b'bytes'
266 },
266 },
267 b'new': {
267 b'new': {
268 b'required': True,
268 b'required': True,
269 b'type': b'bytes'
269 b'type': b'bytes'
270 },
270 },
271 b'old': {
271 b'old': {
272 b'required': True,
272 b'required': True,
273 b'type': b'bytes'
273 b'type': b'bytes'
274 }
274 }
275 },
275 },
276 b'permissions': [
276 b'permissions': [
277 b'push'
277 b'push'
278 ]
278 ]
279 }
279 }
280 },
280 },
281 b'framingmediatypes': [
281 b'framingmediatypes': [
282 b'application/mercurial-exp-framing-0005'
282 b'application/mercurial-exp-framing-0006'
283 ],
283 ],
284 b'pathfilterprefixes': set([
284 b'pathfilterprefixes': set([
285 b'path:',
285 b'path:',
286 b'rootfilesin:'
286 b'rootfilesin:'
287 ]),
287 ]),
288 b'rawrepoformats': [
288 b'rawrepoformats': [
289 b'generaldelta',
289 b'generaldelta',
290 b'revlogv1'
290 b'revlogv1'
291 ],
291 ],
292 b'redirect': {
292 b'redirect': {
293 b'hashes': [
293 b'hashes': [
294 b'sha256',
294 b'sha256',
295 b'sha1'
295 b'sha1'
296 ],
296 ],
297 b'targets': [
297 b'targets': [
298 {
298 {
299 b'name': b'target-a',
299 b'name': b'target-a',
300 b'protocol': b'http',
300 b'protocol': b'http',
301 b'snirequired': False,
301 b'snirequired': False,
302 b'tlsversions': [
302 b'tlsversions': [
303 b'1.2',
303 b'1.2',
304 b'1.3'
304 b'1.3'
305 ],
305 ],
306 b'uris': [
306 b'uris': [
307 b'http://example.com/'
307 b'http://example.com/'
308 ]
308 ]
309 }
309 }
310 ]
310 ]
311 }
311 }
312 }
312 }
313 ]
313 ]
314 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
314 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
315
315
316 Unknown protocol is filtered from compatible targets
316 Unknown protocol is filtered from compatible targets
317
317
318 $ cat > redirects.py << EOF
318 $ cat > redirects.py << EOF
319 > [
319 > [
320 > {
320 > {
321 > b'name': b'target-a',
321 > b'name': b'target-a',
322 > b'protocol': b'http',
322 > b'protocol': b'http',
323 > b'uris': [b'http://example.com/'],
323 > b'uris': [b'http://example.com/'],
324 > },
324 > },
325 > {
325 > {
326 > b'name': b'target-b',
326 > b'name': b'target-b',
327 > b'protocol': b'unknown',
327 > b'protocol': b'unknown',
328 > b'uris': [b'unknown://example.com/'],
328 > b'uris': [b'unknown://example.com/'],
329 > },
329 > },
330 > ]
330 > ]
331 > EOF
331 > EOF
332
332
333 $ sendhttpv2peerhandshake << EOF
333 $ sendhttpv2peerhandshake << EOF
334 > command capabilities
334 > command capabilities
335 > EOF
335 > EOF
336 creating http peer for wire protocol version 2
336 creating http peer for wire protocol version 2
337 s> GET /?cmd=capabilities HTTP/1.1\r\n
337 s> GET /?cmd=capabilities HTTP/1.1\r\n
338 s> Accept-Encoding: identity\r\n
338 s> Accept-Encoding: identity\r\n
339 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
339 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
340 s> x-hgproto-1: cbor\r\n
340 s> x-hgproto-1: cbor\r\n
341 s> x-hgupgrade-1: exp-http-v2-0002\r\n
341 s> x-hgupgrade-1: exp-http-v2-0002\r\n
342 s> accept: application/mercurial-0.1\r\n
342 s> accept: application/mercurial-0.1\r\n
343 s> host: $LOCALIP:$HGPORT\r\n (glob)
343 s> host: $LOCALIP:$HGPORT\r\n (glob)
344 s> user-agent: Mercurial debugwireproto\r\n
344 s> user-agent: Mercurial debugwireproto\r\n
345 s> \r\n
345 s> \r\n
346 s> makefile('rb', None)
346 s> makefile('rb', None)
347 s> HTTP/1.1 200 OK\r\n
347 s> HTTP/1.1 200 OK\r\n
348 s> Server: testing stub value\r\n
348 s> Server: testing stub value\r\n
349 s> Date: $HTTP_DATE$\r\n
349 s> Date: $HTTP_DATE$\r\n
350 s> Content-Type: application/mercurial-cbor\r\n
350 s> Content-Type: application/mercurial-cbor\r\n
351 s> Content-Length: 1957\r\n
351 s> Content-Length: 1957\r\n
352 s> \r\n
352 s> \r\n
353 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
353 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
354 (remote redirect target target-a is compatible)
354 (remote redirect target target-a is compatible)
355 (remote redirect target target-b uses unsupported protocol: unknown)
355 (remote redirect target target-b uses unsupported protocol: unknown)
356 sending capabilities command
356 sending capabilities command
357 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
357 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
358 s> Accept-Encoding: identity\r\n
358 s> Accept-Encoding: identity\r\n
359 s> accept: application/mercurial-exp-framing-0005\r\n
359 s> accept: application/mercurial-exp-framing-0006\r\n
360 s> content-type: application/mercurial-exp-framing-0005\r\n
360 s> content-type: application/mercurial-exp-framing-0006\r\n
361 s> content-length: 75\r\n
361 s> content-length: 111\r\n
362 s> host: $LOCALIP:$HGPORT\r\n (glob)
362 s> host: $LOCALIP:$HGPORT\r\n (glob)
363 s> user-agent: Mercurial debugwireproto\r\n
363 s> user-agent: Mercurial debugwireproto\r\n
364 s> \r\n
364 s> \r\n
365 s> C\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
365 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81HidentityC\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
366 s> makefile('rb', None)
366 s> makefile('rb', None)
367 s> HTTP/1.1 200 OK\r\n
367 s> HTTP/1.1 200 OK\r\n
368 s> Server: testing stub value\r\n
368 s> Server: testing stub value\r\n
369 s> Date: $HTTP_DATE$\r\n
369 s> Date: $HTTP_DATE$\r\n
370 s> Content-Type: application/mercurial-exp-framing-0005\r\n
370 s> Content-Type: application/mercurial-exp-framing-0006\r\n
371 s> Transfer-Encoding: chunked\r\n
371 s> Transfer-Encoding: chunked\r\n
372 s> \r\n
372 s> \r\n
373 s> 13\r\n
373 s> 13\r\n
374 s> \x0b\x00\x00\x01\x00\x02\x011
374 s> \x0b\x00\x00\x01\x00\x02\x011
375 s> \xa1FstatusBok
375 s> \xa1FstatusBok
376 s> \r\n
376 s> \r\n
377 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
377 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
378 s> 5a3\r\n
378 s> 5a3\r\n
379 s> \x9b\x05\x00\x01\x00\x02\x001
379 s> \x9b\x05\x00\x01\x00\x02\x001
380 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/
380 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/
381 s> \r\n
381 s> \r\n
382 received frame(size=1435; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
382 received frame(size=1435; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
383 s> 8\r\n
383 s> 8\r\n
384 s> \x00\x00\x00\x01\x00\x02\x002
384 s> \x00\x00\x00\x01\x00\x02\x002
385 s> \r\n
385 s> \r\n
386 s> 0\r\n
386 s> 0\r\n
387 s> \r\n
387 s> \r\n
388 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
388 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
389 response: gen[
389 response: gen[
390 {
390 {
391 b'commands': {
391 b'commands': {
392 b'branchmap': {
392 b'branchmap': {
393 b'args': {},
393 b'args': {},
394 b'permissions': [
394 b'permissions': [
395 b'pull'
395 b'pull'
396 ]
396 ]
397 },
397 },
398 b'capabilities': {
398 b'capabilities': {
399 b'args': {},
399 b'args': {},
400 b'permissions': [
400 b'permissions': [
401 b'pull'
401 b'pull'
402 ]
402 ]
403 },
403 },
404 b'changesetdata': {
404 b'changesetdata': {
405 b'args': {
405 b'args': {
406 b'fields': {
406 b'fields': {
407 b'default': set([]),
407 b'default': set([]),
408 b'required': False,
408 b'required': False,
409 b'type': b'set',
409 b'type': b'set',
410 b'validvalues': set([
410 b'validvalues': set([
411 b'bookmarks',
411 b'bookmarks',
412 b'parents',
412 b'parents',
413 b'phase',
413 b'phase',
414 b'revision'
414 b'revision'
415 ])
415 ])
416 },
416 },
417 b'noderange': {
417 b'noderange': {
418 b'default': None,
418 b'default': None,
419 b'required': False,
419 b'required': False,
420 b'type': b'list'
420 b'type': b'list'
421 },
421 },
422 b'nodes': {
422 b'nodes': {
423 b'default': None,
423 b'default': None,
424 b'required': False,
424 b'required': False,
425 b'type': b'list'
425 b'type': b'list'
426 },
426 },
427 b'nodesdepth': {
427 b'nodesdepth': {
428 b'default': None,
428 b'default': None,
429 b'required': False,
429 b'required': False,
430 b'type': b'int'
430 b'type': b'int'
431 }
431 }
432 },
432 },
433 b'permissions': [
433 b'permissions': [
434 b'pull'
434 b'pull'
435 ]
435 ]
436 },
436 },
437 b'filedata': {
437 b'filedata': {
438 b'args': {
438 b'args': {
439 b'fields': {
439 b'fields': {
440 b'default': set([]),
440 b'default': set([]),
441 b'required': False,
441 b'required': False,
442 b'type': b'set',
442 b'type': b'set',
443 b'validvalues': set([
443 b'validvalues': set([
444 b'parents',
444 b'parents',
445 b'revision'
445 b'revision'
446 ])
446 ])
447 },
447 },
448 b'haveparents': {
448 b'haveparents': {
449 b'default': False,
449 b'default': False,
450 b'required': False,
450 b'required': False,
451 b'type': b'bool'
451 b'type': b'bool'
452 },
452 },
453 b'nodes': {
453 b'nodes': {
454 b'required': True,
454 b'required': True,
455 b'type': b'list'
455 b'type': b'list'
456 },
456 },
457 b'path': {
457 b'path': {
458 b'required': True,
458 b'required': True,
459 b'type': b'bytes'
459 b'type': b'bytes'
460 }
460 }
461 },
461 },
462 b'permissions': [
462 b'permissions': [
463 b'pull'
463 b'pull'
464 ]
464 ]
465 },
465 },
466 b'heads': {
466 b'heads': {
467 b'args': {
467 b'args': {
468 b'publiconly': {
468 b'publiconly': {
469 b'default': False,
469 b'default': False,
470 b'required': False,
470 b'required': False,
471 b'type': b'bool'
471 b'type': b'bool'
472 }
472 }
473 },
473 },
474 b'permissions': [
474 b'permissions': [
475 b'pull'
475 b'pull'
476 ]
476 ]
477 },
477 },
478 b'known': {
478 b'known': {
479 b'args': {
479 b'args': {
480 b'nodes': {
480 b'nodes': {
481 b'default': [],
481 b'default': [],
482 b'required': False,
482 b'required': False,
483 b'type': b'list'
483 b'type': b'list'
484 }
484 }
485 },
485 },
486 b'permissions': [
486 b'permissions': [
487 b'pull'
487 b'pull'
488 ]
488 ]
489 },
489 },
490 b'listkeys': {
490 b'listkeys': {
491 b'args': {
491 b'args': {
492 b'namespace': {
492 b'namespace': {
493 b'required': True,
493 b'required': True,
494 b'type': b'bytes'
494 b'type': b'bytes'
495 }
495 }
496 },
496 },
497 b'permissions': [
497 b'permissions': [
498 b'pull'
498 b'pull'
499 ]
499 ]
500 },
500 },
501 b'lookup': {
501 b'lookup': {
502 b'args': {
502 b'args': {
503 b'key': {
503 b'key': {
504 b'required': True,
504 b'required': True,
505 b'type': b'bytes'
505 b'type': b'bytes'
506 }
506 }
507 },
507 },
508 b'permissions': [
508 b'permissions': [
509 b'pull'
509 b'pull'
510 ]
510 ]
511 },
511 },
512 b'manifestdata': {
512 b'manifestdata': {
513 b'args': {
513 b'args': {
514 b'fields': {
514 b'fields': {
515 b'default': set([]),
515 b'default': set([]),
516 b'required': False,
516 b'required': False,
517 b'type': b'set',
517 b'type': b'set',
518 b'validvalues': set([
518 b'validvalues': set([
519 b'parents',
519 b'parents',
520 b'revision'
520 b'revision'
521 ])
521 ])
522 },
522 },
523 b'haveparents': {
523 b'haveparents': {
524 b'default': False,
524 b'default': False,
525 b'required': False,
525 b'required': False,
526 b'type': b'bool'
526 b'type': b'bool'
527 },
527 },
528 b'nodes': {
528 b'nodes': {
529 b'required': True,
529 b'required': True,
530 b'type': b'list'
530 b'type': b'list'
531 },
531 },
532 b'tree': {
532 b'tree': {
533 b'required': True,
533 b'required': True,
534 b'type': b'bytes'
534 b'type': b'bytes'
535 }
535 }
536 },
536 },
537 b'permissions': [
537 b'permissions': [
538 b'pull'
538 b'pull'
539 ]
539 ]
540 },
540 },
541 b'pushkey': {
541 b'pushkey': {
542 b'args': {
542 b'args': {
543 b'key': {
543 b'key': {
544 b'required': True,
544 b'required': True,
545 b'type': b'bytes'
545 b'type': b'bytes'
546 },
546 },
547 b'namespace': {
547 b'namespace': {
548 b'required': True,
548 b'required': True,
549 b'type': b'bytes'
549 b'type': b'bytes'
550 },
550 },
551 b'new': {
551 b'new': {
552 b'required': True,
552 b'required': True,
553 b'type': b'bytes'
553 b'type': b'bytes'
554 },
554 },
555 b'old': {
555 b'old': {
556 b'required': True,
556 b'required': True,
557 b'type': b'bytes'
557 b'type': b'bytes'
558 }
558 }
559 },
559 },
560 b'permissions': [
560 b'permissions': [
561 b'push'
561 b'push'
562 ]
562 ]
563 }
563 }
564 },
564 },
565 b'framingmediatypes': [
565 b'framingmediatypes': [
566 b'application/mercurial-exp-framing-0005'
566 b'application/mercurial-exp-framing-0006'
567 ],
567 ],
568 b'pathfilterprefixes': set([
568 b'pathfilterprefixes': set([
569 b'path:',
569 b'path:',
570 b'rootfilesin:'
570 b'rootfilesin:'
571 ]),
571 ]),
572 b'rawrepoformats': [
572 b'rawrepoformats': [
573 b'generaldelta',
573 b'generaldelta',
574 b'revlogv1'
574 b'revlogv1'
575 ],
575 ],
576 b'redirect': {
576 b'redirect': {
577 b'hashes': [
577 b'hashes': [
578 b'sha256',
578 b'sha256',
579 b'sha1'
579 b'sha1'
580 ],
580 ],
581 b'targets': [
581 b'targets': [
582 {
582 {
583 b'name': b'target-a',
583 b'name': b'target-a',
584 b'protocol': b'http',
584 b'protocol': b'http',
585 b'uris': [
585 b'uris': [
586 b'http://example.com/'
586 b'http://example.com/'
587 ]
587 ]
588 },
588 },
589 {
589 {
590 b'name': b'target-b',
590 b'name': b'target-b',
591 b'protocol': b'unknown',
591 b'protocol': b'unknown',
592 b'uris': [
592 b'uris': [
593 b'unknown://example.com/'
593 b'unknown://example.com/'
594 ]
594 ]
595 }
595 }
596 ]
596 ]
597 }
597 }
598 }
598 }
599 ]
599 ]
600 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
600 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
601
601
602 Missing SNI support filters targets that require SNI
602 Missing SNI support filters targets that require SNI
603
603
604 $ cat > nosni.py << EOF
604 $ cat > nosni.py << EOF
605 > from mercurial import sslutil
605 > from mercurial import sslutil
606 > sslutil.hassni = False
606 > sslutil.hassni = False
607 > EOF
607 > EOF
608 $ cat >> $HGRCPATH << EOF
608 $ cat >> $HGRCPATH << EOF
609 > [extensions]
609 > [extensions]
610 > nosni=`pwd`/nosni.py
610 > nosni=`pwd`/nosni.py
611 > EOF
611 > EOF
612
612
613 $ cat > redirects.py << EOF
613 $ cat > redirects.py << EOF
614 > [
614 > [
615 > {
615 > {
616 > b'name': b'target-bad-tls',
616 > b'name': b'target-bad-tls',
617 > b'protocol': b'https',
617 > b'protocol': b'https',
618 > b'uris': [b'https://example.com/'],
618 > b'uris': [b'https://example.com/'],
619 > b'snirequired': True,
619 > b'snirequired': True,
620 > },
620 > },
621 > ]
621 > ]
622 > EOF
622 > EOF
623
623
624 $ sendhttpv2peerhandshake << EOF
624 $ sendhttpv2peerhandshake << EOF
625 > command capabilities
625 > command capabilities
626 > EOF
626 > EOF
627 creating http peer for wire protocol version 2
627 creating http peer for wire protocol version 2
628 s> GET /?cmd=capabilities HTTP/1.1\r\n
628 s> GET /?cmd=capabilities HTTP/1.1\r\n
629 s> Accept-Encoding: identity\r\n
629 s> Accept-Encoding: identity\r\n
630 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
630 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
631 s> x-hgproto-1: cbor\r\n
631 s> x-hgproto-1: cbor\r\n
632 s> x-hgupgrade-1: exp-http-v2-0002\r\n
632 s> x-hgupgrade-1: exp-http-v2-0002\r\n
633 s> accept: application/mercurial-0.1\r\n
633 s> accept: application/mercurial-0.1\r\n
634 s> host: $LOCALIP:$HGPORT\r\n (glob)
634 s> host: $LOCALIP:$HGPORT\r\n (glob)
635 s> user-agent: Mercurial debugwireproto\r\n
635 s> user-agent: Mercurial debugwireproto\r\n
636 s> \r\n
636 s> \r\n
637 s> makefile('rb', None)
637 s> makefile('rb', None)
638 s> HTTP/1.1 200 OK\r\n
638 s> HTTP/1.1 200 OK\r\n
639 s> Server: testing stub value\r\n
639 s> Server: testing stub value\r\n
640 s> Date: $HTTP_DATE$\r\n
640 s> Date: $HTTP_DATE$\r\n
641 s> Content-Type: application/mercurial-cbor\r\n
641 s> Content-Type: application/mercurial-cbor\r\n
642 s> Content-Length: 1917\r\n
642 s> Content-Length: 1917\r\n
643 s> \r\n
643 s> \r\n
644 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
644 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
645 (redirect target target-bad-tls requires SNI, which is unsupported)
645 (redirect target target-bad-tls requires SNI, which is unsupported)
646 sending capabilities command
646 sending capabilities command
647 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
647 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
648 s> Accept-Encoding: identity\r\n
648 s> Accept-Encoding: identity\r\n
649 s> accept: application/mercurial-exp-framing-0005\r\n
649 s> accept: application/mercurial-exp-framing-0006\r\n
650 s> content-type: application/mercurial-exp-framing-0005\r\n
650 s> content-type: application/mercurial-exp-framing-0006\r\n
651 s> content-length: 66\r\n
651 s> content-length: 102\r\n
652 s> host: $LOCALIP:$HGPORT\r\n (glob)
652 s> host: $LOCALIP:$HGPORT\r\n (glob)
653 s> user-agent: Mercurial debugwireproto\r\n
653 s> user-agent: Mercurial debugwireproto\r\n
654 s> \r\n
654 s> \r\n
655 s> :\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
655 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity:\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
656 s> makefile('rb', None)
656 s> makefile('rb', None)
657 s> HTTP/1.1 200 OK\r\n
657 s> HTTP/1.1 200 OK\r\n
658 s> Server: testing stub value\r\n
658 s> Server: testing stub value\r\n
659 s> Date: $HTTP_DATE$\r\n
659 s> Date: $HTTP_DATE$\r\n
660 s> Content-Type: application/mercurial-exp-framing-0005\r\n
660 s> Content-Type: application/mercurial-exp-framing-0006\r\n
661 s> Transfer-Encoding: chunked\r\n
661 s> Transfer-Encoding: chunked\r\n
662 s> \r\n
662 s> \r\n
663 s> 13\r\n
663 s> 13\r\n
664 s> \x0b\x00\x00\x01\x00\x02\x011
664 s> \x0b\x00\x00\x01\x00\x02\x011
665 s> \xa1FstatusBok
665 s> \xa1FstatusBok
666 s> \r\n
666 s> \r\n
667 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
667 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
668 s> 57b\r\n
668 s> 57b\r\n
669 s> s\x05\x00\x01\x00\x02\x001
669 s> s\x05\x00\x01\x00\x02\x001
670 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/
670 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/
671 s> \r\n
671 s> \r\n
672 received frame(size=1395; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
672 received frame(size=1395; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
673 s> 8\r\n
673 s> 8\r\n
674 s> \x00\x00\x00\x01\x00\x02\x002
674 s> \x00\x00\x00\x01\x00\x02\x002
675 s> \r\n
675 s> \r\n
676 s> 0\r\n
676 s> 0\r\n
677 s> \r\n
677 s> \r\n
678 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
678 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
679 response: gen[
679 response: gen[
680 {
680 {
681 b'commands': {
681 b'commands': {
682 b'branchmap': {
682 b'branchmap': {
683 b'args': {},
683 b'args': {},
684 b'permissions': [
684 b'permissions': [
685 b'pull'
685 b'pull'
686 ]
686 ]
687 },
687 },
688 b'capabilities': {
688 b'capabilities': {
689 b'args': {},
689 b'args': {},
690 b'permissions': [
690 b'permissions': [
691 b'pull'
691 b'pull'
692 ]
692 ]
693 },
693 },
694 b'changesetdata': {
694 b'changesetdata': {
695 b'args': {
695 b'args': {
696 b'fields': {
696 b'fields': {
697 b'default': set([]),
697 b'default': set([]),
698 b'required': False,
698 b'required': False,
699 b'type': b'set',
699 b'type': b'set',
700 b'validvalues': set([
700 b'validvalues': set([
701 b'bookmarks',
701 b'bookmarks',
702 b'parents',
702 b'parents',
703 b'phase',
703 b'phase',
704 b'revision'
704 b'revision'
705 ])
705 ])
706 },
706 },
707 b'noderange': {
707 b'noderange': {
708 b'default': None,
708 b'default': None,
709 b'required': False,
709 b'required': False,
710 b'type': b'list'
710 b'type': b'list'
711 },
711 },
712 b'nodes': {
712 b'nodes': {
713 b'default': None,
713 b'default': None,
714 b'required': False,
714 b'required': False,
715 b'type': b'list'
715 b'type': b'list'
716 },
716 },
717 b'nodesdepth': {
717 b'nodesdepth': {
718 b'default': None,
718 b'default': None,
719 b'required': False,
719 b'required': False,
720 b'type': b'int'
720 b'type': b'int'
721 }
721 }
722 },
722 },
723 b'permissions': [
723 b'permissions': [
724 b'pull'
724 b'pull'
725 ]
725 ]
726 },
726 },
727 b'filedata': {
727 b'filedata': {
728 b'args': {
728 b'args': {
729 b'fields': {
729 b'fields': {
730 b'default': set([]),
730 b'default': set([]),
731 b'required': False,
731 b'required': False,
732 b'type': b'set',
732 b'type': b'set',
733 b'validvalues': set([
733 b'validvalues': set([
734 b'parents',
734 b'parents',
735 b'revision'
735 b'revision'
736 ])
736 ])
737 },
737 },
738 b'haveparents': {
738 b'haveparents': {
739 b'default': False,
739 b'default': False,
740 b'required': False,
740 b'required': False,
741 b'type': b'bool'
741 b'type': b'bool'
742 },
742 },
743 b'nodes': {
743 b'nodes': {
744 b'required': True,
744 b'required': True,
745 b'type': b'list'
745 b'type': b'list'
746 },
746 },
747 b'path': {
747 b'path': {
748 b'required': True,
748 b'required': True,
749 b'type': b'bytes'
749 b'type': b'bytes'
750 }
750 }
751 },
751 },
752 b'permissions': [
752 b'permissions': [
753 b'pull'
753 b'pull'
754 ]
754 ]
755 },
755 },
756 b'heads': {
756 b'heads': {
757 b'args': {
757 b'args': {
758 b'publiconly': {
758 b'publiconly': {
759 b'default': False,
759 b'default': False,
760 b'required': False,
760 b'required': False,
761 b'type': b'bool'
761 b'type': b'bool'
762 }
762 }
763 },
763 },
764 b'permissions': [
764 b'permissions': [
765 b'pull'
765 b'pull'
766 ]
766 ]
767 },
767 },
768 b'known': {
768 b'known': {
769 b'args': {
769 b'args': {
770 b'nodes': {
770 b'nodes': {
771 b'default': [],
771 b'default': [],
772 b'required': False,
772 b'required': False,
773 b'type': b'list'
773 b'type': b'list'
774 }
774 }
775 },
775 },
776 b'permissions': [
776 b'permissions': [
777 b'pull'
777 b'pull'
778 ]
778 ]
779 },
779 },
780 b'listkeys': {
780 b'listkeys': {
781 b'args': {
781 b'args': {
782 b'namespace': {
782 b'namespace': {
783 b'required': True,
783 b'required': True,
784 b'type': b'bytes'
784 b'type': b'bytes'
785 }
785 }
786 },
786 },
787 b'permissions': [
787 b'permissions': [
788 b'pull'
788 b'pull'
789 ]
789 ]
790 },
790 },
791 b'lookup': {
791 b'lookup': {
792 b'args': {
792 b'args': {
793 b'key': {
793 b'key': {
794 b'required': True,
794 b'required': True,
795 b'type': b'bytes'
795 b'type': b'bytes'
796 }
796 }
797 },
797 },
798 b'permissions': [
798 b'permissions': [
799 b'pull'
799 b'pull'
800 ]
800 ]
801 },
801 },
802 b'manifestdata': {
802 b'manifestdata': {
803 b'args': {
803 b'args': {
804 b'fields': {
804 b'fields': {
805 b'default': set([]),
805 b'default': set([]),
806 b'required': False,
806 b'required': False,
807 b'type': b'set',
807 b'type': b'set',
808 b'validvalues': set([
808 b'validvalues': set([
809 b'parents',
809 b'parents',
810 b'revision'
810 b'revision'
811 ])
811 ])
812 },
812 },
813 b'haveparents': {
813 b'haveparents': {
814 b'default': False,
814 b'default': False,
815 b'required': False,
815 b'required': False,
816 b'type': b'bool'
816 b'type': b'bool'
817 },
817 },
818 b'nodes': {
818 b'nodes': {
819 b'required': True,
819 b'required': True,
820 b'type': b'list'
820 b'type': b'list'
821 },
821 },
822 b'tree': {
822 b'tree': {
823 b'required': True,
823 b'required': True,
824 b'type': b'bytes'
824 b'type': b'bytes'
825 }
825 }
826 },
826 },
827 b'permissions': [
827 b'permissions': [
828 b'pull'
828 b'pull'
829 ]
829 ]
830 },
830 },
831 b'pushkey': {
831 b'pushkey': {
832 b'args': {
832 b'args': {
833 b'key': {
833 b'key': {
834 b'required': True,
834 b'required': True,
835 b'type': b'bytes'
835 b'type': b'bytes'
836 },
836 },
837 b'namespace': {
837 b'namespace': {
838 b'required': True,
838 b'required': True,
839 b'type': b'bytes'
839 b'type': b'bytes'
840 },
840 },
841 b'new': {
841 b'new': {
842 b'required': True,
842 b'required': True,
843 b'type': b'bytes'
843 b'type': b'bytes'
844 },
844 },
845 b'old': {
845 b'old': {
846 b'required': True,
846 b'required': True,
847 b'type': b'bytes'
847 b'type': b'bytes'
848 }
848 }
849 },
849 },
850 b'permissions': [
850 b'permissions': [
851 b'push'
851 b'push'
852 ]
852 ]
853 }
853 }
854 },
854 },
855 b'framingmediatypes': [
855 b'framingmediatypes': [
856 b'application/mercurial-exp-framing-0005'
856 b'application/mercurial-exp-framing-0006'
857 ],
857 ],
858 b'pathfilterprefixes': set([
858 b'pathfilterprefixes': set([
859 b'path:',
859 b'path:',
860 b'rootfilesin:'
860 b'rootfilesin:'
861 ]),
861 ]),
862 b'rawrepoformats': [
862 b'rawrepoformats': [
863 b'generaldelta',
863 b'generaldelta',
864 b'revlogv1'
864 b'revlogv1'
865 ],
865 ],
866 b'redirect': {
866 b'redirect': {
867 b'hashes': [
867 b'hashes': [
868 b'sha256',
868 b'sha256',
869 b'sha1'
869 b'sha1'
870 ],
870 ],
871 b'targets': [
871 b'targets': [
872 {
872 {
873 b'name': b'target-bad-tls',
873 b'name': b'target-bad-tls',
874 b'protocol': b'https',
874 b'protocol': b'https',
875 b'snirequired': True,
875 b'snirequired': True,
876 b'uris': [
876 b'uris': [
877 b'https://example.com/'
877 b'https://example.com/'
878 ]
878 ]
879 }
879 }
880 ]
880 ]
881 }
881 }
882 }
882 }
883 ]
883 ]
884 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
884 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
885
885
886 $ cat >> $HGRCPATH << EOF
886 $ cat >> $HGRCPATH << EOF
887 > [extensions]
887 > [extensions]
888 > nosni=!
888 > nosni=!
889 > EOF
889 > EOF
890
890
891 Unknown tls value is filtered from compatible targets
891 Unknown tls value is filtered from compatible targets
892
892
893 $ cat > redirects.py << EOF
893 $ cat > redirects.py << EOF
894 > [
894 > [
895 > {
895 > {
896 > b'name': b'target-bad-tls',
896 > b'name': b'target-bad-tls',
897 > b'protocol': b'https',
897 > b'protocol': b'https',
898 > b'uris': [b'https://example.com/'],
898 > b'uris': [b'https://example.com/'],
899 > b'tlsversions': [b'42', b'39'],
899 > b'tlsversions': [b'42', b'39'],
900 > },
900 > },
901 > ]
901 > ]
902 > EOF
902 > EOF
903
903
904 $ sendhttpv2peerhandshake << EOF
904 $ sendhttpv2peerhandshake << EOF
905 > command capabilities
905 > command capabilities
906 > EOF
906 > EOF
907 creating http peer for wire protocol version 2
907 creating http peer for wire protocol version 2
908 s> GET /?cmd=capabilities HTTP/1.1\r\n
908 s> GET /?cmd=capabilities HTTP/1.1\r\n
909 s> Accept-Encoding: identity\r\n
909 s> Accept-Encoding: identity\r\n
910 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
910 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
911 s> x-hgproto-1: cbor\r\n
911 s> x-hgproto-1: cbor\r\n
912 s> x-hgupgrade-1: exp-http-v2-0002\r\n
912 s> x-hgupgrade-1: exp-http-v2-0002\r\n
913 s> accept: application/mercurial-0.1\r\n
913 s> accept: application/mercurial-0.1\r\n
914 s> host: $LOCALIP:$HGPORT\r\n (glob)
914 s> host: $LOCALIP:$HGPORT\r\n (glob)
915 s> user-agent: Mercurial debugwireproto\r\n
915 s> user-agent: Mercurial debugwireproto\r\n
916 s> \r\n
916 s> \r\n
917 s> makefile('rb', None)
917 s> makefile('rb', None)
918 s> HTTP/1.1 200 OK\r\n
918 s> HTTP/1.1 200 OK\r\n
919 s> Server: testing stub value\r\n
919 s> Server: testing stub value\r\n
920 s> Date: $HTTP_DATE$\r\n
920 s> Date: $HTTP_DATE$\r\n
921 s> Content-Type: application/mercurial-cbor\r\n
921 s> Content-Type: application/mercurial-cbor\r\n
922 s> Content-Length: 1923\r\n
922 s> Content-Length: 1923\r\n
923 s> \r\n
923 s> \r\n
924 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
924 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
925 (remote redirect target target-bad-tls requires unsupported TLS versions: 39, 42)
925 (remote redirect target target-bad-tls requires unsupported TLS versions: 39, 42)
926 sending capabilities command
926 sending capabilities command
927 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
927 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
928 s> Accept-Encoding: identity\r\n
928 s> Accept-Encoding: identity\r\n
929 s> accept: application/mercurial-exp-framing-0005\r\n
929 s> accept: application/mercurial-exp-framing-0006\r\n
930 s> content-type: application/mercurial-exp-framing-0005\r\n
930 s> content-type: application/mercurial-exp-framing-0006\r\n
931 s> content-length: 66\r\n
931 s> content-length: 102\r\n
932 s> host: $LOCALIP:$HGPORT\r\n (glob)
932 s> host: $LOCALIP:$HGPORT\r\n (glob)
933 s> user-agent: Mercurial debugwireproto\r\n
933 s> user-agent: Mercurial debugwireproto\r\n
934 s> \r\n
934 s> \r\n
935 s> :\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
935 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity:\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
936 s> makefile('rb', None)
936 s> makefile('rb', None)
937 s> HTTP/1.1 200 OK\r\n
937 s> HTTP/1.1 200 OK\r\n
938 s> Server: testing stub value\r\n
938 s> Server: testing stub value\r\n
939 s> Date: $HTTP_DATE$\r\n
939 s> Date: $HTTP_DATE$\r\n
940 s> Content-Type: application/mercurial-exp-framing-0005\r\n
940 s> Content-Type: application/mercurial-exp-framing-0006\r\n
941 s> Transfer-Encoding: chunked\r\n
941 s> Transfer-Encoding: chunked\r\n
942 s> \r\n
942 s> \r\n
943 s> 13\r\n
943 s> 13\r\n
944 s> \x0b\x00\x00\x01\x00\x02\x011
944 s> \x0b\x00\x00\x01\x00\x02\x011
945 s> \xa1FstatusBok
945 s> \xa1FstatusBok
946 s> \r\n
946 s> \r\n
947 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
947 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
948 s> 581\r\n
948 s> 581\r\n
949 s> y\x05\x00\x01\x00\x02\x001
949 s> y\x05\x00\x01\x00\x02\x001
950 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/
950 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/
951 s> \r\n
951 s> \r\n
952 received frame(size=1401; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
952 received frame(size=1401; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
953 s> 8\r\n
953 s> 8\r\n
954 s> \x00\x00\x00\x01\x00\x02\x002
954 s> \x00\x00\x00\x01\x00\x02\x002
955 s> \r\n
955 s> \r\n
956 s> 0\r\n
956 s> 0\r\n
957 s> \r\n
957 s> \r\n
958 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
958 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
959 response: gen[
959 response: gen[
960 {
960 {
961 b'commands': {
961 b'commands': {
962 b'branchmap': {
962 b'branchmap': {
963 b'args': {},
963 b'args': {},
964 b'permissions': [
964 b'permissions': [
965 b'pull'
965 b'pull'
966 ]
966 ]
967 },
967 },
968 b'capabilities': {
968 b'capabilities': {
969 b'args': {},
969 b'args': {},
970 b'permissions': [
970 b'permissions': [
971 b'pull'
971 b'pull'
972 ]
972 ]
973 },
973 },
974 b'changesetdata': {
974 b'changesetdata': {
975 b'args': {
975 b'args': {
976 b'fields': {
976 b'fields': {
977 b'default': set([]),
977 b'default': set([]),
978 b'required': False,
978 b'required': False,
979 b'type': b'set',
979 b'type': b'set',
980 b'validvalues': set([
980 b'validvalues': set([
981 b'bookmarks',
981 b'bookmarks',
982 b'parents',
982 b'parents',
983 b'phase',
983 b'phase',
984 b'revision'
984 b'revision'
985 ])
985 ])
986 },
986 },
987 b'noderange': {
987 b'noderange': {
988 b'default': None,
988 b'default': None,
989 b'required': False,
989 b'required': False,
990 b'type': b'list'
990 b'type': b'list'
991 },
991 },
992 b'nodes': {
992 b'nodes': {
993 b'default': None,
993 b'default': None,
994 b'required': False,
994 b'required': False,
995 b'type': b'list'
995 b'type': b'list'
996 },
996 },
997 b'nodesdepth': {
997 b'nodesdepth': {
998 b'default': None,
998 b'default': None,
999 b'required': False,
999 b'required': False,
1000 b'type': b'int'
1000 b'type': b'int'
1001 }
1001 }
1002 },
1002 },
1003 b'permissions': [
1003 b'permissions': [
1004 b'pull'
1004 b'pull'
1005 ]
1005 ]
1006 },
1006 },
1007 b'filedata': {
1007 b'filedata': {
1008 b'args': {
1008 b'args': {
1009 b'fields': {
1009 b'fields': {
1010 b'default': set([]),
1010 b'default': set([]),
1011 b'required': False,
1011 b'required': False,
1012 b'type': b'set',
1012 b'type': b'set',
1013 b'validvalues': set([
1013 b'validvalues': set([
1014 b'parents',
1014 b'parents',
1015 b'revision'
1015 b'revision'
1016 ])
1016 ])
1017 },
1017 },
1018 b'haveparents': {
1018 b'haveparents': {
1019 b'default': False,
1019 b'default': False,
1020 b'required': False,
1020 b'required': False,
1021 b'type': b'bool'
1021 b'type': b'bool'
1022 },
1022 },
1023 b'nodes': {
1023 b'nodes': {
1024 b'required': True,
1024 b'required': True,
1025 b'type': b'list'
1025 b'type': b'list'
1026 },
1026 },
1027 b'path': {
1027 b'path': {
1028 b'required': True,
1028 b'required': True,
1029 b'type': b'bytes'
1029 b'type': b'bytes'
1030 }
1030 }
1031 },
1031 },
1032 b'permissions': [
1032 b'permissions': [
1033 b'pull'
1033 b'pull'
1034 ]
1034 ]
1035 },
1035 },
1036 b'heads': {
1036 b'heads': {
1037 b'args': {
1037 b'args': {
1038 b'publiconly': {
1038 b'publiconly': {
1039 b'default': False,
1039 b'default': False,
1040 b'required': False,
1040 b'required': False,
1041 b'type': b'bool'
1041 b'type': b'bool'
1042 }
1042 }
1043 },
1043 },
1044 b'permissions': [
1044 b'permissions': [
1045 b'pull'
1045 b'pull'
1046 ]
1046 ]
1047 },
1047 },
1048 b'known': {
1048 b'known': {
1049 b'args': {
1049 b'args': {
1050 b'nodes': {
1050 b'nodes': {
1051 b'default': [],
1051 b'default': [],
1052 b'required': False,
1052 b'required': False,
1053 b'type': b'list'
1053 b'type': b'list'
1054 }
1054 }
1055 },
1055 },
1056 b'permissions': [
1056 b'permissions': [
1057 b'pull'
1057 b'pull'
1058 ]
1058 ]
1059 },
1059 },
1060 b'listkeys': {
1060 b'listkeys': {
1061 b'args': {
1061 b'args': {
1062 b'namespace': {
1062 b'namespace': {
1063 b'required': True,
1063 b'required': True,
1064 b'type': b'bytes'
1064 b'type': b'bytes'
1065 }
1065 }
1066 },
1066 },
1067 b'permissions': [
1067 b'permissions': [
1068 b'pull'
1068 b'pull'
1069 ]
1069 ]
1070 },
1070 },
1071 b'lookup': {
1071 b'lookup': {
1072 b'args': {
1072 b'args': {
1073 b'key': {
1073 b'key': {
1074 b'required': True,
1074 b'required': True,
1075 b'type': b'bytes'
1075 b'type': b'bytes'
1076 }
1076 }
1077 },
1077 },
1078 b'permissions': [
1078 b'permissions': [
1079 b'pull'
1079 b'pull'
1080 ]
1080 ]
1081 },
1081 },
1082 b'manifestdata': {
1082 b'manifestdata': {
1083 b'args': {
1083 b'args': {
1084 b'fields': {
1084 b'fields': {
1085 b'default': set([]),
1085 b'default': set([]),
1086 b'required': False,
1086 b'required': False,
1087 b'type': b'set',
1087 b'type': b'set',
1088 b'validvalues': set([
1088 b'validvalues': set([
1089 b'parents',
1089 b'parents',
1090 b'revision'
1090 b'revision'
1091 ])
1091 ])
1092 },
1092 },
1093 b'haveparents': {
1093 b'haveparents': {
1094 b'default': False,
1094 b'default': False,
1095 b'required': False,
1095 b'required': False,
1096 b'type': b'bool'
1096 b'type': b'bool'
1097 },
1097 },
1098 b'nodes': {
1098 b'nodes': {
1099 b'required': True,
1099 b'required': True,
1100 b'type': b'list'
1100 b'type': b'list'
1101 },
1101 },
1102 b'tree': {
1102 b'tree': {
1103 b'required': True,
1103 b'required': True,
1104 b'type': b'bytes'
1104 b'type': b'bytes'
1105 }
1105 }
1106 },
1106 },
1107 b'permissions': [
1107 b'permissions': [
1108 b'pull'
1108 b'pull'
1109 ]
1109 ]
1110 },
1110 },
1111 b'pushkey': {
1111 b'pushkey': {
1112 b'args': {
1112 b'args': {
1113 b'key': {
1113 b'key': {
1114 b'required': True,
1114 b'required': True,
1115 b'type': b'bytes'
1115 b'type': b'bytes'
1116 },
1116 },
1117 b'namespace': {
1117 b'namespace': {
1118 b'required': True,
1118 b'required': True,
1119 b'type': b'bytes'
1119 b'type': b'bytes'
1120 },
1120 },
1121 b'new': {
1121 b'new': {
1122 b'required': True,
1122 b'required': True,
1123 b'type': b'bytes'
1123 b'type': b'bytes'
1124 },
1124 },
1125 b'old': {
1125 b'old': {
1126 b'required': True,
1126 b'required': True,
1127 b'type': b'bytes'
1127 b'type': b'bytes'
1128 }
1128 }
1129 },
1129 },
1130 b'permissions': [
1130 b'permissions': [
1131 b'push'
1131 b'push'
1132 ]
1132 ]
1133 }
1133 }
1134 },
1134 },
1135 b'framingmediatypes': [
1135 b'framingmediatypes': [
1136 b'application/mercurial-exp-framing-0005'
1136 b'application/mercurial-exp-framing-0006'
1137 ],
1137 ],
1138 b'pathfilterprefixes': set([
1138 b'pathfilterprefixes': set([
1139 b'path:',
1139 b'path:',
1140 b'rootfilesin:'
1140 b'rootfilesin:'
1141 ]),
1141 ]),
1142 b'rawrepoformats': [
1142 b'rawrepoformats': [
1143 b'generaldelta',
1143 b'generaldelta',
1144 b'revlogv1'
1144 b'revlogv1'
1145 ],
1145 ],
1146 b'redirect': {
1146 b'redirect': {
1147 b'hashes': [
1147 b'hashes': [
1148 b'sha256',
1148 b'sha256',
1149 b'sha1'
1149 b'sha1'
1150 ],
1150 ],
1151 b'targets': [
1151 b'targets': [
1152 {
1152 {
1153 b'name': b'target-bad-tls',
1153 b'name': b'target-bad-tls',
1154 b'protocol': b'https',
1154 b'protocol': b'https',
1155 b'tlsversions': [
1155 b'tlsversions': [
1156 b'42',
1156 b'42',
1157 b'39'
1157 b'39'
1158 ],
1158 ],
1159 b'uris': [
1159 b'uris': [
1160 b'https://example.com/'
1160 b'https://example.com/'
1161 ]
1161 ]
1162 }
1162 }
1163 ]
1163 ]
1164 }
1164 }
1165 }
1165 }
1166 ]
1166 ]
1167 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
1167 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
1168
1168
1169 Set up the server to issue content redirects to its built-in API server.
1169 Set up the server to issue content redirects to its built-in API server.
1170
1170
1171 $ cat > redirects.py << EOF
1171 $ cat > redirects.py << EOF
1172 > [
1172 > [
1173 > {
1173 > {
1174 > b'name': b'local',
1174 > b'name': b'local',
1175 > b'protocol': b'http',
1175 > b'protocol': b'http',
1176 > b'uris': [b'http://example.com/'],
1176 > b'uris': [b'http://example.com/'],
1177 > },
1177 > },
1178 > ]
1178 > ]
1179 > EOF
1179 > EOF
1180
1180
1181 Request to eventual cache URL should return 404 (validating the cache server works)
1181 Request to eventual cache URL should return 404 (validating the cache server works)
1182
1182
1183 $ sendhttpraw << EOF
1183 $ sendhttpraw << EOF
1184 > httprequest GET api/simplecache/missingkey
1184 > httprequest GET api/simplecache/missingkey
1185 > user-agent: test
1185 > user-agent: test
1186 > EOF
1186 > EOF
1187 using raw connection to peer
1187 using raw connection to peer
1188 s> GET /api/simplecache/missingkey HTTP/1.1\r\n
1188 s> GET /api/simplecache/missingkey HTTP/1.1\r\n
1189 s> Accept-Encoding: identity\r\n
1189 s> Accept-Encoding: identity\r\n
1190 s> user-agent: test\r\n
1190 s> user-agent: test\r\n
1191 s> host: $LOCALIP:$HGPORT\r\n (glob)
1191 s> host: $LOCALIP:$HGPORT\r\n (glob)
1192 s> \r\n
1192 s> \r\n
1193 s> makefile('rb', None)
1193 s> makefile('rb', None)
1194 s> HTTP/1.1 404 Not Found\r\n
1194 s> HTTP/1.1 404 Not Found\r\n
1195 s> Server: testing stub value\r\n
1195 s> Server: testing stub value\r\n
1196 s> Date: $HTTP_DATE$\r\n
1196 s> Date: $HTTP_DATE$\r\n
1197 s> Content-Type: text/plain\r\n
1197 s> Content-Type: text/plain\r\n
1198 s> Content-Length: 22\r\n
1198 s> Content-Length: 22\r\n
1199 s> \r\n
1199 s> \r\n
1200 s> key not found in cache
1200 s> key not found in cache
1201
1201
1202 Send a cacheable request
1202 Send a cacheable request
1203
1203
1204 $ sendhttpv2peer << EOF
1204 $ sendhttpv2peer << EOF
1205 > command manifestdata
1205 > command manifestdata
1206 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1206 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1207 > tree eval:b''
1207 > tree eval:b''
1208 > fields eval:[b'parents']
1208 > fields eval:[b'parents']
1209 > EOF
1209 > EOF
1210 creating http peer for wire protocol version 2
1210 creating http peer for wire protocol version 2
1211 sending manifestdata command
1211 sending manifestdata command
1212 response: gen[
1212 response: gen[
1213 {
1213 {
1214 b'totalitems': 1
1214 b'totalitems': 1
1215 },
1215 },
1216 {
1216 {
1217 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1217 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1218 b'parents': [
1218 b'parents': [
1219 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1219 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1220 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1220 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1221 ]
1221 ]
1222 }
1222 }
1223 ]
1223 ]
1224
1224
1225 Cached entry should be available on server
1225 Cached entry should be available on server
1226
1226
1227 $ sendhttpraw << EOF
1227 $ sendhttpraw << EOF
1228 > httprequest GET api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0
1228 > httprequest GET api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca
1229 > user-agent: test
1229 > user-agent: test
1230 > EOF
1230 > EOF
1231 using raw connection to peer
1231 using raw connection to peer
1232 s> GET /api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 HTTP/1.1\r\n
1232 s> GET /api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca HTTP/1.1\r\n
1233 s> Accept-Encoding: identity\r\n
1233 s> Accept-Encoding: identity\r\n
1234 s> user-agent: test\r\n
1234 s> user-agent: test\r\n
1235 s> host: $LOCALIP:$HGPORT\r\n (glob)
1235 s> host: $LOCALIP:$HGPORT\r\n (glob)
1236 s> \r\n
1236 s> \r\n
1237 s> makefile('rb', None)
1237 s> makefile('rb', None)
1238 s> HTTP/1.1 200 OK\r\n
1238 s> HTTP/1.1 200 OK\r\n
1239 s> Server: testing stub value\r\n
1239 s> Server: testing stub value\r\n
1240 s> Date: $HTTP_DATE$\r\n
1240 s> Date: $HTTP_DATE$\r\n
1241 s> Content-Type: application/mercurial-cbor\r\n
1241 s> Content-Type: application/mercurial-cbor\r\n
1242 s> Content-Length: 91\r\n
1242 s> Content-Length: 91\r\n
1243 s> \r\n
1243 s> \r\n
1244 s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
1244 s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
1245 cbor> [
1245 cbor> [
1246 {
1246 {
1247 b'totalitems': 1
1247 b'totalitems': 1
1248 },
1248 },
1249 {
1249 {
1250 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1250 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1251 b'parents': [
1251 b'parents': [
1252 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1252 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1253 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1253 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1254 ]
1254 ]
1255 }
1255 }
1256 ]
1256 ]
1257
1257
1258 2nd request should result in content redirect response
1258 2nd request should result in content redirect response
1259
1259
1260 $ sendhttpv2peer << EOF
1260 $ sendhttpv2peer << EOF
1261 > command manifestdata
1261 > command manifestdata
1262 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1262 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1263 > tree eval:b''
1263 > tree eval:b''
1264 > fields eval:[b'parents']
1264 > fields eval:[b'parents']
1265 > EOF
1265 > EOF
1266 creating http peer for wire protocol version 2
1266 creating http peer for wire protocol version 2
1267 sending manifestdata command
1267 sending manifestdata command
1268 response: gen[
1268 response: gen[
1269 {
1269 {
1270 b'totalitems': 1
1270 b'totalitems': 1
1271 },
1271 },
1272 {
1272 {
1273 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1273 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1274 b'parents': [
1274 b'parents': [
1275 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1275 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1276 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1276 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1277 ]
1277 ]
1278 }
1278 }
1279 ]
1279 ]
1280
1280
1281 $ cat error.log
1281 $ cat error.log
1282 $ killdaemons.py
1282 $ killdaemons.py
1283
1283
1284 $ cat .hg/blackbox.log
1284 $ cat .hg/blackbox.log
1285 *> cacher constructed for manifestdata (glob)
1285 *> cacher constructed for manifestdata (glob)
1286 *> cache miss for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1286 *> cache miss for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1287 *> storing cache entry for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1287 *> storing cache entry for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1288 *> cacher constructed for manifestdata (glob)
1288 *> cacher constructed for manifestdata (glob)
1289 *> cache hit for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1289 *> cache hit for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1290 *> sending content redirect for c045a581599d58608efd3d93d8129841f2af04a0 to http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1290 *> sending content redirect for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca to http://*:$HGPORT/api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
@@ -1,69 +1,72
1 HTTPV2=exp-http-v2-0002
1 HTTPV2=exp-http-v2-0002
2 MEDIATYPE=application/mercurial-exp-framing-0005
2 MEDIATYPE=application/mercurial-exp-framing-0006
3
3
4 sendhttpraw() {
4 sendhttpraw() {
5 hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
5 hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
6 }
6 }
7
7
8 sendhttpv2peer() {
8 sendhttpv2peer() {
9 hg debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/
9 hg --config experimental.httppeer.v2-encoder-order=identity debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/
10 }
10 }
11
11
12 sendhttpv2peerverbose() {
12 sendhttpv2peerverbose() {
13 hg --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/
13 hg --config experimental.httppeer.v2-encoder-order=identity --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/
14 }
14 }
15
15
16 sendhttpv2peerhandshake() {
16 sendhttpv2peerhandshake() {
17 hg --verbose debugwireproto --peer http2 http://$LOCALIP:$HGPORT/
17 hg --config experimental.httppeer.v2-encoder-order=identity --verbose debugwireproto --peer http2 http://$LOCALIP:$HGPORT/
18 }
18 }
19
19
20 cat > dummycommands.py << EOF
20 cat > dummycommands.py << EOF
21 from mercurial import (
21 from mercurial import (
22 wireprototypes,
22 wireprototypes,
23 wireprotov1server,
23 wireprotov1server,
24 wireprotov2server,
24 wireprotov2server,
25 )
25 )
26
26
27 @wireprotov1server.wireprotocommand(b'customreadonly', permission=b'pull')
27 @wireprotov1server.wireprotocommand(b'customreadonly', permission=b'pull')
28 def customreadonlyv1(repo, proto):
28 def customreadonlyv1(repo, proto):
29 return wireprototypes.bytesresponse(b'customreadonly bytes response')
29 return wireprototypes.bytesresponse(b'customreadonly bytes response')
30
30
31 @wireprotov2server.wireprotocommand(b'customreadonly', permission=b'pull')
31 @wireprotov2server.wireprotocommand(b'customreadonly', permission=b'pull')
32 def customreadonlyv2(repo, proto):
32 def customreadonlyv2(repo, proto):
33 yield b'customreadonly bytes response'
33 yield b'customreadonly bytes response'
34
34
35 @wireprotov1server.wireprotocommand(b'customreadwrite', permission=b'push')
35 @wireprotov1server.wireprotocommand(b'customreadwrite', permission=b'push')
36 def customreadwrite(repo, proto):
36 def customreadwrite(repo, proto):
37 return wireprototypes.bytesresponse(b'customreadwrite bytes response')
37 return wireprototypes.bytesresponse(b'customreadwrite bytes response')
38
38
39 @wireprotov2server.wireprotocommand(b'customreadwrite', permission=b'push')
39 @wireprotov2server.wireprotocommand(b'customreadwrite', permission=b'push')
40 def customreadwritev2(repo, proto):
40 def customreadwritev2(repo, proto):
41 yield b'customreadwrite bytes response'
41 yield b'customreadwrite bytes response'
42 EOF
42 EOF
43
43
44 cat >> $HGRCPATH << EOF
44 cat >> $HGRCPATH << EOF
45 [extensions]
45 [extensions]
46 drawdag = $TESTDIR/drawdag.py
46 drawdag = $TESTDIR/drawdag.py
47 EOF
47 EOF
48
48
49 enabledummycommands() {
49 enabledummycommands() {
50 cat >> $HGRCPATH << EOF
50 cat >> $HGRCPATH << EOF
51 [extensions]
51 [extensions]
52 dummycommands = $TESTTMP/dummycommands.py
52 dummycommands = $TESTTMP/dummycommands.py
53 EOF
53 EOF
54 }
54 }
55
55
56 enablehttpv2() {
56 enablehttpv2() {
57 cat >> $1/.hg/hgrc << EOF
57 cat >> $1/.hg/hgrc << EOF
58 [experimental]
58 [experimental]
59 web.apiserver = true
59 web.apiserver = true
60 web.api.http-v2 = true
60 web.api.http-v2 = true
61 EOF
61 EOF
62 }
62 }
63
63
64 enablehttpv2client() {
64 enablehttpv2client() {
65 cat >> $HGRCPATH << EOF
65 cat >> $HGRCPATH << EOF
66 [experimental]
66 [experimental]
67 httppeer.advertise-v2 = true
67 httppeer.advertise-v2 = true
68 # So tests are in plain text. Also, zstd isn't available in all installs,
69 # which would make tests non-deterministic.
70 httppeer.v2-encoder-order = identity
68 EOF
71 EOF
69 }
72 }
General Comments 0
You need to be logged in to leave comments. Login now