##// END OF EJS Templates
support encoding fallback in branchmap to maintain compatibility with 1.3.x
Henrik Stuart -
r9861:0262bb59 default
parent child Browse files
Show More
@@ -1,259 +1,264 b''
1 1 # httprepo.py - HTTP repository proxy classes for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2, incorporated herein by reference.
8 8
9 9 from node import bin, hex, nullid
10 10 from i18n import _
11 11 import repo, changegroup, statichttprepo, error, url, util
12 12 import os, urllib, urllib2, urlparse, zlib, httplib
13 13 import errno, socket
14 import encoding
14 15
15 16 def zgenerator(f):
16 17 zd = zlib.decompressobj()
17 18 try:
18 19 for chunk in util.filechunkiter(f):
19 20 yield zd.decompress(chunk)
20 21 except httplib.HTTPException:
21 22 raise IOError(None, _('connection ended unexpectedly'))
22 23 yield zd.flush()
23 24
24 25 class httprepository(repo.repository):
25 26 def __init__(self, ui, path):
26 27 self.path = path
27 28 self.caps = None
28 29 self.handler = None
29 30 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
30 31 if query or frag:
31 32 raise util.Abort(_('unsupported URL component: "%s"') %
32 33 (query or frag))
33 34
34 35 # urllib cannot handle URLs with embedded user or passwd
35 36 self._url, authinfo = url.getauthinfo(path)
36 37
37 38 self.ui = ui
38 39 self.ui.debug('using %s\n' % self._url)
39 40
40 41 self.urlopener = url.opener(ui, authinfo)
41 42
42 43 def __del__(self):
43 44 for h in self.urlopener.handlers:
44 45 h.close()
45 46 if hasattr(h, "close_all"):
46 47 h.close_all()
47 48
48 49 def url(self):
49 50 return self.path
50 51
51 52 # look up capabilities only when needed
52 53
53 54 def get_caps(self):
54 55 if self.caps is None:
55 56 try:
56 57 self.caps = set(self.do_read('capabilities').split())
57 58 except error.RepoError:
58 59 self.caps = set()
59 60 self.ui.debug('capabilities: %s\n' %
60 61 (' '.join(self.caps or ['none'])))
61 62 return self.caps
62 63
63 64 capabilities = property(get_caps)
64 65
65 66 def lock(self):
66 67 raise util.Abort(_('operation not supported over http'))
67 68
68 69 def do_cmd(self, cmd, **args):
69 70 data = args.pop('data', None)
70 71 headers = args.pop('headers', {})
71 72 self.ui.debug("sending %s command\n" % cmd)
72 73 q = {"cmd": cmd}
73 74 q.update(args)
74 75 qs = '?%s' % urllib.urlencode(q)
75 76 cu = "%s%s" % (self._url, qs)
76 77 try:
77 78 if data:
78 79 self.ui.debug("sending %s bytes\n" % len(data))
79 80 resp = self.urlopener.open(urllib2.Request(cu, data, headers))
80 81 except urllib2.HTTPError, inst:
81 82 if inst.code == 401:
82 83 raise util.Abort(_('authorization failed'))
83 84 raise
84 85 except httplib.HTTPException, inst:
85 86 self.ui.debug('http error while sending %s command\n' % cmd)
86 87 self.ui.traceback()
87 88 raise IOError(None, inst)
88 89 except IndexError:
89 90 # this only happens with Python 2.3, later versions raise URLError
90 91 raise util.Abort(_('http error, possibly caused by proxy setting'))
91 92 # record the url we got redirected to
92 93 resp_url = resp.geturl()
93 94 if resp_url.endswith(qs):
94 95 resp_url = resp_url[:-len(qs)]
95 96 if self._url != resp_url:
96 97 self.ui.status(_('real URL is %s\n') % resp_url)
97 98 self._url = resp_url
98 99 try:
99 100 proto = resp.getheader('content-type')
100 101 except AttributeError:
101 102 proto = resp.headers['content-type']
102 103
103 104 safeurl = url.hidepassword(self._url)
104 105 # accept old "text/plain" and "application/hg-changegroup" for now
105 106 if not (proto.startswith('application/mercurial-') or
106 107 proto.startswith('text/plain') or
107 108 proto.startswith('application/hg-changegroup')):
108 109 self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu))
109 110 raise error.RepoError(_("'%s' does not appear to be an hg repository:\n"
110 111 "---%%<--- (%s)\n%s\n---%%<---\n")
111 112 % (safeurl, proto, resp.read()))
112 113
113 114 if proto.startswith('application/mercurial-'):
114 115 try:
115 116 version = proto.split('-', 1)[1]
116 117 version_info = tuple([int(n) for n in version.split('.')])
117 118 except ValueError:
118 119 raise error.RepoError(_("'%s' sent a broken Content-Type "
119 120 "header (%s)") % (safeurl, proto))
120 121 if version_info > (0, 1):
121 122 raise error.RepoError(_("'%s' uses newer protocol %s") %
122 123 (safeurl, version))
123 124
124 125 return resp
125 126
126 127 def do_read(self, cmd, **args):
127 128 fp = self.do_cmd(cmd, **args)
128 129 try:
129 130 return fp.read()
130 131 finally:
131 132 # if using keepalive, allow connection to be reused
132 133 fp.close()
133 134
134 135 def lookup(self, key):
135 136 self.requirecap('lookup', _('look up remote revision'))
136 137 d = self.do_cmd("lookup", key = key).read()
137 138 success, data = d[:-1].split(' ', 1)
138 139 if int(success):
139 140 return bin(data)
140 141 raise error.RepoError(data)
141 142
142 143 def heads(self):
143 144 d = self.do_read("heads")
144 145 try:
145 146 return map(bin, d[:-1].split(" "))
146 147 except:
147 148 raise error.ResponseError(_("unexpected response:"), d)
148 149
149 150 def branchmap(self):
150 151 d = self.do_read("branchmap")
151 152 try:
152 153 branchmap = {}
153 154 for branchpart in d.splitlines():
154 155 branchheads = branchpart.split(' ')
155 156 branchname = urllib.unquote(branchheads[0])
157 try:
158 branchname.decode('utf-8', 'strict')
159 except UnicodeDecodeError:
160 branchname = encoding.tolocal(branchname)
156 161 branchheads = [bin(x) for x in branchheads[1:]]
157 162 branchmap[branchname] = branchheads
158 163 return branchmap
159 164 except:
160 165 raise error.ResponseError(_("unexpected response:"), d)
161 166
162 167 def branches(self, nodes):
163 168 n = " ".join(map(hex, nodes))
164 169 d = self.do_read("branches", nodes=n)
165 170 try:
166 171 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
167 172 return br
168 173 except:
169 174 raise error.ResponseError(_("unexpected response:"), d)
170 175
171 176 def between(self, pairs):
172 177 batch = 8 # avoid giant requests
173 178 r = []
174 179 for i in xrange(0, len(pairs), batch):
175 180 n = " ".join(["-".join(map(hex, p)) for p in pairs[i:i + batch]])
176 181 d = self.do_read("between", pairs=n)
177 182 try:
178 183 r += [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
179 184 except:
180 185 raise error.ResponseError(_("unexpected response:"), d)
181 186 return r
182 187
183 188 def changegroup(self, nodes, kind):
184 189 n = " ".join(map(hex, nodes))
185 190 f = self.do_cmd("changegroup", roots=n)
186 191 return util.chunkbuffer(zgenerator(f))
187 192
188 193 def changegroupsubset(self, bases, heads, source):
189 194 self.requirecap('changegroupsubset', _('look up remote changes'))
190 195 baselst = " ".join([hex(n) for n in bases])
191 196 headlst = " ".join([hex(n) for n in heads])
192 197 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
193 198 return util.chunkbuffer(zgenerator(f))
194 199
195 200 def unbundle(self, cg, heads, source):
196 201 # have to stream bundle to a temp file because we do not have
197 202 # http 1.1 chunked transfer.
198 203
199 204 type = ""
200 205 types = self.capable('unbundle')
201 206 # servers older than d1b16a746db6 will send 'unbundle' as a
202 207 # boolean capability
203 208 try:
204 209 types = types.split(',')
205 210 except AttributeError:
206 211 types = [""]
207 212 if types:
208 213 for x in types:
209 214 if x in changegroup.bundletypes:
210 215 type = x
211 216 break
212 217
213 218 tempname = changegroup.writebundle(cg, None, type)
214 219 fp = url.httpsendfile(tempname, "rb")
215 220 try:
216 221 try:
217 222 resp = self.do_read(
218 223 'unbundle', data=fp,
219 224 headers={'Content-Type': 'application/octet-stream'},
220 225 heads=' '.join(map(hex, heads)))
221 226 resp_code, output = resp.split('\n', 1)
222 227 try:
223 228 ret = int(resp_code)
224 229 except ValueError, err:
225 230 raise error.ResponseError(
226 231 _('push failed (unexpected response):'), resp)
227 232 self.ui.write(output)
228 233 return ret
229 234 except socket.error, err:
230 235 if err[0] in (errno.ECONNRESET, errno.EPIPE):
231 236 raise util.Abort(_('push failed: %s') % err[1])
232 237 raise util.Abort(err[1])
233 238 finally:
234 239 fp.close()
235 240 os.unlink(tempname)
236 241
237 242 def stream_out(self):
238 243 return self.do_cmd('stream_out')
239 244
240 245 class httpsrepository(httprepository):
241 246 def __init__(self, ui, path):
242 247 if not url.has_https:
243 248 raise util.Abort(_('Python support for SSL and HTTPS '
244 249 'is not installed'))
245 250 httprepository.__init__(self, ui, path)
246 251
247 252 def instance(ui, path, create):
248 253 if create:
249 254 raise util.Abort(_('cannot create new http repository'))
250 255 try:
251 256 if path.startswith('https:'):
252 257 inst = httpsrepository(ui, path)
253 258 else:
254 259 inst = httprepository(ui, path)
255 260 inst.between([(nullid, nullid)])
256 261 return inst
257 262 except error.RepoError:
258 263 ui.note('(falling back to static-http)\n')
259 264 return statichttprepo.instance(ui, "static-" + path, create)
@@ -1,260 +1,264 b''
1 1 # sshrepo.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, incorporated herein by reference.
7 7
8 8 from node import bin, hex
9 9 from i18n import _
10 import repo, util, error
10 import repo, util, error, encoding
11 11 import re, urllib
12 12
13 13 class remotelock(object):
14 14 def __init__(self, repo):
15 15 self.repo = repo
16 16 def release(self):
17 17 self.repo.unlock()
18 18 self.repo = None
19 19 def __del__(self):
20 20 if self.repo:
21 21 self.release()
22 22
23 23 class sshrepository(repo.repository):
24 24 def __init__(self, ui, path, create=0):
25 25 self._url = path
26 26 self.ui = ui
27 27
28 28 m = re.match(r'^ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?$', path)
29 29 if not m:
30 30 self.abort(error.RepoError(_("couldn't parse location %s") % path))
31 31
32 32 self.user = m.group(2)
33 33 self.host = m.group(3)
34 34 self.port = m.group(5)
35 35 self.path = m.group(7) or "."
36 36
37 37 sshcmd = self.ui.config("ui", "ssh", "ssh")
38 38 remotecmd = self.ui.config("ui", "remotecmd", "hg")
39 39
40 40 args = util.sshargs(sshcmd, self.host, self.user, self.port)
41 41
42 42 if create:
43 43 cmd = '%s %s "%s init %s"'
44 44 cmd = cmd % (sshcmd, args, remotecmd, self.path)
45 45
46 46 ui.note(_('running %s\n') % cmd)
47 47 res = util.system(cmd)
48 48 if res != 0:
49 49 self.abort(error.RepoError(_("could not create remote repo")))
50 50
51 51 self.validate_repo(ui, sshcmd, args, remotecmd)
52 52
53 53 def url(self):
54 54 return self._url
55 55
56 56 def validate_repo(self, ui, sshcmd, args, remotecmd):
57 57 # cleanup up previous run
58 58 self.cleanup()
59 59
60 60 cmd = '%s %s "%s -R %s serve --stdio"'
61 61 cmd = cmd % (sshcmd, args, remotecmd, self.path)
62 62
63 63 cmd = util.quotecommand(cmd)
64 64 ui.note(_('running %s\n') % cmd)
65 65 self.pipeo, self.pipei, self.pipee = util.popen3(cmd)
66 66
67 67 # skip any noise generated by remote shell
68 68 self.do_cmd("hello")
69 69 r = self.do_cmd("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
70 70 lines = ["", "dummy"]
71 71 max_noise = 500
72 72 while lines[-1] and max_noise:
73 73 l = r.readline()
74 74 self.readerr()
75 75 if lines[-1] == "1\n" and l == "\n":
76 76 break
77 77 if l:
78 78 ui.debug("remote: ", l)
79 79 lines.append(l)
80 80 max_noise -= 1
81 81 else:
82 82 self.abort(error.RepoError(_("no suitable response from remote hg")))
83 83
84 84 self.capabilities = set()
85 85 for l in reversed(lines):
86 86 if l.startswith("capabilities:"):
87 87 self.capabilities.update(l[:-1].split(":")[1].split())
88 88 break
89 89
90 90 def readerr(self):
91 91 while 1:
92 92 size = util.fstat(self.pipee).st_size
93 93 if size == 0: break
94 94 l = self.pipee.readline()
95 95 if not l: break
96 96 self.ui.status(_("remote: "), l)
97 97
98 98 def abort(self, exception):
99 99 self.cleanup()
100 100 raise exception
101 101
102 102 def cleanup(self):
103 103 try:
104 104 self.pipeo.close()
105 105 self.pipei.close()
106 106 # read the error descriptor until EOF
107 107 for l in self.pipee:
108 108 self.ui.status(_("remote: "), l)
109 109 self.pipee.close()
110 110 except:
111 111 pass
112 112
113 113 __del__ = cleanup
114 114
115 115 def do_cmd(self, cmd, **args):
116 116 self.ui.debug("sending %s command\n" % cmd)
117 117 self.pipeo.write("%s\n" % cmd)
118 118 for k, v in args.iteritems():
119 119 self.pipeo.write("%s %d\n" % (k, len(v)))
120 120 self.pipeo.write(v)
121 121 self.pipeo.flush()
122 122
123 123 return self.pipei
124 124
125 125 def call(self, cmd, **args):
126 126 self.do_cmd(cmd, **args)
127 127 return self._recv()
128 128
129 129 def _recv(self):
130 130 l = self.pipei.readline()
131 131 self.readerr()
132 132 try:
133 133 l = int(l)
134 134 except:
135 135 self.abort(error.ResponseError(_("unexpected response:"), l))
136 136 return self.pipei.read(l)
137 137
138 138 def _send(self, data, flush=False):
139 139 self.pipeo.write("%d\n" % len(data))
140 140 if data:
141 141 self.pipeo.write(data)
142 142 if flush:
143 143 self.pipeo.flush()
144 144 self.readerr()
145 145
146 146 def lock(self):
147 147 self.call("lock")
148 148 return remotelock(self)
149 149
150 150 def unlock(self):
151 151 self.call("unlock")
152 152
153 153 def lookup(self, key):
154 154 self.requirecap('lookup', _('look up remote revision'))
155 155 d = self.call("lookup", key=key)
156 156 success, data = d[:-1].split(" ", 1)
157 157 if int(success):
158 158 return bin(data)
159 159 else:
160 160 self.abort(error.RepoError(data))
161 161
162 162 def heads(self):
163 163 d = self.call("heads")
164 164 try:
165 165 return map(bin, d[:-1].split(" "))
166 166 except:
167 167 self.abort(error.ResponseError(_("unexpected response:"), d))
168 168
169 169 def branchmap(self):
170 170 d = self.call("branchmap")
171 171 try:
172 172 branchmap = {}
173 173 for branchpart in d.splitlines():
174 174 branchheads = branchpart.split(' ')
175 175 branchname = urllib.unquote(branchheads[0])
176 try:
177 branchname.decode('utf-8', 'strict')
178 except UnicodeDecodeError:
179 branchname = encoding.tolocal(branchname)
176 180 branchheads = [bin(x) for x in branchheads[1:]]
177 181 branchmap[branchname] = branchheads
178 182 return branchmap
179 183 except:
180 184 raise error.ResponseError(_("unexpected response:"), d)
181 185
182 186 def branches(self, nodes):
183 187 n = " ".join(map(hex, nodes))
184 188 d = self.call("branches", nodes=n)
185 189 try:
186 190 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
187 191 return br
188 192 except:
189 193 self.abort(error.ResponseError(_("unexpected response:"), d))
190 194
191 195 def between(self, pairs):
192 196 n = " ".join(["-".join(map(hex, p)) for p in pairs])
193 197 d = self.call("between", pairs=n)
194 198 try:
195 199 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
196 200 return p
197 201 except:
198 202 self.abort(error.ResponseError(_("unexpected response:"), d))
199 203
200 204 def changegroup(self, nodes, kind):
201 205 n = " ".join(map(hex, nodes))
202 206 return self.do_cmd("changegroup", roots=n)
203 207
204 208 def changegroupsubset(self, bases, heads, kind):
205 209 self.requirecap('changegroupsubset', _('look up remote changes'))
206 210 bases = " ".join(map(hex, bases))
207 211 heads = " ".join(map(hex, heads))
208 212 return self.do_cmd("changegroupsubset", bases=bases, heads=heads)
209 213
210 214 def unbundle(self, cg, heads, source):
211 215 d = self.call("unbundle", heads=' '.join(map(hex, heads)))
212 216 if d:
213 217 # remote may send "unsynced changes"
214 218 self.abort(error.RepoError(_("push refused: %s") % d))
215 219
216 220 while 1:
217 221 d = cg.read(4096)
218 222 if not d:
219 223 break
220 224 self._send(d)
221 225
222 226 self._send("", flush=True)
223 227
224 228 r = self._recv()
225 229 if r:
226 230 # remote may send "unsynced changes"
227 231 self.abort(error.RepoError(_("push failed: %s") % r))
228 232
229 233 r = self._recv()
230 234 try:
231 235 return int(r)
232 236 except:
233 237 self.abort(error.ResponseError(_("unexpected response:"), r))
234 238
235 239 def addchangegroup(self, cg, source, url):
236 240 d = self.call("addchangegroup")
237 241 if d:
238 242 self.abort(error.RepoError(_("push refused: %s") % d))
239 243 while 1:
240 244 d = cg.read(4096)
241 245 if not d:
242 246 break
243 247 self.pipeo.write(d)
244 248 self.readerr()
245 249
246 250 self.pipeo.flush()
247 251
248 252 self.readerr()
249 253 r = self._recv()
250 254 if not r:
251 255 return 1
252 256 try:
253 257 return int(r)
254 258 except:
255 259 self.abort(error.ResponseError(_("unexpected response:"), r))
256 260
257 261 def stream_out(self):
258 262 return self.do_cmd('stream_out')
259 263
260 264 instance = sshrepository
General Comments 0
You need to be logged in to leave comments. Login now