121
|
1 # sshrepo.py - ssh repository proxy class for mercurial
|
|
2 #
|
|
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
|
|
4 #
|
|
5 # This software may be used and distributed according to the terms of the
|
|
6 # GNU General Public License version 2, incorporated herein by reference.
|
|
7
|
|
8 from node import bin, hex
|
|
9 from i18n import _
|
|
10 import repo, util, error
|
|
11 import re, urllib
|
|
12
|
|
13 class remotelock(object):
|
|
14 def __init__(self, repo):
|
|
15 self.repo = repo
|
|
16 def release(self):
|
|
17 self.repo.unlock()
|
|
18 self.repo = None
|
|
19 def __del__(self):
|
|
20 if self.repo:
|
|
21 self.release()
|
|
22
|
|
23 class sshrepository(repo.repository):
|
|
24 def __init__(self, ui, path, create=0):
|
|
25 self._url = path
|
|
26 self.ui = ui
|
|
27
|
|
28 m = re.match(r'^ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?$', path)
|
|
29 if not m:
|
|
30 self.abort(error.RepoError(_("couldn't parse location %s") % path))
|
|
31
|
|
32 self.user = m.group(2)
|
|
33 self.host = m.group(3)
|
|
34 self.port = m.group(5)
|
|
35 self.path = m.group(7) or "."
|
|
36
|
|
37 sshcmd = self.ui.config("ui", "ssh", "ssh")
|
|
38 remotecmd = self.ui.config("ui", "remotecmd", "hg")
|
|
39
|
|
40 args = util.sshargs(sshcmd, self.host, self.user, self.port)
|
|
41
|
|
42 if create:
|
|
43 cmd = '%s %s "%s init %s"'
|
|
44 cmd = cmd % (sshcmd, args, remotecmd, self.path)
|
|
45
|
|
46 ui.note(_('running %s\n') % cmd)
|
|
47 res = util.system(cmd)
|
|
48 if res != 0:
|
|
49 self.abort(error.RepoError(_("could not create remote repo")))
|
|
50
|
|
51 self.validate_repo(ui, sshcmd, args, remotecmd)
|
|
52
|
|
53 def url(self):
|
|
54 return self._url
|
|
55
|
|
56 def validate_repo(self, ui, sshcmd, args, remotecmd):
|
|
57 # cleanup up previous run
|
|
58 self.cleanup()
|
|
59
|
|
60 cmd = '%s %s "%s -R %s serve --stdio"'
|
|
61 cmd = cmd % (sshcmd, args, remotecmd, self.path)
|
|
62
|
|
63 cmd = util.quotecommand(cmd)
|
|
64 ui.note(_('running %s\n') % cmd)
|
|
65 self.pipeo, self.pipei, self.pipee = util.popen3(cmd)
|
|
66
|
|
67 # skip any noise generated by remote shell
|
|
68 self.do_cmd("hello")
|
|
69 r = self.do_cmd("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
|
|
70 lines = ["", "dummy"]
|
|
71 max_noise = 500
|
|
72 while lines[-1] and max_noise:
|
|
73 l = r.readline()
|
|
74 self.readerr()
|
|
75 if lines[-1] == "1\n" and l == "\n":
|
|
76 break
|
|
77 if l:
|
|
78 ui.debug(_("remote: "), l)
|
|
79 lines.append(l)
|
|
80 max_noise -= 1
|
|
81 else:
|
|
82 self.abort(error.RepoError(_("no suitable response from remote hg")))
|
|
83
|
|
84 self.capabilities = set()
|
|
85 for l in reversed(lines):
|
|
86 if l.startswith("capabilities:"):
|
|
87 self.capabilities.update(l[:-1].split(":")[1].split())
|
|
88 break
|
|
89
|
|
90 def readerr(self):
|
|
91 while 1:
|
|
92 size = util.fstat(self.pipee).st_size
|
|
93 if size == 0: break
|
|
94 l = self.pipee.readline()
|
|
95 if not l: break
|
|
96 self.ui.status(_("remote: "), l)
|
|
97
|
|
98 def abort(self, exception):
|
|
99 self.cleanup()
|
|
100 raise exception
|
|
101
|
|
102 def cleanup(self):
|
|
103 try:
|
|
104 self.pipeo.close()
|
|
105 self.pipei.close()
|
|
106 # read the error descriptor until EOF
|
|
107 for l in self.pipee:
|
|
108 self.ui.status(_("remote: "), l)
|
|
109 self.pipee.close()
|
|
110 except:
|
|
111 pass
|
|
112
|
|
113 __del__ = cleanup
|
|
114
|
|
115 def do_cmd(self, cmd, **args):
|
|
116 self.ui.debug(_("sending %s command\n") % cmd)
|
|
117 self.pipeo.write("%s\n" % cmd)
|
|
118 for k, v in args.iteritems():
|
|
119 self.pipeo.write("%s %d\n" % (k, len(v)))
|
|
120 self.pipeo.write(v)
|
|
121 self.pipeo.flush()
|
|
122
|
|
123 return self.pipei
|
|
124
|
|
125 def call(self, cmd, **args):
|
|
126 self.do_cmd(cmd, **args)
|
|
127 return self._recv()
|
|
128
|
|
129 def _recv(self):
|
|
130 l = self.pipei.readline()
|
|
131 self.readerr()
|
|
132 try:
|
|
133 l = int(l)
|
|
134 except:
|
|
135 self.abort(error.ResponseError(_("unexpected response:"), l))
|
|
136 return self.pipei.read(l)
|
|
137
|
|
138 def _send(self, data, flush=False):
|
|
139 self.pipeo.write("%d\n" % len(data))
|
|
140 if data:
|
|
141 self.pipeo.write(data)
|
|
142 if flush:
|
|
143 self.pipeo.flush()
|
|
144 self.readerr()
|
|
145
|
|
146 def lock(self):
|
|
147 self.call("lock")
|
|
148 return remotelock(self)
|
|
149
|
|
150 def unlock(self):
|
|
151 self.call("unlock")
|
|
152
|
|
153 def lookup(self, key):
|
|
154 self.requirecap('lookup', _('look up remote revision'))
|
|
155 d = self.call("lookup", key=key)
|
|
156 success, data = d[:-1].split(" ", 1)
|
|
157 if int(success):
|
|
158 return bin(data)
|
|
159 else:
|
|
160 self.abort(error.RepoError(data))
|
|
161
|
|
162 def heads(self):
|
|
163 d = self.call("heads")
|
|
164 try:
|
|
165 return map(bin, d[:-1].split(" "))
|
|
166 except:
|
|
167 self.abort(error.ResponseError(_("unexpected response:"), d))
|
|
168
|
|
169 def branchmap(self):
|
|
170 d = self.call("branchmap")
|
|
171 try:
|
|
172 branchmap = {}
|
|
173 for branchpart in d.splitlines():
|
|
174 branchheads = branchpart.split(' ')
|
|
175 branchname = urllib.unquote(branchheads[0])
|
|
176 branchheads = [bin(x) for x in branchheads[1:]]
|
|
177 branchmap[branchname] = branchheads
|
|
178 return branchmap
|
|
179 except:
|
|
180 raise error.ResponseError(_("unexpected response:"), d)
|
|
181
|
|
182 def branches(self, nodes):
|
|
183 n = " ".join(map(hex, nodes))
|
|
184 d = self.call("branches", nodes=n)
|
|
185 try:
|
|
186 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
|
|
187 return br
|
|
188 except:
|
|
189 self.abort(error.ResponseError(_("unexpected response:"), d))
|
|
190
|
|
191 def between(self, pairs):
|
|
192 n = " ".join(["-".join(map(hex, p)) for p in pairs])
|
|
193 d = self.call("between", pairs=n)
|
|
194 try:
|
|
195 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
|
|
196 return p
|
|
197 except:
|
|
198 self.abort(error.ResponseError(_("unexpected response:"), d))
|
|
199
|
|
200 def changegroup(self, nodes, kind):
|
|
201 n = " ".join(map(hex, nodes))
|
|
202 return self.do_cmd("changegroup", roots=n)
|
|
203
|
|
204 def changegroupsubset(self, bases, heads, kind):
|
|
205 self.requirecap('changegroupsubset', _('look up remote changes'))
|
|
206 bases = " ".join(map(hex, bases))
|
|
207 heads = " ".join(map(hex, heads))
|
|
208 return self.do_cmd("changegroupsubset", bases=bases, heads=heads)
|
|
209
|
|
210 def unbundle(self, cg, heads, source):
|
|
211 d = self.call("unbundle", heads=' '.join(map(hex, heads)))
|
|
212 if d:
|
|
213 # remote may send "unsynced changes"
|
|
214 self.abort(error.RepoError(_("push refused: %s") % d))
|
|
215
|
|
216 while 1:
|
|
217 d = cg.read(4096)
|
|
218 if not d:
|
|
219 break
|
|
220 self._send(d)
|
|
221
|
|
222 self._send("", flush=True)
|
|
223
|
|
224 r = self._recv()
|
|
225 if r:
|
|
226 # remote may send "unsynced changes"
|
|
227 self.abort(error.RepoError(_("push failed: %s") % r))
|
|
228
|
|
229 r = self._recv()
|
|
230 try:
|
|
231 return int(r)
|
|
232 except:
|
|
233 self.abort(error.ResponseError(_("unexpected response:"), r))
|
|
234
|
|
235 def addchangegroup(self, cg, source, url):
|
|
236 d = self.call("addchangegroup")
|
|
237 if d:
|
|
238 self.abort(error.RepoError(_("push refused: %s") % d))
|
|
239 while 1:
|
|
240 d = cg.read(4096)
|
|
241 if not d:
|
|
242 break
|
|
243 self.pipeo.write(d)
|
|
244 self.readerr()
|
|
245
|
|
246 self.pipeo.flush()
|
|
247
|
|
248 self.readerr()
|
|
249 r = self._recv()
|
|
250 if not r:
|
|
251 return 1
|
|
252 try:
|
|
253 return int(r)
|
|
254 except:
|
|
255 self.abort(error.ResponseError(_("unexpected response:"), r))
|
|
256
|
|
257 def stream_out(self):
|
|
258 return self.do_cmd('stream_out')
|
|
259
|
|
260 instance = sshrepository
|