Show More
@@ -574,6 +574,9 b" coreconfigitem('experimental', 'treemani" | |||
|
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, |
@@ -218,6 +218,95 b' part of the response payload and not par' | |||
|
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 | |
@@ -281,6 +370,31 b' 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 |
@@ -8,6 +8,7 b'' | |||
|
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 ( |
@@ -15,6 +16,7 b' from . import (' | |||
|
15 | 16 | pycompat, |
|
16 | 17 | util, |
|
17 | 18 | wireproto, |
|
19 | wireprotoserver, | |
|
18 | 20 | ) |
|
19 | 21 | |
|
20 | 22 | def _serverquote(s): |
@@ -162,15 +164,24 b' def _performhandshake(ui, stdin, stdout,' | |||
|
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 |
@@ -186,6 +197,18 b' def _performhandshake(ui, stdin, stdout,' | |||
|
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 |
@@ -195,6 +218,14 b' def _performhandshake(ui, stdin, stdout,' | |||
|
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 = [ |
@@ -204,6 +235,11 b' def _performhandshake(ui, stdin, stdout,' | |||
|
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') |
@@ -217,12 +253,31 b' def _performhandshake(ui, stdin, stdout,' | |||
|
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: |
@@ -235,20 +290,39 b' def _performhandshake(ui, stdin, stdout,' | |||
|
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 |
@@ -32,6 +32,12 b" 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 |
@@ -53,6 +53,35 b' class prehelloserver(wireprotoserver.ssh' | |||
|
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') |
@@ -85,6 +114,8 b' def extsetup(ui):' | |||
|
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 |
@@ -388,3 +388,107 b' And one with arguments' | |||
|
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