Show More
@@ -1,1305 +1,1308 b'' | |||
|
1 | 1 | # configitems.py - centralized declaration of configuration option |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net> |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2 or any later version. |
|
7 | 7 | |
|
8 | 8 | from __future__ import absolute_import |
|
9 | 9 | |
|
10 | 10 | import functools |
|
11 | 11 | import re |
|
12 | 12 | |
|
13 | 13 | from . import ( |
|
14 | 14 | encoding, |
|
15 | 15 | error, |
|
16 | 16 | ) |
|
17 | 17 | |
|
18 | 18 | def loadconfigtable(ui, extname, configtable): |
|
19 | 19 | """update config item known to the ui with the extension ones""" |
|
20 | 20 | for section, items in sorted(configtable.items()): |
|
21 | 21 | knownitems = ui._knownconfig.setdefault(section, itemregister()) |
|
22 | 22 | knownkeys = set(knownitems) |
|
23 | 23 | newkeys = set(items) |
|
24 | 24 | for key in sorted(knownkeys & newkeys): |
|
25 | 25 | msg = "extension '%s' overwrite config item '%s.%s'" |
|
26 | 26 | msg %= (extname, section, key) |
|
27 | 27 | ui.develwarn(msg, config='warn-config') |
|
28 | 28 | |
|
29 | 29 | knownitems.update(items) |
|
30 | 30 | |
|
31 | 31 | class configitem(object): |
|
32 | 32 | """represent a known config item |
|
33 | 33 | |
|
34 | 34 | :section: the official config section where to find this item, |
|
35 | 35 | :name: the official name within the section, |
|
36 | 36 | :default: default value for this item, |
|
37 | 37 | :alias: optional list of tuples as alternatives, |
|
38 | 38 | :generic: this is a generic definition, match name using regular expression. |
|
39 | 39 | """ |
|
40 | 40 | |
|
41 | 41 | def __init__(self, section, name, default=None, alias=(), |
|
42 | 42 | generic=False, priority=0): |
|
43 | 43 | self.section = section |
|
44 | 44 | self.name = name |
|
45 | 45 | self.default = default |
|
46 | 46 | self.alias = list(alias) |
|
47 | 47 | self.generic = generic |
|
48 | 48 | self.priority = priority |
|
49 | 49 | self._re = None |
|
50 | 50 | if generic: |
|
51 | 51 | self._re = re.compile(self.name) |
|
52 | 52 | |
|
53 | 53 | class itemregister(dict): |
|
54 | 54 | """A specialized dictionary that can handle wild-card selection""" |
|
55 | 55 | |
|
56 | 56 | def __init__(self): |
|
57 | 57 | super(itemregister, self).__init__() |
|
58 | 58 | self._generics = set() |
|
59 | 59 | |
|
60 | 60 | def update(self, other): |
|
61 | 61 | super(itemregister, self).update(other) |
|
62 | 62 | self._generics.update(other._generics) |
|
63 | 63 | |
|
64 | 64 | def __setitem__(self, key, item): |
|
65 | 65 | super(itemregister, self).__setitem__(key, item) |
|
66 | 66 | if item.generic: |
|
67 | 67 | self._generics.add(item) |
|
68 | 68 | |
|
69 | 69 | def get(self, key): |
|
70 | 70 | baseitem = super(itemregister, self).get(key) |
|
71 | 71 | if baseitem is not None and not baseitem.generic: |
|
72 | 72 | return baseitem |
|
73 | 73 | |
|
74 | 74 | # search for a matching generic item |
|
75 | 75 | generics = sorted(self._generics, key=(lambda x: (x.priority, x.name))) |
|
76 | 76 | for item in generics: |
|
77 | 77 | # we use 'match' instead of 'search' to make the matching simpler |
|
78 | 78 | # for people unfamiliar with regular expression. Having the match |
|
79 | 79 | # rooted to the start of the string will produce less surprising |
|
80 | 80 | # result for user writing simple regex for sub-attribute. |
|
81 | 81 | # |
|
82 | 82 | # For example using "color\..*" match produces an unsurprising |
|
83 | 83 | # result, while using search could suddenly match apparently |
|
84 | 84 | # unrelated configuration that happens to contains "color." |
|
85 | 85 | # anywhere. This is a tradeoff where we favor requiring ".*" on |
|
86 | 86 | # some match to avoid the need to prefix most pattern with "^". |
|
87 | 87 | # The "^" seems more error prone. |
|
88 | 88 | if item._re.match(key): |
|
89 | 89 | return item |
|
90 | 90 | |
|
91 | 91 | return None |
|
92 | 92 | |
|
93 | 93 | coreitems = {} |
|
94 | 94 | |
|
95 | 95 | def _register(configtable, *args, **kwargs): |
|
96 | 96 | item = configitem(*args, **kwargs) |
|
97 | 97 | section = configtable.setdefault(item.section, itemregister()) |
|
98 | 98 | if item.name in section: |
|
99 | 99 | msg = "duplicated config item registration for '%s.%s'" |
|
100 | 100 | raise error.ProgrammingError(msg % (item.section, item.name)) |
|
101 | 101 | section[item.name] = item |
|
102 | 102 | |
|
103 | 103 | # special value for case where the default is derived from other values |
|
104 | 104 | dynamicdefault = object() |
|
105 | 105 | |
|
106 | 106 | # Registering actual config items |
|
107 | 107 | |
|
108 | 108 | def getitemregister(configtable): |
|
109 | 109 | f = functools.partial(_register, configtable) |
|
110 | 110 | # export pseudo enum as configitem.* |
|
111 | 111 | f.dynamicdefault = dynamicdefault |
|
112 | 112 | return f |
|
113 | 113 | |
|
114 | 114 | coreconfigitem = getitemregister(coreitems) |
|
115 | 115 | |
|
116 | 116 | coreconfigitem('alias', '.*', |
|
117 | 117 | default=None, |
|
118 | 118 | generic=True, |
|
119 | 119 | ) |
|
120 | 120 | coreconfigitem('annotate', 'nodates', |
|
121 | 121 | default=False, |
|
122 | 122 | ) |
|
123 | 123 | coreconfigitem('annotate', 'showfunc', |
|
124 | 124 | default=False, |
|
125 | 125 | ) |
|
126 | 126 | coreconfigitem('annotate', 'unified', |
|
127 | 127 | default=None, |
|
128 | 128 | ) |
|
129 | 129 | coreconfigitem('annotate', 'git', |
|
130 | 130 | default=False, |
|
131 | 131 | ) |
|
132 | 132 | coreconfigitem('annotate', 'ignorews', |
|
133 | 133 | default=False, |
|
134 | 134 | ) |
|
135 | 135 | coreconfigitem('annotate', 'ignorewsamount', |
|
136 | 136 | default=False, |
|
137 | 137 | ) |
|
138 | 138 | coreconfigitem('annotate', 'ignoreblanklines', |
|
139 | 139 | default=False, |
|
140 | 140 | ) |
|
141 | 141 | coreconfigitem('annotate', 'ignorewseol', |
|
142 | 142 | default=False, |
|
143 | 143 | ) |
|
144 | 144 | coreconfigitem('annotate', 'nobinary', |
|
145 | 145 | default=False, |
|
146 | 146 | ) |
|
147 | 147 | coreconfigitem('annotate', 'noprefix', |
|
148 | 148 | default=False, |
|
149 | 149 | ) |
|
150 | 150 | coreconfigitem('auth', 'cookiefile', |
|
151 | 151 | default=None, |
|
152 | 152 | ) |
|
153 | 153 | # bookmarks.pushing: internal hack for discovery |
|
154 | 154 | coreconfigitem('bookmarks', 'pushing', |
|
155 | 155 | default=list, |
|
156 | 156 | ) |
|
157 | 157 | # bundle.mainreporoot: internal hack for bundlerepo |
|
158 | 158 | coreconfigitem('bundle', 'mainreporoot', |
|
159 | 159 | default='', |
|
160 | 160 | ) |
|
161 | 161 | # bundle.reorder: experimental config |
|
162 | 162 | coreconfigitem('bundle', 'reorder', |
|
163 | 163 | default='auto', |
|
164 | 164 | ) |
|
165 | 165 | coreconfigitem('censor', 'policy', |
|
166 | 166 | default='abort', |
|
167 | 167 | ) |
|
168 | 168 | coreconfigitem('chgserver', 'idletimeout', |
|
169 | 169 | default=3600, |
|
170 | 170 | ) |
|
171 | 171 | coreconfigitem('chgserver', 'skiphash', |
|
172 | 172 | default=False, |
|
173 | 173 | ) |
|
174 | 174 | coreconfigitem('cmdserver', 'log', |
|
175 | 175 | default=None, |
|
176 | 176 | ) |
|
177 | 177 | coreconfigitem('color', '.*', |
|
178 | 178 | default=None, |
|
179 | 179 | generic=True, |
|
180 | 180 | ) |
|
181 | 181 | coreconfigitem('color', 'mode', |
|
182 | 182 | default='auto', |
|
183 | 183 | ) |
|
184 | 184 | coreconfigitem('color', 'pagermode', |
|
185 | 185 | default=dynamicdefault, |
|
186 | 186 | ) |
|
187 | 187 | coreconfigitem('commands', 'show.aliasprefix', |
|
188 | 188 | default=list, |
|
189 | 189 | ) |
|
190 | 190 | coreconfigitem('commands', 'status.relative', |
|
191 | 191 | default=False, |
|
192 | 192 | ) |
|
193 | 193 | coreconfigitem('commands', 'status.skipstates', |
|
194 | 194 | default=[], |
|
195 | 195 | ) |
|
196 | 196 | coreconfigitem('commands', 'status.verbose', |
|
197 | 197 | default=False, |
|
198 | 198 | ) |
|
199 | 199 | coreconfigitem('commands', 'update.check', |
|
200 | 200 | default=None, |
|
201 | 201 | # Deprecated, remove after 4.4 release |
|
202 | 202 | alias=[('experimental', 'updatecheck')] |
|
203 | 203 | ) |
|
204 | 204 | coreconfigitem('commands', 'update.requiredest', |
|
205 | 205 | default=False, |
|
206 | 206 | ) |
|
207 | 207 | coreconfigitem('committemplate', '.*', |
|
208 | 208 | default=None, |
|
209 | 209 | generic=True, |
|
210 | 210 | ) |
|
211 | 211 | coreconfigitem('convert', 'cvsps.cache', |
|
212 | 212 | default=True, |
|
213 | 213 | ) |
|
214 | 214 | coreconfigitem('convert', 'cvsps.fuzz', |
|
215 | 215 | default=60, |
|
216 | 216 | ) |
|
217 | 217 | coreconfigitem('convert', 'cvsps.logencoding', |
|
218 | 218 | default=None, |
|
219 | 219 | ) |
|
220 | 220 | coreconfigitem('convert', 'cvsps.mergefrom', |
|
221 | 221 | default=None, |
|
222 | 222 | ) |
|
223 | 223 | coreconfigitem('convert', 'cvsps.mergeto', |
|
224 | 224 | default=None, |
|
225 | 225 | ) |
|
226 | 226 | coreconfigitem('convert', 'git.committeractions', |
|
227 | 227 | default=lambda: ['messagedifferent'], |
|
228 | 228 | ) |
|
229 | 229 | coreconfigitem('convert', 'git.extrakeys', |
|
230 | 230 | default=list, |
|
231 | 231 | ) |
|
232 | 232 | coreconfigitem('convert', 'git.findcopiesharder', |
|
233 | 233 | default=False, |
|
234 | 234 | ) |
|
235 | 235 | coreconfigitem('convert', 'git.remoteprefix', |
|
236 | 236 | default='remote', |
|
237 | 237 | ) |
|
238 | 238 | coreconfigitem('convert', 'git.renamelimit', |
|
239 | 239 | default=400, |
|
240 | 240 | ) |
|
241 | 241 | coreconfigitem('convert', 'git.saverev', |
|
242 | 242 | default=True, |
|
243 | 243 | ) |
|
244 | 244 | coreconfigitem('convert', 'git.similarity', |
|
245 | 245 | default=50, |
|
246 | 246 | ) |
|
247 | 247 | coreconfigitem('convert', 'git.skipsubmodules', |
|
248 | 248 | default=False, |
|
249 | 249 | ) |
|
250 | 250 | coreconfigitem('convert', 'hg.clonebranches', |
|
251 | 251 | default=False, |
|
252 | 252 | ) |
|
253 | 253 | coreconfigitem('convert', 'hg.ignoreerrors', |
|
254 | 254 | default=False, |
|
255 | 255 | ) |
|
256 | 256 | coreconfigitem('convert', 'hg.revs', |
|
257 | 257 | default=None, |
|
258 | 258 | ) |
|
259 | 259 | coreconfigitem('convert', 'hg.saverev', |
|
260 | 260 | default=False, |
|
261 | 261 | ) |
|
262 | 262 | coreconfigitem('convert', 'hg.sourcename', |
|
263 | 263 | default=None, |
|
264 | 264 | ) |
|
265 | 265 | coreconfigitem('convert', 'hg.startrev', |
|
266 | 266 | default=None, |
|
267 | 267 | ) |
|
268 | 268 | coreconfigitem('convert', 'hg.tagsbranch', |
|
269 | 269 | default='default', |
|
270 | 270 | ) |
|
271 | 271 | coreconfigitem('convert', 'hg.usebranchnames', |
|
272 | 272 | default=True, |
|
273 | 273 | ) |
|
274 | 274 | coreconfigitem('convert', 'ignoreancestorcheck', |
|
275 | 275 | default=False, |
|
276 | 276 | ) |
|
277 | 277 | coreconfigitem('convert', 'localtimezone', |
|
278 | 278 | default=False, |
|
279 | 279 | ) |
|
280 | 280 | coreconfigitem('convert', 'p4.encoding', |
|
281 | 281 | default=dynamicdefault, |
|
282 | 282 | ) |
|
283 | 283 | coreconfigitem('convert', 'p4.startrev', |
|
284 | 284 | default=0, |
|
285 | 285 | ) |
|
286 | 286 | coreconfigitem('convert', 'skiptags', |
|
287 | 287 | default=False, |
|
288 | 288 | ) |
|
289 | 289 | coreconfigitem('convert', 'svn.debugsvnlog', |
|
290 | 290 | default=True, |
|
291 | 291 | ) |
|
292 | 292 | coreconfigitem('convert', 'svn.trunk', |
|
293 | 293 | default=None, |
|
294 | 294 | ) |
|
295 | 295 | coreconfigitem('convert', 'svn.tags', |
|
296 | 296 | default=None, |
|
297 | 297 | ) |
|
298 | 298 | coreconfigitem('convert', 'svn.branches', |
|
299 | 299 | default=None, |
|
300 | 300 | ) |
|
301 | 301 | coreconfigitem('convert', 'svn.startrev', |
|
302 | 302 | default=0, |
|
303 | 303 | ) |
|
304 | 304 | coreconfigitem('debug', 'dirstate.delaywrite', |
|
305 | 305 | default=0, |
|
306 | 306 | ) |
|
307 | 307 | coreconfigitem('defaults', '.*', |
|
308 | 308 | default=None, |
|
309 | 309 | generic=True, |
|
310 | 310 | ) |
|
311 | 311 | coreconfigitem('devel', 'all-warnings', |
|
312 | 312 | default=False, |
|
313 | 313 | ) |
|
314 | 314 | coreconfigitem('devel', 'bundle2.debug', |
|
315 | 315 | default=False, |
|
316 | 316 | ) |
|
317 | 317 | coreconfigitem('devel', 'cache-vfs', |
|
318 | 318 | default=None, |
|
319 | 319 | ) |
|
320 | 320 | coreconfigitem('devel', 'check-locks', |
|
321 | 321 | default=False, |
|
322 | 322 | ) |
|
323 | 323 | coreconfigitem('devel', 'check-relroot', |
|
324 | 324 | default=False, |
|
325 | 325 | ) |
|
326 | 326 | coreconfigitem('devel', 'default-date', |
|
327 | 327 | default=None, |
|
328 | 328 | ) |
|
329 | 329 | coreconfigitem('devel', 'deprec-warn', |
|
330 | 330 | default=False, |
|
331 | 331 | ) |
|
332 | 332 | coreconfigitem('devel', 'disableloaddefaultcerts', |
|
333 | 333 | default=False, |
|
334 | 334 | ) |
|
335 | 335 | coreconfigitem('devel', 'warn-empty-changegroup', |
|
336 | 336 | default=False, |
|
337 | 337 | ) |
|
338 | 338 | coreconfigitem('devel', 'legacy.exchange', |
|
339 | 339 | default=list, |
|
340 | 340 | ) |
|
341 | 341 | coreconfigitem('devel', 'servercafile', |
|
342 | 342 | default='', |
|
343 | 343 | ) |
|
344 | 344 | coreconfigitem('devel', 'serverexactprotocol', |
|
345 | 345 | default='', |
|
346 | 346 | ) |
|
347 | 347 | coreconfigitem('devel', 'serverrequirecert', |
|
348 | 348 | default=False, |
|
349 | 349 | ) |
|
350 | 350 | coreconfigitem('devel', 'strip-obsmarkers', |
|
351 | 351 | default=True, |
|
352 | 352 | ) |
|
353 | 353 | coreconfigitem('devel', 'warn-config', |
|
354 | 354 | default=None, |
|
355 | 355 | ) |
|
356 | 356 | coreconfigitem('devel', 'warn-config-default', |
|
357 | 357 | default=None, |
|
358 | 358 | ) |
|
359 | 359 | coreconfigitem('devel', 'user.obsmarker', |
|
360 | 360 | default=None, |
|
361 | 361 | ) |
|
362 | 362 | coreconfigitem('devel', 'warn-config-unknown', |
|
363 | 363 | default=None, |
|
364 | 364 | ) |
|
365 | 365 | coreconfigitem('devel', 'debug.peer-request', |
|
366 | 366 | default=False, |
|
367 | 367 | ) |
|
368 | 368 | coreconfigitem('diff', 'nodates', |
|
369 | 369 | default=False, |
|
370 | 370 | ) |
|
371 | 371 | coreconfigitem('diff', 'showfunc', |
|
372 | 372 | default=False, |
|
373 | 373 | ) |
|
374 | 374 | coreconfigitem('diff', 'unified', |
|
375 | 375 | default=None, |
|
376 | 376 | ) |
|
377 | 377 | coreconfigitem('diff', 'git', |
|
378 | 378 | default=False, |
|
379 | 379 | ) |
|
380 | 380 | coreconfigitem('diff', 'ignorews', |
|
381 | 381 | default=False, |
|
382 | 382 | ) |
|
383 | 383 | coreconfigitem('diff', 'ignorewsamount', |
|
384 | 384 | default=False, |
|
385 | 385 | ) |
|
386 | 386 | coreconfigitem('diff', 'ignoreblanklines', |
|
387 | 387 | default=False, |
|
388 | 388 | ) |
|
389 | 389 | coreconfigitem('diff', 'ignorewseol', |
|
390 | 390 | default=False, |
|
391 | 391 | ) |
|
392 | 392 | coreconfigitem('diff', 'nobinary', |
|
393 | 393 | default=False, |
|
394 | 394 | ) |
|
395 | 395 | coreconfigitem('diff', 'noprefix', |
|
396 | 396 | default=False, |
|
397 | 397 | ) |
|
398 | 398 | coreconfigitem('email', 'bcc', |
|
399 | 399 | default=None, |
|
400 | 400 | ) |
|
401 | 401 | coreconfigitem('email', 'cc', |
|
402 | 402 | default=None, |
|
403 | 403 | ) |
|
404 | 404 | coreconfigitem('email', 'charsets', |
|
405 | 405 | default=list, |
|
406 | 406 | ) |
|
407 | 407 | coreconfigitem('email', 'from', |
|
408 | 408 | default=None, |
|
409 | 409 | ) |
|
410 | 410 | coreconfigitem('email', 'method', |
|
411 | 411 | default='smtp', |
|
412 | 412 | ) |
|
413 | 413 | coreconfigitem('email', 'reply-to', |
|
414 | 414 | default=None, |
|
415 | 415 | ) |
|
416 | 416 | coreconfigitem('email', 'to', |
|
417 | 417 | default=None, |
|
418 | 418 | ) |
|
419 | 419 | coreconfigitem('experimental', 'archivemetatemplate', |
|
420 | 420 | default=dynamicdefault, |
|
421 | 421 | ) |
|
422 | 422 | coreconfigitem('experimental', 'bundle-phases', |
|
423 | 423 | default=False, |
|
424 | 424 | ) |
|
425 | 425 | coreconfigitem('experimental', 'bundle2-advertise', |
|
426 | 426 | default=True, |
|
427 | 427 | ) |
|
428 | 428 | coreconfigitem('experimental', 'bundle2-output-capture', |
|
429 | 429 | default=False, |
|
430 | 430 | ) |
|
431 | 431 | coreconfigitem('experimental', 'bundle2.pushback', |
|
432 | 432 | default=False, |
|
433 | 433 | ) |
|
434 | 434 | coreconfigitem('experimental', 'bundle2.stream', |
|
435 | 435 | default=False, |
|
436 | 436 | ) |
|
437 | 437 | coreconfigitem('experimental', 'bundle2lazylocking', |
|
438 | 438 | default=False, |
|
439 | 439 | ) |
|
440 | 440 | coreconfigitem('experimental', 'bundlecomplevel', |
|
441 | 441 | default=None, |
|
442 | 442 | ) |
|
443 | 443 | coreconfigitem('experimental', 'changegroup3', |
|
444 | 444 | default=False, |
|
445 | 445 | ) |
|
446 | 446 | coreconfigitem('experimental', 'clientcompressionengines', |
|
447 | 447 | default=list, |
|
448 | 448 | ) |
|
449 | 449 | coreconfigitem('experimental', 'copytrace', |
|
450 | 450 | default='on', |
|
451 | 451 | ) |
|
452 | 452 | coreconfigitem('experimental', 'copytrace.movecandidateslimit', |
|
453 | 453 | default=100, |
|
454 | 454 | ) |
|
455 | 455 | coreconfigitem('experimental', 'copytrace.sourcecommitlimit', |
|
456 | 456 | default=100, |
|
457 | 457 | ) |
|
458 | 458 | coreconfigitem('experimental', 'crecordtest', |
|
459 | 459 | default=None, |
|
460 | 460 | ) |
|
461 | 461 | coreconfigitem('experimental', 'directaccess', |
|
462 | 462 | default=False, |
|
463 | 463 | ) |
|
464 | 464 | coreconfigitem('experimental', 'directaccess.revnums', |
|
465 | 465 | default=False, |
|
466 | 466 | ) |
|
467 | 467 | coreconfigitem('experimental', 'editortmpinhg', |
|
468 | 468 | default=False, |
|
469 | 469 | ) |
|
470 | 470 | coreconfigitem('experimental', 'evolution', |
|
471 | 471 | default=list, |
|
472 | 472 | ) |
|
473 | 473 | coreconfigitem('experimental', 'evolution.allowdivergence', |
|
474 | 474 | default=False, |
|
475 | 475 | alias=[('experimental', 'allowdivergence')] |
|
476 | 476 | ) |
|
477 | 477 | coreconfigitem('experimental', 'evolution.allowunstable', |
|
478 | 478 | default=None, |
|
479 | 479 | ) |
|
480 | 480 | coreconfigitem('experimental', 'evolution.createmarkers', |
|
481 | 481 | default=None, |
|
482 | 482 | ) |
|
483 | 483 | coreconfigitem('experimental', 'evolution.effect-flags', |
|
484 | 484 | default=True, |
|
485 | 485 | alias=[('experimental', 'effect-flags')] |
|
486 | 486 | ) |
|
487 | 487 | coreconfigitem('experimental', 'evolution.exchange', |
|
488 | 488 | default=None, |
|
489 | 489 | ) |
|
490 | 490 | coreconfigitem('experimental', 'evolution.bundle-obsmarker', |
|
491 | 491 | default=False, |
|
492 | 492 | ) |
|
493 | 493 | coreconfigitem('experimental', 'evolution.report-instabilities', |
|
494 | 494 | default=True, |
|
495 | 495 | ) |
|
496 | 496 | coreconfigitem('experimental', 'evolution.track-operation', |
|
497 | 497 | default=True, |
|
498 | 498 | ) |
|
499 | 499 | coreconfigitem('experimental', 'worddiff', |
|
500 | 500 | default=False, |
|
501 | 501 | ) |
|
502 | 502 | coreconfigitem('experimental', 'maxdeltachainspan', |
|
503 | 503 | default=-1, |
|
504 | 504 | ) |
|
505 | 505 | coreconfigitem('experimental', 'mmapindexthreshold', |
|
506 | 506 | default=None, |
|
507 | 507 | ) |
|
508 | 508 | coreconfigitem('experimental', 'nonnormalparanoidcheck', |
|
509 | 509 | default=False, |
|
510 | 510 | ) |
|
511 | 511 | coreconfigitem('experimental', 'exportableenviron', |
|
512 | 512 | default=list, |
|
513 | 513 | ) |
|
514 | 514 | coreconfigitem('experimental', 'extendedheader.index', |
|
515 | 515 | default=None, |
|
516 | 516 | ) |
|
517 | 517 | coreconfigitem('experimental', 'extendedheader.similarity', |
|
518 | 518 | default=False, |
|
519 | 519 | ) |
|
520 | 520 | coreconfigitem('experimental', 'format.compression', |
|
521 | 521 | default='zlib', |
|
522 | 522 | ) |
|
523 | 523 | coreconfigitem('experimental', 'graphshorten', |
|
524 | 524 | default=False, |
|
525 | 525 | ) |
|
526 | 526 | coreconfigitem('experimental', 'graphstyle.parent', |
|
527 | 527 | default=dynamicdefault, |
|
528 | 528 | ) |
|
529 | 529 | coreconfigitem('experimental', 'graphstyle.missing', |
|
530 | 530 | default=dynamicdefault, |
|
531 | 531 | ) |
|
532 | 532 | coreconfigitem('experimental', 'graphstyle.grandparent', |
|
533 | 533 | default=dynamicdefault, |
|
534 | 534 | ) |
|
535 | 535 | coreconfigitem('experimental', 'hook-track-tags', |
|
536 | 536 | default=False, |
|
537 | 537 | ) |
|
538 | 538 | coreconfigitem('experimental', 'httppostargs', |
|
539 | 539 | default=False, |
|
540 | 540 | ) |
|
541 | 541 | coreconfigitem('experimental', 'manifestv2', |
|
542 | 542 | default=False, |
|
543 | 543 | ) |
|
544 | 544 | coreconfigitem('experimental', 'mergedriver', |
|
545 | 545 | default=None, |
|
546 | 546 | ) |
|
547 | 547 | coreconfigitem('experimental', 'obsmarkers-exchange-debug', |
|
548 | 548 | default=False, |
|
549 | 549 | ) |
|
550 | 550 | coreconfigitem('experimental', 'remotenames', |
|
551 | 551 | default=False, |
|
552 | 552 | ) |
|
553 | 553 | coreconfigitem('experimental', 'revlogv2', |
|
554 | 554 | default=None, |
|
555 | 555 | ) |
|
556 | 556 | coreconfigitem('experimental', 'single-head-per-branch', |
|
557 | 557 | default=False, |
|
558 | 558 | ) |
|
559 | 559 | coreconfigitem('experimental', 'spacemovesdown', |
|
560 | 560 | default=False, |
|
561 | 561 | ) |
|
562 | 562 | coreconfigitem('experimental', 'sparse-read', |
|
563 | 563 | default=False, |
|
564 | 564 | ) |
|
565 | 565 | coreconfigitem('experimental', 'sparse-read.density-threshold', |
|
566 | 566 | default=0.25, |
|
567 | 567 | ) |
|
568 | 568 | coreconfigitem('experimental', 'sparse-read.min-gap-size', |
|
569 | 569 | default='256K', |
|
570 | 570 | ) |
|
571 | 571 | coreconfigitem('experimental', 'treemanifest', |
|
572 | 572 | default=False, |
|
573 | 573 | ) |
|
574 | 574 | coreconfigitem('experimental', 'update.atomic-file', |
|
575 | 575 | default=False, |
|
576 | 576 | ) |
|
577 | coreconfigitem('experimental', 'sshpeer.advertise-v2', | |
|
578 | default=False, | |
|
579 | ) | |
|
577 | 580 | coreconfigitem('extensions', '.*', |
|
578 | 581 | default=None, |
|
579 | 582 | generic=True, |
|
580 | 583 | ) |
|
581 | 584 | coreconfigitem('extdata', '.*', |
|
582 | 585 | default=None, |
|
583 | 586 | generic=True, |
|
584 | 587 | ) |
|
585 | 588 | coreconfigitem('format', 'aggressivemergedeltas', |
|
586 | 589 | default=False, |
|
587 | 590 | ) |
|
588 | 591 | coreconfigitem('format', 'chunkcachesize', |
|
589 | 592 | default=None, |
|
590 | 593 | ) |
|
591 | 594 | coreconfigitem('format', 'dotencode', |
|
592 | 595 | default=True, |
|
593 | 596 | ) |
|
594 | 597 | coreconfigitem('format', 'generaldelta', |
|
595 | 598 | default=False, |
|
596 | 599 | ) |
|
597 | 600 | coreconfigitem('format', 'manifestcachesize', |
|
598 | 601 | default=None, |
|
599 | 602 | ) |
|
600 | 603 | coreconfigitem('format', 'maxchainlen', |
|
601 | 604 | default=None, |
|
602 | 605 | ) |
|
603 | 606 | coreconfigitem('format', 'obsstore-version', |
|
604 | 607 | default=None, |
|
605 | 608 | ) |
|
606 | 609 | coreconfigitem('format', 'usefncache', |
|
607 | 610 | default=True, |
|
608 | 611 | ) |
|
609 | 612 | coreconfigitem('format', 'usegeneraldelta', |
|
610 | 613 | default=True, |
|
611 | 614 | ) |
|
612 | 615 | coreconfigitem('format', 'usestore', |
|
613 | 616 | default=True, |
|
614 | 617 | ) |
|
615 | 618 | coreconfigitem('fsmonitor', 'warn_when_unused', |
|
616 | 619 | default=True, |
|
617 | 620 | ) |
|
618 | 621 | coreconfigitem('fsmonitor', 'warn_update_file_count', |
|
619 | 622 | default=50000, |
|
620 | 623 | ) |
|
621 | 624 | coreconfigitem('hooks', '.*', |
|
622 | 625 | default=dynamicdefault, |
|
623 | 626 | generic=True, |
|
624 | 627 | ) |
|
625 | 628 | coreconfigitem('hgweb-paths', '.*', |
|
626 | 629 | default=list, |
|
627 | 630 | generic=True, |
|
628 | 631 | ) |
|
629 | 632 | coreconfigitem('hostfingerprints', '.*', |
|
630 | 633 | default=list, |
|
631 | 634 | generic=True, |
|
632 | 635 | ) |
|
633 | 636 | coreconfigitem('hostsecurity', 'ciphers', |
|
634 | 637 | default=None, |
|
635 | 638 | ) |
|
636 | 639 | coreconfigitem('hostsecurity', 'disabletls10warning', |
|
637 | 640 | default=False, |
|
638 | 641 | ) |
|
639 | 642 | coreconfigitem('hostsecurity', 'minimumprotocol', |
|
640 | 643 | default=dynamicdefault, |
|
641 | 644 | ) |
|
642 | 645 | coreconfigitem('hostsecurity', '.*:minimumprotocol$', |
|
643 | 646 | default=dynamicdefault, |
|
644 | 647 | generic=True, |
|
645 | 648 | ) |
|
646 | 649 | coreconfigitem('hostsecurity', '.*:ciphers$', |
|
647 | 650 | default=dynamicdefault, |
|
648 | 651 | generic=True, |
|
649 | 652 | ) |
|
650 | 653 | coreconfigitem('hostsecurity', '.*:fingerprints$', |
|
651 | 654 | default=list, |
|
652 | 655 | generic=True, |
|
653 | 656 | ) |
|
654 | 657 | coreconfigitem('hostsecurity', '.*:verifycertsfile$', |
|
655 | 658 | default=None, |
|
656 | 659 | generic=True, |
|
657 | 660 | ) |
|
658 | 661 | |
|
659 | 662 | coreconfigitem('http_proxy', 'always', |
|
660 | 663 | default=False, |
|
661 | 664 | ) |
|
662 | 665 | coreconfigitem('http_proxy', 'host', |
|
663 | 666 | default=None, |
|
664 | 667 | ) |
|
665 | 668 | coreconfigitem('http_proxy', 'no', |
|
666 | 669 | default=list, |
|
667 | 670 | ) |
|
668 | 671 | coreconfigitem('http_proxy', 'passwd', |
|
669 | 672 | default=None, |
|
670 | 673 | ) |
|
671 | 674 | coreconfigitem('http_proxy', 'user', |
|
672 | 675 | default=None, |
|
673 | 676 | ) |
|
674 | 677 | coreconfigitem('logtoprocess', 'commandexception', |
|
675 | 678 | default=None, |
|
676 | 679 | ) |
|
677 | 680 | coreconfigitem('logtoprocess', 'commandfinish', |
|
678 | 681 | default=None, |
|
679 | 682 | ) |
|
680 | 683 | coreconfigitem('logtoprocess', 'command', |
|
681 | 684 | default=None, |
|
682 | 685 | ) |
|
683 | 686 | coreconfigitem('logtoprocess', 'develwarn', |
|
684 | 687 | default=None, |
|
685 | 688 | ) |
|
686 | 689 | coreconfigitem('logtoprocess', 'uiblocked', |
|
687 | 690 | default=None, |
|
688 | 691 | ) |
|
689 | 692 | coreconfigitem('merge', 'checkunknown', |
|
690 | 693 | default='abort', |
|
691 | 694 | ) |
|
692 | 695 | coreconfigitem('merge', 'checkignored', |
|
693 | 696 | default='abort', |
|
694 | 697 | ) |
|
695 | 698 | coreconfigitem('experimental', 'merge.checkpathconflicts', |
|
696 | 699 | default=False, |
|
697 | 700 | ) |
|
698 | 701 | coreconfigitem('merge', 'followcopies', |
|
699 | 702 | default=True, |
|
700 | 703 | ) |
|
701 | 704 | coreconfigitem('merge', 'on-failure', |
|
702 | 705 | default='continue', |
|
703 | 706 | ) |
|
704 | 707 | coreconfigitem('merge', 'preferancestor', |
|
705 | 708 | default=lambda: ['*'], |
|
706 | 709 | ) |
|
707 | 710 | coreconfigitem('merge-tools', '.*', |
|
708 | 711 | default=None, |
|
709 | 712 | generic=True, |
|
710 | 713 | ) |
|
711 | 714 | coreconfigitem('merge-tools', br'.*\.args$', |
|
712 | 715 | default="$local $base $other", |
|
713 | 716 | generic=True, |
|
714 | 717 | priority=-1, |
|
715 | 718 | ) |
|
716 | 719 | coreconfigitem('merge-tools', br'.*\.binary$', |
|
717 | 720 | default=False, |
|
718 | 721 | generic=True, |
|
719 | 722 | priority=-1, |
|
720 | 723 | ) |
|
721 | 724 | coreconfigitem('merge-tools', br'.*\.check$', |
|
722 | 725 | default=list, |
|
723 | 726 | generic=True, |
|
724 | 727 | priority=-1, |
|
725 | 728 | ) |
|
726 | 729 | coreconfigitem('merge-tools', br'.*\.checkchanged$', |
|
727 | 730 | default=False, |
|
728 | 731 | generic=True, |
|
729 | 732 | priority=-1, |
|
730 | 733 | ) |
|
731 | 734 | coreconfigitem('merge-tools', br'.*\.executable$', |
|
732 | 735 | default=dynamicdefault, |
|
733 | 736 | generic=True, |
|
734 | 737 | priority=-1, |
|
735 | 738 | ) |
|
736 | 739 | coreconfigitem('merge-tools', br'.*\.fixeol$', |
|
737 | 740 | default=False, |
|
738 | 741 | generic=True, |
|
739 | 742 | priority=-1, |
|
740 | 743 | ) |
|
741 | 744 | coreconfigitem('merge-tools', br'.*\.gui$', |
|
742 | 745 | default=False, |
|
743 | 746 | generic=True, |
|
744 | 747 | priority=-1, |
|
745 | 748 | ) |
|
746 | 749 | coreconfigitem('merge-tools', br'.*\.mergemarkers$', |
|
747 | 750 | default='basic', |
|
748 | 751 | generic=True, |
|
749 | 752 | priority=-1, |
|
750 | 753 | ) |
|
751 | 754 | coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$', |
|
752 | 755 | default=dynamicdefault, # take from ui.mergemarkertemplate |
|
753 | 756 | generic=True, |
|
754 | 757 | priority=-1, |
|
755 | 758 | ) |
|
756 | 759 | coreconfigitem('merge-tools', br'.*\.priority$', |
|
757 | 760 | default=0, |
|
758 | 761 | generic=True, |
|
759 | 762 | priority=-1, |
|
760 | 763 | ) |
|
761 | 764 | coreconfigitem('merge-tools', br'.*\.premerge$', |
|
762 | 765 | default=dynamicdefault, |
|
763 | 766 | generic=True, |
|
764 | 767 | priority=-1, |
|
765 | 768 | ) |
|
766 | 769 | coreconfigitem('merge-tools', br'.*\.symlink$', |
|
767 | 770 | default=False, |
|
768 | 771 | generic=True, |
|
769 | 772 | priority=-1, |
|
770 | 773 | ) |
|
771 | 774 | coreconfigitem('pager', 'attend-.*', |
|
772 | 775 | default=dynamicdefault, |
|
773 | 776 | generic=True, |
|
774 | 777 | ) |
|
775 | 778 | coreconfigitem('pager', 'ignore', |
|
776 | 779 | default=list, |
|
777 | 780 | ) |
|
778 | 781 | coreconfigitem('pager', 'pager', |
|
779 | 782 | default=dynamicdefault, |
|
780 | 783 | ) |
|
781 | 784 | coreconfigitem('patch', 'eol', |
|
782 | 785 | default='strict', |
|
783 | 786 | ) |
|
784 | 787 | coreconfigitem('patch', 'fuzz', |
|
785 | 788 | default=2, |
|
786 | 789 | ) |
|
787 | 790 | coreconfigitem('paths', 'default', |
|
788 | 791 | default=None, |
|
789 | 792 | ) |
|
790 | 793 | coreconfigitem('paths', 'default-push', |
|
791 | 794 | default=None, |
|
792 | 795 | ) |
|
793 | 796 | coreconfigitem('paths', '.*', |
|
794 | 797 | default=None, |
|
795 | 798 | generic=True, |
|
796 | 799 | ) |
|
797 | 800 | coreconfigitem('phases', 'checksubrepos', |
|
798 | 801 | default='follow', |
|
799 | 802 | ) |
|
800 | 803 | coreconfigitem('phases', 'new-commit', |
|
801 | 804 | default='draft', |
|
802 | 805 | ) |
|
803 | 806 | coreconfigitem('phases', 'publish', |
|
804 | 807 | default=True, |
|
805 | 808 | ) |
|
806 | 809 | coreconfigitem('profiling', 'enabled', |
|
807 | 810 | default=False, |
|
808 | 811 | ) |
|
809 | 812 | coreconfigitem('profiling', 'format', |
|
810 | 813 | default='text', |
|
811 | 814 | ) |
|
812 | 815 | coreconfigitem('profiling', 'freq', |
|
813 | 816 | default=1000, |
|
814 | 817 | ) |
|
815 | 818 | coreconfigitem('profiling', 'limit', |
|
816 | 819 | default=30, |
|
817 | 820 | ) |
|
818 | 821 | coreconfigitem('profiling', 'nested', |
|
819 | 822 | default=0, |
|
820 | 823 | ) |
|
821 | 824 | coreconfigitem('profiling', 'output', |
|
822 | 825 | default=None, |
|
823 | 826 | ) |
|
824 | 827 | coreconfigitem('profiling', 'showmax', |
|
825 | 828 | default=0.999, |
|
826 | 829 | ) |
|
827 | 830 | coreconfigitem('profiling', 'showmin', |
|
828 | 831 | default=dynamicdefault, |
|
829 | 832 | ) |
|
830 | 833 | coreconfigitem('profiling', 'sort', |
|
831 | 834 | default='inlinetime', |
|
832 | 835 | ) |
|
833 | 836 | coreconfigitem('profiling', 'statformat', |
|
834 | 837 | default='hotpath', |
|
835 | 838 | ) |
|
836 | 839 | coreconfigitem('profiling', 'type', |
|
837 | 840 | default='stat', |
|
838 | 841 | ) |
|
839 | 842 | coreconfigitem('progress', 'assume-tty', |
|
840 | 843 | default=False, |
|
841 | 844 | ) |
|
842 | 845 | coreconfigitem('progress', 'changedelay', |
|
843 | 846 | default=1, |
|
844 | 847 | ) |
|
845 | 848 | coreconfigitem('progress', 'clear-complete', |
|
846 | 849 | default=True, |
|
847 | 850 | ) |
|
848 | 851 | coreconfigitem('progress', 'debug', |
|
849 | 852 | default=False, |
|
850 | 853 | ) |
|
851 | 854 | coreconfigitem('progress', 'delay', |
|
852 | 855 | default=3, |
|
853 | 856 | ) |
|
854 | 857 | coreconfigitem('progress', 'disable', |
|
855 | 858 | default=False, |
|
856 | 859 | ) |
|
857 | 860 | coreconfigitem('progress', 'estimateinterval', |
|
858 | 861 | default=60.0, |
|
859 | 862 | ) |
|
860 | 863 | coreconfigitem('progress', 'format', |
|
861 | 864 | default=lambda: ['topic', 'bar', 'number', 'estimate'], |
|
862 | 865 | ) |
|
863 | 866 | coreconfigitem('progress', 'refresh', |
|
864 | 867 | default=0.1, |
|
865 | 868 | ) |
|
866 | 869 | coreconfigitem('progress', 'width', |
|
867 | 870 | default=dynamicdefault, |
|
868 | 871 | ) |
|
869 | 872 | coreconfigitem('push', 'pushvars.server', |
|
870 | 873 | default=False, |
|
871 | 874 | ) |
|
872 | 875 | coreconfigitem('server', 'bookmarks-pushkey-compat', |
|
873 | 876 | default=True, |
|
874 | 877 | ) |
|
875 | 878 | coreconfigitem('server', 'bundle1', |
|
876 | 879 | default=True, |
|
877 | 880 | ) |
|
878 | 881 | coreconfigitem('server', 'bundle1gd', |
|
879 | 882 | default=None, |
|
880 | 883 | ) |
|
881 | 884 | coreconfigitem('server', 'bundle1.pull', |
|
882 | 885 | default=None, |
|
883 | 886 | ) |
|
884 | 887 | coreconfigitem('server', 'bundle1gd.pull', |
|
885 | 888 | default=None, |
|
886 | 889 | ) |
|
887 | 890 | coreconfigitem('server', 'bundle1.push', |
|
888 | 891 | default=None, |
|
889 | 892 | ) |
|
890 | 893 | coreconfigitem('server', 'bundle1gd.push', |
|
891 | 894 | default=None, |
|
892 | 895 | ) |
|
893 | 896 | coreconfigitem('server', 'compressionengines', |
|
894 | 897 | default=list, |
|
895 | 898 | ) |
|
896 | 899 | coreconfigitem('server', 'concurrent-push-mode', |
|
897 | 900 | default='strict', |
|
898 | 901 | ) |
|
899 | 902 | coreconfigitem('server', 'disablefullbundle', |
|
900 | 903 | default=False, |
|
901 | 904 | ) |
|
902 | 905 | coreconfigitem('server', 'maxhttpheaderlen', |
|
903 | 906 | default=1024, |
|
904 | 907 | ) |
|
905 | 908 | coreconfigitem('server', 'preferuncompressed', |
|
906 | 909 | default=False, |
|
907 | 910 | ) |
|
908 | 911 | coreconfigitem('server', 'uncompressed', |
|
909 | 912 | default=True, |
|
910 | 913 | ) |
|
911 | 914 | coreconfigitem('server', 'uncompressedallowsecret', |
|
912 | 915 | default=False, |
|
913 | 916 | ) |
|
914 | 917 | coreconfigitem('server', 'validate', |
|
915 | 918 | default=False, |
|
916 | 919 | ) |
|
917 | 920 | coreconfigitem('server', 'zliblevel', |
|
918 | 921 | default=-1, |
|
919 | 922 | ) |
|
920 | 923 | coreconfigitem('share', 'pool', |
|
921 | 924 | default=None, |
|
922 | 925 | ) |
|
923 | 926 | coreconfigitem('share', 'poolnaming', |
|
924 | 927 | default='identity', |
|
925 | 928 | ) |
|
926 | 929 | coreconfigitem('smtp', 'host', |
|
927 | 930 | default=None, |
|
928 | 931 | ) |
|
929 | 932 | coreconfigitem('smtp', 'local_hostname', |
|
930 | 933 | default=None, |
|
931 | 934 | ) |
|
932 | 935 | coreconfigitem('smtp', 'password', |
|
933 | 936 | default=None, |
|
934 | 937 | ) |
|
935 | 938 | coreconfigitem('smtp', 'port', |
|
936 | 939 | default=dynamicdefault, |
|
937 | 940 | ) |
|
938 | 941 | coreconfigitem('smtp', 'tls', |
|
939 | 942 | default='none', |
|
940 | 943 | ) |
|
941 | 944 | coreconfigitem('smtp', 'username', |
|
942 | 945 | default=None, |
|
943 | 946 | ) |
|
944 | 947 | coreconfigitem('sparse', 'missingwarning', |
|
945 | 948 | default=True, |
|
946 | 949 | ) |
|
947 | 950 | coreconfigitem('subrepos', 'allowed', |
|
948 | 951 | default=dynamicdefault, # to make backporting simpler |
|
949 | 952 | ) |
|
950 | 953 | coreconfigitem('subrepos', 'hg:allowed', |
|
951 | 954 | default=dynamicdefault, |
|
952 | 955 | ) |
|
953 | 956 | coreconfigitem('subrepos', 'git:allowed', |
|
954 | 957 | default=dynamicdefault, |
|
955 | 958 | ) |
|
956 | 959 | coreconfigitem('subrepos', 'svn:allowed', |
|
957 | 960 | default=dynamicdefault, |
|
958 | 961 | ) |
|
959 | 962 | coreconfigitem('templates', '.*', |
|
960 | 963 | default=None, |
|
961 | 964 | generic=True, |
|
962 | 965 | ) |
|
963 | 966 | coreconfigitem('trusted', 'groups', |
|
964 | 967 | default=list, |
|
965 | 968 | ) |
|
966 | 969 | coreconfigitem('trusted', 'users', |
|
967 | 970 | default=list, |
|
968 | 971 | ) |
|
969 | 972 | coreconfigitem('ui', '_usedassubrepo', |
|
970 | 973 | default=False, |
|
971 | 974 | ) |
|
972 | 975 | coreconfigitem('ui', 'allowemptycommit', |
|
973 | 976 | default=False, |
|
974 | 977 | ) |
|
975 | 978 | coreconfigitem('ui', 'archivemeta', |
|
976 | 979 | default=True, |
|
977 | 980 | ) |
|
978 | 981 | coreconfigitem('ui', 'askusername', |
|
979 | 982 | default=False, |
|
980 | 983 | ) |
|
981 | 984 | coreconfigitem('ui', 'clonebundlefallback', |
|
982 | 985 | default=False, |
|
983 | 986 | ) |
|
984 | 987 | coreconfigitem('ui', 'clonebundleprefers', |
|
985 | 988 | default=list, |
|
986 | 989 | ) |
|
987 | 990 | coreconfigitem('ui', 'clonebundles', |
|
988 | 991 | default=True, |
|
989 | 992 | ) |
|
990 | 993 | coreconfigitem('ui', 'color', |
|
991 | 994 | default='auto', |
|
992 | 995 | ) |
|
993 | 996 | coreconfigitem('ui', 'commitsubrepos', |
|
994 | 997 | default=False, |
|
995 | 998 | ) |
|
996 | 999 | coreconfigitem('ui', 'debug', |
|
997 | 1000 | default=False, |
|
998 | 1001 | ) |
|
999 | 1002 | coreconfigitem('ui', 'debugger', |
|
1000 | 1003 | default=None, |
|
1001 | 1004 | ) |
|
1002 | 1005 | coreconfigitem('ui', 'editor', |
|
1003 | 1006 | default=dynamicdefault, |
|
1004 | 1007 | ) |
|
1005 | 1008 | coreconfigitem('ui', 'fallbackencoding', |
|
1006 | 1009 | default=None, |
|
1007 | 1010 | ) |
|
1008 | 1011 | coreconfigitem('ui', 'forcecwd', |
|
1009 | 1012 | default=None, |
|
1010 | 1013 | ) |
|
1011 | 1014 | coreconfigitem('ui', 'forcemerge', |
|
1012 | 1015 | default=None, |
|
1013 | 1016 | ) |
|
1014 | 1017 | coreconfigitem('ui', 'formatdebug', |
|
1015 | 1018 | default=False, |
|
1016 | 1019 | ) |
|
1017 | 1020 | coreconfigitem('ui', 'formatjson', |
|
1018 | 1021 | default=False, |
|
1019 | 1022 | ) |
|
1020 | 1023 | coreconfigitem('ui', 'formatted', |
|
1021 | 1024 | default=None, |
|
1022 | 1025 | ) |
|
1023 | 1026 | coreconfigitem('ui', 'graphnodetemplate', |
|
1024 | 1027 | default=None, |
|
1025 | 1028 | ) |
|
1026 | 1029 | coreconfigitem('ui', 'http2debuglevel', |
|
1027 | 1030 | default=None, |
|
1028 | 1031 | ) |
|
1029 | 1032 | coreconfigitem('ui', 'interactive', |
|
1030 | 1033 | default=None, |
|
1031 | 1034 | ) |
|
1032 | 1035 | coreconfigitem('ui', 'interface', |
|
1033 | 1036 | default=None, |
|
1034 | 1037 | ) |
|
1035 | 1038 | coreconfigitem('ui', 'interface.chunkselector', |
|
1036 | 1039 | default=None, |
|
1037 | 1040 | ) |
|
1038 | 1041 | coreconfigitem('ui', 'logblockedtimes', |
|
1039 | 1042 | default=False, |
|
1040 | 1043 | ) |
|
1041 | 1044 | coreconfigitem('ui', 'logtemplate', |
|
1042 | 1045 | default=None, |
|
1043 | 1046 | ) |
|
1044 | 1047 | coreconfigitem('ui', 'merge', |
|
1045 | 1048 | default=None, |
|
1046 | 1049 | ) |
|
1047 | 1050 | coreconfigitem('ui', 'mergemarkers', |
|
1048 | 1051 | default='basic', |
|
1049 | 1052 | ) |
|
1050 | 1053 | coreconfigitem('ui', 'mergemarkertemplate', |
|
1051 | 1054 | default=('{node|short} ' |
|
1052 | 1055 | '{ifeq(tags, "tip", "", ' |
|
1053 | 1056 | 'ifeq(tags, "", "", "{tags} "))}' |
|
1054 | 1057 | '{if(bookmarks, "{bookmarks} ")}' |
|
1055 | 1058 | '{ifeq(branch, "default", "", "{branch} ")}' |
|
1056 | 1059 | '- {author|user}: {desc|firstline}') |
|
1057 | 1060 | ) |
|
1058 | 1061 | coreconfigitem('ui', 'nontty', |
|
1059 | 1062 | default=False, |
|
1060 | 1063 | ) |
|
1061 | 1064 | coreconfigitem('ui', 'origbackuppath', |
|
1062 | 1065 | default=None, |
|
1063 | 1066 | ) |
|
1064 | 1067 | coreconfigitem('ui', 'paginate', |
|
1065 | 1068 | default=True, |
|
1066 | 1069 | ) |
|
1067 | 1070 | coreconfigitem('ui', 'patch', |
|
1068 | 1071 | default=None, |
|
1069 | 1072 | ) |
|
1070 | 1073 | coreconfigitem('ui', 'portablefilenames', |
|
1071 | 1074 | default='warn', |
|
1072 | 1075 | ) |
|
1073 | 1076 | coreconfigitem('ui', 'promptecho', |
|
1074 | 1077 | default=False, |
|
1075 | 1078 | ) |
|
1076 | 1079 | coreconfigitem('ui', 'quiet', |
|
1077 | 1080 | default=False, |
|
1078 | 1081 | ) |
|
1079 | 1082 | coreconfigitem('ui', 'quietbookmarkmove', |
|
1080 | 1083 | default=False, |
|
1081 | 1084 | ) |
|
1082 | 1085 | coreconfigitem('ui', 'remotecmd', |
|
1083 | 1086 | default='hg', |
|
1084 | 1087 | ) |
|
1085 | 1088 | coreconfigitem('ui', 'report_untrusted', |
|
1086 | 1089 | default=True, |
|
1087 | 1090 | ) |
|
1088 | 1091 | coreconfigitem('ui', 'rollback', |
|
1089 | 1092 | default=True, |
|
1090 | 1093 | ) |
|
1091 | 1094 | coreconfigitem('ui', 'slash', |
|
1092 | 1095 | default=False, |
|
1093 | 1096 | ) |
|
1094 | 1097 | coreconfigitem('ui', 'ssh', |
|
1095 | 1098 | default='ssh', |
|
1096 | 1099 | ) |
|
1097 | 1100 | coreconfigitem('ui', 'ssherrorhint', |
|
1098 | 1101 | default=None, |
|
1099 | 1102 | ) |
|
1100 | 1103 | coreconfigitem('ui', 'statuscopies', |
|
1101 | 1104 | default=False, |
|
1102 | 1105 | ) |
|
1103 | 1106 | coreconfigitem('ui', 'strict', |
|
1104 | 1107 | default=False, |
|
1105 | 1108 | ) |
|
1106 | 1109 | coreconfigitem('ui', 'style', |
|
1107 | 1110 | default='', |
|
1108 | 1111 | ) |
|
1109 | 1112 | coreconfigitem('ui', 'supportcontact', |
|
1110 | 1113 | default=None, |
|
1111 | 1114 | ) |
|
1112 | 1115 | coreconfigitem('ui', 'textwidth', |
|
1113 | 1116 | default=78, |
|
1114 | 1117 | ) |
|
1115 | 1118 | coreconfigitem('ui', 'timeout', |
|
1116 | 1119 | default='600', |
|
1117 | 1120 | ) |
|
1118 | 1121 | coreconfigitem('ui', 'timeout.warn', |
|
1119 | 1122 | default=0, |
|
1120 | 1123 | ) |
|
1121 | 1124 | coreconfigitem('ui', 'traceback', |
|
1122 | 1125 | default=False, |
|
1123 | 1126 | ) |
|
1124 | 1127 | coreconfigitem('ui', 'tweakdefaults', |
|
1125 | 1128 | default=False, |
|
1126 | 1129 | ) |
|
1127 | 1130 | coreconfigitem('ui', 'usehttp2', |
|
1128 | 1131 | default=False, |
|
1129 | 1132 | ) |
|
1130 | 1133 | coreconfigitem('ui', 'username', |
|
1131 | 1134 | alias=[('ui', 'user')] |
|
1132 | 1135 | ) |
|
1133 | 1136 | coreconfigitem('ui', 'verbose', |
|
1134 | 1137 | default=False, |
|
1135 | 1138 | ) |
|
1136 | 1139 | coreconfigitem('verify', 'skipflags', |
|
1137 | 1140 | default=None, |
|
1138 | 1141 | ) |
|
1139 | 1142 | coreconfigitem('web', 'allowbz2', |
|
1140 | 1143 | default=False, |
|
1141 | 1144 | ) |
|
1142 | 1145 | coreconfigitem('web', 'allowgz', |
|
1143 | 1146 | default=False, |
|
1144 | 1147 | ) |
|
1145 | 1148 | coreconfigitem('web', 'allow-pull', |
|
1146 | 1149 | alias=[('web', 'allowpull')], |
|
1147 | 1150 | default=True, |
|
1148 | 1151 | ) |
|
1149 | 1152 | coreconfigitem('web', 'allow-push', |
|
1150 | 1153 | alias=[('web', 'allow_push')], |
|
1151 | 1154 | default=list, |
|
1152 | 1155 | ) |
|
1153 | 1156 | coreconfigitem('web', 'allowzip', |
|
1154 | 1157 | default=False, |
|
1155 | 1158 | ) |
|
1156 | 1159 | coreconfigitem('web', 'archivesubrepos', |
|
1157 | 1160 | default=False, |
|
1158 | 1161 | ) |
|
1159 | 1162 | coreconfigitem('web', 'cache', |
|
1160 | 1163 | default=True, |
|
1161 | 1164 | ) |
|
1162 | 1165 | coreconfigitem('web', 'contact', |
|
1163 | 1166 | default=None, |
|
1164 | 1167 | ) |
|
1165 | 1168 | coreconfigitem('web', 'deny_push', |
|
1166 | 1169 | default=list, |
|
1167 | 1170 | ) |
|
1168 | 1171 | coreconfigitem('web', 'guessmime', |
|
1169 | 1172 | default=False, |
|
1170 | 1173 | ) |
|
1171 | 1174 | coreconfigitem('web', 'hidden', |
|
1172 | 1175 | default=False, |
|
1173 | 1176 | ) |
|
1174 | 1177 | coreconfigitem('web', 'labels', |
|
1175 | 1178 | default=list, |
|
1176 | 1179 | ) |
|
1177 | 1180 | coreconfigitem('web', 'logoimg', |
|
1178 | 1181 | default='hglogo.png', |
|
1179 | 1182 | ) |
|
1180 | 1183 | coreconfigitem('web', 'logourl', |
|
1181 | 1184 | default='https://mercurial-scm.org/', |
|
1182 | 1185 | ) |
|
1183 | 1186 | coreconfigitem('web', 'accesslog', |
|
1184 | 1187 | default='-', |
|
1185 | 1188 | ) |
|
1186 | 1189 | coreconfigitem('web', 'address', |
|
1187 | 1190 | default='', |
|
1188 | 1191 | ) |
|
1189 | 1192 | coreconfigitem('web', 'allow_archive', |
|
1190 | 1193 | default=list, |
|
1191 | 1194 | ) |
|
1192 | 1195 | coreconfigitem('web', 'allow_read', |
|
1193 | 1196 | default=list, |
|
1194 | 1197 | ) |
|
1195 | 1198 | coreconfigitem('web', 'baseurl', |
|
1196 | 1199 | default=None, |
|
1197 | 1200 | ) |
|
1198 | 1201 | coreconfigitem('web', 'cacerts', |
|
1199 | 1202 | default=None, |
|
1200 | 1203 | ) |
|
1201 | 1204 | coreconfigitem('web', 'certificate', |
|
1202 | 1205 | default=None, |
|
1203 | 1206 | ) |
|
1204 | 1207 | coreconfigitem('web', 'collapse', |
|
1205 | 1208 | default=False, |
|
1206 | 1209 | ) |
|
1207 | 1210 | coreconfigitem('web', 'csp', |
|
1208 | 1211 | default=None, |
|
1209 | 1212 | ) |
|
1210 | 1213 | coreconfigitem('web', 'deny_read', |
|
1211 | 1214 | default=list, |
|
1212 | 1215 | ) |
|
1213 | 1216 | coreconfigitem('web', 'descend', |
|
1214 | 1217 | default=True, |
|
1215 | 1218 | ) |
|
1216 | 1219 | coreconfigitem('web', 'description', |
|
1217 | 1220 | default="", |
|
1218 | 1221 | ) |
|
1219 | 1222 | coreconfigitem('web', 'encoding', |
|
1220 | 1223 | default=lambda: encoding.encoding, |
|
1221 | 1224 | ) |
|
1222 | 1225 | coreconfigitem('web', 'errorlog', |
|
1223 | 1226 | default='-', |
|
1224 | 1227 | ) |
|
1225 | 1228 | coreconfigitem('web', 'ipv6', |
|
1226 | 1229 | default=False, |
|
1227 | 1230 | ) |
|
1228 | 1231 | coreconfigitem('web', 'maxchanges', |
|
1229 | 1232 | default=10, |
|
1230 | 1233 | ) |
|
1231 | 1234 | coreconfigitem('web', 'maxfiles', |
|
1232 | 1235 | default=10, |
|
1233 | 1236 | ) |
|
1234 | 1237 | coreconfigitem('web', 'maxshortchanges', |
|
1235 | 1238 | default=60, |
|
1236 | 1239 | ) |
|
1237 | 1240 | coreconfigitem('web', 'motd', |
|
1238 | 1241 | default='', |
|
1239 | 1242 | ) |
|
1240 | 1243 | coreconfigitem('web', 'name', |
|
1241 | 1244 | default=dynamicdefault, |
|
1242 | 1245 | ) |
|
1243 | 1246 | coreconfigitem('web', 'port', |
|
1244 | 1247 | default=8000, |
|
1245 | 1248 | ) |
|
1246 | 1249 | coreconfigitem('web', 'prefix', |
|
1247 | 1250 | default='', |
|
1248 | 1251 | ) |
|
1249 | 1252 | coreconfigitem('web', 'push_ssl', |
|
1250 | 1253 | default=True, |
|
1251 | 1254 | ) |
|
1252 | 1255 | coreconfigitem('web', 'refreshinterval', |
|
1253 | 1256 | default=20, |
|
1254 | 1257 | ) |
|
1255 | 1258 | coreconfigitem('web', 'staticurl', |
|
1256 | 1259 | default=None, |
|
1257 | 1260 | ) |
|
1258 | 1261 | coreconfigitem('web', 'stripes', |
|
1259 | 1262 | default=1, |
|
1260 | 1263 | ) |
|
1261 | 1264 | coreconfigitem('web', 'style', |
|
1262 | 1265 | default='paper', |
|
1263 | 1266 | ) |
|
1264 | 1267 | coreconfigitem('web', 'templates', |
|
1265 | 1268 | default=None, |
|
1266 | 1269 | ) |
|
1267 | 1270 | coreconfigitem('web', 'view', |
|
1268 | 1271 | default='served', |
|
1269 | 1272 | ) |
|
1270 | 1273 | coreconfigitem('worker', 'backgroundclose', |
|
1271 | 1274 | default=dynamicdefault, |
|
1272 | 1275 | ) |
|
1273 | 1276 | # Windows defaults to a limit of 512 open files. A buffer of 128 |
|
1274 | 1277 | # should give us enough headway. |
|
1275 | 1278 | coreconfigitem('worker', 'backgroundclosemaxqueue', |
|
1276 | 1279 | default=384, |
|
1277 | 1280 | ) |
|
1278 | 1281 | coreconfigitem('worker', 'backgroundcloseminfilecount', |
|
1279 | 1282 | default=2048, |
|
1280 | 1283 | ) |
|
1281 | 1284 | coreconfigitem('worker', 'backgroundclosethreadcount', |
|
1282 | 1285 | default=4, |
|
1283 | 1286 | ) |
|
1284 | 1287 | coreconfigitem('worker', 'enabled', |
|
1285 | 1288 | default=True, |
|
1286 | 1289 | ) |
|
1287 | 1290 | coreconfigitem('worker', 'numcpus', |
|
1288 | 1291 | default=None, |
|
1289 | 1292 | ) |
|
1290 | 1293 | |
|
1291 | 1294 | # Rebase related configuration moved to core because other extension are doing |
|
1292 | 1295 | # strange things. For example, shelve import the extensions to reuse some bit |
|
1293 | 1296 | # without formally loading it. |
|
1294 | 1297 | coreconfigitem('commands', 'rebase.requiredest', |
|
1295 | 1298 | default=False, |
|
1296 | 1299 | ) |
|
1297 | 1300 | coreconfigitem('experimental', 'rebaseskipobsolete', |
|
1298 | 1301 | default=True, |
|
1299 | 1302 | ) |
|
1300 | 1303 | coreconfigitem('rebase', 'singletransaction', |
|
1301 | 1304 | default=False, |
|
1302 | 1305 | ) |
|
1303 | 1306 | coreconfigitem('rebase', 'experimental.inmemory', |
|
1304 | 1307 | default=False, |
|
1305 | 1308 | ) |
@@ -1,990 +1,1104 b'' | |||
|
1 | 1 | The Mercurial wire protocol is a request-response based protocol |
|
2 | 2 | with multiple wire representations. |
|
3 | 3 | |
|
4 | 4 | Each request is modeled as a command name, a dictionary of arguments, and |
|
5 | 5 | optional raw input. Command arguments and their types are intrinsic |
|
6 | 6 | properties of commands. So is the response type of the command. This means |
|
7 | 7 | clients can't always send arbitrary arguments to servers and servers can't |
|
8 | 8 | return multiple response types. |
|
9 | 9 | |
|
10 | 10 | The protocol is synchronous and does not support multiplexing (concurrent |
|
11 | 11 | commands). |
|
12 | 12 | |
|
13 | 13 | Handshake |
|
14 | 14 | ========= |
|
15 | 15 | |
|
16 | 16 | It is required or common for clients to perform a *handshake* when connecting |
|
17 | 17 | to a server. The handshake serves the following purposes: |
|
18 | 18 | |
|
19 | 19 | * Negotiating protocol/transport level options |
|
20 | 20 | * Allows the client to learn about server capabilities to influence |
|
21 | 21 | future requests |
|
22 | 22 | * Ensures the underlying transport channel is in a *clean* state |
|
23 | 23 | |
|
24 | 24 | An important goal of the handshake is to allow clients to use more modern |
|
25 | 25 | wire protocol features. By default, clients must assume they are talking |
|
26 | 26 | to an old version of Mercurial server (possibly even the very first |
|
27 | 27 | implementation). So, clients should not attempt to call or utilize modern |
|
28 | 28 | wire protocol features until they have confirmation that the server |
|
29 | 29 | supports them. The handshake implementation is designed to allow both |
|
30 | 30 | ends to utilize the latest set of features and capabilities with as |
|
31 | 31 | few round trips as possible. |
|
32 | 32 | |
|
33 | 33 | The handshake mechanism varies by transport and protocol and is documented |
|
34 | 34 | in the sections below. |
|
35 | 35 | |
|
36 | 36 | HTTP Protocol |
|
37 | 37 | ============= |
|
38 | 38 | |
|
39 | 39 | Handshake |
|
40 | 40 | --------- |
|
41 | 41 | |
|
42 | 42 | The client sends a ``capabilities`` command request (``?cmd=capabilities``) |
|
43 | 43 | as soon as HTTP requests may be issued. |
|
44 | 44 | |
|
45 | 45 | The server responds with a capabilities string, which the client parses to |
|
46 | 46 | learn about the server's abilities. |
|
47 | 47 | |
|
48 | 48 | HTTP Version 1 Transport |
|
49 | 49 | ------------------------ |
|
50 | 50 | |
|
51 | 51 | Commands are issued as HTTP/1.0 or HTTP/1.1 requests. Commands are |
|
52 | 52 | sent to the base URL of the repository with the command name sent in |
|
53 | 53 | the ``cmd`` query string parameter. e.g. |
|
54 | 54 | ``https://example.com/repo?cmd=capabilities``. The HTTP method is ``GET`` |
|
55 | 55 | or ``POST`` depending on the command and whether there is a request |
|
56 | 56 | body. |
|
57 | 57 | |
|
58 | 58 | Command arguments can be sent multiple ways. |
|
59 | 59 | |
|
60 | 60 | The simplest is part of the URL query string using ``x-www-form-urlencoded`` |
|
61 | 61 | encoding (see Python's ``urllib.urlencode()``. However, many servers impose |
|
62 | 62 | length limitations on the URL. So this mechanism is typically only used if |
|
63 | 63 | the server doesn't support other mechanisms. |
|
64 | 64 | |
|
65 | 65 | If the server supports the ``httpheader`` capability, command arguments can |
|
66 | 66 | be sent in HTTP request headers named ``X-HgArg-<N>`` where ``<N>`` is an |
|
67 | 67 | integer starting at 1. A ``x-www-form-urlencoded`` representation of the |
|
68 | 68 | arguments is obtained. This full string is then split into chunks and sent |
|
69 | 69 | in numbered ``X-HgArg-<N>`` headers. The maximum length of each HTTP header |
|
70 | 70 | is defined by the server in the ``httpheader`` capability value, which defaults |
|
71 | 71 | to ``1024``. The server reassembles the encoded arguments string by |
|
72 | 72 | concatenating the ``X-HgArg-<N>`` headers then URL decodes them into a |
|
73 | 73 | dictionary. |
|
74 | 74 | |
|
75 | 75 | The list of ``X-HgArg-<N>`` headers should be added to the ``Vary`` request |
|
76 | 76 | header to instruct caches to take these headers into consideration when caching |
|
77 | 77 | requests. |
|
78 | 78 | |
|
79 | 79 | If the server supports the ``httppostargs`` capability, the client |
|
80 | 80 | may send command arguments in the HTTP request body as part of an |
|
81 | 81 | HTTP POST request. The command arguments will be URL encoded just like |
|
82 | 82 | they would for sending them via HTTP headers. However, no splitting is |
|
83 | 83 | performed: the raw arguments are included in the HTTP request body. |
|
84 | 84 | |
|
85 | 85 | The client sends a ``X-HgArgs-Post`` header with the string length of the |
|
86 | 86 | encoded arguments data. Additional data may be included in the HTTP |
|
87 | 87 | request body immediately following the argument data. The offset of the |
|
88 | 88 | non-argument data is defined by the ``X-HgArgs-Post`` header. The |
|
89 | 89 | ``X-HgArgs-Post`` header is not required if there is no argument data. |
|
90 | 90 | |
|
91 | 91 | Additional command data can be sent as part of the HTTP request body. The |
|
92 | 92 | default ``Content-Type`` when sending data is ``application/mercurial-0.1``. |
|
93 | 93 | A ``Content-Length`` header is currently always sent. |
|
94 | 94 | |
|
95 | 95 | Example HTTP requests:: |
|
96 | 96 | |
|
97 | 97 | GET /repo?cmd=capabilities |
|
98 | 98 | X-HgArg-1: foo=bar&baz=hello%20world |
|
99 | 99 | |
|
100 | 100 | The request media type should be chosen based on server support. If the |
|
101 | 101 | ``httpmediatype`` server capability is present, the client should send |
|
102 | 102 | the newest mutually supported media type. If this capability is absent, |
|
103 | 103 | the client must assume the server only supports the |
|
104 | 104 | ``application/mercurial-0.1`` media type. |
|
105 | 105 | |
|
106 | 106 | The ``Content-Type`` HTTP response header identifies the response as coming |
|
107 | 107 | from Mercurial and can also be used to signal an error has occurred. |
|
108 | 108 | |
|
109 | 109 | The ``application/mercurial-*`` media types indicate a generic Mercurial |
|
110 | 110 | data type. |
|
111 | 111 | |
|
112 | 112 | The ``application/mercurial-0.1`` media type is raw Mercurial data. It is the |
|
113 | 113 | predecessor of the format below. |
|
114 | 114 | |
|
115 | 115 | The ``application/mercurial-0.2`` media type is compression framed Mercurial |
|
116 | 116 | data. The first byte of the payload indicates the length of the compression |
|
117 | 117 | format identifier that follows. Next are N bytes indicating the compression |
|
118 | 118 | format. e.g. ``zlib``. The remaining bytes are compressed according to that |
|
119 | 119 | compression format. The decompressed data behaves the same as with |
|
120 | 120 | ``application/mercurial-0.1``. |
|
121 | 121 | |
|
122 | 122 | The ``application/hg-error`` media type indicates a generic error occurred. |
|
123 | 123 | The content of the HTTP response body typically holds text describing the |
|
124 | 124 | error. |
|
125 | 125 | |
|
126 | 126 | The ``application/hg-changegroup`` media type indicates a changegroup response |
|
127 | 127 | type. |
|
128 | 128 | |
|
129 | 129 | Clients also accept the ``text/plain`` media type. All other media |
|
130 | 130 | types should cause the client to error. |
|
131 | 131 | |
|
132 | 132 | Behavior of media types is further described in the ``Content Negotiation`` |
|
133 | 133 | section below. |
|
134 | 134 | |
|
135 | 135 | Clients should issue a ``User-Agent`` request header that identifies the client. |
|
136 | 136 | The server should not use the ``User-Agent`` for feature detection. |
|
137 | 137 | |
|
138 | 138 | A command returning a ``string`` response issues a |
|
139 | 139 | ``application/mercurial-0.*`` media type and the HTTP response body contains |
|
140 | 140 | the raw string value (after compression decoding, if used). A |
|
141 | 141 | ``Content-Length`` header is typically issued, but not required. |
|
142 | 142 | |
|
143 | 143 | A command returning a ``stream`` response issues a |
|
144 | 144 | ``application/mercurial-0.*`` media type and the HTTP response is typically |
|
145 | 145 | using *chunked transfer* (``Transfer-Encoding: chunked``). |
|
146 | 146 | |
|
147 | 147 | SSH Protocol |
|
148 | 148 | ============ |
|
149 | 149 | |
|
150 | 150 | Handshake |
|
151 | 151 | --------- |
|
152 | 152 | |
|
153 | 153 | For all clients, the handshake consists of the client sending 1 or more |
|
154 | 154 | commands to the server using version 1 of the transport. Servers respond |
|
155 | 155 | to commands they know how to respond to and send an empty response (``0\n``) |
|
156 | 156 | for unknown commands (per standard behavior of version 1 of the transport). |
|
157 | 157 | Clients then typically look for a response to the newest sent command to |
|
158 | 158 | determine which transport version to use and what the available features for |
|
159 | 159 | the connection and server are. |
|
160 | 160 | |
|
161 | 161 | Preceding any response from client-issued commands, the server may print |
|
162 | 162 | non-protocol output. It is common for SSH servers to print banners, message |
|
163 | 163 | of the day announcements, etc when clients connect. It is assumed that any |
|
164 | 164 | such *banner* output will precede any Mercurial server output. So clients |
|
165 | 165 | must be prepared to handle server output on initial connect that isn't |
|
166 | 166 | in response to any client-issued command and doesn't conform to Mercurial's |
|
167 | 167 | wire protocol. This *banner* output should only be on stdout. However, |
|
168 | 168 | some servers may send output on stderr. |
|
169 | 169 | |
|
170 | 170 | Pre 0.9.1 clients issue a ``between`` command with the ``pairs`` argument |
|
171 | 171 | having the value |
|
172 | 172 | ``0000000000000000000000000000000000000000-0000000000000000000000000000000000000000``. |
|
173 | 173 | |
|
174 | 174 | The ``between`` command has been supported since the original Mercurial |
|
175 | 175 | SSH server. Requesting the empty range will return a ``\n`` string response, |
|
176 | 176 | which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline |
|
177 | 177 | followed by the value, which happens to be a newline). |
|
178 | 178 | |
|
179 | 179 | For pre 0.9.1 clients and all servers, the exchange looks like:: |
|
180 | 180 | |
|
181 | 181 | c: between\n |
|
182 | 182 | c: pairs 81\n |
|
183 | 183 | c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 |
|
184 | 184 | s: 1\n |
|
185 | 185 | s: \n |
|
186 | 186 | |
|
187 | 187 | 0.9.1+ clients send a ``hello`` command (with no arguments) before the |
|
188 | 188 | ``between`` command. The response to this command allows clients to |
|
189 | 189 | discover server capabilities and settings. |
|
190 | 190 | |
|
191 | 191 | An example exchange between 0.9.1+ clients and a ``hello`` aware server looks |
|
192 | 192 | like:: |
|
193 | 193 | |
|
194 | 194 | c: hello\n |
|
195 | 195 | c: between\n |
|
196 | 196 | c: pairs 81\n |
|
197 | 197 | c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 |
|
198 | 198 | s: 324\n |
|
199 | 199 | s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n |
|
200 | 200 | s: 1\n |
|
201 | 201 | s: \n |
|
202 | 202 | |
|
203 | 203 | And a similar scenario but with servers sending a banner on connect:: |
|
204 | 204 | |
|
205 | 205 | c: hello\n |
|
206 | 206 | c: between\n |
|
207 | 207 | c: pairs 81\n |
|
208 | 208 | c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 |
|
209 | 209 | s: welcome to the server\n |
|
210 | 210 | s: if you find any issues, email someone@somewhere.com\n |
|
211 | 211 | s: 324\n |
|
212 | 212 | s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n |
|
213 | 213 | s: 1\n |
|
214 | 214 | s: \n |
|
215 | 215 | |
|
216 | 216 | Note that output from the ``hello`` command is terminated by a ``\n``. This is |
|
217 | 217 | part of the response payload and not part of the wire protocol adding a newline |
|
218 | 218 | after responses. In other words, the length of the response contains the |
|
219 | 219 | trailing ``\n``. |
|
220 | 220 | |
|
221 | Clients supporting version 2 of the SSH transport send a line beginning | |
|
222 | with ``upgrade`` before the ``hello`` and ``between`` commands. The line | |
|
223 | (which isn't a well-formed command line because it doesn't consist of a | |
|
224 | single command name) serves to both communicate the client's intent to | |
|
225 | switch to transport version 2 (transports are version 1 by default) as | |
|
226 | well as to advertise the client's transport-level capabilities so the | |
|
227 | server may satisfy that request immediately. | |
|
228 | ||
|
229 | The upgrade line has the form: | |
|
230 | ||
|
231 | upgrade <token> <transport capabilities> | |
|
232 | ||
|
233 | That is the literal string ``upgrade`` followed by a space, followed by | |
|
234 | a randomly generated string, followed by a space, followed by a string | |
|
235 | denoting the client's transport capabilities. | |
|
236 | ||
|
237 | The token can be anything. However, a random UUID is recommended. (Use | |
|
238 | of version 4 UUIDs is recommended because version 1 UUIDs can leak the | |
|
239 | client's MAC address.) | |
|
240 | ||
|
241 | The transport capabilities string is a URL/percent encoded string | |
|
242 | containing key-value pairs defining the client's transport-level | |
|
243 | capabilities. The following capabilities are defined: | |
|
244 | ||
|
245 | proto | |
|
246 | A comma-delimited list of transport protocol versions the client | |
|
247 | supports. e.g. ``ssh-v2``. | |
|
248 | ||
|
249 | If the server does not recognize the ``upgrade`` line, it should issue | |
|
250 | an empty response and continue processing the ``hello`` and ``between`` | |
|
251 | commands. Here is an example handshake between a version 2 aware client | |
|
252 | and a non version 2 aware server: | |
|
253 | ||
|
254 | c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2 | |
|
255 | c: hello\n | |
|
256 | c: between\n | |
|
257 | c: pairs 81\n | |
|
258 | c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 | |
|
259 | s: 0\n | |
|
260 | s: 324\n | |
|
261 | s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n | |
|
262 | s: 1\n | |
|
263 | s: \n | |
|
264 | ||
|
265 | (The initial ``0\n`` line from the server indicates an empty response to | |
|
266 | the unknown ``upgrade ..`` command/line.) | |
|
267 | ||
|
268 | If the server recognizes the ``upgrade`` line and is willing to satisfy that | |
|
269 | upgrade request, it replies to with a payload of the following form: | |
|
270 | ||
|
271 | upgraded <token> <transport name>\n | |
|
272 | ||
|
273 | This line is the literal string ``upgraded``, a space, the token that was | |
|
274 | specified by the client in its ``upgrade ...`` request line, a space, and the | |
|
275 | name of the transport protocol that was chosen by the server. The transport | |
|
276 | name MUST match one of the names the client specified in the ``proto`` field | |
|
277 | of its ``upgrade ...`` request line. | |
|
278 | ||
|
279 | If a server issues an ``upgraded`` response, it MUST also read and ignore | |
|
280 | the lines associated with the ``hello`` and ``between`` command requests | |
|
281 | that were issued by the server. It is assumed that the negotiated transport | |
|
282 | will respond with equivalent requested information following the transport | |
|
283 | handshake. | |
|
284 | ||
|
285 | All data following the ``\n`` terminating the ``upgraded`` line is the | |
|
286 | domain of the negotiated transport. It is common for the data immediately | |
|
287 | following to contain additional metadata about the state of the transport and | |
|
288 | the server. However, this isn't strictly speaking part of the transport | |
|
289 | handshake and isn't covered by this section. | |
|
290 | ||
|
291 | Here is an example handshake between a version 2 aware client and a version | |
|
292 | 2 aware server: | |
|
293 | ||
|
294 | c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2 | |
|
295 | c: hello\n | |
|
296 | c: between\n | |
|
297 | c: pairs 81\n | |
|
298 | c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 | |
|
299 | s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n | |
|
300 | s: <additional transport specific data> | |
|
301 | ||
|
302 | The client-issued token that is echoed in the response provides a more | |
|
303 | resilient mechanism for differentiating *banner* output from Mercurial | |
|
304 | output. In version 1, properly formatted banner output could get confused | |
|
305 | for Mercurial server output. By submitting a randomly generated token | |
|
306 | that is then present in the response, the client can look for that token | |
|
307 | in response lines and have reasonable certainty that the line did not | |
|
308 | originate from a *banner* message. | |
|
309 | ||
|
221 | 310 | SSH Version 1 Transport |
|
222 | 311 | ----------------------- |
|
223 | 312 | |
|
224 | 313 | The SSH transport (version 1) is a custom text-based protocol suitable for |
|
225 | 314 | use over any bi-directional stream transport. It is most commonly used with |
|
226 | 315 | SSH. |
|
227 | 316 | |
|
228 | 317 | A SSH transport server can be started with ``hg serve --stdio``. The stdin, |
|
229 | 318 | stderr, and stdout file descriptors of the started process are used to exchange |
|
230 | 319 | data. When Mercurial connects to a remote server over SSH, it actually starts |
|
231 | 320 | a ``hg serve --stdio`` process on the remote server. |
|
232 | 321 | |
|
233 | 322 | Commands are issued by sending the command name followed by a trailing newline |
|
234 | 323 | ``\n`` to the server. e.g. ``capabilities\n``. |
|
235 | 324 | |
|
236 | 325 | Command arguments are sent in the following format:: |
|
237 | 326 | |
|
238 | 327 | <argument> <length>\n<value> |
|
239 | 328 | |
|
240 | 329 | That is, the argument string name followed by a space followed by the |
|
241 | 330 | integer length of the value (expressed as a string) followed by a newline |
|
242 | 331 | (``\n``) followed by the raw argument value. |
|
243 | 332 | |
|
244 | 333 | Dictionary arguments are encoded differently:: |
|
245 | 334 | |
|
246 | 335 | <argument> <# elements>\n |
|
247 | 336 | <key1> <length1>\n<value1> |
|
248 | 337 | <key2> <length2>\n<value2> |
|
249 | 338 | ... |
|
250 | 339 | |
|
251 | 340 | Non-argument data is sent immediately after the final argument value. It is |
|
252 | 341 | encoded in chunks:: |
|
253 | 342 | |
|
254 | 343 | <length>\n<data> |
|
255 | 344 | |
|
256 | 345 | Each command declares a list of supported arguments and their types. If a |
|
257 | 346 | client sends an unknown argument to the server, the server should abort |
|
258 | 347 | immediately. The special argument ``*`` in a command's definition indicates |
|
259 | 348 | that all argument names are allowed. |
|
260 | 349 | |
|
261 | 350 | The definition of supported arguments and types is initially made when a |
|
262 | 351 | new command is implemented. The client and server must initially independently |
|
263 | 352 | agree on the arguments and their types. This initial set of arguments can be |
|
264 | 353 | supplemented through the presence of *capabilities* advertised by the server. |
|
265 | 354 | |
|
266 | 355 | Each command has a defined expected response type. |
|
267 | 356 | |
|
268 | 357 | A ``string`` response type is a length framed value. The response consists of |
|
269 | 358 | the string encoded integer length of a value followed by a newline (``\n``) |
|
270 | 359 | followed by the value. Empty values are allowed (and are represented as |
|
271 | 360 | ``0\n``). |
|
272 | 361 | |
|
273 | 362 | A ``stream`` response type consists of raw bytes of data. There is no framing. |
|
274 | 363 | |
|
275 | 364 | A generic error response type is also supported. It consists of a an error |
|
276 | 365 | message written to ``stderr`` followed by ``\n-\n``. In addition, ``\n`` is |
|
277 | 366 | written to ``stdout``. |
|
278 | 367 | |
|
279 | 368 | If the server receives an unknown command, it will send an empty ``string`` |
|
280 | 369 | response. |
|
281 | 370 | |
|
282 | 371 | The server terminates if it receives an empty command (a ``\n`` character). |
|
283 | 372 | |
|
373 | SSH Version 2 Transport | |
|
374 | ----------------------- | |
|
375 | ||
|
376 | **Experimental** | |
|
377 | ||
|
378 | Version 2 of the SSH transport behaves identically to version 1 of the SSH | |
|
379 | transport with the exception of handshake semantics. See above for how | |
|
380 | version 2 of the SSH transport is negotiated. | |
|
381 | ||
|
382 | Immediately following the ``upgraded`` line signaling a switch to version | |
|
383 | 2 of the SSH protocol, the server automatically sends additional details | |
|
384 | about the capabilities of the remote server. This has the form: | |
|
385 | ||
|
386 | <integer length of value>\n | |
|
387 | capabilities: ...\n | |
|
388 | ||
|
389 | e.g. | |
|
390 | ||
|
391 | s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n | |
|
392 | s: 240\n | |
|
393 | s: capabilities: known getbundle batch ...\n | |
|
394 | ||
|
395 | Following capabilities advertisement, the peers communicate using version | |
|
396 | 1 of the SSH transport. | |
|
397 | ||
|
284 | 398 | Capabilities |
|
285 | 399 | ============ |
|
286 | 400 | |
|
287 | 401 | Servers advertise supported wire protocol features. This allows clients to |
|
288 | 402 | probe for server features before blindly calling a command or passing a |
|
289 | 403 | specific argument. |
|
290 | 404 | |
|
291 | 405 | The server's features are exposed via a *capabilities* string. This is a |
|
292 | 406 | space-delimited string of tokens/features. Some features are single words |
|
293 | 407 | like ``lookup`` or ``batch``. Others are complicated key-value pairs |
|
294 | 408 | advertising sub-features. e.g. ``httpheader=2048``. When complex, non-word |
|
295 | 409 | values are used, each feature name can define its own encoding of sub-values. |
|
296 | 410 | Comma-delimited and ``x-www-form-urlencoded`` values are common. |
|
297 | 411 | |
|
298 | 412 | The following document capabilities defined by the canonical Mercurial server |
|
299 | 413 | implementation. |
|
300 | 414 | |
|
301 | 415 | batch |
|
302 | 416 | ----- |
|
303 | 417 | |
|
304 | 418 | Whether the server supports the ``batch`` command. |
|
305 | 419 | |
|
306 | 420 | This capability/command was introduced in Mercurial 1.9 (released July 2011). |
|
307 | 421 | |
|
308 | 422 | branchmap |
|
309 | 423 | --------- |
|
310 | 424 | |
|
311 | 425 | Whether the server supports the ``branchmap`` command. |
|
312 | 426 | |
|
313 | 427 | This capability/command was introduced in Mercurial 1.3 (released July 2009). |
|
314 | 428 | |
|
315 | 429 | bundle2-exp |
|
316 | 430 | ----------- |
|
317 | 431 | |
|
318 | 432 | Precursor to ``bundle2`` capability that was used before bundle2 was a |
|
319 | 433 | stable feature. |
|
320 | 434 | |
|
321 | 435 | This capability was introduced in Mercurial 3.0 behind an experimental |
|
322 | 436 | flag. This capability should not be observed in the wild. |
|
323 | 437 | |
|
324 | 438 | bundle2 |
|
325 | 439 | ------- |
|
326 | 440 | |
|
327 | 441 | Indicates whether the server supports the ``bundle2`` data exchange format. |
|
328 | 442 | |
|
329 | 443 | The value of the capability is a URL quoted, newline (``\n``) delimited |
|
330 | 444 | list of keys or key-value pairs. |
|
331 | 445 | |
|
332 | 446 | A key is simply a URL encoded string. |
|
333 | 447 | |
|
334 | 448 | A key-value pair is a URL encoded key separated from a URL encoded value by |
|
335 | 449 | an ``=``. If the value is a list, elements are delimited by a ``,`` after |
|
336 | 450 | URL encoding. |
|
337 | 451 | |
|
338 | 452 | For example, say we have the values:: |
|
339 | 453 | |
|
340 | 454 | {'HG20': [], 'changegroup': ['01', '02'], 'digests': ['sha1', 'sha512']} |
|
341 | 455 | |
|
342 | 456 | We would first construct a string:: |
|
343 | 457 | |
|
344 | 458 | HG20\nchangegroup=01,02\ndigests=sha1,sha512 |
|
345 | 459 | |
|
346 | 460 | We would then URL quote this string:: |
|
347 | 461 | |
|
348 | 462 | HG20%0Achangegroup%3D01%2C02%0Adigests%3Dsha1%2Csha512 |
|
349 | 463 | |
|
350 | 464 | This capability was introduced in Mercurial 3.4 (released May 2015). |
|
351 | 465 | |
|
352 | 466 | changegroupsubset |
|
353 | 467 | ----------------- |
|
354 | 468 | |
|
355 | 469 | Whether the server supports the ``changegroupsubset`` command. |
|
356 | 470 | |
|
357 | 471 | This capability was introduced in Mercurial 0.9.2 (released December |
|
358 | 472 | 2006). |
|
359 | 473 | |
|
360 | 474 | This capability was introduced at the same time as the ``lookup`` |
|
361 | 475 | capability/command. |
|
362 | 476 | |
|
363 | 477 | compression |
|
364 | 478 | ----------- |
|
365 | 479 | |
|
366 | 480 | Declares support for negotiating compression formats. |
|
367 | 481 | |
|
368 | 482 | Presence of this capability indicates the server supports dynamic selection |
|
369 | 483 | of compression formats based on the client request. |
|
370 | 484 | |
|
371 | 485 | Servers advertising this capability are required to support the |
|
372 | 486 | ``application/mercurial-0.2`` media type in response to commands returning |
|
373 | 487 | streams. Servers may support this media type on any command. |
|
374 | 488 | |
|
375 | 489 | The value of the capability is a comma-delimited list of strings declaring |
|
376 | 490 | supported compression formats. The order of the compression formats is in |
|
377 | 491 | server-preferred order, most preferred first. |
|
378 | 492 | |
|
379 | 493 | The identifiers used by the official Mercurial distribution are: |
|
380 | 494 | |
|
381 | 495 | bzip2 |
|
382 | 496 | bzip2 |
|
383 | 497 | none |
|
384 | 498 | uncompressed / raw data |
|
385 | 499 | zlib |
|
386 | 500 | zlib (no gzip header) |
|
387 | 501 | zstd |
|
388 | 502 | zstd |
|
389 | 503 | |
|
390 | 504 | This capability was introduced in Mercurial 4.1 (released February 2017). |
|
391 | 505 | |
|
392 | 506 | getbundle |
|
393 | 507 | --------- |
|
394 | 508 | |
|
395 | 509 | Whether the server supports the ``getbundle`` command. |
|
396 | 510 | |
|
397 | 511 | This capability was introduced in Mercurial 1.9 (released July 2011). |
|
398 | 512 | |
|
399 | 513 | httpheader |
|
400 | 514 | ---------- |
|
401 | 515 | |
|
402 | 516 | Whether the server supports receiving command arguments via HTTP request |
|
403 | 517 | headers. |
|
404 | 518 | |
|
405 | 519 | The value of the capability is an integer describing the max header |
|
406 | 520 | length that clients should send. Clients should ignore any content after a |
|
407 | 521 | comma in the value, as this is reserved for future use. |
|
408 | 522 | |
|
409 | 523 | This capability was introduced in Mercurial 1.9 (released July 2011). |
|
410 | 524 | |
|
411 | 525 | httpmediatype |
|
412 | 526 | ------------- |
|
413 | 527 | |
|
414 | 528 | Indicates which HTTP media types (``Content-Type`` header) the server is |
|
415 | 529 | capable of receiving and sending. |
|
416 | 530 | |
|
417 | 531 | The value of the capability is a comma-delimited list of strings identifying |
|
418 | 532 | support for media type and transmission direction. The following strings may |
|
419 | 533 | be present: |
|
420 | 534 | |
|
421 | 535 | 0.1rx |
|
422 | 536 | Indicates server support for receiving ``application/mercurial-0.1`` media |
|
423 | 537 | types. |
|
424 | 538 | |
|
425 | 539 | 0.1tx |
|
426 | 540 | Indicates server support for sending ``application/mercurial-0.1`` media |
|
427 | 541 | types. |
|
428 | 542 | |
|
429 | 543 | 0.2rx |
|
430 | 544 | Indicates server support for receiving ``application/mercurial-0.2`` media |
|
431 | 545 | types. |
|
432 | 546 | |
|
433 | 547 | 0.2tx |
|
434 | 548 | Indicates server support for sending ``application/mercurial-0.2`` media |
|
435 | 549 | types. |
|
436 | 550 | |
|
437 | 551 | minrx=X |
|
438 | 552 | Minimum media type version the server is capable of receiving. Value is a |
|
439 | 553 | string like ``0.2``. |
|
440 | 554 | |
|
441 | 555 | This capability can be used by servers to limit connections from legacy |
|
442 | 556 | clients not using the latest supported media type. However, only clients |
|
443 | 557 | with knowledge of this capability will know to consult this value. This |
|
444 | 558 | capability is present so the client may issue a more user-friendly error |
|
445 | 559 | when the server has locked out a legacy client. |
|
446 | 560 | |
|
447 | 561 | mintx=X |
|
448 | 562 | Minimum media type version the server is capable of sending. Value is a |
|
449 | 563 | string like ``0.1``. |
|
450 | 564 | |
|
451 | 565 | Servers advertising support for the ``application/mercurial-0.2`` media type |
|
452 | 566 | should also advertise the ``compression`` capability. |
|
453 | 567 | |
|
454 | 568 | This capability was introduced in Mercurial 4.1 (released February 2017). |
|
455 | 569 | |
|
456 | 570 | httppostargs |
|
457 | 571 | ------------ |
|
458 | 572 | |
|
459 | 573 | **Experimental** |
|
460 | 574 | |
|
461 | 575 | Indicates that the server supports and prefers clients send command arguments |
|
462 | 576 | via a HTTP POST request as part of the request body. |
|
463 | 577 | |
|
464 | 578 | This capability was introduced in Mercurial 3.8 (released May 2016). |
|
465 | 579 | |
|
466 | 580 | known |
|
467 | 581 | ----- |
|
468 | 582 | |
|
469 | 583 | Whether the server supports the ``known`` command. |
|
470 | 584 | |
|
471 | 585 | This capability/command was introduced in Mercurial 1.9 (released July 2011). |
|
472 | 586 | |
|
473 | 587 | lookup |
|
474 | 588 | ------ |
|
475 | 589 | |
|
476 | 590 | Whether the server supports the ``lookup`` command. |
|
477 | 591 | |
|
478 | 592 | This capability was introduced in Mercurial 0.9.2 (released December |
|
479 | 593 | 2006). |
|
480 | 594 | |
|
481 | 595 | This capability was introduced at the same time as the ``changegroupsubset`` |
|
482 | 596 | capability/command. |
|
483 | 597 | |
|
484 | 598 | pushkey |
|
485 | 599 | ------- |
|
486 | 600 | |
|
487 | 601 | Whether the server supports the ``pushkey`` and ``listkeys`` commands. |
|
488 | 602 | |
|
489 | 603 | This capability was introduced in Mercurial 1.6 (released July 2010). |
|
490 | 604 | |
|
491 | 605 | standardbundle |
|
492 | 606 | -------------- |
|
493 | 607 | |
|
494 | 608 | **Unsupported** |
|
495 | 609 | |
|
496 | 610 | This capability was introduced during the Mercurial 0.9.2 development cycle in |
|
497 | 611 | 2006. It was never present in a release, as it was replaced by the ``unbundle`` |
|
498 | 612 | capability. This capability should not be encountered in the wild. |
|
499 | 613 | |
|
500 | 614 | stream-preferred |
|
501 | 615 | ---------------- |
|
502 | 616 | |
|
503 | 617 | If present the server prefers that clients clone using the streaming clone |
|
504 | 618 | protocol (``hg clone --stream``) rather than the standard |
|
505 | 619 | changegroup/bundle based protocol. |
|
506 | 620 | |
|
507 | 621 | This capability was introduced in Mercurial 2.2 (released May 2012). |
|
508 | 622 | |
|
509 | 623 | streamreqs |
|
510 | 624 | ---------- |
|
511 | 625 | |
|
512 | 626 | Indicates whether the server supports *streaming clones* and the *requirements* |
|
513 | 627 | that clients must support to receive it. |
|
514 | 628 | |
|
515 | 629 | If present, the server supports the ``stream_out`` command, which transmits |
|
516 | 630 | raw revlogs from the repository instead of changegroups. This provides a faster |
|
517 | 631 | cloning mechanism at the expense of more bandwidth used. |
|
518 | 632 | |
|
519 | 633 | The value of this capability is a comma-delimited list of repo format |
|
520 | 634 | *requirements*. These are requirements that impact the reading of data in |
|
521 | 635 | the ``.hg/store`` directory. An example value is |
|
522 | 636 | ``streamreqs=generaldelta,revlogv1`` indicating the server repo requires |
|
523 | 637 | the ``revlogv1`` and ``generaldelta`` requirements. |
|
524 | 638 | |
|
525 | 639 | If the only format requirement is ``revlogv1``, the server may expose the |
|
526 | 640 | ``stream`` capability instead of the ``streamreqs`` capability. |
|
527 | 641 | |
|
528 | 642 | This capability was introduced in Mercurial 1.7 (released November 2010). |
|
529 | 643 | |
|
530 | 644 | stream |
|
531 | 645 | ------ |
|
532 | 646 | |
|
533 | 647 | Whether the server supports *streaming clones* from ``revlogv1`` repos. |
|
534 | 648 | |
|
535 | 649 | If present, the server supports the ``stream_out`` command, which transmits |
|
536 | 650 | raw revlogs from the repository instead of changegroups. This provides a faster |
|
537 | 651 | cloning mechanism at the expense of more bandwidth used. |
|
538 | 652 | |
|
539 | 653 | This capability was introduced in Mercurial 0.9.1 (released July 2006). |
|
540 | 654 | |
|
541 | 655 | When initially introduced, the value of the capability was the numeric |
|
542 | 656 | revlog revision. e.g. ``stream=1``. This indicates the changegroup is using |
|
543 | 657 | ``revlogv1``. This simple integer value wasn't powerful enough, so the |
|
544 | 658 | ``streamreqs`` capability was invented to handle cases where the repo |
|
545 | 659 | requirements have more than just ``revlogv1``. Newer servers omit the |
|
546 | 660 | ``=1`` since it was the only value supported and the value of ``1`` can |
|
547 | 661 | be implied by clients. |
|
548 | 662 | |
|
549 | 663 | unbundlehash |
|
550 | 664 | ------------ |
|
551 | 665 | |
|
552 | 666 | Whether the ``unbundle`` commands supports receiving a hash of all the |
|
553 | 667 | heads instead of a list. |
|
554 | 668 | |
|
555 | 669 | For more, see the documentation for the ``unbundle`` command. |
|
556 | 670 | |
|
557 | 671 | This capability was introduced in Mercurial 1.9 (released July 2011). |
|
558 | 672 | |
|
559 | 673 | unbundle |
|
560 | 674 | -------- |
|
561 | 675 | |
|
562 | 676 | Whether the server supports pushing via the ``unbundle`` command. |
|
563 | 677 | |
|
564 | 678 | This capability/command has been present since Mercurial 0.9.1 (released |
|
565 | 679 | July 2006). |
|
566 | 680 | |
|
567 | 681 | Mercurial 0.9.2 (released December 2006) added values to the capability |
|
568 | 682 | indicating which bundle types the server supports receiving. This value is a |
|
569 | 683 | comma-delimited list. e.g. ``HG10GZ,HG10BZ,HG10UN``. The order of values |
|
570 | 684 | reflects the priority/preference of that type, where the first value is the |
|
571 | 685 | most preferred type. |
|
572 | 686 | |
|
573 | 687 | Content Negotiation |
|
574 | 688 | =================== |
|
575 | 689 | |
|
576 | 690 | The wire protocol has some mechanisms to help peers determine what content |
|
577 | 691 | types and encoding the other side will accept. Historically, these mechanisms |
|
578 | 692 | have been built into commands themselves because most commands only send a |
|
579 | 693 | well-defined response type and only certain commands needed to support |
|
580 | 694 | functionality like compression. |
|
581 | 695 | |
|
582 | 696 | Currently, only the HTTP version 1 transport supports content negotiation |
|
583 | 697 | at the protocol layer. |
|
584 | 698 | |
|
585 | 699 | HTTP requests advertise supported response formats via the ``X-HgProto-<N>`` |
|
586 | 700 | request header, where ``<N>`` is an integer starting at 1 allowing the logical |
|
587 | 701 | value to span multiple headers. This value consists of a list of |
|
588 | 702 | space-delimited parameters. Each parameter denotes a feature or capability. |
|
589 | 703 | |
|
590 | 704 | The following parameters are defined: |
|
591 | 705 | |
|
592 | 706 | 0.1 |
|
593 | 707 | Indicates the client supports receiving ``application/mercurial-0.1`` |
|
594 | 708 | responses. |
|
595 | 709 | |
|
596 | 710 | 0.2 |
|
597 | 711 | Indicates the client supports receiving ``application/mercurial-0.2`` |
|
598 | 712 | responses. |
|
599 | 713 | |
|
600 | 714 | comp |
|
601 | 715 | Indicates compression formats the client can decode. Value is a list of |
|
602 | 716 | comma delimited strings identifying compression formats ordered from |
|
603 | 717 | most preferential to least preferential. e.g. ``comp=zstd,zlib,none``. |
|
604 | 718 | |
|
605 | 719 | This parameter does not have an effect if only the ``0.1`` parameter |
|
606 | 720 | is defined, as support for ``application/mercurial-0.2`` or greater is |
|
607 | 721 | required to use arbitrary compression formats. |
|
608 | 722 | |
|
609 | 723 | If this parameter is not advertised, the server interprets this as |
|
610 | 724 | equivalent to ``zlib,none``. |
|
611 | 725 | |
|
612 | 726 | Clients may choose to only send this header if the ``httpmediatype`` |
|
613 | 727 | server capability is present, as currently all server-side features |
|
614 | 728 | consulting this header require the client to opt in to new protocol features |
|
615 | 729 | advertised via the ``httpmediatype`` capability. |
|
616 | 730 | |
|
617 | 731 | A server that doesn't receive an ``X-HgProto-<N>`` header should infer a |
|
618 | 732 | value of ``0.1``. This is compatible with legacy clients. |
|
619 | 733 | |
|
620 | 734 | A server receiving a request indicating support for multiple media type |
|
621 | 735 | versions may respond with any of the supported media types. Not all servers |
|
622 | 736 | may support all media types on all commands. |
|
623 | 737 | |
|
624 | 738 | Commands |
|
625 | 739 | ======== |
|
626 | 740 | |
|
627 | 741 | This section contains a list of all wire protocol commands implemented by |
|
628 | 742 | the canonical Mercurial server. |
|
629 | 743 | |
|
630 | 744 | batch |
|
631 | 745 | ----- |
|
632 | 746 | |
|
633 | 747 | Issue multiple commands while sending a single command request. The purpose |
|
634 | 748 | of this command is to allow a client to issue multiple commands while avoiding |
|
635 | 749 | multiple round trips to the server therefore enabling commands to complete |
|
636 | 750 | quicker. |
|
637 | 751 | |
|
638 | 752 | The command accepts a ``cmds`` argument that contains a list of commands to |
|
639 | 753 | execute. |
|
640 | 754 | |
|
641 | 755 | The value of ``cmds`` is a ``;`` delimited list of strings. Each string has the |
|
642 | 756 | form ``<command> <arguments>``. That is, the command name followed by a space |
|
643 | 757 | followed by an argument string. |
|
644 | 758 | |
|
645 | 759 | The argument string is a ``,`` delimited list of ``<key>=<value>`` values |
|
646 | 760 | corresponding to command arguments. Both the argument name and value are |
|
647 | 761 | escaped using a special substitution map:: |
|
648 | 762 | |
|
649 | 763 | : -> :c |
|
650 | 764 | , -> :o |
|
651 | 765 | ; -> :s |
|
652 | 766 | = -> :e |
|
653 | 767 | |
|
654 | 768 | The response type for this command is ``string``. The value contains a |
|
655 | 769 | ``;`` delimited list of responses for each requested command. Each value |
|
656 | 770 | in this list is escaped using the same substitution map used for arguments. |
|
657 | 771 | |
|
658 | 772 | If an error occurs, the generic error response may be sent. |
|
659 | 773 | |
|
660 | 774 | between |
|
661 | 775 | ------- |
|
662 | 776 | |
|
663 | 777 | (Legacy command used for discovery in old clients) |
|
664 | 778 | |
|
665 | 779 | Obtain nodes between pairs of nodes. |
|
666 | 780 | |
|
667 | 781 | The ``pairs`` arguments contains a space-delimited list of ``-`` delimited |
|
668 | 782 | hex node pairs. e.g.:: |
|
669 | 783 | |
|
670 | 784 | a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896-6dc58916e7c070f678682bfe404d2e2d68291a18 |
|
671 | 785 | |
|
672 | 786 | Return type is a ``string``. Value consists of lines corresponding to each |
|
673 | 787 | requested range. Each line contains a space-delimited list of hex nodes. |
|
674 | 788 | A newline ``\n`` terminates each line, including the last one. |
|
675 | 789 | |
|
676 | 790 | branchmap |
|
677 | 791 | --------- |
|
678 | 792 | |
|
679 | 793 | Obtain heads in named branches. |
|
680 | 794 | |
|
681 | 795 | Accepts no arguments. Return type is a ``string``. |
|
682 | 796 | |
|
683 | 797 | Return value contains lines with URL encoded branch names followed by a space |
|
684 | 798 | followed by a space-delimited list of hex nodes of heads on that branch. |
|
685 | 799 | e.g.:: |
|
686 | 800 | |
|
687 | 801 | default a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896 6dc58916e7c070f678682bfe404d2e2d68291a18 |
|
688 | 802 | stable baae3bf31522f41dd5e6d7377d0edd8d1cf3fccc |
|
689 | 803 | |
|
690 | 804 | There is no trailing newline. |
|
691 | 805 | |
|
692 | 806 | branches |
|
693 | 807 | -------- |
|
694 | 808 | |
|
695 | 809 | (Legacy command used for discovery in old clients. Clients with ``getbundle`` |
|
696 | 810 | use the ``known`` and ``heads`` commands instead.) |
|
697 | 811 | |
|
698 | 812 | Obtain ancestor changesets of specific nodes back to a branch point. |
|
699 | 813 | |
|
700 | 814 | Despite the name, this command has nothing to do with Mercurial named branches. |
|
701 | 815 | Instead, it is related to DAG branches. |
|
702 | 816 | |
|
703 | 817 | The command accepts a ``nodes`` argument, which is a string of space-delimited |
|
704 | 818 | hex nodes. |
|
705 | 819 | |
|
706 | 820 | For each node requested, the server will find the first ancestor node that is |
|
707 | 821 | a DAG root or is a merge. |
|
708 | 822 | |
|
709 | 823 | Return type is a ``string``. Return value contains lines with result data for |
|
710 | 824 | each requested node. Each line contains space-delimited nodes followed by a |
|
711 | 825 | newline (``\n``). The 4 nodes reported on each line correspond to the requested |
|
712 | 826 | node, the ancestor node found, and its 2 parent nodes (which may be the null |
|
713 | 827 | node). |
|
714 | 828 | |
|
715 | 829 | capabilities |
|
716 | 830 | ------------ |
|
717 | 831 | |
|
718 | 832 | Obtain the capabilities string for the repo. |
|
719 | 833 | |
|
720 | 834 | Unlike the ``hello`` command, the capabilities string is not prefixed. |
|
721 | 835 | There is no trailing newline. |
|
722 | 836 | |
|
723 | 837 | This command does not accept any arguments. Return type is a ``string``. |
|
724 | 838 | |
|
725 | 839 | This command was introduced in Mercurial 0.9.1 (released July 2006). |
|
726 | 840 | |
|
727 | 841 | changegroup |
|
728 | 842 | ----------- |
|
729 | 843 | |
|
730 | 844 | (Legacy command: use ``getbundle`` instead) |
|
731 | 845 | |
|
732 | 846 | Obtain a changegroup version 1 with data for changesets that are |
|
733 | 847 | descendants of client-specified changesets. |
|
734 | 848 | |
|
735 | 849 | The ``roots`` arguments contains a list of space-delimited hex nodes. |
|
736 | 850 | |
|
737 | 851 | The server responds with a changegroup version 1 containing all |
|
738 | 852 | changesets between the requested root/base nodes and the repo's head nodes |
|
739 | 853 | at the time of the request. |
|
740 | 854 | |
|
741 | 855 | The return type is a ``stream``. |
|
742 | 856 | |
|
743 | 857 | changegroupsubset |
|
744 | 858 | ----------------- |
|
745 | 859 | |
|
746 | 860 | (Legacy command: use ``getbundle`` instead) |
|
747 | 861 | |
|
748 | 862 | Obtain a changegroup version 1 with data for changesetsets between |
|
749 | 863 | client specified base and head nodes. |
|
750 | 864 | |
|
751 | 865 | The ``bases`` argument contains a list of space-delimited hex nodes. |
|
752 | 866 | The ``heads`` argument contains a list of space-delimited hex nodes. |
|
753 | 867 | |
|
754 | 868 | The server responds with a changegroup version 1 containing all |
|
755 | 869 | changesets between the requested base and head nodes at the time of the |
|
756 | 870 | request. |
|
757 | 871 | |
|
758 | 872 | The return type is a ``stream``. |
|
759 | 873 | |
|
760 | 874 | clonebundles |
|
761 | 875 | ------------ |
|
762 | 876 | |
|
763 | 877 | Obtains a manifest of bundle URLs available to seed clones. |
|
764 | 878 | |
|
765 | 879 | Each returned line contains a URL followed by metadata. See the |
|
766 | 880 | documentation in the ``clonebundles`` extension for more. |
|
767 | 881 | |
|
768 | 882 | The return type is a ``string``. |
|
769 | 883 | |
|
770 | 884 | getbundle |
|
771 | 885 | --------- |
|
772 | 886 | |
|
773 | 887 | Obtain a bundle containing repository data. |
|
774 | 888 | |
|
775 | 889 | This command accepts the following arguments: |
|
776 | 890 | |
|
777 | 891 | heads |
|
778 | 892 | List of space-delimited hex nodes of heads to retrieve. |
|
779 | 893 | common |
|
780 | 894 | List of space-delimited hex nodes that the client has in common with the |
|
781 | 895 | server. |
|
782 | 896 | obsmarkers |
|
783 | 897 | Boolean indicating whether to include obsolescence markers as part |
|
784 | 898 | of the response. Only works with bundle2. |
|
785 | 899 | bundlecaps |
|
786 | 900 | Comma-delimited set of strings defining client bundle capabilities. |
|
787 | 901 | listkeys |
|
788 | 902 | Comma-delimited list of strings of ``pushkey`` namespaces. For each |
|
789 | 903 | namespace listed, a bundle2 part will be included with the content of |
|
790 | 904 | that namespace. |
|
791 | 905 | cg |
|
792 | 906 | Boolean indicating whether changegroup data is requested. |
|
793 | 907 | cbattempted |
|
794 | 908 | Boolean indicating whether the client attempted to use the *clone bundles* |
|
795 | 909 | feature before performing this request. |
|
796 | 910 | bookmarks |
|
797 | 911 | Boolean indicating whether bookmark data is requested. |
|
798 | 912 | phases |
|
799 | 913 | Boolean indicating whether phases data is requested. |
|
800 | 914 | |
|
801 | 915 | The return type on success is a ``stream`` where the value is bundle. |
|
802 | 916 | On the HTTP version 1 transport, the response is zlib compressed. |
|
803 | 917 | |
|
804 | 918 | If an error occurs, a generic error response can be sent. |
|
805 | 919 | |
|
806 | 920 | Unless the client sends a false value for the ``cg`` argument, the returned |
|
807 | 921 | bundle contains a changegroup with the nodes between the specified ``common`` |
|
808 | 922 | and ``heads`` nodes. Depending on the command arguments, the type and content |
|
809 | 923 | of the returned bundle can vary significantly. |
|
810 | 924 | |
|
811 | 925 | The default behavior is for the server to send a raw changegroup version |
|
812 | 926 | ``01`` response. |
|
813 | 927 | |
|
814 | 928 | If the ``bundlecaps`` provided by the client contain a value beginning |
|
815 | 929 | with ``HG2``, a bundle2 will be returned. The bundle2 data may contain |
|
816 | 930 | additional repository data, such as ``pushkey`` namespace values. |
|
817 | 931 | |
|
818 | 932 | heads |
|
819 | 933 | ----- |
|
820 | 934 | |
|
821 | 935 | Returns a list of space-delimited hex nodes of repository heads followed |
|
822 | 936 | by a newline. e.g. |
|
823 | 937 | ``a9eeb3adc7ddb5006c088e9eda61791c777cbf7c 31f91a3da534dc849f0d6bfc00a395a97cf218a1\n`` |
|
824 | 938 | |
|
825 | 939 | This command does not accept any arguments. The return type is a ``string``. |
|
826 | 940 | |
|
827 | 941 | hello |
|
828 | 942 | ----- |
|
829 | 943 | |
|
830 | 944 | Returns lines describing interesting things about the server in an RFC-822 |
|
831 | 945 | like format. |
|
832 | 946 | |
|
833 | 947 | Currently, the only line defines the server capabilities. It has the form:: |
|
834 | 948 | |
|
835 | 949 | capabilities: <value> |
|
836 | 950 | |
|
837 | 951 | See above for more about the capabilities string. |
|
838 | 952 | |
|
839 | 953 | SSH clients typically issue this command as soon as a connection is |
|
840 | 954 | established. |
|
841 | 955 | |
|
842 | 956 | This command does not accept any arguments. The return type is a ``string``. |
|
843 | 957 | |
|
844 | 958 | This command was introduced in Mercurial 0.9.1 (released July 2006). |
|
845 | 959 | |
|
846 | 960 | listkeys |
|
847 | 961 | -------- |
|
848 | 962 | |
|
849 | 963 | List values in a specified ``pushkey`` namespace. |
|
850 | 964 | |
|
851 | 965 | The ``namespace`` argument defines the pushkey namespace to operate on. |
|
852 | 966 | |
|
853 | 967 | The return type is a ``string``. The value is an encoded dictionary of keys. |
|
854 | 968 | |
|
855 | 969 | Key-value pairs are delimited by newlines (``\n``). Within each line, keys and |
|
856 | 970 | values are separated by a tab (``\t``). Keys and values are both strings. |
|
857 | 971 | |
|
858 | 972 | lookup |
|
859 | 973 | ------ |
|
860 | 974 | |
|
861 | 975 | Try to resolve a value to a known repository revision. |
|
862 | 976 | |
|
863 | 977 | The ``key`` argument is converted from bytes to an |
|
864 | 978 | ``encoding.localstr`` instance then passed into |
|
865 | 979 | ``localrepository.__getitem__`` in an attempt to resolve it. |
|
866 | 980 | |
|
867 | 981 | The return type is a ``string``. |
|
868 | 982 | |
|
869 | 983 | Upon successful resolution, returns ``1 <hex node>\n``. On failure, |
|
870 | 984 | returns ``0 <error string>\n``. e.g.:: |
|
871 | 985 | |
|
872 | 986 | 1 273ce12ad8f155317b2c078ec75a4eba507f1fba\n |
|
873 | 987 | |
|
874 | 988 | 0 unknown revision 'foo'\n |
|
875 | 989 | |
|
876 | 990 | known |
|
877 | 991 | ----- |
|
878 | 992 | |
|
879 | 993 | Determine whether multiple nodes are known. |
|
880 | 994 | |
|
881 | 995 | The ``nodes`` argument is a list of space-delimited hex nodes to check |
|
882 | 996 | for existence. |
|
883 | 997 | |
|
884 | 998 | The return type is ``string``. |
|
885 | 999 | |
|
886 | 1000 | Returns a string consisting of ``0``s and ``1``s indicating whether nodes |
|
887 | 1001 | are known. If the Nth node specified in the ``nodes`` argument is known, |
|
888 | 1002 | a ``1`` will be returned at byte offset N. If the node isn't known, ``0`` |
|
889 | 1003 | will be present at byte offset N. |
|
890 | 1004 | |
|
891 | 1005 | There is no trailing newline. |
|
892 | 1006 | |
|
893 | 1007 | pushkey |
|
894 | 1008 | ------- |
|
895 | 1009 | |
|
896 | 1010 | Set a value using the ``pushkey`` protocol. |
|
897 | 1011 | |
|
898 | 1012 | Accepts arguments ``namespace``, ``key``, ``old``, and ``new``, which |
|
899 | 1013 | correspond to the pushkey namespace to operate on, the key within that |
|
900 | 1014 | namespace to change, the old value (which may be empty), and the new value. |
|
901 | 1015 | All arguments are string types. |
|
902 | 1016 | |
|
903 | 1017 | The return type is a ``string``. The value depends on the transport protocol. |
|
904 | 1018 | |
|
905 | 1019 | The SSH version 1 transport sends a string encoded integer followed by a |
|
906 | 1020 | newline (``\n``) which indicates operation result. The server may send |
|
907 | 1021 | additional output on the ``stderr`` stream that should be displayed to the |
|
908 | 1022 | user. |
|
909 | 1023 | |
|
910 | 1024 | The HTTP version 1 transport sends a string encoded integer followed by a |
|
911 | 1025 | newline followed by additional server output that should be displayed to |
|
912 | 1026 | the user. This may include output from hooks, etc. |
|
913 | 1027 | |
|
914 | 1028 | The integer result varies by namespace. ``0`` means an error has occurred |
|
915 | 1029 | and there should be additional output to display to the user. |
|
916 | 1030 | |
|
917 | 1031 | stream_out |
|
918 | 1032 | ---------- |
|
919 | 1033 | |
|
920 | 1034 | Obtain *streaming clone* data. |
|
921 | 1035 | |
|
922 | 1036 | The return type is either a ``string`` or a ``stream``, depending on |
|
923 | 1037 | whether the request was fulfilled properly. |
|
924 | 1038 | |
|
925 | 1039 | A return value of ``1\n`` indicates the server is not configured to serve |
|
926 | 1040 | this data. If this is seen by the client, they may not have verified the |
|
927 | 1041 | ``stream`` capability is set before making the request. |
|
928 | 1042 | |
|
929 | 1043 | A return value of ``2\n`` indicates the server was unable to lock the |
|
930 | 1044 | repository to generate data. |
|
931 | 1045 | |
|
932 | 1046 | All other responses are a ``stream`` of bytes. The first line of this data |
|
933 | 1047 | contains 2 space-delimited integers corresponding to the path count and |
|
934 | 1048 | payload size, respectively:: |
|
935 | 1049 | |
|
936 | 1050 | <path count> <payload size>\n |
|
937 | 1051 | |
|
938 | 1052 | The ``<payload size>`` is the total size of path data: it does not include |
|
939 | 1053 | the size of the per-path header lines. |
|
940 | 1054 | |
|
941 | 1055 | Following that header are ``<path count>`` entries. Each entry consists of a |
|
942 | 1056 | line with metadata followed by raw revlog data. The line consists of:: |
|
943 | 1057 | |
|
944 | 1058 | <store path>\0<size>\n |
|
945 | 1059 | |
|
946 | 1060 | The ``<store path>`` is the encoded store path of the data that follows. |
|
947 | 1061 | ``<size>`` is the amount of data for this store path/revlog that follows the |
|
948 | 1062 | newline. |
|
949 | 1063 | |
|
950 | 1064 | There is no trailer to indicate end of data. Instead, the client should stop |
|
951 | 1065 | reading after ``<path count>`` entries are consumed. |
|
952 | 1066 | |
|
953 | 1067 | unbundle |
|
954 | 1068 | -------- |
|
955 | 1069 | |
|
956 | 1070 | Send a bundle containing data (usually changegroup data) to the server. |
|
957 | 1071 | |
|
958 | 1072 | Accepts the argument ``heads``, which is a space-delimited list of hex nodes |
|
959 | 1073 | corresponding to server repository heads observed by the client. This is used |
|
960 | 1074 | to detect race conditions and abort push operations before a server performs |
|
961 | 1075 | too much work or a client transfers too much data. |
|
962 | 1076 | |
|
963 | 1077 | The request payload consists of a bundle to be applied to the repository, |
|
964 | 1078 | similarly to as if :hg:`unbundle` were called. |
|
965 | 1079 | |
|
966 | 1080 | In most scenarios, a special ``push response`` type is returned. This type |
|
967 | 1081 | contains an integer describing the change in heads as a result of the |
|
968 | 1082 | operation. A value of ``0`` indicates nothing changed. ``1`` means the number |
|
969 | 1083 | of heads remained the same. Values ``2`` and larger indicate the number of |
|
970 | 1084 | added heads minus 1. e.g. ``3`` means 2 heads were added. Negative values |
|
971 | 1085 | indicate the number of fewer heads, also off by 1. e.g. ``-2`` means there |
|
972 | 1086 | is 1 fewer head. |
|
973 | 1087 | |
|
974 | 1088 | The encoding of the ``push response`` type varies by transport. |
|
975 | 1089 | |
|
976 | 1090 | For the SSH version 1 transport, this type is composed of 2 ``string`` |
|
977 | 1091 | responses: an empty response (``0\n``) followed by the integer result value. |
|
978 | 1092 | e.g. ``1\n2``. So the full response might be ``0\n1\n2``. |
|
979 | 1093 | |
|
980 | 1094 | For the HTTP version 1 transport, the response is a ``string`` type composed |
|
981 | 1095 | of an integer result value followed by a newline (``\n``) followed by string |
|
982 | 1096 | content holding server output that should be displayed on the client (output |
|
983 | 1097 | hooks, etc). |
|
984 | 1098 | |
|
985 | 1099 | In some cases, the server may respond with a ``bundle2`` bundle. In this |
|
986 | 1100 | case, the response type is ``stream``. For the HTTP version 1 transport, the |
|
987 | 1101 | response is zlib compressed. |
|
988 | 1102 | |
|
989 | 1103 | The server may also respond with a generic error type, which contains a string |
|
990 | 1104 | indicating the failure. |
@@ -1,466 +1,540 b'' | |||
|
1 | 1 | # sshpeer.py - ssh repository proxy class for mercurial |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com> |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2 or any later version. |
|
7 | 7 | |
|
8 | 8 | from __future__ import absolute_import |
|
9 | 9 | |
|
10 | 10 | import re |
|
11 | import uuid | |
|
11 | 12 | |
|
12 | 13 | from .i18n import _ |
|
13 | 14 | from . import ( |
|
14 | 15 | error, |
|
15 | 16 | pycompat, |
|
16 | 17 | util, |
|
17 | 18 | wireproto, |
|
19 | wireprotoserver, | |
|
18 | 20 | ) |
|
19 | 21 | |
|
20 | 22 | def _serverquote(s): |
|
21 | 23 | """quote a string for the remote shell ... which we assume is sh""" |
|
22 | 24 | if not s: |
|
23 | 25 | return s |
|
24 | 26 | if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s): |
|
25 | 27 | return s |
|
26 | 28 | return "'%s'" % s.replace("'", "'\\''") |
|
27 | 29 | |
|
28 | 30 | def _forwardoutput(ui, pipe): |
|
29 | 31 | """display all data currently available on pipe as remote output. |
|
30 | 32 | |
|
31 | 33 | This is non blocking.""" |
|
32 | 34 | s = util.readpipe(pipe) |
|
33 | 35 | if s: |
|
34 | 36 | for l in s.splitlines(): |
|
35 | 37 | ui.status(_("remote: "), l, '\n') |
|
36 | 38 | |
|
37 | 39 | class doublepipe(object): |
|
38 | 40 | """Operate a side-channel pipe in addition of a main one |
|
39 | 41 | |
|
40 | 42 | The side-channel pipe contains server output to be forwarded to the user |
|
41 | 43 | input. The double pipe will behave as the "main" pipe, but will ensure the |
|
42 | 44 | content of the "side" pipe is properly processed while we wait for blocking |
|
43 | 45 | call on the "main" pipe. |
|
44 | 46 | |
|
45 | 47 | If large amounts of data are read from "main", the forward will cease after |
|
46 | 48 | the first bytes start to appear. This simplifies the implementation |
|
47 | 49 | without affecting actual output of sshpeer too much as we rarely issue |
|
48 | 50 | large read for data not yet emitted by the server. |
|
49 | 51 | |
|
50 | 52 | The main pipe is expected to be a 'bufferedinputpipe' from the util module |
|
51 | 53 | that handle all the os specific bits. This class lives in this module |
|
52 | 54 | because it focus on behavior specific to the ssh protocol.""" |
|
53 | 55 | |
|
54 | 56 | def __init__(self, ui, main, side): |
|
55 | 57 | self._ui = ui |
|
56 | 58 | self._main = main |
|
57 | 59 | self._side = side |
|
58 | 60 | |
|
59 | 61 | def _wait(self): |
|
60 | 62 | """wait until some data are available on main or side |
|
61 | 63 | |
|
62 | 64 | return a pair of boolean (ismainready, issideready) |
|
63 | 65 | |
|
64 | 66 | (This will only wait for data if the setup is supported by `util.poll`) |
|
65 | 67 | """ |
|
66 | 68 | if getattr(self._main, 'hasbuffer', False): # getattr for classic pipe |
|
67 | 69 | return (True, True) # main has data, assume side is worth poking at. |
|
68 | 70 | fds = [self._main.fileno(), self._side.fileno()] |
|
69 | 71 | try: |
|
70 | 72 | act = util.poll(fds) |
|
71 | 73 | except NotImplementedError: |
|
72 | 74 | # non supported yet case, assume all have data. |
|
73 | 75 | act = fds |
|
74 | 76 | return (self._main.fileno() in act, self._side.fileno() in act) |
|
75 | 77 | |
|
76 | 78 | def write(self, data): |
|
77 | 79 | return self._call('write', data) |
|
78 | 80 | |
|
79 | 81 | def read(self, size): |
|
80 | 82 | r = self._call('read', size) |
|
81 | 83 | if size != 0 and not r: |
|
82 | 84 | # We've observed a condition that indicates the |
|
83 | 85 | # stdout closed unexpectedly. Check stderr one |
|
84 | 86 | # more time and snag anything that's there before |
|
85 | 87 | # letting anyone know the main part of the pipe |
|
86 | 88 | # closed prematurely. |
|
87 | 89 | _forwardoutput(self._ui, self._side) |
|
88 | 90 | return r |
|
89 | 91 | |
|
90 | 92 | def readline(self): |
|
91 | 93 | return self._call('readline') |
|
92 | 94 | |
|
93 | 95 | def _call(self, methname, data=None): |
|
94 | 96 | """call <methname> on "main", forward output of "side" while blocking |
|
95 | 97 | """ |
|
96 | 98 | # data can be '' or 0 |
|
97 | 99 | if (data is not None and not data) or self._main.closed: |
|
98 | 100 | _forwardoutput(self._ui, self._side) |
|
99 | 101 | return '' |
|
100 | 102 | while True: |
|
101 | 103 | mainready, sideready = self._wait() |
|
102 | 104 | if sideready: |
|
103 | 105 | _forwardoutput(self._ui, self._side) |
|
104 | 106 | if mainready: |
|
105 | 107 | meth = getattr(self._main, methname) |
|
106 | 108 | if data is None: |
|
107 | 109 | return meth() |
|
108 | 110 | else: |
|
109 | 111 | return meth(data) |
|
110 | 112 | |
|
111 | 113 | def close(self): |
|
112 | 114 | return self._main.close() |
|
113 | 115 | |
|
114 | 116 | def flush(self): |
|
115 | 117 | return self._main.flush() |
|
116 | 118 | |
|
117 | 119 | def _cleanuppipes(ui, pipei, pipeo, pipee): |
|
118 | 120 | """Clean up pipes used by an SSH connection.""" |
|
119 | 121 | if pipeo: |
|
120 | 122 | pipeo.close() |
|
121 | 123 | if pipei: |
|
122 | 124 | pipei.close() |
|
123 | 125 | |
|
124 | 126 | if pipee: |
|
125 | 127 | # Try to read from the err descriptor until EOF. |
|
126 | 128 | try: |
|
127 | 129 | for l in pipee: |
|
128 | 130 | ui.status(_('remote: '), l) |
|
129 | 131 | except (IOError, ValueError): |
|
130 | 132 | pass |
|
131 | 133 | |
|
132 | 134 | pipee.close() |
|
133 | 135 | |
|
134 | 136 | def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None): |
|
135 | 137 | """Create an SSH connection to a server. |
|
136 | 138 | |
|
137 | 139 | Returns a tuple of (process, stdin, stdout, stderr) for the |
|
138 | 140 | spawned process. |
|
139 | 141 | """ |
|
140 | 142 | cmd = '%s %s %s' % ( |
|
141 | 143 | sshcmd, |
|
142 | 144 | args, |
|
143 | 145 | util.shellquote('%s -R %s serve --stdio' % ( |
|
144 | 146 | _serverquote(remotecmd), _serverquote(path)))) |
|
145 | 147 | |
|
146 | 148 | ui.debug('running %s\n' % cmd) |
|
147 | 149 | cmd = util.quotecommand(cmd) |
|
148 | 150 | |
|
149 | 151 | # no buffer allow the use of 'select' |
|
150 | 152 | # feel free to remove buffering and select usage when we ultimately |
|
151 | 153 | # move to threading. |
|
152 | 154 | stdin, stdout, stderr, proc = util.popen4(cmd, bufsize=0, env=sshenv) |
|
153 | 155 | |
|
154 | 156 | stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr) |
|
155 | 157 | stdin = doublepipe(ui, stdin, stderr) |
|
156 | 158 | |
|
157 | 159 | return proc, stdin, stdout, stderr |
|
158 | 160 | |
|
159 | 161 | def _performhandshake(ui, stdin, stdout, stderr): |
|
160 | 162 | def badresponse(): |
|
161 | 163 | msg = _('no suitable response from remote hg') |
|
162 | 164 | hint = ui.config('ui', 'ssherrorhint') |
|
163 | 165 | raise error.RepoError(msg, hint=hint) |
|
164 | 166 | |
|
165 |
# The handshake consists of sending |
|
|
166 | # ``hello`` and ``between``. | |
|
167 | # The handshake consists of sending wire protocol commands in reverse | |
|
168 | # order of protocol implementation and then sniffing for a response | |
|
169 | # to one of them. | |
|
170 | # | |
|
171 | # Those commands (from oldest to newest) are: | |
|
167 | 172 | # |
|
168 | # The ``hello`` command (which was introduced in Mercurial 0.9.1) | |
|
169 | # instructs the server to advertise its capabilities. | |
|
173 | # ``between`` | |
|
174 | # Asks for the set of revisions between a pair of revisions. Command | |
|
175 | # present in all Mercurial server implementations. | |
|
170 | 176 | # |
|
171 | # The ``between`` command (which has existed in all Mercurial servers | |
|
172 | # for as long as SSH support has existed), asks for the set of revisions | |
|
173 | # between a pair of revisions. | |
|
177 | # ``hello`` | |
|
178 | # Instructs the server to advertise its capabilities. Introduced in | |
|
179 | # Mercurial 0.9.1. | |
|
180 | # | |
|
181 | # ``upgrade`` | |
|
182 | # Requests upgrade from default transport protocol version 1 to | |
|
183 | # a newer version. Introduced in Mercurial 4.6 as an experimental | |
|
184 | # feature. | |
|
174 | 185 | # |
|
175 | 186 | # The ``between`` command is issued with a request for the null |
|
176 | 187 | # range. If the remote is a Mercurial server, this request will |
|
177 | 188 | # generate a specific response: ``1\n\n``. This represents the |
|
178 | 189 | # wire protocol encoded value for ``\n``. We look for ``1\n\n`` |
|
179 | 190 | # in the output stream and know this is the response to ``between`` |
|
180 | 191 | # and we're at the end of our handshake reply. |
|
181 | 192 | # |
|
182 | 193 | # The response to the ``hello`` command will be a line with the |
|
183 | 194 | # length of the value returned by that command followed by that |
|
184 | 195 | # value. If the server doesn't support ``hello`` (which should be |
|
185 | 196 | # rare), that line will be ``0\n``. Otherwise, the value will contain |
|
186 | 197 | # RFC 822 like lines. Of these, the ``capabilities:`` line contains |
|
187 | 198 | # the capabilities of the server. |
|
188 | 199 | # |
|
200 | # The ``upgrade`` command isn't really a command in the traditional | |
|
201 | # sense of version 1 of the transport because it isn't using the | |
|
202 | # proper mechanism for formatting insteads: instead, it just encodes | |
|
203 | # arguments on the line, delimited by spaces. | |
|
204 | # | |
|
205 | # The ``upgrade`` line looks like ``upgrade <token> <capabilities>``. | |
|
206 | # If the server doesn't support protocol upgrades, it will reply to | |
|
207 | # this line with ``0\n``. Otherwise, it emits an | |
|
208 | # ``upgraded <token> <protocol>`` line to both stdout and stderr. | |
|
209 | # Content immediately following this line describes additional | |
|
210 | # protocol and server state. | |
|
211 | # | |
|
189 | 212 | # In addition to the responses to our command requests, the server |
|
190 | 213 | # may emit "banner" output on stdout. SSH servers are allowed to |
|
191 | 214 | # print messages to stdout on login. Issuing commands on connection |
|
192 | 215 | # allows us to flush this banner output from the server by scanning |
|
193 | 216 | # for output to our well-known ``between`` command. Of course, if |
|
194 | 217 | # the banner contains ``1\n\n``, this will throw off our detection. |
|
195 | 218 | |
|
196 | 219 | requestlog = ui.configbool('devel', 'debug.peer-request') |
|
197 | 220 | |
|
221 | # Generate a random token to help identify responses to version 2 | |
|
222 | # upgrade request. | |
|
223 | token = bytes(uuid.uuid4()) | |
|
224 | upgradecaps = [ | |
|
225 | ('proto', wireprotoserver.SSHV2), | |
|
226 | ] | |
|
227 | upgradecaps = util.urlreq.urlencode(upgradecaps) | |
|
228 | ||
|
198 | 229 | try: |
|
199 | 230 | pairsarg = '%s-%s' % ('0' * 40, '0' * 40) |
|
200 | 231 | handshake = [ |
|
201 | 232 | 'hello\n', |
|
202 | 233 | 'between\n', |
|
203 | 234 | 'pairs %d\n' % len(pairsarg), |
|
204 | 235 | pairsarg, |
|
205 | 236 | ] |
|
206 | 237 | |
|
238 | # Request upgrade to version 2 if configured. | |
|
239 | if ui.configbool('experimental', 'sshpeer.advertise-v2'): | |
|
240 | ui.debug('sending upgrade request: %s %s\n' % (token, upgradecaps)) | |
|
241 | handshake.insert(0, 'upgrade %s %s\n' % (token, upgradecaps)) | |
|
242 | ||
|
207 | 243 | if requestlog: |
|
208 | 244 | ui.debug('devel-peer-request: hello\n') |
|
209 | 245 | ui.debug('sending hello command\n') |
|
210 | 246 | if requestlog: |
|
211 | 247 | ui.debug('devel-peer-request: between\n') |
|
212 | 248 | ui.debug('devel-peer-request: pairs: %d bytes\n' % len(pairsarg)) |
|
213 | 249 | ui.debug('sending between command\n') |
|
214 | 250 | |
|
215 | 251 | stdin.write(''.join(handshake)) |
|
216 | 252 | stdin.flush() |
|
217 | 253 | except IOError: |
|
218 | 254 | badresponse() |
|
219 | 255 | |
|
256 | # Assume version 1 of wire protocol by default. | |
|
257 | protoname = wireprotoserver.SSHV1 | |
|
258 | reupgraded = re.compile(b'^upgraded %s (.*)$' % re.escape(token)) | |
|
259 | ||
|
220 | 260 | lines = ['', 'dummy'] |
|
221 | 261 | max_noise = 500 |
|
222 | 262 | while lines[-1] and max_noise: |
|
223 | 263 | try: |
|
224 | 264 | l = stdout.readline() |
|
225 | 265 | _forwardoutput(ui, stderr) |
|
266 | ||
|
267 | # Look for reply to protocol upgrade request. It has a token | |
|
268 | # in it, so there should be no false positives. | |
|
269 | m = reupgraded.match(l) | |
|
270 | if m: | |
|
271 | protoname = m.group(1) | |
|
272 | ui.debug('protocol upgraded to %s\n' % protoname) | |
|
273 | # If an upgrade was handled, the ``hello`` and ``between`` | |
|
274 | # requests are ignored. The next output belongs to the | |
|
275 | # protocol, so stop scanning lines. | |
|
276 | break | |
|
277 | ||
|
278 | # Otherwise it could be a banner, ``0\n`` response if server | |
|
279 | # doesn't support upgrade. | |
|
280 | ||
|
226 | 281 | if lines[-1] == '1\n' and l == '\n': |
|
227 | 282 | break |
|
228 | 283 | if l: |
|
229 | 284 | ui.debug('remote: ', l) |
|
230 | 285 | lines.append(l) |
|
231 | 286 | max_noise -= 1 |
|
232 | 287 | except IOError: |
|
233 | 288 | badresponse() |
|
234 | 289 | else: |
|
235 | 290 | badresponse() |
|
236 | 291 | |
|
237 | 292 | caps = set() |
|
238 | for l in reversed(lines): | |
|
239 | # Look for response to ``hello`` command. Scan from the back so | |
|
240 | # we don't misinterpret banner output as the command reply. | |
|
241 | if l.startswith('capabilities:'): | |
|
242 | caps.update(l[:-1].split(':')[1].split()) | |
|
243 | break | |
|
244 | 293 | |
|
245 | # Error if we couldn't find a response to ``hello``. This could | |
|
246 | # mean: | |
|
294 | # For version 1, we should see a ``capabilities`` line in response to the | |
|
295 | # ``hello`` command. | |
|
296 | if protoname == wireprotoserver.SSHV1: | |
|
297 | for l in reversed(lines): | |
|
298 | # Look for response to ``hello`` command. Scan from the back so | |
|
299 | # we don't misinterpret banner output as the command reply. | |
|
300 | if l.startswith('capabilities:'): | |
|
301 | caps.update(l[:-1].split(':')[1].split()) | |
|
302 | break | |
|
303 | elif protoname == wireprotoserver.SSHV2: | |
|
304 | # We see a line with number of bytes to follow and then a value | |
|
305 | # looking like ``capabilities: *``. | |
|
306 | line = stdout.readline() | |
|
307 | try: | |
|
308 | valuelen = int(line) | |
|
309 | except ValueError: | |
|
310 | badresponse() | |
|
311 | ||
|
312 | capsline = stdout.read(valuelen) | |
|
313 | if not capsline.startswith('capabilities: '): | |
|
314 | badresponse() | |
|
315 | ||
|
316 | caps.update(capsline.split(':')[1].split()) | |
|
317 | # Trailing newline. | |
|
318 | stdout.read(1) | |
|
319 | ||
|
320 | # Error if we couldn't find capabilities, this means: | |
|
247 | 321 | # |
|
248 | 322 | # 1. Remote isn't a Mercurial server |
|
249 | 323 | # 2. Remote is a <0.9.1 Mercurial server |
|
250 | 324 | # 3. Remote is a future Mercurial server that dropped ``hello`` |
|
251 | # support. | |
|
325 | # and other attempted handshake mechanisms. | |
|
252 | 326 | if not caps: |
|
253 | 327 | badresponse() |
|
254 | 328 | |
|
255 | 329 | return caps |
|
256 | 330 | |
|
257 | 331 | class sshpeer(wireproto.wirepeer): |
|
258 | 332 | def __init__(self, ui, url, proc, stdin, stdout, stderr, caps): |
|
259 | 333 | """Create a peer from an existing SSH connection. |
|
260 | 334 | |
|
261 | 335 | ``proc`` is a handle on the underlying SSH process. |
|
262 | 336 | ``stdin``, ``stdout``, and ``stderr`` are handles on the stdio |
|
263 | 337 | pipes for that process. |
|
264 | 338 | ``caps`` is a set of capabilities supported by the remote. |
|
265 | 339 | """ |
|
266 | 340 | self._url = url |
|
267 | 341 | self._ui = ui |
|
268 | 342 | # self._subprocess is unused. Keeping a handle on the process |
|
269 | 343 | # holds a reference and prevents it from being garbage collected. |
|
270 | 344 | self._subprocess = proc |
|
271 | 345 | self._pipeo = stdin |
|
272 | 346 | self._pipei = stdout |
|
273 | 347 | self._pipee = stderr |
|
274 | 348 | self._caps = caps |
|
275 | 349 | |
|
276 | 350 | # Begin of _basepeer interface. |
|
277 | 351 | |
|
278 | 352 | @util.propertycache |
|
279 | 353 | def ui(self): |
|
280 | 354 | return self._ui |
|
281 | 355 | |
|
282 | 356 | def url(self): |
|
283 | 357 | return self._url |
|
284 | 358 | |
|
285 | 359 | def local(self): |
|
286 | 360 | return None |
|
287 | 361 | |
|
288 | 362 | def peer(self): |
|
289 | 363 | return self |
|
290 | 364 | |
|
291 | 365 | def canpush(self): |
|
292 | 366 | return True |
|
293 | 367 | |
|
294 | 368 | def close(self): |
|
295 | 369 | pass |
|
296 | 370 | |
|
297 | 371 | # End of _basepeer interface. |
|
298 | 372 | |
|
299 | 373 | # Begin of _basewirecommands interface. |
|
300 | 374 | |
|
301 | 375 | def capabilities(self): |
|
302 | 376 | return self._caps |
|
303 | 377 | |
|
304 | 378 | # End of _basewirecommands interface. |
|
305 | 379 | |
|
306 | 380 | def _readerr(self): |
|
307 | 381 | _forwardoutput(self.ui, self._pipee) |
|
308 | 382 | |
|
309 | 383 | def _abort(self, exception): |
|
310 | 384 | self._cleanup() |
|
311 | 385 | raise exception |
|
312 | 386 | |
|
313 | 387 | def _cleanup(self): |
|
314 | 388 | _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee) |
|
315 | 389 | |
|
316 | 390 | __del__ = _cleanup |
|
317 | 391 | |
|
318 | 392 | def _submitbatch(self, req): |
|
319 | 393 | rsp = self._callstream("batch", cmds=wireproto.encodebatchcmds(req)) |
|
320 | 394 | available = self._getamount() |
|
321 | 395 | # TODO this response parsing is probably suboptimal for large |
|
322 | 396 | # batches with large responses. |
|
323 | 397 | toread = min(available, 1024) |
|
324 | 398 | work = rsp.read(toread) |
|
325 | 399 | available -= toread |
|
326 | 400 | chunk = work |
|
327 | 401 | while chunk: |
|
328 | 402 | while ';' in work: |
|
329 | 403 | one, work = work.split(';', 1) |
|
330 | 404 | yield wireproto.unescapearg(one) |
|
331 | 405 | toread = min(available, 1024) |
|
332 | 406 | chunk = rsp.read(toread) |
|
333 | 407 | available -= toread |
|
334 | 408 | work += chunk |
|
335 | 409 | yield wireproto.unescapearg(work) |
|
336 | 410 | |
|
337 | 411 | def _callstream(self, cmd, **args): |
|
338 | 412 | args = pycompat.byteskwargs(args) |
|
339 | 413 | if (self.ui.debugflag |
|
340 | 414 | and self.ui.configbool('devel', 'debug.peer-request')): |
|
341 | 415 | dbg = self.ui.debug |
|
342 | 416 | line = 'devel-peer-request: %s\n' |
|
343 | 417 | dbg(line % cmd) |
|
344 | 418 | for key, value in sorted(args.items()): |
|
345 | 419 | if not isinstance(value, dict): |
|
346 | 420 | dbg(line % ' %s: %d bytes' % (key, len(value))) |
|
347 | 421 | else: |
|
348 | 422 | for dk, dv in sorted(value.items()): |
|
349 | 423 | dbg(line % ' %s-%s: %d' % (key, dk, len(dv))) |
|
350 | 424 | self.ui.debug("sending %s command\n" % cmd) |
|
351 | 425 | self._pipeo.write("%s\n" % cmd) |
|
352 | 426 | _func, names = wireproto.commands[cmd] |
|
353 | 427 | keys = names.split() |
|
354 | 428 | wireargs = {} |
|
355 | 429 | for k in keys: |
|
356 | 430 | if k == '*': |
|
357 | 431 | wireargs['*'] = args |
|
358 | 432 | break |
|
359 | 433 | else: |
|
360 | 434 | wireargs[k] = args[k] |
|
361 | 435 | del args[k] |
|
362 | 436 | for k, v in sorted(wireargs.iteritems()): |
|
363 | 437 | self._pipeo.write("%s %d\n" % (k, len(v))) |
|
364 | 438 | if isinstance(v, dict): |
|
365 | 439 | for dk, dv in v.iteritems(): |
|
366 | 440 | self._pipeo.write("%s %d\n" % (dk, len(dv))) |
|
367 | 441 | self._pipeo.write(dv) |
|
368 | 442 | else: |
|
369 | 443 | self._pipeo.write(v) |
|
370 | 444 | self._pipeo.flush() |
|
371 | 445 | |
|
372 | 446 | return self._pipei |
|
373 | 447 | |
|
374 | 448 | def _callcompressable(self, cmd, **args): |
|
375 | 449 | return self._callstream(cmd, **args) |
|
376 | 450 | |
|
377 | 451 | def _call(self, cmd, **args): |
|
378 | 452 | self._callstream(cmd, **args) |
|
379 | 453 | return self._recv() |
|
380 | 454 | |
|
381 | 455 | def _callpush(self, cmd, fp, **args): |
|
382 | 456 | r = self._call(cmd, **args) |
|
383 | 457 | if r: |
|
384 | 458 | return '', r |
|
385 | 459 | for d in iter(lambda: fp.read(4096), ''): |
|
386 | 460 | self._send(d) |
|
387 | 461 | self._send("", flush=True) |
|
388 | 462 | r = self._recv() |
|
389 | 463 | if r: |
|
390 | 464 | return '', r |
|
391 | 465 | return self._recv(), '' |
|
392 | 466 | |
|
393 | 467 | def _calltwowaystream(self, cmd, fp, **args): |
|
394 | 468 | r = self._call(cmd, **args) |
|
395 | 469 | if r: |
|
396 | 470 | # XXX needs to be made better |
|
397 | 471 | raise error.Abort(_('unexpected remote reply: %s') % r) |
|
398 | 472 | for d in iter(lambda: fp.read(4096), ''): |
|
399 | 473 | self._send(d) |
|
400 | 474 | self._send("", flush=True) |
|
401 | 475 | return self._pipei |
|
402 | 476 | |
|
403 | 477 | def _getamount(self): |
|
404 | 478 | l = self._pipei.readline() |
|
405 | 479 | if l == '\n': |
|
406 | 480 | self._readerr() |
|
407 | 481 | msg = _('check previous remote output') |
|
408 | 482 | self._abort(error.OutOfBandError(hint=msg)) |
|
409 | 483 | self._readerr() |
|
410 | 484 | try: |
|
411 | 485 | return int(l) |
|
412 | 486 | except ValueError: |
|
413 | 487 | self._abort(error.ResponseError(_("unexpected response:"), l)) |
|
414 | 488 | |
|
415 | 489 | def _recv(self): |
|
416 | 490 | return self._pipei.read(self._getamount()) |
|
417 | 491 | |
|
418 | 492 | def _send(self, data, flush=False): |
|
419 | 493 | self._pipeo.write("%d\n" % len(data)) |
|
420 | 494 | if data: |
|
421 | 495 | self._pipeo.write(data) |
|
422 | 496 | if flush: |
|
423 | 497 | self._pipeo.flush() |
|
424 | 498 | self._readerr() |
|
425 | 499 | |
|
426 | 500 | def instance(ui, path, create): |
|
427 | 501 | """Create an SSH peer. |
|
428 | 502 | |
|
429 | 503 | The returned object conforms to the ``wireproto.wirepeer`` interface. |
|
430 | 504 | """ |
|
431 | 505 | u = util.url(path, parsequery=False, parsefragment=False) |
|
432 | 506 | if u.scheme != 'ssh' or not u.host or u.path is None: |
|
433 | 507 | raise error.RepoError(_("couldn't parse location %s") % path) |
|
434 | 508 | |
|
435 | 509 | util.checksafessh(path) |
|
436 | 510 | |
|
437 | 511 | if u.passwd is not None: |
|
438 | 512 | raise error.RepoError(_('password in URL not supported')) |
|
439 | 513 | |
|
440 | 514 | sshcmd = ui.config('ui', 'ssh') |
|
441 | 515 | remotecmd = ui.config('ui', 'remotecmd') |
|
442 | 516 | sshaddenv = dict(ui.configitems('sshenv')) |
|
443 | 517 | sshenv = util.shellenviron(sshaddenv) |
|
444 | 518 | remotepath = u.path or '.' |
|
445 | 519 | |
|
446 | 520 | args = util.sshargs(sshcmd, u.host, u.user, u.port) |
|
447 | 521 | |
|
448 | 522 | if create: |
|
449 | 523 | cmd = '%s %s %s' % (sshcmd, args, |
|
450 | 524 | util.shellquote('%s init %s' % |
|
451 | 525 | (_serverquote(remotecmd), _serverquote(remotepath)))) |
|
452 | 526 | ui.debug('running %s\n' % cmd) |
|
453 | 527 | res = ui.system(cmd, blockedtag='sshpeer', environ=sshenv) |
|
454 | 528 | if res != 0: |
|
455 | 529 | raise error.RepoError(_('could not create remote repo')) |
|
456 | 530 | |
|
457 | 531 | proc, stdin, stdout, stderr = _makeconnection(ui, sshcmd, args, remotecmd, |
|
458 | 532 | remotepath, sshenv) |
|
459 | 533 | |
|
460 | 534 | try: |
|
461 | 535 | caps = _performhandshake(ui, stdin, stdout, stderr) |
|
462 | 536 | except Exception: |
|
463 | 537 | _cleanuppipes(ui, stdout, stdin, stderr) |
|
464 | 538 | raise |
|
465 | 539 | |
|
466 | 540 | return sshpeer(ui, path, proc, stdin, stdout, stderr, caps) |
@@ -1,357 +1,363 b'' | |||
|
1 | 1 | # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> |
|
2 | 2 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
3 | 3 | # |
|
4 | 4 | # This software may be used and distributed according to the terms of the |
|
5 | 5 | # GNU General Public License version 2 or any later version. |
|
6 | 6 | |
|
7 | 7 | from __future__ import absolute_import |
|
8 | 8 | |
|
9 | 9 | import abc |
|
10 | 10 | import cgi |
|
11 | 11 | import struct |
|
12 | 12 | import sys |
|
13 | 13 | |
|
14 | 14 | from .i18n import _ |
|
15 | 15 | from . import ( |
|
16 | 16 | encoding, |
|
17 | 17 | error, |
|
18 | 18 | hook, |
|
19 | 19 | pycompat, |
|
20 | 20 | util, |
|
21 | 21 | wireproto, |
|
22 | 22 | ) |
|
23 | 23 | |
|
24 | 24 | stringio = util.stringio |
|
25 | 25 | |
|
26 | 26 | urlerr = util.urlerr |
|
27 | 27 | urlreq = util.urlreq |
|
28 | 28 | |
|
29 | 29 | HTTP_OK = 200 |
|
30 | 30 | |
|
31 | 31 | HGTYPE = 'application/mercurial-0.1' |
|
32 | 32 | HGTYPE2 = 'application/mercurial-0.2' |
|
33 | 33 | HGERRTYPE = 'application/hg-error' |
|
34 | 34 | |
|
35 | # Names of the SSH protocol implementations. | |
|
36 | SSHV1 = 'ssh-v1' | |
|
37 | # This is advertised over the wire. Incremental the counter at the end | |
|
38 | # to reflect BC breakages. | |
|
39 | SSHV2 = 'exp-ssh-v2-0001' | |
|
40 | ||
|
35 | 41 | class abstractserverproto(object): |
|
36 | 42 | """abstract class that summarizes the protocol API |
|
37 | 43 | |
|
38 | 44 | Used as reference and documentation. |
|
39 | 45 | """ |
|
40 | 46 | |
|
41 | 47 | __metaclass__ = abc.ABCMeta |
|
42 | 48 | |
|
43 | 49 | @abc.abstractproperty |
|
44 | 50 | def name(self): |
|
45 | 51 | """The name of the protocol implementation. |
|
46 | 52 | |
|
47 | 53 | Used for uniquely identifying the transport type. |
|
48 | 54 | """ |
|
49 | 55 | |
|
50 | 56 | @abc.abstractmethod |
|
51 | 57 | def getargs(self, args): |
|
52 | 58 | """return the value for arguments in <args> |
|
53 | 59 | |
|
54 | 60 | returns a list of values (same order as <args>)""" |
|
55 | 61 | |
|
56 | 62 | @abc.abstractmethod |
|
57 | 63 | def getfile(self, fp): |
|
58 | 64 | """write the whole content of a file into a file like object |
|
59 | 65 | |
|
60 | 66 | The file is in the form:: |
|
61 | 67 | |
|
62 | 68 | (<chunk-size>\n<chunk>)+0\n |
|
63 | 69 | |
|
64 | 70 | chunk size is the ascii version of the int. |
|
65 | 71 | """ |
|
66 | 72 | |
|
67 | 73 | @abc.abstractmethod |
|
68 | 74 | def redirect(self): |
|
69 | 75 | """may setup interception for stdout and stderr |
|
70 | 76 | |
|
71 | 77 | See also the `restore` method.""" |
|
72 | 78 | |
|
73 | 79 | # If the `redirect` function does install interception, the `restore` |
|
74 | 80 | # function MUST be defined. If interception is not used, this function |
|
75 | 81 | # MUST NOT be defined. |
|
76 | 82 | # |
|
77 | 83 | # left commented here on purpose |
|
78 | 84 | # |
|
79 | 85 | #def restore(self): |
|
80 | 86 | # """reinstall previous stdout and stderr and return intercepted stdout |
|
81 | 87 | # """ |
|
82 | 88 | # raise NotImplementedError() |
|
83 | 89 | |
|
84 | 90 | def decodevaluefromheaders(req, headerprefix): |
|
85 | 91 | """Decode a long value from multiple HTTP request headers. |
|
86 | 92 | |
|
87 | 93 | Returns the value as a bytes, not a str. |
|
88 | 94 | """ |
|
89 | 95 | chunks = [] |
|
90 | 96 | i = 1 |
|
91 | 97 | prefix = headerprefix.upper().replace(r'-', r'_') |
|
92 | 98 | while True: |
|
93 | 99 | v = req.env.get(r'HTTP_%s_%d' % (prefix, i)) |
|
94 | 100 | if v is None: |
|
95 | 101 | break |
|
96 | 102 | chunks.append(pycompat.bytesurl(v)) |
|
97 | 103 | i += 1 |
|
98 | 104 | |
|
99 | 105 | return ''.join(chunks) |
|
100 | 106 | |
|
101 | 107 | class webproto(abstractserverproto): |
|
102 | 108 | def __init__(self, req, ui): |
|
103 | 109 | self._req = req |
|
104 | 110 | self._ui = ui |
|
105 | 111 | |
|
106 | 112 | @property |
|
107 | 113 | def name(self): |
|
108 | 114 | return 'http' |
|
109 | 115 | |
|
110 | 116 | def getargs(self, args): |
|
111 | 117 | knownargs = self._args() |
|
112 | 118 | data = {} |
|
113 | 119 | keys = args.split() |
|
114 | 120 | for k in keys: |
|
115 | 121 | if k == '*': |
|
116 | 122 | star = {} |
|
117 | 123 | for key in knownargs.keys(): |
|
118 | 124 | if key != 'cmd' and key not in keys: |
|
119 | 125 | star[key] = knownargs[key][0] |
|
120 | 126 | data['*'] = star |
|
121 | 127 | else: |
|
122 | 128 | data[k] = knownargs[k][0] |
|
123 | 129 | return [data[k] for k in keys] |
|
124 | 130 | |
|
125 | 131 | def _args(self): |
|
126 | 132 | args = util.rapply(pycompat.bytesurl, self._req.form.copy()) |
|
127 | 133 | postlen = int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0)) |
|
128 | 134 | if postlen: |
|
129 | 135 | args.update(cgi.parse_qs( |
|
130 | 136 | self._req.read(postlen), keep_blank_values=True)) |
|
131 | 137 | return args |
|
132 | 138 | |
|
133 | 139 | argvalue = decodevaluefromheaders(self._req, r'X-HgArg') |
|
134 | 140 | args.update(cgi.parse_qs(argvalue, keep_blank_values=True)) |
|
135 | 141 | return args |
|
136 | 142 | |
|
137 | 143 | def getfile(self, fp): |
|
138 | 144 | length = int(self._req.env[r'CONTENT_LENGTH']) |
|
139 | 145 | # If httppostargs is used, we need to read Content-Length |
|
140 | 146 | # minus the amount that was consumed by args. |
|
141 | 147 | length -= int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0)) |
|
142 | 148 | for s in util.filechunkiter(self._req, limit=length): |
|
143 | 149 | fp.write(s) |
|
144 | 150 | |
|
145 | 151 | def redirect(self): |
|
146 | 152 | self._oldio = self._ui.fout, self._ui.ferr |
|
147 | 153 | self._ui.ferr = self._ui.fout = stringio() |
|
148 | 154 | |
|
149 | 155 | def restore(self): |
|
150 | 156 | val = self._ui.fout.getvalue() |
|
151 | 157 | self._ui.ferr, self._ui.fout = self._oldio |
|
152 | 158 | return val |
|
153 | 159 | |
|
154 | 160 | def _client(self): |
|
155 | 161 | return 'remote:%s:%s:%s' % ( |
|
156 | 162 | self._req.env.get('wsgi.url_scheme') or 'http', |
|
157 | 163 | urlreq.quote(self._req.env.get('REMOTE_HOST', '')), |
|
158 | 164 | urlreq.quote(self._req.env.get('REMOTE_USER', ''))) |
|
159 | 165 | |
|
160 | 166 | def responsetype(self, prefer_uncompressed): |
|
161 | 167 | """Determine the appropriate response type and compression settings. |
|
162 | 168 | |
|
163 | 169 | Returns a tuple of (mediatype, compengine, engineopts). |
|
164 | 170 | """ |
|
165 | 171 | # Determine the response media type and compression engine based |
|
166 | 172 | # on the request parameters. |
|
167 | 173 | protocaps = decodevaluefromheaders(self._req, r'X-HgProto').split(' ') |
|
168 | 174 | |
|
169 | 175 | if '0.2' in protocaps: |
|
170 | 176 | # All clients are expected to support uncompressed data. |
|
171 | 177 | if prefer_uncompressed: |
|
172 | 178 | return HGTYPE2, util._noopengine(), {} |
|
173 | 179 | |
|
174 | 180 | # Default as defined by wire protocol spec. |
|
175 | 181 | compformats = ['zlib', 'none'] |
|
176 | 182 | for cap in protocaps: |
|
177 | 183 | if cap.startswith('comp='): |
|
178 | 184 | compformats = cap[5:].split(',') |
|
179 | 185 | break |
|
180 | 186 | |
|
181 | 187 | # Now find an agreed upon compression format. |
|
182 | 188 | for engine in wireproto.supportedcompengines(self._ui, self, |
|
183 | 189 | util.SERVERROLE): |
|
184 | 190 | if engine.wireprotosupport().name in compformats: |
|
185 | 191 | opts = {} |
|
186 | 192 | level = self._ui.configint('server', |
|
187 | 193 | '%slevel' % engine.name()) |
|
188 | 194 | if level is not None: |
|
189 | 195 | opts['level'] = level |
|
190 | 196 | |
|
191 | 197 | return HGTYPE2, engine, opts |
|
192 | 198 | |
|
193 | 199 | # No mutually supported compression format. Fall back to the |
|
194 | 200 | # legacy protocol. |
|
195 | 201 | |
|
196 | 202 | # Don't allow untrusted settings because disabling compression or |
|
197 | 203 | # setting a very high compression level could lead to flooding |
|
198 | 204 | # the server's network or CPU. |
|
199 | 205 | opts = {'level': self._ui.configint('server', 'zliblevel')} |
|
200 | 206 | return HGTYPE, util.compengines['zlib'], opts |
|
201 | 207 | |
|
202 | 208 | def iscmd(cmd): |
|
203 | 209 | return cmd in wireproto.commands |
|
204 | 210 | |
|
205 | 211 | def callhttp(repo, req, cmd): |
|
206 | 212 | proto = webproto(req, repo.ui) |
|
207 | 213 | |
|
208 | 214 | def genversion2(gen, engine, engineopts): |
|
209 | 215 | # application/mercurial-0.2 always sends a payload header |
|
210 | 216 | # identifying the compression engine. |
|
211 | 217 | name = engine.wireprotosupport().name |
|
212 | 218 | assert 0 < len(name) < 256 |
|
213 | 219 | yield struct.pack('B', len(name)) |
|
214 | 220 | yield name |
|
215 | 221 | |
|
216 | 222 | for chunk in gen: |
|
217 | 223 | yield chunk |
|
218 | 224 | |
|
219 | 225 | rsp = wireproto.dispatch(repo, proto, cmd) |
|
220 | 226 | if isinstance(rsp, bytes): |
|
221 | 227 | req.respond(HTTP_OK, HGTYPE, body=rsp) |
|
222 | 228 | return [] |
|
223 | 229 | elif isinstance(rsp, wireproto.streamres_legacy): |
|
224 | 230 | gen = rsp.gen |
|
225 | 231 | req.respond(HTTP_OK, HGTYPE) |
|
226 | 232 | return gen |
|
227 | 233 | elif isinstance(rsp, wireproto.streamres): |
|
228 | 234 | gen = rsp.gen |
|
229 | 235 | |
|
230 | 236 | # This code for compression should not be streamres specific. It |
|
231 | 237 | # is here because we only compress streamres at the moment. |
|
232 | 238 | mediatype, engine, engineopts = proto.responsetype( |
|
233 | 239 | rsp.prefer_uncompressed) |
|
234 | 240 | gen = engine.compressstream(gen, engineopts) |
|
235 | 241 | |
|
236 | 242 | if mediatype == HGTYPE2: |
|
237 | 243 | gen = genversion2(gen, engine, engineopts) |
|
238 | 244 | |
|
239 | 245 | req.respond(HTTP_OK, mediatype) |
|
240 | 246 | return gen |
|
241 | 247 | elif isinstance(rsp, wireproto.pushres): |
|
242 | 248 | val = proto.restore() |
|
243 | 249 | rsp = '%d\n%s' % (rsp.res, val) |
|
244 | 250 | req.respond(HTTP_OK, HGTYPE, body=rsp) |
|
245 | 251 | return [] |
|
246 | 252 | elif isinstance(rsp, wireproto.pusherr): |
|
247 | 253 | # drain the incoming bundle |
|
248 | 254 | req.drain() |
|
249 | 255 | proto.restore() |
|
250 | 256 | rsp = '0\n%s\n' % rsp.res |
|
251 | 257 | req.respond(HTTP_OK, HGTYPE, body=rsp) |
|
252 | 258 | return [] |
|
253 | 259 | elif isinstance(rsp, wireproto.ooberror): |
|
254 | 260 | rsp = rsp.message |
|
255 | 261 | req.respond(HTTP_OK, HGERRTYPE, body=rsp) |
|
256 | 262 | return [] |
|
257 | 263 | raise error.ProgrammingError('hgweb.protocol internal failure', rsp) |
|
258 | 264 | |
|
259 | 265 | class sshserver(abstractserverproto): |
|
260 | 266 | def __init__(self, ui, repo): |
|
261 | 267 | self._ui = ui |
|
262 | 268 | self._repo = repo |
|
263 | 269 | self._fin = ui.fin |
|
264 | 270 | self._fout = ui.fout |
|
265 | 271 | |
|
266 | 272 | hook.redirect(True) |
|
267 | 273 | ui.fout = repo.ui.fout = ui.ferr |
|
268 | 274 | |
|
269 | 275 | # Prevent insertion/deletion of CRs |
|
270 | 276 | util.setbinary(self._fin) |
|
271 | 277 | util.setbinary(self._fout) |
|
272 | 278 | |
|
273 | 279 | @property |
|
274 | 280 | def name(self): |
|
275 | 281 | return 'ssh' |
|
276 | 282 | |
|
277 | 283 | def getargs(self, args): |
|
278 | 284 | data = {} |
|
279 | 285 | keys = args.split() |
|
280 | 286 | for n in xrange(len(keys)): |
|
281 | 287 | argline = self._fin.readline()[:-1] |
|
282 | 288 | arg, l = argline.split() |
|
283 | 289 | if arg not in keys: |
|
284 | 290 | raise error.Abort(_("unexpected parameter %r") % arg) |
|
285 | 291 | if arg == '*': |
|
286 | 292 | star = {} |
|
287 | 293 | for k in xrange(int(l)): |
|
288 | 294 | argline = self._fin.readline()[:-1] |
|
289 | 295 | arg, l = argline.split() |
|
290 | 296 | val = self._fin.read(int(l)) |
|
291 | 297 | star[arg] = val |
|
292 | 298 | data['*'] = star |
|
293 | 299 | else: |
|
294 | 300 | val = self._fin.read(int(l)) |
|
295 | 301 | data[arg] = val |
|
296 | 302 | return [data[k] for k in keys] |
|
297 | 303 | |
|
298 | 304 | def getfile(self, fpout): |
|
299 | 305 | self._sendresponse('') |
|
300 | 306 | count = int(self._fin.readline()) |
|
301 | 307 | while count: |
|
302 | 308 | fpout.write(self._fin.read(count)) |
|
303 | 309 | count = int(self._fin.readline()) |
|
304 | 310 | |
|
305 | 311 | def redirect(self): |
|
306 | 312 | pass |
|
307 | 313 | |
|
308 | 314 | def _sendresponse(self, v): |
|
309 | 315 | self._fout.write("%d\n" % len(v)) |
|
310 | 316 | self._fout.write(v) |
|
311 | 317 | self._fout.flush() |
|
312 | 318 | |
|
313 | 319 | def _sendstream(self, source): |
|
314 | 320 | write = self._fout.write |
|
315 | 321 | for chunk in source.gen: |
|
316 | 322 | write(chunk) |
|
317 | 323 | self._fout.flush() |
|
318 | 324 | |
|
319 | 325 | def _sendpushresponse(self, rsp): |
|
320 | 326 | self._sendresponse('') |
|
321 | 327 | self._sendresponse(str(rsp.res)) |
|
322 | 328 | |
|
323 | 329 | def _sendpusherror(self, rsp): |
|
324 | 330 | self._sendresponse(rsp.res) |
|
325 | 331 | |
|
326 | 332 | def _sendooberror(self, rsp): |
|
327 | 333 | self._ui.ferr.write('%s\n-\n' % rsp.message) |
|
328 | 334 | self._ui.ferr.flush() |
|
329 | 335 | self._fout.write('\n') |
|
330 | 336 | self._fout.flush() |
|
331 | 337 | |
|
332 | 338 | def serve_forever(self): |
|
333 | 339 | while self.serve_one(): |
|
334 | 340 | pass |
|
335 | 341 | sys.exit(0) |
|
336 | 342 | |
|
337 | 343 | _handlers = { |
|
338 | 344 | str: _sendresponse, |
|
339 | 345 | wireproto.streamres: _sendstream, |
|
340 | 346 | wireproto.streamres_legacy: _sendstream, |
|
341 | 347 | wireproto.pushres: _sendpushresponse, |
|
342 | 348 | wireproto.pusherr: _sendpusherror, |
|
343 | 349 | wireproto.ooberror: _sendooberror, |
|
344 | 350 | } |
|
345 | 351 | |
|
346 | 352 | def serve_one(self): |
|
347 | 353 | cmd = self._fin.readline()[:-1] |
|
348 | 354 | if cmd and cmd in wireproto.commands: |
|
349 | 355 | rsp = wireproto.dispatch(self._repo, self, cmd) |
|
350 | 356 | self._handlers[rsp.__class__](self, rsp) |
|
351 | 357 | elif cmd: |
|
352 | 358 | self._sendresponse("") |
|
353 | 359 | return cmd != '' |
|
354 | 360 | |
|
355 | 361 | def _client(self): |
|
356 | 362 | client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] |
|
357 | 363 | return 'remote:ssh:' + client |
@@ -1,96 +1,127 b'' | |||
|
1 | 1 | # sshprotoext.py - Extension to test behavior of SSH protocol |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com> |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2 or any later version. |
|
7 | 7 | |
|
8 | 8 | # This extension replaces the SSH server started via `hg serve --stdio`. |
|
9 | 9 | # The server behaves differently depending on environment variables. |
|
10 | 10 | |
|
11 | 11 | from __future__ import absolute_import |
|
12 | 12 | |
|
13 | 13 | from mercurial import ( |
|
14 | 14 | error, |
|
15 | 15 | extensions, |
|
16 | 16 | registrar, |
|
17 | 17 | sshpeer, |
|
18 | 18 | wireproto, |
|
19 | 19 | wireprotoserver, |
|
20 | 20 | ) |
|
21 | 21 | |
|
22 | 22 | configtable = {} |
|
23 | 23 | configitem = registrar.configitem(configtable) |
|
24 | 24 | |
|
25 | 25 | configitem('sshpeer', 'mode', default=None) |
|
26 | 26 | configitem('sshpeer', 'handshake-mode', default=None) |
|
27 | 27 | |
|
28 | 28 | class bannerserver(wireprotoserver.sshserver): |
|
29 | 29 | """Server that sends a banner to stdout.""" |
|
30 | 30 | def serve_forever(self): |
|
31 | 31 | for i in range(10): |
|
32 | 32 | self._fout.write(b'banner: line %d\n' % i) |
|
33 | 33 | |
|
34 | 34 | super(bannerserver, self).serve_forever() |
|
35 | 35 | |
|
36 | 36 | class prehelloserver(wireprotoserver.sshserver): |
|
37 | 37 | """Tests behavior when connecting to <0.9.1 servers. |
|
38 | 38 | |
|
39 | 39 | The ``hello`` wire protocol command was introduced in Mercurial |
|
40 | 40 | 0.9.1. Modern clients send the ``hello`` command when connecting |
|
41 | 41 | to SSH servers. This mock server tests behavior of the handshake |
|
42 | 42 | when ``hello`` is not supported. |
|
43 | 43 | """ |
|
44 | 44 | def serve_forever(self): |
|
45 | 45 | l = self._fin.readline() |
|
46 | 46 | assert l == b'hello\n' |
|
47 | 47 | # Respond to unknown commands with an empty reply. |
|
48 | 48 | self._sendresponse(b'') |
|
49 | 49 | l = self._fin.readline() |
|
50 | 50 | assert l == b'between\n' |
|
51 | 51 | rsp = wireproto.dispatch(self._repo, self, b'between') |
|
52 | 52 | self._handlers[rsp.__class__](self, rsp) |
|
53 | 53 | |
|
54 | 54 | super(prehelloserver, self).serve_forever() |
|
55 | 55 | |
|
56 | class upgradev2server(wireprotoserver.sshserver): | |
|
57 | """Tests behavior for clients that issue upgrade to version 2.""" | |
|
58 | def serve_forever(self): | |
|
59 | name = wireprotoserver.SSHV2 | |
|
60 | l = self._fin.readline() | |
|
61 | assert l.startswith(b'upgrade ') | |
|
62 | token, caps = l[:-1].split(b' ')[1:] | |
|
63 | assert caps == b'proto=%s' % name | |
|
64 | ||
|
65 | # Filter hello and between requests. | |
|
66 | l = self._fin.readline() | |
|
67 | assert l == b'hello\n' | |
|
68 | l = self._fin.readline() | |
|
69 | assert l == b'between\n' | |
|
70 | l = self._fin.readline() | |
|
71 | assert l == 'pairs 81\n' | |
|
72 | self._fin.read(81) | |
|
73 | ||
|
74 | # Send the upgrade response. | |
|
75 | self._fout.write(b'upgraded %s %s\n' % (token, name)) | |
|
76 | servercaps = wireproto.capabilities(self._repo, self) | |
|
77 | rsp = b'capabilities: %s' % servercaps | |
|
78 | self._fout.write(b'%d\n' % len(rsp)) | |
|
79 | self._fout.write(rsp) | |
|
80 | self._fout.write(b'\n') | |
|
81 | self._fout.flush() | |
|
82 | ||
|
83 | super(upgradev2server, self).serve_forever() | |
|
84 | ||
|
56 | 85 | def performhandshake(orig, ui, stdin, stdout, stderr): |
|
57 | 86 | """Wrapped version of sshpeer._performhandshake to send extra commands.""" |
|
58 | 87 | mode = ui.config(b'sshpeer', b'handshake-mode') |
|
59 | 88 | if mode == b'pre-no-args': |
|
60 | 89 | ui.debug(b'sending no-args command\n') |
|
61 | 90 | stdin.write(b'no-args\n') |
|
62 | 91 | stdin.flush() |
|
63 | 92 | return orig(ui, stdin, stdout, stderr) |
|
64 | 93 | elif mode == b'pre-multiple-no-args': |
|
65 | 94 | ui.debug(b'sending unknown1 command\n') |
|
66 | 95 | stdin.write(b'unknown1\n') |
|
67 | 96 | ui.debug(b'sending unknown2 command\n') |
|
68 | 97 | stdin.write(b'unknown2\n') |
|
69 | 98 | ui.debug(b'sending unknown3 command\n') |
|
70 | 99 | stdin.write(b'unknown3\n') |
|
71 | 100 | stdin.flush() |
|
72 | 101 | return orig(ui, stdin, stdout, stderr) |
|
73 | 102 | else: |
|
74 | 103 | raise error.ProgrammingError(b'unknown HANDSHAKECOMMANDMODE: %s' % |
|
75 | 104 | mode) |
|
76 | 105 | |
|
77 | 106 | def extsetup(ui): |
|
78 | 107 | # It's easier for tests to define the server behavior via environment |
|
79 | 108 | # variables than config options. This is because `hg serve --stdio` |
|
80 | 109 | # has to be invoked with a certain form for security reasons and |
|
81 | 110 | # `dummyssh` can't just add `--config` flags to the command line. |
|
82 | 111 | servermode = ui.environ.get(b'SSHSERVERMODE') |
|
83 | 112 | |
|
84 | 113 | if servermode == b'banner': |
|
85 | 114 | wireprotoserver.sshserver = bannerserver |
|
86 | 115 | elif servermode == b'no-hello': |
|
87 | 116 | wireprotoserver.sshserver = prehelloserver |
|
117 | elif servermode == b'upgradev2': | |
|
118 | wireprotoserver.sshserver = upgradev2server | |
|
88 | 119 | elif servermode: |
|
89 | 120 | raise error.ProgrammingError(b'unknown server mode: %s' % servermode) |
|
90 | 121 | |
|
91 | 122 | peermode = ui.config(b'sshpeer', b'mode') |
|
92 | 123 | |
|
93 | 124 | if peermode == b'extra-handshake-commands': |
|
94 | 125 | extensions.wrapfunction(sshpeer, '_performhandshake', performhandshake) |
|
95 | 126 | elif peermode: |
|
96 | 127 | raise error.ProgrammingError(b'unknown peer mode: %s' % peermode) |
@@ -1,390 +1,494 b'' | |||
|
1 | 1 | $ cat >> $HGRCPATH << EOF |
|
2 | 2 | > [ui] |
|
3 | 3 | > ssh = $PYTHON "$TESTDIR/dummyssh" |
|
4 | 4 | > [devel] |
|
5 | 5 | > debug.peer-request = true |
|
6 | 6 | > [extensions] |
|
7 | 7 | > sshprotoext = $TESTDIR/sshprotoext.py |
|
8 | 8 | > EOF |
|
9 | 9 | |
|
10 | 10 | $ hg init server |
|
11 | 11 | $ cd server |
|
12 | 12 | $ echo 0 > foo |
|
13 | 13 | $ hg -q add foo |
|
14 | 14 | $ hg commit -m initial |
|
15 | 15 | $ cd .. |
|
16 | 16 | |
|
17 | 17 | Test a normal behaving server, for sanity |
|
18 | 18 | |
|
19 | 19 | $ hg --debug debugpeer ssh://user@dummy/server |
|
20 | 20 | running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) |
|
21 | 21 | devel-peer-request: hello |
|
22 | 22 | sending hello command |
|
23 | 23 | devel-peer-request: between |
|
24 | 24 | devel-peer-request: pairs: 81 bytes |
|
25 | 25 | sending between command |
|
26 | 26 | remote: 384 |
|
27 | 27 | remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
28 | 28 | remote: 1 |
|
29 | 29 | url: ssh://user@dummy/server |
|
30 | 30 | local: no |
|
31 | 31 | pushable: yes |
|
32 | 32 | |
|
33 | 33 | Server should answer the "hello" command in isolation |
|
34 | 34 | |
|
35 | 35 | $ hg -R server serve --stdio << EOF |
|
36 | 36 | > hello |
|
37 | 37 | > EOF |
|
38 | 38 | 384 |
|
39 | 39 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
40 | 40 | |
|
41 | 41 | >=0.9.1 clients send a "hello" + "between" for the null range as part of handshake. |
|
42 | 42 | Server should reply with capabilities and should send "1\n\n" as a successful |
|
43 | 43 | reply with empty response to the "between". |
|
44 | 44 | |
|
45 | 45 | $ hg -R server serve --stdio << EOF |
|
46 | 46 | > hello |
|
47 | 47 | > between |
|
48 | 48 | > pairs 81 |
|
49 | 49 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 |
|
50 | 50 | > EOF |
|
51 | 51 | 384 |
|
52 | 52 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
53 | 53 | 1 |
|
54 | 54 | |
|
55 | 55 | |
|
56 | 56 | SSH banner is not printed by default, ignored by clients |
|
57 | 57 | |
|
58 | 58 | $ SSHSERVERMODE=banner hg debugpeer ssh://user@dummy/server |
|
59 | 59 | url: ssh://user@dummy/server |
|
60 | 60 | local: no |
|
61 | 61 | pushable: yes |
|
62 | 62 | |
|
63 | 63 | --debug will print the banner |
|
64 | 64 | |
|
65 | 65 | $ SSHSERVERMODE=banner hg --debug debugpeer ssh://user@dummy/server |
|
66 | 66 | running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) |
|
67 | 67 | devel-peer-request: hello |
|
68 | 68 | sending hello command |
|
69 | 69 | devel-peer-request: between |
|
70 | 70 | devel-peer-request: pairs: 81 bytes |
|
71 | 71 | sending between command |
|
72 | 72 | remote: banner: line 0 |
|
73 | 73 | remote: banner: line 1 |
|
74 | 74 | remote: banner: line 2 |
|
75 | 75 | remote: banner: line 3 |
|
76 | 76 | remote: banner: line 4 |
|
77 | 77 | remote: banner: line 5 |
|
78 | 78 | remote: banner: line 6 |
|
79 | 79 | remote: banner: line 7 |
|
80 | 80 | remote: banner: line 8 |
|
81 | 81 | remote: banner: line 9 |
|
82 | 82 | remote: 384 |
|
83 | 83 | remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
84 | 84 | remote: 1 |
|
85 | 85 | url: ssh://user@dummy/server |
|
86 | 86 | local: no |
|
87 | 87 | pushable: yes |
|
88 | 88 | |
|
89 | 89 | And test the banner with the raw protocol |
|
90 | 90 | |
|
91 | 91 | $ SSHSERVERMODE=banner hg -R server serve --stdio << EOF |
|
92 | 92 | > hello |
|
93 | 93 | > between |
|
94 | 94 | > pairs 81 |
|
95 | 95 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 |
|
96 | 96 | > EOF |
|
97 | 97 | banner: line 0 |
|
98 | 98 | banner: line 1 |
|
99 | 99 | banner: line 2 |
|
100 | 100 | banner: line 3 |
|
101 | 101 | banner: line 4 |
|
102 | 102 | banner: line 5 |
|
103 | 103 | banner: line 6 |
|
104 | 104 | banner: line 7 |
|
105 | 105 | banner: line 8 |
|
106 | 106 | banner: line 9 |
|
107 | 107 | 384 |
|
108 | 108 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
109 | 109 | 1 |
|
110 | 110 | |
|
111 | 111 | |
|
112 | 112 | Connecting to a <0.9.1 server that doesn't support the hello command. |
|
113 | 113 | The client should refuse, as we dropped support for connecting to such |
|
114 | 114 | servers. |
|
115 | 115 | |
|
116 | 116 | $ SSHSERVERMODE=no-hello hg --debug debugpeer ssh://user@dummy/server |
|
117 | 117 | running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) |
|
118 | 118 | devel-peer-request: hello |
|
119 | 119 | sending hello command |
|
120 | 120 | devel-peer-request: between |
|
121 | 121 | devel-peer-request: pairs: 81 bytes |
|
122 | 122 | sending between command |
|
123 | 123 | remote: 0 |
|
124 | 124 | remote: 1 |
|
125 | 125 | abort: no suitable response from remote hg! |
|
126 | 126 | [255] |
|
127 | 127 | |
|
128 | 128 | Sending an unknown command to the server results in an empty response to that command |
|
129 | 129 | |
|
130 | 130 | $ hg -R server serve --stdio << EOF |
|
131 | 131 | > pre-hello |
|
132 | 132 | > hello |
|
133 | 133 | > between |
|
134 | 134 | > pairs 81 |
|
135 | 135 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 |
|
136 | 136 | > EOF |
|
137 | 137 | 0 |
|
138 | 138 | 384 |
|
139 | 139 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
140 | 140 | 1 |
|
141 | 141 | |
|
142 | 142 | |
|
143 | 143 | $ hg --config sshpeer.mode=extra-handshake-commands --config sshpeer.handshake-mode=pre-no-args --debug debugpeer ssh://user@dummy/server |
|
144 | 144 | running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) |
|
145 | 145 | sending no-args command |
|
146 | 146 | devel-peer-request: hello |
|
147 | 147 | sending hello command |
|
148 | 148 | devel-peer-request: between |
|
149 | 149 | devel-peer-request: pairs: 81 bytes |
|
150 | 150 | sending between command |
|
151 | 151 | remote: 0 |
|
152 | 152 | remote: 384 |
|
153 | 153 | remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
154 | 154 | remote: 1 |
|
155 | 155 | url: ssh://user@dummy/server |
|
156 | 156 | local: no |
|
157 | 157 | pushable: yes |
|
158 | 158 | |
|
159 | 159 | Send multiple unknown commands before hello |
|
160 | 160 | |
|
161 | 161 | $ hg -R server serve --stdio << EOF |
|
162 | 162 | > unknown1 |
|
163 | 163 | > unknown2 |
|
164 | 164 | > unknown3 |
|
165 | 165 | > hello |
|
166 | 166 | > between |
|
167 | 167 | > pairs 81 |
|
168 | 168 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 |
|
169 | 169 | > EOF |
|
170 | 170 | 0 |
|
171 | 171 | 0 |
|
172 | 172 | 0 |
|
173 | 173 | 384 |
|
174 | 174 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
175 | 175 | 1 |
|
176 | 176 | |
|
177 | 177 | |
|
178 | 178 | $ hg --config sshpeer.mode=extra-handshake-commands --config sshpeer.handshake-mode=pre-multiple-no-args --debug debugpeer ssh://user@dummy/server |
|
179 | 179 | running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) |
|
180 | 180 | sending unknown1 command |
|
181 | 181 | sending unknown2 command |
|
182 | 182 | sending unknown3 command |
|
183 | 183 | devel-peer-request: hello |
|
184 | 184 | sending hello command |
|
185 | 185 | devel-peer-request: between |
|
186 | 186 | devel-peer-request: pairs: 81 bytes |
|
187 | 187 | sending between command |
|
188 | 188 | remote: 0 |
|
189 | 189 | remote: 0 |
|
190 | 190 | remote: 0 |
|
191 | 191 | remote: 384 |
|
192 | 192 | remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
193 | 193 | remote: 1 |
|
194 | 194 | url: ssh://user@dummy/server |
|
195 | 195 | local: no |
|
196 | 196 | pushable: yes |
|
197 | 197 | |
|
198 | 198 | Send an unknown command before hello that has arguments |
|
199 | 199 | |
|
200 | 200 | $ hg -R server serve --stdio << EOF |
|
201 | 201 | > with-args |
|
202 | 202 | > foo 13 |
|
203 | 203 | > value for foo |
|
204 | 204 | > bar 13 |
|
205 | 205 | > value for bar |
|
206 | 206 | > hello |
|
207 | 207 | > between |
|
208 | 208 | > pairs 81 |
|
209 | 209 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 |
|
210 | 210 | > EOF |
|
211 | 211 | 0 |
|
212 | 212 | 0 |
|
213 | 213 | 0 |
|
214 | 214 | 0 |
|
215 | 215 | 0 |
|
216 | 216 | 384 |
|
217 | 217 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
218 | 218 | 1 |
|
219 | 219 | |
|
220 | 220 | |
|
221 | 221 | Send an unknown command having an argument that looks numeric |
|
222 | 222 | |
|
223 | 223 | $ hg -R server serve --stdio << EOF |
|
224 | 224 | > unknown |
|
225 | 225 | > foo 1 |
|
226 | 226 | > 0 |
|
227 | 227 | > hello |
|
228 | 228 | > between |
|
229 | 229 | > pairs 81 |
|
230 | 230 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 |
|
231 | 231 | > EOF |
|
232 | 232 | 0 |
|
233 | 233 | 0 |
|
234 | 234 | 0 |
|
235 | 235 | 384 |
|
236 | 236 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
237 | 237 | 1 |
|
238 | 238 | |
|
239 | 239 | |
|
240 | 240 | $ hg -R server serve --stdio << EOF |
|
241 | 241 | > unknown |
|
242 | 242 | > foo 1 |
|
243 | 243 | > 1 |
|
244 | 244 | > hello |
|
245 | 245 | > between |
|
246 | 246 | > pairs 81 |
|
247 | 247 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 |
|
248 | 248 | > EOF |
|
249 | 249 | 0 |
|
250 | 250 | 0 |
|
251 | 251 | 0 |
|
252 | 252 | 384 |
|
253 | 253 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
254 | 254 | 1 |
|
255 | 255 | |
|
256 | 256 | |
|
257 | 257 | When sending a dict argument value, it is serialized to |
|
258 | 258 | "<arg> <item count>" followed by "<key> <len>\n<value>" for each item |
|
259 | 259 | in the dict. |
|
260 | 260 | |
|
261 | 261 | Dictionary value for unknown command |
|
262 | 262 | |
|
263 | 263 | $ hg -R server serve --stdio << EOF |
|
264 | 264 | > unknown |
|
265 | 265 | > dict 3 |
|
266 | 266 | > key1 3 |
|
267 | 267 | > foo |
|
268 | 268 | > key2 3 |
|
269 | 269 | > bar |
|
270 | 270 | > key3 3 |
|
271 | 271 | > baz |
|
272 | 272 | > hello |
|
273 | 273 | > EOF |
|
274 | 274 | 0 |
|
275 | 275 | 0 |
|
276 | 276 | 0 |
|
277 | 277 | 0 |
|
278 | 278 | 0 |
|
279 | 279 | 0 |
|
280 | 280 | 0 |
|
281 | 281 | 0 |
|
282 | 282 | 384 |
|
283 | 283 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
284 | 284 | |
|
285 | 285 | Incomplete dictionary send |
|
286 | 286 | |
|
287 | 287 | $ hg -R server serve --stdio << EOF |
|
288 | 288 | > unknown |
|
289 | 289 | > dict 3 |
|
290 | 290 | > key1 3 |
|
291 | 291 | > foo |
|
292 | 292 | > EOF |
|
293 | 293 | 0 |
|
294 | 294 | 0 |
|
295 | 295 | 0 |
|
296 | 296 | 0 |
|
297 | 297 | |
|
298 | 298 | Incomplete value send |
|
299 | 299 | |
|
300 | 300 | $ hg -R server serve --stdio << EOF |
|
301 | 301 | > unknown |
|
302 | 302 | > dict 3 |
|
303 | 303 | > key1 3 |
|
304 | 304 | > fo |
|
305 | 305 | > EOF |
|
306 | 306 | 0 |
|
307 | 307 | 0 |
|
308 | 308 | 0 |
|
309 | 309 | 0 |
|
310 | 310 | |
|
311 | 311 | Send a command line with spaces |
|
312 | 312 | |
|
313 | 313 | $ hg -R server serve --stdio << EOF |
|
314 | 314 | > unknown withspace |
|
315 | 315 | > hello |
|
316 | 316 | > between |
|
317 | 317 | > pairs 81 |
|
318 | 318 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 |
|
319 | 319 | > EOF |
|
320 | 320 | 0 |
|
321 | 321 | 384 |
|
322 | 322 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
323 | 323 | 1 |
|
324 | 324 | |
|
325 | 325 | |
|
326 | 326 | $ hg -R server serve --stdio << EOF |
|
327 | 327 | > unknown with multiple spaces |
|
328 | 328 | > hello |
|
329 | 329 | > between |
|
330 | 330 | > pairs 81 |
|
331 | 331 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 |
|
332 | 332 | > EOF |
|
333 | 333 | 0 |
|
334 | 334 | 384 |
|
335 | 335 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
336 | 336 | 1 |
|
337 | 337 | |
|
338 | 338 | |
|
339 | 339 | $ hg -R server serve --stdio << EOF |
|
340 | 340 | > unknown with spaces |
|
341 | 341 | > key 10 |
|
342 | 342 | > some value |
|
343 | 343 | > hello |
|
344 | 344 | > between |
|
345 | 345 | > pairs 81 |
|
346 | 346 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 |
|
347 | 347 | > EOF |
|
348 | 348 | 0 |
|
349 | 349 | 0 |
|
350 | 350 | 0 |
|
351 | 351 | 384 |
|
352 | 352 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
353 | 353 | 1 |
|
354 | 354 | |
|
355 | 355 | |
|
356 | 356 | Send an unknown command after the "between" |
|
357 | 357 | |
|
358 | 358 | $ hg -R server serve --stdio << EOF |
|
359 | 359 | > hello |
|
360 | 360 | > between |
|
361 | 361 | > pairs 81 |
|
362 | 362 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000unknown |
|
363 | 363 | > EOF |
|
364 | 364 | 384 |
|
365 | 365 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
366 | 366 | 1 |
|
367 | 367 | |
|
368 | 368 | 0 |
|
369 | 369 | |
|
370 | 370 | And one with arguments |
|
371 | 371 | |
|
372 | 372 | $ hg -R server serve --stdio << EOF |
|
373 | 373 | > hello |
|
374 | 374 | > between |
|
375 | 375 | > pairs 81 |
|
376 | 376 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000unknown |
|
377 | 377 | > foo 5 |
|
378 | 378 | > value |
|
379 | 379 | > bar 3 |
|
380 | 380 | > baz |
|
381 | 381 | > EOF |
|
382 | 382 | 384 |
|
383 | 383 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN |
|
384 | 384 | 1 |
|
385 | 385 | |
|
386 | 386 | 0 |
|
387 | 387 | 0 |
|
388 | 388 | 0 |
|
389 | 389 | 0 |
|
390 | 390 | 0 |
|
391 | ||
|
392 | Send an upgrade request to a server that doesn't support that command | |
|
393 | ||
|
394 | $ hg -R server serve --stdio << EOF | |
|
395 | > upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=irrelevant1%2Cirrelevant2 | |
|
396 | > hello | |
|
397 | > between | |
|
398 | > pairs 81 | |
|
399 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 | |
|
400 | > EOF | |
|
401 | 0 | |
|
402 | 384 | |
|
403 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN | |
|
404 | 1 | |
|
405 | ||
|
406 | ||
|
407 | $ hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server | |
|
408 | running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) | |
|
409 | sending upgrade request: * proto=exp-ssh-v2-0001 (glob) | |
|
410 | devel-peer-request: hello | |
|
411 | sending hello command | |
|
412 | devel-peer-request: between | |
|
413 | devel-peer-request: pairs: 81 bytes | |
|
414 | sending between command | |
|
415 | remote: 0 | |
|
416 | remote: 384 | |
|
417 | remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN | |
|
418 | remote: 1 | |
|
419 | url: ssh://user@dummy/server | |
|
420 | local: no | |
|
421 | pushable: yes | |
|
422 | ||
|
423 | Send an upgrade request to a server that supports upgrade | |
|
424 | ||
|
425 | $ SSHSERVERMODE=upgradev2 hg -R server serve --stdio << EOF | |
|
426 | > upgrade this-is-some-token proto=exp-ssh-v2-0001 | |
|
427 | > hello | |
|
428 | > between | |
|
429 | > pairs 81 | |
|
430 | > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 | |
|
431 | > EOF | |
|
432 | upgraded this-is-some-token exp-ssh-v2-0001 | |
|
433 | 383 | |
|
434 | capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN | |
|
435 | ||
|
436 | $ SSHSERVERMODE=upgradev2 hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server | |
|
437 | running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) | |
|
438 | sending upgrade request: * proto=exp-ssh-v2-0001 (glob) | |
|
439 | devel-peer-request: hello | |
|
440 | sending hello command | |
|
441 | devel-peer-request: between | |
|
442 | devel-peer-request: pairs: 81 bytes | |
|
443 | sending between command | |
|
444 | protocol upgraded to exp-ssh-v2-0001 | |
|
445 | url: ssh://user@dummy/server | |
|
446 | local: no | |
|
447 | pushable: yes | |
|
448 | ||
|
449 | Verify the peer has capabilities | |
|
450 | ||
|
451 | $ SSHSERVERMODE=upgradev2 hg --config experimental.sshpeer.advertise-v2=true --debug debugcapabilities ssh://user@dummy/server | |
|
452 | running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) | |
|
453 | sending upgrade request: * proto=exp-ssh-v2-0001 (glob) | |
|
454 | devel-peer-request: hello | |
|
455 | sending hello command | |
|
456 | devel-peer-request: between | |
|
457 | devel-peer-request: pairs: 81 bytes | |
|
458 | sending between command | |
|
459 | protocol upgraded to exp-ssh-v2-0001 | |
|
460 | Main capabilities: | |
|
461 | batch | |
|
462 | branchmap | |
|
463 | $USUAL_BUNDLE2_CAPS_SERVER$ | |
|
464 | changegroupsubset | |
|
465 | getbundle | |
|
466 | known | |
|
467 | lookup | |
|
468 | pushkey | |
|
469 | streamreqs=generaldelta,revlogv1 | |
|
470 | unbundle=HG10GZ,HG10BZ,HG10UN | |
|
471 | unbundlehash | |
|
472 | Bundle2 capabilities: | |
|
473 | HG20 | |
|
474 | bookmarks | |
|
475 | changegroup | |
|
476 | 01 | |
|
477 | 02 | |
|
478 | digests | |
|
479 | md5 | |
|
480 | sha1 | |
|
481 | sha512 | |
|
482 | error | |
|
483 | abort | |
|
484 | unsupportedcontent | |
|
485 | pushraced | |
|
486 | pushkey | |
|
487 | hgtagsfnodes | |
|
488 | listkeys | |
|
489 | phases | |
|
490 | heads | |
|
491 | pushkey | |
|
492 | remote-changegroup | |
|
493 | http | |
|
494 | https |
General Comments 0
You need to be logged in to leave comments.
Login now