##// END OF EJS Templates
sshpeer: add support for request tracing...
Boris Feld -
r35717:f7ef49e4 default
parent child Browse files
Show More
@@ -1,362 +1,373 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 11
12 12 from .i18n import _
13 13 from . import (
14 14 error,
15 15 pycompat,
16 16 util,
17 17 wireproto,
18 18 )
19 19
20 20 def _serverquote(s):
21 21 """quote a string for the remote shell ... which we assume is sh"""
22 22 if not s:
23 23 return s
24 24 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
25 25 return s
26 26 return "'%s'" % s.replace("'", "'\\''")
27 27
28 28 def _forwardoutput(ui, pipe):
29 29 """display all data currently available on pipe as remote output.
30 30
31 31 This is non blocking."""
32 32 s = util.readpipe(pipe)
33 33 if s:
34 34 for l in s.splitlines():
35 35 ui.status(_("remote: "), l, '\n')
36 36
37 37 class doublepipe(object):
38 38 """Operate a side-channel pipe in addition of a main one
39 39
40 40 The side-channel pipe contains server output to be forwarded to the user
41 41 input. The double pipe will behave as the "main" pipe, but will ensure the
42 42 content of the "side" pipe is properly processed while we wait for blocking
43 43 call on the "main" pipe.
44 44
45 45 If large amounts of data are read from "main", the forward will cease after
46 46 the first bytes start to appear. This simplifies the implementation
47 47 without affecting actual output of sshpeer too much as we rarely issue
48 48 large read for data not yet emitted by the server.
49 49
50 50 The main pipe is expected to be a 'bufferedinputpipe' from the util module
51 51 that handle all the os specific bits. This class lives in this module
52 52 because it focus on behavior specific to the ssh protocol."""
53 53
54 54 def __init__(self, ui, main, side):
55 55 self._ui = ui
56 56 self._main = main
57 57 self._side = side
58 58
59 59 def _wait(self):
60 60 """wait until some data are available on main or side
61 61
62 62 return a pair of boolean (ismainready, issideready)
63 63
64 64 (This will only wait for data if the setup is supported by `util.poll`)
65 65 """
66 66 if getattr(self._main, 'hasbuffer', False): # getattr for classic pipe
67 67 return (True, True) # main has data, assume side is worth poking at.
68 68 fds = [self._main.fileno(), self._side.fileno()]
69 69 try:
70 70 act = util.poll(fds)
71 71 except NotImplementedError:
72 72 # non supported yet case, assume all have data.
73 73 act = fds
74 74 return (self._main.fileno() in act, self._side.fileno() in act)
75 75
76 76 def write(self, data):
77 77 return self._call('write', data)
78 78
79 79 def read(self, size):
80 80 r = self._call('read', size)
81 81 if size != 0 and not r:
82 82 # We've observed a condition that indicates the
83 83 # stdout closed unexpectedly. Check stderr one
84 84 # more time and snag anything that's there before
85 85 # letting anyone know the main part of the pipe
86 86 # closed prematurely.
87 87 _forwardoutput(self._ui, self._side)
88 88 return r
89 89
90 90 def readline(self):
91 91 return self._call('readline')
92 92
93 93 def _call(self, methname, data=None):
94 94 """call <methname> on "main", forward output of "side" while blocking
95 95 """
96 96 # data can be '' or 0
97 97 if (data is not None and not data) or self._main.closed:
98 98 _forwardoutput(self._ui, self._side)
99 99 return ''
100 100 while True:
101 101 mainready, sideready = self._wait()
102 102 if sideready:
103 103 _forwardoutput(self._ui, self._side)
104 104 if mainready:
105 105 meth = getattr(self._main, methname)
106 106 if data is None:
107 107 return meth()
108 108 else:
109 109 return meth(data)
110 110
111 111 def close(self):
112 112 return self._main.close()
113 113
114 114 def flush(self):
115 115 return self._main.flush()
116 116
117 117 class sshpeer(wireproto.wirepeer):
118 118 def __init__(self, ui, path, create=False):
119 119 self._url = path
120 120 self._ui = ui
121 121 self._pipeo = self._pipei = self._pipee = None
122 122
123 123 u = util.url(path, parsequery=False, parsefragment=False)
124 124 if u.scheme != 'ssh' or not u.host or u.path is None:
125 125 self._abort(error.RepoError(_("couldn't parse location %s") % path))
126 126
127 127 util.checksafessh(path)
128 128
129 129 if u.passwd is not None:
130 130 self._abort(error.RepoError(_("password in URL not supported")))
131 131
132 132 self._user = u.user
133 133 self._host = u.host
134 134 self._port = u.port
135 135 self._path = u.path or '.'
136 136
137 137 sshcmd = self.ui.config("ui", "ssh")
138 138 remotecmd = self.ui.config("ui", "remotecmd")
139 139 sshaddenv = dict(self.ui.configitems("sshenv"))
140 140 sshenv = util.shellenviron(sshaddenv)
141 141
142 142 args = util.sshargs(sshcmd, self._host, self._user, self._port)
143 143
144 144 if create:
145 145 cmd = '%s %s %s' % (sshcmd, args,
146 146 util.shellquote("%s init %s" %
147 147 (_serverquote(remotecmd), _serverquote(self._path))))
148 148 ui.debug('running %s\n' % cmd)
149 149 res = ui.system(cmd, blockedtag='sshpeer', environ=sshenv)
150 150 if res != 0:
151 151 self._abort(error.RepoError(_("could not create remote repo")))
152 152
153 153 self._validaterepo(sshcmd, args, remotecmd, sshenv)
154 154
155 155 # Begin of _basepeer interface.
156 156
157 157 @util.propertycache
158 158 def ui(self):
159 159 return self._ui
160 160
161 161 def url(self):
162 162 return self._url
163 163
164 164 def local(self):
165 165 return None
166 166
167 167 def peer(self):
168 168 return self
169 169
170 170 def canpush(self):
171 171 return True
172 172
173 173 def close(self):
174 174 pass
175 175
176 176 # End of _basepeer interface.
177 177
178 178 # Begin of _basewirecommands interface.
179 179
180 180 def capabilities(self):
181 181 return self._caps
182 182
183 183 # End of _basewirecommands interface.
184 184
185 185 def _validaterepo(self, sshcmd, args, remotecmd, sshenv=None):
186 186 # cleanup up previous run
187 187 self._cleanup()
188 188
189 189 cmd = '%s %s %s' % (sshcmd, args,
190 190 util.shellquote("%s -R %s serve --stdio" %
191 191 (_serverquote(remotecmd), _serverquote(self._path))))
192 192 self.ui.debug('running %s\n' % cmd)
193 193 cmd = util.quotecommand(cmd)
194 194
195 195 # while self._subprocess isn't used, having it allows the subprocess to
196 196 # to clean up correctly later
197 197 #
198 198 # no buffer allow the use of 'select'
199 199 # feel free to remove buffering and select usage when we ultimately
200 200 # move to threading.
201 201 sub = util.popen4(cmd, bufsize=0, env=sshenv)
202 202 self._pipeo, self._pipei, self._pipee, self._subprocess = sub
203 203
204 204 self._pipei = util.bufferedinputpipe(self._pipei)
205 205 self._pipei = doublepipe(self.ui, self._pipei, self._pipee)
206 206 self._pipeo = doublepipe(self.ui, self._pipeo, self._pipee)
207 207
208 208 def badresponse():
209 209 msg = _("no suitable response from remote hg")
210 210 hint = self.ui.config("ui", "ssherrorhint")
211 211 self._abort(error.RepoError(msg, hint=hint))
212 212
213 213 try:
214 214 # skip any noise generated by remote shell
215 215 self._callstream("hello")
216 216 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
217 217 except IOError:
218 218 badresponse()
219 219
220 220 lines = ["", "dummy"]
221 221 max_noise = 500
222 222 while lines[-1] and max_noise:
223 223 try:
224 224 l = r.readline()
225 225 self._readerr()
226 226 if lines[-1] == "1\n" and l == "\n":
227 227 break
228 228 if l:
229 229 self.ui.debug("remote: ", l)
230 230 lines.append(l)
231 231 max_noise -= 1
232 232 except IOError:
233 233 badresponse()
234 234 else:
235 235 badresponse()
236 236
237 237 self._caps = set()
238 238 for l in reversed(lines):
239 239 if l.startswith("capabilities:"):
240 240 self._caps.update(l[:-1].split(":")[1].split())
241 241 break
242 242
243 243 def _readerr(self):
244 244 _forwardoutput(self.ui, self._pipee)
245 245
246 246 def _abort(self, exception):
247 247 self._cleanup()
248 248 raise exception
249 249
250 250 def _cleanup(self):
251 251 if self._pipeo is None:
252 252 return
253 253 self._pipeo.close()
254 254 self._pipei.close()
255 255 try:
256 256 # read the error descriptor until EOF
257 257 for l in self._pipee:
258 258 self.ui.status(_("remote: "), l)
259 259 except (IOError, ValueError):
260 260 pass
261 261 self._pipee.close()
262 262
263 263 __del__ = _cleanup
264 264
265 265 def _submitbatch(self, req):
266 266 rsp = self._callstream("batch", cmds=wireproto.encodebatchcmds(req))
267 267 available = self._getamount()
268 268 # TODO this response parsing is probably suboptimal for large
269 269 # batches with large responses.
270 270 toread = min(available, 1024)
271 271 work = rsp.read(toread)
272 272 available -= toread
273 273 chunk = work
274 274 while chunk:
275 275 while ';' in work:
276 276 one, work = work.split(';', 1)
277 277 yield wireproto.unescapearg(one)
278 278 toread = min(available, 1024)
279 279 chunk = rsp.read(toread)
280 280 available -= toread
281 281 work += chunk
282 282 yield wireproto.unescapearg(work)
283 283
284 284 def _callstream(self, cmd, **args):
285 285 args = pycompat.byteskwargs(args)
286 if (self.ui.debugflag
287 and self.ui.configbool('devel', 'debug.peer-request')):
288 dbg = self.ui.debug
289 line = 'devel-peer-request: %s\n'
290 dbg(line % cmd)
291 for key, value in sorted(args.items()):
292 if not isinstance(value, dict):
293 dbg(line % ' %s: %d bytes' % (key, len(value)))
294 else:
295 for dk, dv in sorted(value.items()):
296 dbg(line % ' %s-%s: %d' % (key, dk, len(dv)))
286 297 self.ui.debug("sending %s command\n" % cmd)
287 298 self._pipeo.write("%s\n" % cmd)
288 299 _func, names = wireproto.commands[cmd]
289 300 keys = names.split()
290 301 wireargs = {}
291 302 for k in keys:
292 303 if k == '*':
293 304 wireargs['*'] = args
294 305 break
295 306 else:
296 307 wireargs[k] = args[k]
297 308 del args[k]
298 309 for k, v in sorted(wireargs.iteritems()):
299 310 self._pipeo.write("%s %d\n" % (k, len(v)))
300 311 if isinstance(v, dict):
301 312 for dk, dv in v.iteritems():
302 313 self._pipeo.write("%s %d\n" % (dk, len(dv)))
303 314 self._pipeo.write(dv)
304 315 else:
305 316 self._pipeo.write(v)
306 317 self._pipeo.flush()
307 318
308 319 return self._pipei
309 320
310 321 def _callcompressable(self, cmd, **args):
311 322 return self._callstream(cmd, **args)
312 323
313 324 def _call(self, cmd, **args):
314 325 self._callstream(cmd, **args)
315 326 return self._recv()
316 327
317 328 def _callpush(self, cmd, fp, **args):
318 329 r = self._call(cmd, **args)
319 330 if r:
320 331 return '', r
321 332 for d in iter(lambda: fp.read(4096), ''):
322 333 self._send(d)
323 334 self._send("", flush=True)
324 335 r = self._recv()
325 336 if r:
326 337 return '', r
327 338 return self._recv(), ''
328 339
329 340 def _calltwowaystream(self, cmd, fp, **args):
330 341 r = self._call(cmd, **args)
331 342 if r:
332 343 # XXX needs to be made better
333 344 raise error.Abort(_('unexpected remote reply: %s') % r)
334 345 for d in iter(lambda: fp.read(4096), ''):
335 346 self._send(d)
336 347 self._send("", flush=True)
337 348 return self._pipei
338 349
339 350 def _getamount(self):
340 351 l = self._pipei.readline()
341 352 if l == '\n':
342 353 self._readerr()
343 354 msg = _('check previous remote output')
344 355 self._abort(error.OutOfBandError(hint=msg))
345 356 self._readerr()
346 357 try:
347 358 return int(l)
348 359 except ValueError:
349 360 self._abort(error.ResponseError(_("unexpected response:"), l))
350 361
351 362 def _recv(self):
352 363 return self._pipei.read(self._getamount())
353 364
354 365 def _send(self, data, flush=False):
355 366 self._pipeo.write("%d\n" % len(data))
356 367 if data:
357 368 self._pipeo.write(data)
358 369 if flush:
359 370 self._pipeo.flush()
360 371 self._readerr()
361 372
362 373 instance = sshpeer
@@ -1,616 +1,629 b''
1 1
2 2 This test tries to exercise the ssh functionality with a dummy script
3 3
4 4 $ cat <<EOF >> $HGRCPATH
5 5 > [format]
6 6 > usegeneraldelta=yes
7 7 > EOF
8 8
9 9 creating 'remote' repo
10 10
11 11 $ hg init remote
12 12 $ cd remote
13 13 $ echo this > foo
14 14 $ echo this > fooO
15 15 $ hg ci -A -m "init" foo fooO
16 16
17 17 insert a closed branch (issue4428)
18 18
19 19 $ hg up null
20 20 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
21 21 $ hg branch closed
22 22 marked working directory as branch closed
23 23 (branches are permanent and global, did you want a bookmark?)
24 24 $ hg ci -mc0
25 25 $ hg ci --close-branch -mc1
26 26 $ hg up -q default
27 27
28 28 configure for serving
29 29
30 30 $ cat <<EOF > .hg/hgrc
31 31 > [server]
32 32 > uncompressed = True
33 33 >
34 34 > [hooks]
35 35 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
36 36 > EOF
37 37 $ cd ..
38 38
39 39 repo not found error
40 40
41 41 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
42 42 remote: abort: repository nonexistent not found!
43 43 abort: no suitable response from remote hg!
44 44 [255]
45 45
46 46 non-existent absolute path
47 47
48 48 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
49 49 remote: abort: repository $TESTTMP/nonexistent not found!
50 50 abort: no suitable response from remote hg!
51 51 [255]
52 52
53 53 clone remote via stream
54 54
55 55 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
56 56 streaming all changes
57 57 4 files to transfer, 602 bytes of data
58 58 transferred 602 bytes in * seconds (*) (glob)
59 59 searching for changes
60 60 no changes found
61 61 updating to branch default
62 62 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 63 $ cd local-stream
64 64 $ hg verify
65 65 checking changesets
66 66 checking manifests
67 67 crosschecking files in changesets and manifests
68 68 checking files
69 69 2 files, 3 changesets, 2 total revisions
70 70 $ hg branches
71 71 default 0:1160648e36ce
72 72 $ cd ..
73 73
74 74 clone bookmarks via stream
75 75
76 76 $ hg -R local-stream book mybook
77 77 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
78 78 streaming all changes
79 79 4 files to transfer, 602 bytes of data
80 80 transferred 602 bytes in * seconds (*) (glob)
81 81 searching for changes
82 82 no changes found
83 83 updating to branch default
84 84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 85 $ cd stream2
86 86 $ hg book
87 87 mybook 0:1160648e36ce
88 88 $ cd ..
89 89 $ rm -rf local-stream stream2
90 90
91 91 clone remote via pull
92 92
93 93 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
94 94 requesting all changes
95 95 adding changesets
96 96 adding manifests
97 97 adding file changes
98 98 added 3 changesets with 2 changes to 2 files
99 99 new changesets 1160648e36ce:ad076bfb429d
100 100 updating to branch default
101 101 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 102
103 103 verify
104 104
105 105 $ cd local
106 106 $ hg verify
107 107 checking changesets
108 108 checking manifests
109 109 crosschecking files in changesets and manifests
110 110 checking files
111 111 2 files, 3 changesets, 2 total revisions
112 112 $ cat >> .hg/hgrc <<EOF
113 113 > [hooks]
114 114 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
115 115 > EOF
116 116
117 117 empty default pull
118 118
119 119 $ hg paths
120 120 default = ssh://user@dummy/remote
121 121 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
122 122 pulling from ssh://user@dummy/remote
123 123 searching for changes
124 124 no changes found
125 125
126 126 pull from wrong ssh URL
127 127
128 128 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
129 129 pulling from ssh://user@dummy/doesnotexist
130 130 remote: abort: repository doesnotexist not found!
131 131 abort: no suitable response from remote hg!
132 132 [255]
133 133
134 134 local change
135 135
136 136 $ echo bleah > foo
137 137 $ hg ci -m "add"
138 138
139 139 updating rc
140 140
141 141 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
142 142 $ echo "[ui]" >> .hg/hgrc
143 143 $ echo "ssh = \"$PYTHON\" \"$TESTDIR/dummyssh\"" >> .hg/hgrc
144 144
145 145 find outgoing
146 146
147 147 $ hg out ssh://user@dummy/remote
148 148 comparing with ssh://user@dummy/remote
149 149 searching for changes
150 150 changeset: 3:a28a9d1a809c
151 151 tag: tip
152 152 parent: 0:1160648e36ce
153 153 user: test
154 154 date: Thu Jan 01 00:00:00 1970 +0000
155 155 summary: add
156 156
157 157
158 158 find incoming on the remote side
159 159
160 160 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
161 161 comparing with ssh://user@dummy/local
162 162 searching for changes
163 163 changeset: 3:a28a9d1a809c
164 164 tag: tip
165 165 parent: 0:1160648e36ce
166 166 user: test
167 167 date: Thu Jan 01 00:00:00 1970 +0000
168 168 summary: add
169 169
170 170
171 171 find incoming on the remote side (using absolute path)
172 172
173 173 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
174 174 comparing with ssh://user@dummy/$TESTTMP/local
175 175 searching for changes
176 176 changeset: 3:a28a9d1a809c
177 177 tag: tip
178 178 parent: 0:1160648e36ce
179 179 user: test
180 180 date: Thu Jan 01 00:00:00 1970 +0000
181 181 summary: add
182 182
183 183
184 184 push
185 185
186 186 $ hg push
187 187 pushing to ssh://user@dummy/remote
188 188 searching for changes
189 189 remote: adding changesets
190 190 remote: adding manifests
191 191 remote: adding file changes
192 192 remote: added 1 changesets with 1 changes to 1 files
193 193 $ cd ../remote
194 194
195 195 check remote tip
196 196
197 197 $ hg tip
198 198 changeset: 3:a28a9d1a809c
199 199 tag: tip
200 200 parent: 0:1160648e36ce
201 201 user: test
202 202 date: Thu Jan 01 00:00:00 1970 +0000
203 203 summary: add
204 204
205 205 $ hg verify
206 206 checking changesets
207 207 checking manifests
208 208 crosschecking files in changesets and manifests
209 209 checking files
210 210 2 files, 4 changesets, 3 total revisions
211 211 $ hg cat -r tip foo
212 212 bleah
213 213 $ echo z > z
214 214 $ hg ci -A -m z z
215 215 created new head
216 216
217 217 test pushkeys and bookmarks
218 218
219 219 $ cd ../local
220 220 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
221 221 bookmarks
222 222 namespaces
223 223 phases
224 224 $ hg book foo -r 0
225 225 $ hg out -B
226 226 comparing with ssh://user@dummy/remote
227 227 searching for changed bookmarks
228 228 foo 1160648e36ce
229 229 $ hg push -B foo
230 230 pushing to ssh://user@dummy/remote
231 231 searching for changes
232 232 no changes found
233 233 exporting bookmark foo
234 234 [1]
235 235 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
236 236 foo 1160648e36cec0054048a7edc4110c6f84fde594
237 237 $ hg book -f foo
238 238 $ hg push --traceback
239 239 pushing to ssh://user@dummy/remote
240 240 searching for changes
241 241 no changes found
242 242 updating bookmark foo
243 243 [1]
244 244 $ hg book -d foo
245 245 $ hg in -B
246 246 comparing with ssh://user@dummy/remote
247 247 searching for changed bookmarks
248 248 foo a28a9d1a809c
249 249 $ hg book -f -r 0 foo
250 250 $ hg pull -B foo
251 251 pulling from ssh://user@dummy/remote
252 252 no changes found
253 253 updating bookmark foo
254 254 $ hg book -d foo
255 255 $ hg push -B foo
256 256 pushing to ssh://user@dummy/remote
257 257 searching for changes
258 258 no changes found
259 259 deleting remote bookmark foo
260 260 [1]
261 261
262 262 a bad, evil hook that prints to stdout
263 263
264 264 $ cat <<EOF > $TESTTMP/badhook
265 265 > import sys
266 266 > sys.stdout.write("KABOOM\n")
267 267 > EOF
268 268
269 269 $ cat <<EOF > $TESTTMP/badpyhook.py
270 270 > import sys
271 271 > def hook(ui, repo, hooktype, **kwargs):
272 272 > sys.stdout.write("KABOOM IN PROCESS\n")
273 273 > EOF
274 274
275 275 $ cat <<EOF >> ../remote/.hg/hgrc
276 276 > [hooks]
277 277 > changegroup.stdout = $PYTHON $TESTTMP/badhook
278 278 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
279 279 > EOF
280 280 $ echo r > r
281 281 $ hg ci -A -m z r
282 282
283 283 push should succeed even though it has an unexpected response
284 284
285 285 $ hg push
286 286 pushing to ssh://user@dummy/remote
287 287 searching for changes
288 288 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
289 289 remote: adding changesets
290 290 remote: adding manifests
291 291 remote: adding file changes
292 292 remote: added 1 changesets with 1 changes to 1 files
293 293 remote: KABOOM
294 294 remote: KABOOM IN PROCESS
295 295 $ hg -R ../remote heads
296 296 changeset: 5:1383141674ec
297 297 tag: tip
298 298 parent: 3:a28a9d1a809c
299 299 user: test
300 300 date: Thu Jan 01 00:00:00 1970 +0000
301 301 summary: z
302 302
303 303 changeset: 4:6c0482d977a3
304 304 parent: 0:1160648e36ce
305 305 user: test
306 306 date: Thu Jan 01 00:00:00 1970 +0000
307 307 summary: z
308 308
309 309
310 310 clone bookmarks
311 311
312 312 $ hg -R ../remote bookmark test
313 313 $ hg -R ../remote bookmarks
314 314 * test 4:6c0482d977a3
315 315 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
316 316 requesting all changes
317 317 adding changesets
318 318 adding manifests
319 319 adding file changes
320 320 added 6 changesets with 5 changes to 4 files (+1 heads)
321 321 new changesets 1160648e36ce:1383141674ec
322 322 updating to branch default
323 323 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
324 324 $ hg -R local-bookmarks bookmarks
325 325 test 4:6c0482d977a3
326 326
327 327 passwords in ssh urls are not supported
328 328 (we use a glob here because different Python versions give different
329 329 results here)
330 330
331 331 $ hg push ssh://user:erroneouspwd@dummy/remote
332 332 pushing to ssh://user:*@dummy/remote (glob)
333 333 abort: password in URL not supported!
334 334 [255]
335 335
336 336 $ cd ..
337 337
338 338 hide outer repo
339 339 $ hg init
340 340
341 341 Test remote paths with spaces (issue2983):
342 342
343 343 $ hg init --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
344 344 $ touch "$TESTTMP/a repo/test"
345 345 $ hg -R 'a repo' commit -A -m "test"
346 346 adding test
347 347 $ hg -R 'a repo' tag tag
348 348 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
349 349 73649e48688a
350 350
351 351 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
352 352 abort: unknown revision 'noNoNO'!
353 353 [255]
354 354
355 355 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
356 356
357 357 $ hg clone --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
358 358 destination directory: a repo
359 359 abort: destination 'a repo' is not empty
360 360 [255]
361 361
362 362 Make sure hg is really paranoid in serve --stdio mode. It used to be
363 363 possible to get a debugger REPL by specifying a repo named --debugger.
364 364 $ hg -R --debugger serve --stdio
365 365 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio']
366 366 [255]
367 367 $ hg -R --config=ui.debugger=yes serve --stdio
368 368 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio']
369 369 [255]
370 370 Abbreviations of 'serve' also don't work, to avoid shenanigans.
371 371 $ hg -R narf serv --stdio
372 372 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
373 373 [255]
374 374
375 375 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
376 376 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
377 377 parameters:
378 378
379 379 $ cat > ssh.sh << EOF
380 380 > userhost="\$1"
381 381 > SSH_ORIGINAL_COMMAND="\$2"
382 382 > export SSH_ORIGINAL_COMMAND
383 383 > PYTHONPATH="$PYTHONPATH"
384 384 > export PYTHONPATH
385 385 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
386 386 > EOF
387 387
388 388 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
389 389 73649e48688a
390 390
391 391 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
392 392 remote: Illegal repository "$TESTTMP/a'repo"
393 393 abort: no suitable response from remote hg!
394 394 [255]
395 395
396 396 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
397 397 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
398 398 abort: no suitable response from remote hg!
399 399 [255]
400 400
401 401 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" $PYTHON "$TESTDIR/../contrib/hg-ssh"
402 402 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
403 403 [255]
404 404
405 405 Test hg-ssh in read-only mode:
406 406
407 407 $ cat > ssh.sh << EOF
408 408 > userhost="\$1"
409 409 > SSH_ORIGINAL_COMMAND="\$2"
410 410 > export SSH_ORIGINAL_COMMAND
411 411 > PYTHONPATH="$PYTHONPATH"
412 412 > export PYTHONPATH
413 413 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
414 414 > EOF
415 415
416 416 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
417 417 requesting all changes
418 418 adding changesets
419 419 adding manifests
420 420 adding file changes
421 421 added 6 changesets with 5 changes to 4 files (+1 heads)
422 422 new changesets 1160648e36ce:1383141674ec
423 423 updating to branch default
424 424 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
425 425
426 426 $ cd read-only-local
427 427 $ echo "baz" > bar
428 428 $ hg ci -A -m "unpushable commit" bar
429 429 $ hg push --ssh "sh ../ssh.sh"
430 430 pushing to ssh://user@dummy/*/remote (glob)
431 431 searching for changes
432 432 remote: Permission denied
433 433 remote: pretxnopen.hg-ssh hook failed
434 434 abort: push failed on remote
435 435 [255]
436 436
437 437 $ cd ..
438 438
439 439 stderr from remote commands should be printed before stdout from local code (issue4336)
440 440
441 441 $ hg clone remote stderr-ordering
442 442 updating to branch default
443 443 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
444 444 $ cd stderr-ordering
445 445 $ cat >> localwrite.py << EOF
446 446 > from mercurial import exchange, extensions
447 447 >
448 448 > def wrappedpush(orig, repo, *args, **kwargs):
449 449 > res = orig(repo, *args, **kwargs)
450 450 > repo.ui.write('local stdout\n')
451 451 > return res
452 452 >
453 453 > def extsetup(ui):
454 454 > extensions.wrapfunction(exchange, 'push', wrappedpush)
455 455 > EOF
456 456
457 457 $ cat >> .hg/hgrc << EOF
458 458 > [paths]
459 459 > default-push = ssh://user@dummy/remote
460 460 > [ui]
461 461 > ssh = "$PYTHON" "$TESTDIR/dummyssh"
462 462 > [extensions]
463 463 > localwrite = localwrite.py
464 464 > EOF
465 465
466 466 $ echo localwrite > foo
467 467 $ hg commit -m 'testing localwrite'
468 468 $ hg push
469 469 pushing to ssh://user@dummy/remote
470 470 searching for changes
471 471 remote: adding changesets
472 472 remote: adding manifests
473 473 remote: adding file changes
474 474 remote: added 1 changesets with 1 changes to 1 files
475 475 remote: KABOOM
476 476 remote: KABOOM IN PROCESS
477 477 local stdout
478 478
479 479 debug output
480 480
481 $ hg pull --debug ssh://user@dummy/remote
481 $ hg pull --debug ssh://user@dummy/remote --config devel.debug.peer-request=yes
482 482 pulling from ssh://user@dummy/remote
483 483 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
484 devel-peer-request: hello
484 485 sending hello command
486 devel-peer-request: between
487 devel-peer-request: pairs: 81 bytes
485 488 sending between command
486 489 remote: 384
487 490 remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS$ unbundle=HG10GZ,HG10BZ,HG10UN
488 491 remote: 1
489 492 query 1; heads
493 devel-peer-request: batch
494 devel-peer-request: cmds: 141 bytes
490 495 sending batch command
491 496 searching for changes
492 497 all remote heads known locally
493 498 no changes found
499 devel-peer-request: getbundle
500 devel-peer-request: bookmarks: 1 bytes
501 devel-peer-request: bundlecaps: 233 bytes
502 devel-peer-request: cg: 1 bytes
503 devel-peer-request: common: 122 bytes
504 devel-peer-request: heads: 122 bytes
505 devel-peer-request: listkeys: 9 bytes
506 devel-peer-request: phases: 1 bytes
494 507 sending getbundle command
495 508 bundle2-input-bundle: with-transaction
496 509 bundle2-input-part: "bookmarks" supported
497 510 bundle2-input-part: total payload size 26
498 511 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
499 512 bundle2-input-part: total payload size 45
500 513 bundle2-input-part: "phase-heads" supported
501 514 bundle2-input-part: total payload size 72
502 515 bundle2-input-bundle: 2 parts total
503 516 checking for updated bookmarks
504 517
505 518 $ cd ..
506 519
507 520 $ cat dummylog
508 521 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
509 522 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
510 523 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
511 524 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
512 525 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
513 526 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
514 527 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
515 528 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
516 529 Got arguments 1:user@dummy 2:hg -R local serve --stdio
517 530 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
518 531 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
519 532 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
520 533 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
521 534 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
522 535 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
523 536 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
524 537 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
525 538 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
526 539 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
527 540 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
528 541 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
529 542 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
530 543 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
531 544 Got arguments 1:user@dummy 2:hg init 'a repo'
532 545 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
533 546 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
534 547 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
535 548 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
536 549 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
537 550 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
538 551 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
539 552
540 553 remote hook failure is attributed to remote
541 554
542 555 $ cat > $TESTTMP/failhook << EOF
543 556 > def hook(ui, repo, **kwargs):
544 557 > ui.write('hook failure!\n')
545 558 > ui.flush()
546 559 > return 1
547 560 > EOF
548 561
549 562 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
550 563
551 564 $ hg -q --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
552 565 $ cd hookout
553 566 $ touch hookfailure
554 567 $ hg -q commit -A -m 'remote hook failure'
555 568 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" push
556 569 pushing to ssh://user@dummy/remote
557 570 searching for changes
558 571 remote: adding changesets
559 572 remote: adding manifests
560 573 remote: adding file changes
561 574 remote: added 1 changesets with 1 changes to 1 files
562 575 remote: hook failure!
563 576 remote: transaction abort!
564 577 remote: rollback completed
565 578 remote: pretxnchangegroup.fail hook failed
566 579 abort: push failed on remote
567 580 [255]
568 581
569 582 abort during pull is properly reported as such
570 583
571 584 $ echo morefoo >> ../remote/foo
572 585 $ hg -R ../remote commit --message "more foo to be pulled"
573 586 $ cat >> ../remote/.hg/hgrc << EOF
574 587 > [extensions]
575 588 > crash = ${TESTDIR}/crashgetbundler.py
576 589 > EOF
577 590 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" pull
578 591 pulling from ssh://user@dummy/remote
579 592 searching for changes
580 593 remote: abort: this is an exercise
581 594 abort: pull failed on remote
582 595 [255]
583 596
584 597 abort with no error hint when there is a ssh problem when pulling
585 598
586 599 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
587 600 pulling from ssh://brokenrepository/
588 601 abort: no suitable response from remote hg!
589 602 [255]
590 603
591 604 abort with configured error hint when there is a ssh problem when pulling
592 605
593 606 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" \
594 607 > --config ui.ssherrorhint="Please see http://company/internalwiki/ssh.html"
595 608 pulling from ssh://brokenrepository/
596 609 abort: no suitable response from remote hg!
597 610 (Please see http://company/internalwiki/ssh.html)
598 611 [255]
599 612
600 613 test that custom environment is passed down to ssh executable
601 614 $ cat >>dumpenv <<EOF
602 615 > #! /bin/sh
603 616 > echo \$VAR >&2
604 617 > EOF
605 618 $ chmod +x dumpenv
606 619 $ hg pull ssh://something --config ui.ssh="sh dumpenv"
607 620 pulling from ssh://something/
608 621 remote:
609 622 abort: no suitable response from remote hg!
610 623 [255]
611 624 $ hg pull ssh://something --config ui.ssh="sh dumpenv" --config sshenv.VAR=17
612 625 pulling from ssh://something/
613 626 remote: 17
614 627 abort: no suitable response from remote hg!
615 628 [255]
616 629
General Comments 0
You need to be logged in to leave comments. Login now