##// END OF EJS Templates
sshpeer: use the doublepipe object for the server to client channel...
Pierre-Yves David -
r25422:8dc5ee5b default
parent child Browse files
Show More
@@ -1,326 +1,327
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 import re
9 9 from i18n import _
10 10 import util, error, wireproto
11 11
12 12 class remotelock(object):
13 13 def __init__(self, repo):
14 14 self.repo = repo
15 15 def release(self):
16 16 self.repo.unlock()
17 17 self.repo = None
18 18 def __del__(self):
19 19 if self.repo:
20 20 self.release()
21 21
22 22 def _serverquote(s):
23 23 if not s:
24 24 return s
25 25 '''quote a string for the remote shell ... which we assume is sh'''
26 26 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
27 27 return s
28 28 return "'%s'" % s.replace("'", "'\\''")
29 29
30 30 def _forwardoutput(ui, pipe):
31 31 """display all data currently available on pipe as remote output.
32 32
33 33 This is non blocking."""
34 34 s = util.readpipe(pipe)
35 35 if s:
36 36 for l in s.splitlines():
37 37 ui.status(_("remote: "), l, '\n')
38 38
39 39 class doublepipe(object):
40 40 """Operate a side-channel pipe in addition of a main one
41 41
42 42 The side-channel pipe contains server output to be forwarded to the user
43 43 input. The double pipe will behave as the "main" pipe, but will ensure the
44 44 content of the "side" pipe is properly processed while we wait for blocking
45 45 call on the "main" pipe.
46 46
47 47 If large amounts of data are read from "main", the forward will cease after
48 48 the first bytes start to appear. This simplifies the implementation
49 49 without affecting actual output of sshpeer too much as we rarely issue
50 50 large read for data not yet emitted by the server.
51 51
52 52 The main pipe is expected to be a 'bufferedinputpipe' from the util module
53 53 that handle all the os specific bites. This class lives in this module
54 54 because it focus on behavior specifig to the ssh protocol."""
55 55
56 56 def __init__(self, ui, main, side):
57 57 self._ui = ui
58 58 self._main = main
59 59 self._side = side
60 60
61 61 def _wait(self):
62 62 """wait until some data are available on main or side
63 63
64 64 return a pair of boolean (ismainready, issideready)
65 65
66 66 (This will only wait for data if the setup is supported by `util.poll`)
67 67 """
68 68 if self._main.hasbuffer:
69 69 return (True, True) # main has data, assume side is worth poking at.
70 70 fds = [self._main.fileno(), self._side.fileno()]
71 71 try:
72 72 act = util.poll(fds)
73 73 except NotImplementedError:
74 74 # non supported yet case, assume all have data.
75 75 act = fds
76 76 return (self._main.fileno() in act, self._side.fileno() in act)
77 77
78 78 def read(self, size):
79 79 return self._call('read', size)
80 80
81 81 def readline(self):
82 82 return self._call('readline')
83 83
84 84 def _call(self, methname, size=None):
85 85 """call <methname> on "main", forward output of "side" while blocking
86 86 """
87 87 if size == 0 or self._main.closed:
88 88 _forwardoutput(self._ui, self._side)
89 89 return ''
90 90 while True:
91 91 mainready, sideready = self._wait()
92 92 if sideready:
93 93 _forwardoutput(self._ui, self._side)
94 94 if mainready:
95 95 meth = getattr(self._main, methname)
96 96 if size is None:
97 97 return meth()
98 98 else:
99 99 return meth(size)
100 100
101 101 def close(self):
102 102 return self._main.close()
103 103
104 104 class sshpeer(wireproto.wirepeer):
105 105 def __init__(self, ui, path, create=False):
106 106 self._url = path
107 107 self.ui = ui
108 108 self.pipeo = self.pipei = self.pipee = None
109 109
110 110 u = util.url(path, parsequery=False, parsefragment=False)
111 111 if u.scheme != 'ssh' or not u.host or u.path is None:
112 112 self._abort(error.RepoError(_("couldn't parse location %s") % path))
113 113
114 114 self.user = u.user
115 115 if u.passwd is not None:
116 116 self._abort(error.RepoError(_("password in URL not supported")))
117 117 self.host = u.host
118 118 self.port = u.port
119 119 self.path = u.path or "."
120 120
121 121 sshcmd = self.ui.config("ui", "ssh", "ssh")
122 122 remotecmd = self.ui.config("ui", "remotecmd", "hg")
123 123
124 124 args = util.sshargs(sshcmd,
125 125 _serverquote(self.host),
126 126 _serverquote(self.user),
127 127 _serverquote(self.port))
128 128
129 129 if create:
130 130 cmd = '%s %s %s' % (sshcmd, args,
131 131 util.shellquote("%s init %s" %
132 132 (_serverquote(remotecmd), _serverquote(self.path))))
133 133 ui.debug('running %s\n' % cmd)
134 134 res = ui.system(cmd)
135 135 if res != 0:
136 136 self._abort(error.RepoError(_("could not create remote repo")))
137 137
138 138 self._validaterepo(sshcmd, args, remotecmd)
139 139
140 140 def url(self):
141 141 return self._url
142 142
143 143 def _validaterepo(self, sshcmd, args, remotecmd):
144 144 # cleanup up previous run
145 145 self.cleanup()
146 146
147 147 cmd = '%s %s %s' % (sshcmd, args,
148 148 util.shellquote("%s -R %s serve --stdio" %
149 149 (_serverquote(remotecmd), _serverquote(self.path))))
150 150 self.ui.debug('running %s\n' % cmd)
151 151 cmd = util.quotecommand(cmd)
152 152
153 153 # while self.subprocess isn't used, having it allows the subprocess to
154 154 # to clean up correctly later
155 155 #
156 156 # no buffer allow the use of 'select'
157 157 # feel free to remove buffering and select usage when we ultimately
158 158 # move to threading.
159 159 sub = util.popen4(cmd, bufsize=0)
160 160 self.pipeo, self.pipei, self.pipee, self.subprocess = sub
161 161
162 162 self.pipei = util.bufferedinputpipe(self.pipei)
163 self.pipei = doublepipe(self.ui, self.pipei, self.pipee)
163 164
164 165 # skip any noise generated by remote shell
165 166 self._callstream("hello")
166 167 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
167 168 lines = ["", "dummy"]
168 169 max_noise = 500
169 170 while lines[-1] and max_noise:
170 171 l = r.readline()
171 172 self.readerr()
172 173 if lines[-1] == "1\n" and l == "\n":
173 174 break
174 175 if l:
175 176 self.ui.debug("remote: ", l)
176 177 lines.append(l)
177 178 max_noise -= 1
178 179 else:
179 180 self._abort(error.RepoError(_('no suitable response from '
180 181 'remote hg')))
181 182
182 183 self._caps = set()
183 184 for l in reversed(lines):
184 185 if l.startswith("capabilities:"):
185 186 self._caps.update(l[:-1].split(":")[1].split())
186 187 break
187 188
188 189 def _capabilities(self):
189 190 return self._caps
190 191
191 192 def readerr(self):
192 193 _forwardoutput(self.ui, self.pipee)
193 194
194 195 def _abort(self, exception):
195 196 self.cleanup()
196 197 raise exception
197 198
198 199 def cleanup(self):
199 200 if self.pipeo is None:
200 201 return
201 202 self.pipeo.close()
202 203 self.pipei.close()
203 204 try:
204 205 # read the error descriptor until EOF
205 206 for l in self.pipee:
206 207 self.ui.status(_("remote: "), l)
207 208 except (IOError, ValueError):
208 209 pass
209 210 self.pipee.close()
210 211
211 212 __del__ = cleanup
212 213
213 214 def _callstream(self, cmd, **args):
214 215 self.ui.debug("sending %s command\n" % cmd)
215 216 self.pipeo.write("%s\n" % cmd)
216 217 _func, names = wireproto.commands[cmd]
217 218 keys = names.split()
218 219 wireargs = {}
219 220 for k in keys:
220 221 if k == '*':
221 222 wireargs['*'] = args
222 223 break
223 224 else:
224 225 wireargs[k] = args[k]
225 226 del args[k]
226 227 for k, v in sorted(wireargs.iteritems()):
227 228 self.pipeo.write("%s %d\n" % (k, len(v)))
228 229 if isinstance(v, dict):
229 230 for dk, dv in v.iteritems():
230 231 self.pipeo.write("%s %d\n" % (dk, len(dv)))
231 232 self.pipeo.write(dv)
232 233 else:
233 234 self.pipeo.write(v)
234 235 self.pipeo.flush()
235 236
236 237 return self.pipei
237 238
238 239 def _callcompressable(self, cmd, **args):
239 240 return self._callstream(cmd, **args)
240 241
241 242 def _call(self, cmd, **args):
242 243 self._callstream(cmd, **args)
243 244 return self._recv()
244 245
245 246 def _callpush(self, cmd, fp, **args):
246 247 r = self._call(cmd, **args)
247 248 if r:
248 249 return '', r
249 250 while True:
250 251 d = fp.read(4096)
251 252 if not d:
252 253 break
253 254 self._send(d)
254 255 self._send("", flush=True)
255 256 r = self._recv()
256 257 if r:
257 258 return '', r
258 259 return self._recv(), ''
259 260
260 261 def _calltwowaystream(self, cmd, fp, **args):
261 262 r = self._call(cmd, **args)
262 263 if r:
263 264 # XXX needs to be made better
264 265 raise util.Abort('unexpected remote reply: %s' % r)
265 266 while True:
266 267 d = fp.read(4096)
267 268 if not d:
268 269 break
269 270 self._send(d)
270 271 self._send("", flush=True)
271 272 return self.pipei
272 273
273 274 def _recv(self):
274 275 l = self.pipei.readline()
275 276 if l == '\n':
276 277 self.readerr()
277 278 msg = _('check previous remote output')
278 279 self._abort(error.OutOfBandError(hint=msg))
279 280 self.readerr()
280 281 try:
281 282 l = int(l)
282 283 except ValueError:
283 284 self._abort(error.ResponseError(_("unexpected response:"), l))
284 285 return self.pipei.read(l)
285 286
286 287 def _send(self, data, flush=False):
287 288 self.pipeo.write("%d\n" % len(data))
288 289 if data:
289 290 self.pipeo.write(data)
290 291 if flush:
291 292 self.pipeo.flush()
292 293 self.readerr()
293 294
294 295 def lock(self):
295 296 self._call("lock")
296 297 return remotelock(self)
297 298
298 299 def unlock(self):
299 300 self._call("unlock")
300 301
301 302 def addchangegroup(self, cg, source, url, lock=None):
302 303 '''Send a changegroup to the remote server. Return an integer
303 304 similar to unbundle(). DEPRECATED, since it requires locking the
304 305 remote.'''
305 306 d = self._call("addchangegroup")
306 307 if d:
307 308 self._abort(error.RepoError(_("push refused: %s") % d))
308 309 while True:
309 310 d = cg.read(4096)
310 311 if not d:
311 312 break
312 313 self.pipeo.write(d)
313 314 self.readerr()
314 315
315 316 self.pipeo.flush()
316 317
317 318 self.readerr()
318 319 r = self._recv()
319 320 if not r:
320 321 return 1
321 322 try:
322 323 return int(r)
323 324 except ValueError:
324 325 self._abort(error.ResponseError(_("unexpected response:"), r))
325 326
326 327 instance = sshpeer
@@ -1,503 +1,503
1 1
2 2 This test tries to exercise the ssh functionality with a dummy script
3 3
4 4 creating 'remote' repo
5 5
6 6 $ hg init remote
7 7 $ cd remote
8 8 $ echo this > foo
9 9 $ echo this > fooO
10 10 $ hg ci -A -m "init" foo fooO
11 11
12 12 insert a closed branch (issue4428)
13 13
14 14 $ hg up null
15 15 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
16 16 $ hg branch closed
17 17 marked working directory as branch closed
18 18 (branches are permanent and global, did you want a bookmark?)
19 19 $ hg ci -mc0
20 20 $ hg ci --close-branch -mc1
21 21 $ hg up -q default
22 22
23 23 configure for serving
24 24
25 25 $ cat <<EOF > .hg/hgrc
26 26 > [server]
27 27 > uncompressed = True
28 28 >
29 29 > [hooks]
30 30 > changegroup = python "$TESTDIR/printenv.py" changegroup-in-remote 0 ../dummylog
31 31 > EOF
32 32 $ cd ..
33 33
34 34 repo not found error
35 35
36 36 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
37 37 remote: abort: there is no Mercurial repository here (.hg not found)!
38 38 abort: no suitable response from remote hg!
39 39 [255]
40 40
41 41 non-existent absolute path
42 42
43 43 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy//`pwd`/nonexistent local
44 44 remote: abort: there is no Mercurial repository here (.hg not found)!
45 45 abort: no suitable response from remote hg!
46 46 [255]
47 47
48 48 clone remote via stream
49 49
50 50 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
51 51 streaming all changes
52 52 4 files to transfer, 615 bytes of data
53 53 transferred 615 bytes in * seconds (*) (glob)
54 54 searching for changes
55 55 no changes found
56 56 updating to branch default
57 57 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 58 $ cd local-stream
59 59 $ hg verify
60 60 checking changesets
61 61 checking manifests
62 62 crosschecking files in changesets and manifests
63 63 checking files
64 64 2 files, 3 changesets, 2 total revisions
65 65 $ hg branches
66 66 default 0:1160648e36ce
67 67 $ cd ..
68 68
69 69 clone bookmarks via stream
70 70
71 71 $ hg -R local-stream book mybook
72 72 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
73 73 streaming all changes
74 74 4 files to transfer, 615 bytes of data
75 75 transferred 615 bytes in * seconds (*) (glob)
76 76 searching for changes
77 77 no changes found
78 78 updating to branch default
79 79 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 80 $ cd stream2
81 81 $ hg book
82 82 mybook 0:1160648e36ce
83 83 $ cd ..
84 84 $ rm -rf local-stream stream2
85 85
86 86 clone remote via pull
87 87
88 88 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
89 89 requesting all changes
90 90 adding changesets
91 91 adding manifests
92 92 adding file changes
93 93 added 3 changesets with 2 changes to 2 files
94 94 updating to branch default
95 95 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
96 96
97 97 verify
98 98
99 99 $ cd local
100 100 $ hg verify
101 101 checking changesets
102 102 checking manifests
103 103 crosschecking files in changesets and manifests
104 104 checking files
105 105 2 files, 3 changesets, 2 total revisions
106 106 $ echo '[hooks]' >> .hg/hgrc
107 107 $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup-in-local 0 ../dummylog" >> .hg/hgrc
108 108
109 109 empty default pull
110 110
111 111 $ hg paths
112 112 default = ssh://user@dummy/remote
113 113 $ hg pull -e "python \"$TESTDIR/dummyssh\""
114 114 pulling from ssh://user@dummy/remote
115 115 searching for changes
116 116 no changes found
117 117
118 118 pull from wrong ssh URL
119 119
120 120 $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
121 121 pulling from ssh://user@dummy/doesnotexist
122 122 remote: abort: there is no Mercurial repository here (.hg not found)!
123 123 abort: no suitable response from remote hg!
124 124 [255]
125 125
126 126 local change
127 127
128 128 $ echo bleah > foo
129 129 $ hg ci -m "add"
130 130
131 131 updating rc
132 132
133 133 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
134 134 $ echo "[ui]" >> .hg/hgrc
135 135 $ echo "ssh = python \"$TESTDIR/dummyssh\"" >> .hg/hgrc
136 136
137 137 find outgoing
138 138
139 139 $ hg out ssh://user@dummy/remote
140 140 comparing with ssh://user@dummy/remote
141 141 searching for changes
142 142 changeset: 3:a28a9d1a809c
143 143 tag: tip
144 144 parent: 0:1160648e36ce
145 145 user: test
146 146 date: Thu Jan 01 00:00:00 1970 +0000
147 147 summary: add
148 148
149 149
150 150 find incoming on the remote side
151 151
152 152 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
153 153 comparing with ssh://user@dummy/local
154 154 searching for changes
155 155 changeset: 3:a28a9d1a809c
156 156 tag: tip
157 157 parent: 0:1160648e36ce
158 158 user: test
159 159 date: Thu Jan 01 00:00:00 1970 +0000
160 160 summary: add
161 161
162 162
163 163 find incoming on the remote side (using absolute path)
164 164
165 165 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
166 166 comparing with ssh://user@dummy/$TESTTMP/local
167 167 searching for changes
168 168 changeset: 3:a28a9d1a809c
169 169 tag: tip
170 170 parent: 0:1160648e36ce
171 171 user: test
172 172 date: Thu Jan 01 00:00:00 1970 +0000
173 173 summary: add
174 174
175 175
176 176 push
177 177
178 178 $ hg push
179 179 pushing to ssh://user@dummy/remote
180 180 searching for changes
181 181 remote: adding changesets
182 182 remote: adding manifests
183 183 remote: adding file changes
184 184 remote: added 1 changesets with 1 changes to 1 files
185 185 $ cd ../remote
186 186
187 187 check remote tip
188 188
189 189 $ hg tip
190 190 changeset: 3:a28a9d1a809c
191 191 tag: tip
192 192 parent: 0:1160648e36ce
193 193 user: test
194 194 date: Thu Jan 01 00:00:00 1970 +0000
195 195 summary: add
196 196
197 197 $ hg verify
198 198 checking changesets
199 199 checking manifests
200 200 crosschecking files in changesets and manifests
201 201 checking files
202 202 2 files, 4 changesets, 3 total revisions
203 203 $ hg cat -r tip foo
204 204 bleah
205 205 $ echo z > z
206 206 $ hg ci -A -m z z
207 207 created new head
208 208
209 209 test pushkeys and bookmarks
210 210
211 211 $ cd ../local
212 212 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
213 213 bookmarks
214 214 namespaces
215 215 phases
216 216 $ hg book foo -r 0
217 217 $ hg out -B
218 218 comparing with ssh://user@dummy/remote
219 219 searching for changed bookmarks
220 220 foo 1160648e36ce
221 221 $ hg push -B foo
222 222 pushing to ssh://user@dummy/remote
223 223 searching for changes
224 224 no changes found
225 225 exporting bookmark foo
226 226 [1]
227 227 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
228 228 foo 1160648e36cec0054048a7edc4110c6f84fde594
229 229 $ hg book -f foo
230 230 $ hg push --traceback
231 231 pushing to ssh://user@dummy/remote
232 232 searching for changes
233 233 no changes found
234 234 updating bookmark foo
235 235 [1]
236 236 $ hg book -d foo
237 237 $ hg in -B
238 238 comparing with ssh://user@dummy/remote
239 239 searching for changed bookmarks
240 240 foo a28a9d1a809c
241 241 $ hg book -f -r 0 foo
242 242 $ hg pull -B foo
243 243 pulling from ssh://user@dummy/remote
244 244 no changes found
245 245 updating bookmark foo
246 246 $ hg book -d foo
247 247 $ hg push -B foo
248 248 pushing to ssh://user@dummy/remote
249 249 searching for changes
250 250 no changes found
251 251 deleting remote bookmark foo
252 252 [1]
253 253
254 254 a bad, evil hook that prints to stdout
255 255
256 256 $ cat <<EOF > $TESTTMP/badhook
257 257 > import sys
258 258 > sys.stdout.write("KABOOM\n")
259 259 > EOF
260 260
261 261 $ echo '[hooks]' >> ../remote/.hg/hgrc
262 262 $ echo "changegroup.stdout = python $TESTTMP/badhook" >> ../remote/.hg/hgrc
263 263 $ echo r > r
264 264 $ hg ci -A -m z r
265 265
266 266 push should succeed even though it has an unexpected response
267 267
268 268 $ hg push
269 269 pushing to ssh://user@dummy/remote
270 270 searching for changes
271 271 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
272 272 remote: adding changesets
273 273 remote: adding manifests
274 274 remote: adding file changes
275 275 remote: added 1 changesets with 1 changes to 1 files
276 276 remote: KABOOM
277 277 $ hg -R ../remote heads
278 278 changeset: 5:1383141674ec
279 279 tag: tip
280 280 parent: 3:a28a9d1a809c
281 281 user: test
282 282 date: Thu Jan 01 00:00:00 1970 +0000
283 283 summary: z
284 284
285 285 changeset: 4:6c0482d977a3
286 286 parent: 0:1160648e36ce
287 287 user: test
288 288 date: Thu Jan 01 00:00:00 1970 +0000
289 289 summary: z
290 290
291 291
292 292 clone bookmarks
293 293
294 294 $ hg -R ../remote bookmark test
295 295 $ hg -R ../remote bookmarks
296 296 * test 4:6c0482d977a3
297 297 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
298 298 requesting all changes
299 299 adding changesets
300 300 adding manifests
301 301 adding file changes
302 302 added 6 changesets with 5 changes to 4 files (+1 heads)
303 303 updating to branch default
304 304 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
305 305 $ hg -R local-bookmarks bookmarks
306 306 test 4:6c0482d977a3
307 307
308 308 passwords in ssh urls are not supported
309 309 (we use a glob here because different Python versions give different
310 310 results here)
311 311
312 312 $ hg push ssh://user:erroneouspwd@dummy/remote
313 313 pushing to ssh://user:*@dummy/remote (glob)
314 314 abort: password in URL not supported!
315 315 [255]
316 316
317 317 $ cd ..
318 318
319 319 hide outer repo
320 320 $ hg init
321 321
322 322 Test remote paths with spaces (issue2983):
323 323
324 324 $ hg init --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
325 325 $ touch "$TESTTMP/a repo/test"
326 326 $ hg -R 'a repo' commit -A -m "test"
327 327 adding test
328 328 $ hg -R 'a repo' tag tag
329 329 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
330 330 73649e48688a
331 331
332 332 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
333 333 abort: unknown revision 'noNoNO'!
334 334 [255]
335 335
336 336 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
337 337
338 338 $ hg clone --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
339 339 destination directory: a repo
340 340 abort: destination 'a repo' is not empty
341 341 [255]
342 342
343 343 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
344 344 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
345 345 parameters:
346 346
347 347 $ cat > ssh.sh << EOF
348 348 > userhost="\$1"
349 349 > SSH_ORIGINAL_COMMAND="\$2"
350 350 > export SSH_ORIGINAL_COMMAND
351 351 > PYTHONPATH="$PYTHONPATH"
352 352 > export PYTHONPATH
353 353 > python "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
354 354 > EOF
355 355
356 356 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
357 357 73649e48688a
358 358
359 359 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
360 360 remote: Illegal repository "$TESTTMP/a'repo" (glob)
361 361 abort: no suitable response from remote hg!
362 362 [255]
363 363
364 364 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
365 365 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
366 366 abort: no suitable response from remote hg!
367 367 [255]
368 368
369 369 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" python "$TESTDIR/../contrib/hg-ssh"
370 370 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
371 371 [255]
372 372
373 373 Test hg-ssh in read-only mode:
374 374
375 375 $ cat > ssh.sh << EOF
376 376 > userhost="\$1"
377 377 > SSH_ORIGINAL_COMMAND="\$2"
378 378 > export SSH_ORIGINAL_COMMAND
379 379 > PYTHONPATH="$PYTHONPATH"
380 380 > export PYTHONPATH
381 381 > python "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
382 382 > EOF
383 383
384 384 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
385 385 requesting all changes
386 386 adding changesets
387 387 adding manifests
388 388 adding file changes
389 389 added 6 changesets with 5 changes to 4 files (+1 heads)
390 390 updating to branch default
391 391 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
392 392
393 393 $ cd read-only-local
394 394 $ echo "baz" > bar
395 395 $ hg ci -A -m "unpushable commit" bar
396 396 $ hg push --ssh "sh ../ssh.sh"
397 397 pushing to ssh://user@dummy/*/remote (glob)
398 398 searching for changes
399 remote: Permission denied
399 400 abort: pretxnopen.hg-ssh hook failed
400 remote: Permission denied
401 401 [255]
402 402
403 403 $ cd ..
404 404
405 405 stderr from remote commands should be printed before stdout from local code (issue4336)
406 406
407 407 $ hg clone remote stderr-ordering
408 408 updating to branch default
409 409 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
410 410 $ cd stderr-ordering
411 411 $ cat >> localwrite.py << EOF
412 412 > from mercurial import exchange, extensions
413 413 >
414 414 > def wrappedpush(orig, repo, *args, **kwargs):
415 415 > res = orig(repo, *args, **kwargs)
416 416 > repo.ui.write('local stdout\n')
417 417 > return res
418 418 >
419 419 > def extsetup(ui):
420 420 > extensions.wrapfunction(exchange, 'push', wrappedpush)
421 421 > EOF
422 422
423 423 $ cat >> .hg/hgrc << EOF
424 424 > [paths]
425 425 > default-push = ssh://user@dummy/remote
426 426 > [ui]
427 427 > ssh = python "$TESTDIR/dummyssh"
428 428 > [extensions]
429 429 > localwrite = localwrite.py
430 430 > EOF
431 431
432 432 $ echo localwrite > foo
433 433 $ hg commit -m 'testing localwrite'
434 434 $ hg push
435 435 pushing to ssh://user@dummy/remote
436 436 searching for changes
437 437 remote: adding changesets
438 438 remote: adding manifests
439 439 remote: adding file changes
440 440 remote: added 1 changesets with 1 changes to 1 files
441 441 remote: KABOOM
442 442 local stdout
443 443
444 444 debug output
445 445
446 446 $ hg pull --debug ssh://user@dummy/remote
447 447 pulling from ssh://user@dummy/remote
448 448 running python "*/dummyssh" user@dummy 'hg -R remote serve --stdio' (glob)
449 449 sending hello command
450 450 sending between command
451 451 remote: 286
452 452 remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
453 453 remote: 1
454 454 query 1; heads
455 455 sending batch command
456 456 searching for changes
457 457 all remote heads known locally
458 458 no changes found
459 459 sending getbundle command
460 460 bundle2-input-bundle: with-transaction
461 461 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
462 462 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
463 463 bundle2-input-part: total payload size 45
464 464 bundle2-input-bundle: 1 parts total
465 465 checking for updated bookmarks
466 466 preparing listkeys for "phases"
467 467 sending listkeys command
468 468 received listkey for "phases": 15 bytes
469 469
470 470 $ cd ..
471 471
472 472 $ cat dummylog
473 473 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
474 474 Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio
475 475 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
476 476 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
477 477 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
478 478 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
479 479 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
480 480 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
481 481 Got arguments 1:user@dummy 2:hg -R local serve --stdio
482 482 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
483 483 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
484 484 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
485 485 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
486 486 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
487 487 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
488 488 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
489 489 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
490 490 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
491 491 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
492 492 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
493 493 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
494 494 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
495 495 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
496 496 Got arguments 1:user@dummy 2:hg init 'a repo'
497 497 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
498 498 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
499 499 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
500 500 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
501 501 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
502 502 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
503 503 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
General Comments 0
You need to be logged in to leave comments. Login now