##// END OF EJS Templates
Merge
Bryan O'Sullivan -
r6294:9cd6292a merge default
parent child Browse files
Show More
@@ -1,458 +1,458 b''
1 # httprepo.py - HTTP repository proxy classes for mercurial
1 # httprepo.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from node import bin, hex
9 from node import bin, hex
10 from remoterepo import remoterepository
10 from remoterepo import remoterepository
11 from i18n import _
11 from i18n import _
12 import repo, os, urllib, urllib2, urlparse, zlib, util, httplib
12 import repo, os, urllib, urllib2, urlparse, zlib, util, httplib
13 import errno, keepalive, socket, changegroup, version
13 import errno, keepalive, socket, changegroup
14
14
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
16 def __init__(self, ui):
16 def __init__(self, ui):
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
18 self.ui = ui
18 self.ui = ui
19
19
20 def find_user_password(self, realm, authuri):
20 def find_user_password(self, realm, authuri):
21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
22 self, realm, authuri)
22 self, realm, authuri)
23 user, passwd = authinfo
23 user, passwd = authinfo
24 if user and passwd:
24 if user and passwd:
25 return (user, passwd)
25 return (user, passwd)
26
26
27 if not self.ui.interactive:
27 if not self.ui.interactive:
28 raise util.Abort(_('http authorization required'))
28 raise util.Abort(_('http authorization required'))
29
29
30 self.ui.write(_("http authorization required\n"))
30 self.ui.write(_("http authorization required\n"))
31 self.ui.status(_("realm: %s\n") % realm)
31 self.ui.status(_("realm: %s\n") % realm)
32 if user:
32 if user:
33 self.ui.status(_("user: %s\n") % user)
33 self.ui.status(_("user: %s\n") % user)
34 else:
34 else:
35 user = self.ui.prompt(_("user:"), default=None)
35 user = self.ui.prompt(_("user:"), default=None)
36
36
37 if not passwd:
37 if not passwd:
38 passwd = self.ui.getpass()
38 passwd = self.ui.getpass()
39
39
40 self.add_password(realm, authuri, user, passwd)
40 self.add_password(realm, authuri, user, passwd)
41 return (user, passwd)
41 return (user, passwd)
42
42
43 def netlocsplit(netloc):
43 def netlocsplit(netloc):
44 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
44 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
45
45
46 a = netloc.find('@')
46 a = netloc.find('@')
47 if a == -1:
47 if a == -1:
48 user, passwd = None, None
48 user, passwd = None, None
49 else:
49 else:
50 userpass, netloc = netloc[:a], netloc[a+1:]
50 userpass, netloc = netloc[:a], netloc[a+1:]
51 c = userpass.find(':')
51 c = userpass.find(':')
52 if c == -1:
52 if c == -1:
53 user, passwd = urllib.unquote(userpass), None
53 user, passwd = urllib.unquote(userpass), None
54 else:
54 else:
55 user = urllib.unquote(userpass[:c])
55 user = urllib.unquote(userpass[:c])
56 passwd = urllib.unquote(userpass[c+1:])
56 passwd = urllib.unquote(userpass[c+1:])
57 c = netloc.find(':')
57 c = netloc.find(':')
58 if c == -1:
58 if c == -1:
59 host, port = netloc, None
59 host, port = netloc, None
60 else:
60 else:
61 host, port = netloc[:c], netloc[c+1:]
61 host, port = netloc[:c], netloc[c+1:]
62 return host, port, user, passwd
62 return host, port, user, passwd
63
63
64 def netlocunsplit(host, port, user=None, passwd=None):
64 def netlocunsplit(host, port, user=None, passwd=None):
65 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
65 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
66 if port:
66 if port:
67 hostport = host + ':' + port
67 hostport = host + ':' + port
68 else:
68 else:
69 hostport = host
69 hostport = host
70 if user:
70 if user:
71 if passwd:
71 if passwd:
72 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
72 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
73 else:
73 else:
74 userpass = urllib.quote(user)
74 userpass = urllib.quote(user)
75 return userpass + '@' + hostport
75 return userpass + '@' + hostport
76 return hostport
76 return hostport
77
77
78 # work around a bug in Python < 2.4.2
78 # work around a bug in Python < 2.4.2
79 # (it leaves a "\n" at the end of Proxy-authorization headers)
79 # (it leaves a "\n" at the end of Proxy-authorization headers)
80 class request(urllib2.Request):
80 class request(urllib2.Request):
81 def add_header(self, key, val):
81 def add_header(self, key, val):
82 if key.lower() == 'proxy-authorization':
82 if key.lower() == 'proxy-authorization':
83 val = val.strip()
83 val = val.strip()
84 return urllib2.Request.add_header(self, key, val)
84 return urllib2.Request.add_header(self, key, val)
85
85
86 class httpsendfile(file):
86 class httpsendfile(file):
87 def __len__(self):
87 def __len__(self):
88 return os.fstat(self.fileno()).st_size
88 return os.fstat(self.fileno()).st_size
89
89
90 def _gen_sendfile(connection):
90 def _gen_sendfile(connection):
91 def _sendfile(self, data):
91 def _sendfile(self, data):
92 # send a file
92 # send a file
93 if isinstance(data, httpsendfile):
93 if isinstance(data, httpsendfile):
94 # if auth required, some data sent twice, so rewind here
94 # if auth required, some data sent twice, so rewind here
95 data.seek(0)
95 data.seek(0)
96 for chunk in util.filechunkiter(data):
96 for chunk in util.filechunkiter(data):
97 connection.send(self, chunk)
97 connection.send(self, chunk)
98 else:
98 else:
99 connection.send(self, data)
99 connection.send(self, data)
100 return _sendfile
100 return _sendfile
101
101
102 class httpconnection(keepalive.HTTPConnection):
102 class httpconnection(keepalive.HTTPConnection):
103 # must be able to send big bundle as stream.
103 # must be able to send big bundle as stream.
104 send = _gen_sendfile(keepalive.HTTPConnection)
104 send = _gen_sendfile(keepalive.HTTPConnection)
105
105
106 class httphandler(keepalive.HTTPHandler):
106 class httphandler(keepalive.HTTPHandler):
107 def http_open(self, req):
107 def http_open(self, req):
108 return self.do_open(httpconnection, req)
108 return self.do_open(httpconnection, req)
109
109
110 def __del__(self):
110 def __del__(self):
111 self.close_all()
111 self.close_all()
112
112
113 has_https = hasattr(urllib2, 'HTTPSHandler')
113 has_https = hasattr(urllib2, 'HTTPSHandler')
114 if has_https:
114 if has_https:
115 class httpsconnection(httplib.HTTPSConnection):
115 class httpsconnection(httplib.HTTPSConnection):
116 response_class = keepalive.HTTPResponse
116 response_class = keepalive.HTTPResponse
117 # must be able to send big bundle as stream.
117 # must be able to send big bundle as stream.
118 send = _gen_sendfile(httplib.HTTPSConnection)
118 send = _gen_sendfile(httplib.HTTPSConnection)
119
119
120 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
120 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
121 def https_open(self, req):
121 def https_open(self, req):
122 return self.do_open(httpsconnection, req)
122 return self.do_open(httpsconnection, req)
123
123
124 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
124 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
125 # it doesn't know about the auth type requested. This can happen if
125 # it doesn't know about the auth type requested. This can happen if
126 # somebody is using BasicAuth and types a bad password.
126 # somebody is using BasicAuth and types a bad password.
127 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
127 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
128 def http_error_auth_reqed(self, auth_header, host, req, headers):
128 def http_error_auth_reqed(self, auth_header, host, req, headers):
129 try:
129 try:
130 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
130 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
131 self, auth_header, host, req, headers)
131 self, auth_header, host, req, headers)
132 except ValueError, inst:
132 except ValueError, inst:
133 arg = inst.args[0]
133 arg = inst.args[0]
134 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
134 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
135 return
135 return
136 raise
136 raise
137
137
138 def zgenerator(f):
138 def zgenerator(f):
139 zd = zlib.decompressobj()
139 zd = zlib.decompressobj()
140 try:
140 try:
141 for chunk in util.filechunkiter(f):
141 for chunk in util.filechunkiter(f):
142 yield zd.decompress(chunk)
142 yield zd.decompress(chunk)
143 except httplib.HTTPException, inst:
143 except httplib.HTTPException, inst:
144 raise IOError(None, _('connection ended unexpectedly'))
144 raise IOError(None, _('connection ended unexpectedly'))
145 yield zd.flush()
145 yield zd.flush()
146
146
147 _safe = ('abcdefghijklmnopqrstuvwxyz'
147 _safe = ('abcdefghijklmnopqrstuvwxyz'
148 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
148 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
149 '0123456789' '_.-/')
149 '0123456789' '_.-/')
150 _safeset = None
150 _safeset = None
151 _hex = None
151 _hex = None
152 def quotepath(path):
152 def quotepath(path):
153 '''quote the path part of a URL
153 '''quote the path part of a URL
154
154
155 This is similar to urllib.quote, but it also tries to avoid
155 This is similar to urllib.quote, but it also tries to avoid
156 quoting things twice (inspired by wget):
156 quoting things twice (inspired by wget):
157
157
158 >>> quotepath('abc def')
158 >>> quotepath('abc def')
159 'abc%20def'
159 'abc%20def'
160 >>> quotepath('abc%20def')
160 >>> quotepath('abc%20def')
161 'abc%20def'
161 'abc%20def'
162 >>> quotepath('abc%20 def')
162 >>> quotepath('abc%20 def')
163 'abc%20%20def'
163 'abc%20%20def'
164 >>> quotepath('abc def%20')
164 >>> quotepath('abc def%20')
165 'abc%20def%20'
165 'abc%20def%20'
166 >>> quotepath('abc def%2')
166 >>> quotepath('abc def%2')
167 'abc%20def%252'
167 'abc%20def%252'
168 >>> quotepath('abc def%')
168 >>> quotepath('abc def%')
169 'abc%20def%25'
169 'abc%20def%25'
170 '''
170 '''
171 global _safeset, _hex
171 global _safeset, _hex
172 if _safeset is None:
172 if _safeset is None:
173 _safeset = util.set(_safe)
173 _safeset = util.set(_safe)
174 _hex = util.set('abcdefABCDEF0123456789')
174 _hex = util.set('abcdefABCDEF0123456789')
175 l = list(path)
175 l = list(path)
176 for i in xrange(len(l)):
176 for i in xrange(len(l)):
177 c = l[i]
177 c = l[i]
178 if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
178 if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
179 pass
179 pass
180 elif c not in _safeset:
180 elif c not in _safeset:
181 l[i] = '%%%02X' % ord(c)
181 l[i] = '%%%02X' % ord(c)
182 return ''.join(l)
182 return ''.join(l)
183
183
184 class httprepository(remoterepository):
184 class httprepository(remoterepository):
185 def __init__(self, ui, path):
185 def __init__(self, ui, path):
186 self.path = path
186 self.path = path
187 self.caps = None
187 self.caps = None
188 self.handler = None
188 self.handler = None
189 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
189 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
190 if query or frag:
190 if query or frag:
191 raise util.Abort(_('unsupported URL component: "%s"') %
191 raise util.Abort(_('unsupported URL component: "%s"') %
192 (query or frag))
192 (query or frag))
193 if not urlpath:
193 if not urlpath:
194 urlpath = '/'
194 urlpath = '/'
195 urlpath = quotepath(urlpath)
195 urlpath = quotepath(urlpath)
196 host, port, user, passwd = netlocsplit(netloc)
196 host, port, user, passwd = netlocsplit(netloc)
197
197
198 # urllib cannot handle URLs with embedded user or passwd
198 # urllib cannot handle URLs with embedded user or passwd
199 self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
199 self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
200 urlpath, '', ''))
200 urlpath, '', ''))
201 self.ui = ui
201 self.ui = ui
202 self.ui.debug(_('using %s\n') % self._url)
202 self.ui.debug(_('using %s\n') % self._url)
203
203
204 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
204 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
205 # XXX proxyauthinfo = None
205 # XXX proxyauthinfo = None
206 handlers = [httphandler()]
206 handlers = [httphandler()]
207 if has_https:
207 if has_https:
208 handlers.append(httpshandler())
208 handlers.append(httpshandler())
209
209
210 if proxyurl:
210 if proxyurl:
211 # proxy can be proper url or host[:port]
211 # proxy can be proper url or host[:port]
212 if not (proxyurl.startswith('http:') or
212 if not (proxyurl.startswith('http:') or
213 proxyurl.startswith('https:')):
213 proxyurl.startswith('https:')):
214 proxyurl = 'http://' + proxyurl + '/'
214 proxyurl = 'http://' + proxyurl + '/'
215 snpqf = urlparse.urlsplit(proxyurl)
215 snpqf = urlparse.urlsplit(proxyurl)
216 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
216 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
217 hpup = netlocsplit(proxynetloc)
217 hpup = netlocsplit(proxynetloc)
218
218
219 proxyhost, proxyport, proxyuser, proxypasswd = hpup
219 proxyhost, proxyport, proxyuser, proxypasswd = hpup
220 if not proxyuser:
220 if not proxyuser:
221 proxyuser = ui.config("http_proxy", "user")
221 proxyuser = ui.config("http_proxy", "user")
222 proxypasswd = ui.config("http_proxy", "passwd")
222 proxypasswd = ui.config("http_proxy", "passwd")
223
223
224 # see if we should use a proxy for this url
224 # see if we should use a proxy for this url
225 no_list = [ "localhost", "127.0.0.1" ]
225 no_list = [ "localhost", "127.0.0.1" ]
226 no_list.extend([p.lower() for
226 no_list.extend([p.lower() for
227 p in ui.configlist("http_proxy", "no")])
227 p in ui.configlist("http_proxy", "no")])
228 no_list.extend([p.strip().lower() for
228 no_list.extend([p.strip().lower() for
229 p in os.getenv("no_proxy", '').split(',')
229 p in os.getenv("no_proxy", '').split(',')
230 if p.strip()])
230 if p.strip()])
231 # "http_proxy.always" config is for running tests on localhost
231 # "http_proxy.always" config is for running tests on localhost
232 if (not ui.configbool("http_proxy", "always") and
232 if (not ui.configbool("http_proxy", "always") and
233 host.lower() in no_list):
233 host.lower() in no_list):
234 # avoid auto-detection of proxy settings by appending
234 # avoid auto-detection of proxy settings by appending
235 # a ProxyHandler with no proxies defined.
235 # a ProxyHandler with no proxies defined.
236 handlers.append(urllib2.ProxyHandler({}))
236 handlers.append(urllib2.ProxyHandler({}))
237 ui.debug(_('disabling proxy for %s\n') % host)
237 ui.debug(_('disabling proxy for %s\n') % host)
238 else:
238 else:
239 proxyurl = urlparse.urlunsplit((
239 proxyurl = urlparse.urlunsplit((
240 proxyscheme, netlocunsplit(proxyhost, proxyport,
240 proxyscheme, netlocunsplit(proxyhost, proxyport,
241 proxyuser, proxypasswd or ''),
241 proxyuser, proxypasswd or ''),
242 proxypath, proxyquery, proxyfrag))
242 proxypath, proxyquery, proxyfrag))
243 handlers.append(urllib2.ProxyHandler({scheme: proxyurl}))
243 handlers.append(urllib2.ProxyHandler({scheme: proxyurl}))
244 ui.debug(_('proxying through http://%s:%s\n') %
244 ui.debug(_('proxying through http://%s:%s\n') %
245 (proxyhost, proxyport))
245 (proxyhost, proxyport))
246
246
247 # urllib2 takes proxy values from the environment and those
247 # urllib2 takes proxy values from the environment and those
248 # will take precedence if found, so drop them
248 # will take precedence if found, so drop them
249 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
249 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
250 try:
250 try:
251 if env in os.environ:
251 if env in os.environ:
252 del os.environ[env]
252 del os.environ[env]
253 except OSError:
253 except OSError:
254 pass
254 pass
255
255
256 passmgr = passwordmgr(ui)
256 passmgr = passwordmgr(ui)
257 if user:
257 if user:
258 ui.debug(_('http auth: user %s, password %s\n') %
258 ui.debug(_('http auth: user %s, password %s\n') %
259 (user, passwd and '*' * len(passwd) or 'not set'))
259 (user, passwd and '*' * len(passwd) or 'not set'))
260 netloc = host
260 netloc = host
261 if port:
261 if port:
262 netloc += ':' + port
262 netloc += ':' + port
263 # Python < 2.4.3 uses only the netloc to search for a password
263 # Python < 2.4.3 uses only the netloc to search for a password
264 passmgr.add_password(None, (self._url, netloc), user, passwd or '')
264 passmgr.add_password(None, (self._url, netloc), user, passwd or '')
265
265
266 handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
266 handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
267 httpdigestauthhandler(passmgr)))
267 httpdigestauthhandler(passmgr)))
268 opener = urllib2.build_opener(*handlers)
268 opener = urllib2.build_opener(*handlers)
269
269
270 # 1.0 here is the _protocol_ version
270 # 1.0 here is the _protocol_ version
271 opener.addheaders = [('User-agent', version.get_useragent())]
271 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
272 urllib2.install_opener(opener)
272 urllib2.install_opener(opener)
273
273
274 def url(self):
274 def url(self):
275 return self.path
275 return self.path
276
276
277 # look up capabilities only when needed
277 # look up capabilities only when needed
278
278
279 def get_caps(self):
279 def get_caps(self):
280 if self.caps is None:
280 if self.caps is None:
281 try:
281 try:
282 self.caps = util.set(self.do_read('capabilities').split())
282 self.caps = util.set(self.do_read('capabilities').split())
283 except repo.RepoError:
283 except repo.RepoError:
284 self.caps = util.set()
284 self.caps = util.set()
285 self.ui.debug(_('capabilities: %s\n') %
285 self.ui.debug(_('capabilities: %s\n') %
286 (' '.join(self.caps or ['none'])))
286 (' '.join(self.caps or ['none'])))
287 return self.caps
287 return self.caps
288
288
289 capabilities = property(get_caps)
289 capabilities = property(get_caps)
290
290
291 def lock(self):
291 def lock(self):
292 raise util.Abort(_('operation not supported over http'))
292 raise util.Abort(_('operation not supported over http'))
293
293
294 def do_cmd(self, cmd, **args):
294 def do_cmd(self, cmd, **args):
295 data = args.pop('data', None)
295 data = args.pop('data', None)
296 headers = args.pop('headers', {})
296 headers = args.pop('headers', {})
297 self.ui.debug(_("sending %s command\n") % cmd)
297 self.ui.debug(_("sending %s command\n") % cmd)
298 q = {"cmd": cmd}
298 q = {"cmd": cmd}
299 q.update(args)
299 q.update(args)
300 qs = '?%s' % urllib.urlencode(q)
300 qs = '?%s' % urllib.urlencode(q)
301 cu = "%s%s" % (self._url, qs)
301 cu = "%s%s" % (self._url, qs)
302 try:
302 try:
303 if data:
303 if data:
304 self.ui.debug(_("sending %s bytes\n") % len(data))
304 self.ui.debug(_("sending %s bytes\n") % len(data))
305 resp = urllib2.urlopen(request(cu, data, headers))
305 resp = urllib2.urlopen(request(cu, data, headers))
306 except urllib2.HTTPError, inst:
306 except urllib2.HTTPError, inst:
307 if inst.code == 401:
307 if inst.code == 401:
308 raise util.Abort(_('authorization failed'))
308 raise util.Abort(_('authorization failed'))
309 raise
309 raise
310 except httplib.HTTPException, inst:
310 except httplib.HTTPException, inst:
311 self.ui.debug(_('http error while sending %s command\n') % cmd)
311 self.ui.debug(_('http error while sending %s command\n') % cmd)
312 self.ui.print_exc()
312 self.ui.print_exc()
313 raise IOError(None, inst)
313 raise IOError(None, inst)
314 except IndexError:
314 except IndexError:
315 # this only happens with Python 2.3, later versions raise URLError
315 # this only happens with Python 2.3, later versions raise URLError
316 raise util.Abort(_('http error, possibly caused by proxy setting'))
316 raise util.Abort(_('http error, possibly caused by proxy setting'))
317 # record the url we got redirected to
317 # record the url we got redirected to
318 resp_url = resp.geturl()
318 resp_url = resp.geturl()
319 if resp_url.endswith(qs):
319 if resp_url.endswith(qs):
320 resp_url = resp_url[:-len(qs)]
320 resp_url = resp_url[:-len(qs)]
321 if self._url != resp_url:
321 if self._url != resp_url:
322 self.ui.status(_('real URL is %s\n') % resp_url)
322 self.ui.status(_('real URL is %s\n') % resp_url)
323 self._url = resp_url
323 self._url = resp_url
324 try:
324 try:
325 proto = resp.getheader('content-type')
325 proto = resp.getheader('content-type')
326 except AttributeError:
326 except AttributeError:
327 proto = resp.headers['content-type']
327 proto = resp.headers['content-type']
328
328
329 # accept old "text/plain" and "application/hg-changegroup" for now
329 # accept old "text/plain" and "application/hg-changegroup" for now
330 if not (proto.startswith('application/mercurial-') or
330 if not (proto.startswith('application/mercurial-') or
331 proto.startswith('text/plain') or
331 proto.startswith('text/plain') or
332 proto.startswith('application/hg-changegroup')):
332 proto.startswith('application/hg-changegroup')):
333 self.ui.debug(_("Requested URL: '%s'\n") % cu)
333 self.ui.debug(_("Requested URL: '%s'\n") % cu)
334 raise repo.RepoError(_("'%s' does not appear to be an hg repository")
334 raise repo.RepoError(_("'%s' does not appear to be an hg repository")
335 % self._url)
335 % self._url)
336
336
337 if proto.startswith('application/mercurial-'):
337 if proto.startswith('application/mercurial-'):
338 try:
338 try:
339 version = proto.split('-', 1)[1]
339 version = proto.split('-', 1)[1]
340 version_info = tuple([int(n) for n in version.split('.')])
340 version_info = tuple([int(n) for n in version.split('.')])
341 except ValueError:
341 except ValueError:
342 raise repo.RepoError(_("'%s' sent a broken Content-Type "
342 raise repo.RepoError(_("'%s' sent a broken Content-Type "
343 "header (%s)") % (self._url, proto))
343 "header (%s)") % (self._url, proto))
344 if version_info > (0, 1):
344 if version_info > (0, 1):
345 raise repo.RepoError(_("'%s' uses newer protocol %s") %
345 raise repo.RepoError(_("'%s' uses newer protocol %s") %
346 (self._url, version))
346 (self._url, version))
347
347
348 return resp
348 return resp
349
349
350 def do_read(self, cmd, **args):
350 def do_read(self, cmd, **args):
351 fp = self.do_cmd(cmd, **args)
351 fp = self.do_cmd(cmd, **args)
352 try:
352 try:
353 return fp.read()
353 return fp.read()
354 finally:
354 finally:
355 # if using keepalive, allow connection to be reused
355 # if using keepalive, allow connection to be reused
356 fp.close()
356 fp.close()
357
357
358 def lookup(self, key):
358 def lookup(self, key):
359 self.requirecap('lookup', _('look up remote revision'))
359 self.requirecap('lookup', _('look up remote revision'))
360 d = self.do_cmd("lookup", key = key).read()
360 d = self.do_cmd("lookup", key = key).read()
361 success, data = d[:-1].split(' ', 1)
361 success, data = d[:-1].split(' ', 1)
362 if int(success):
362 if int(success):
363 return bin(data)
363 return bin(data)
364 raise repo.RepoError(data)
364 raise repo.RepoError(data)
365
365
366 def heads(self):
366 def heads(self):
367 d = self.do_read("heads")
367 d = self.do_read("heads")
368 try:
368 try:
369 return map(bin, d[:-1].split(" "))
369 return map(bin, d[:-1].split(" "))
370 except:
370 except:
371 raise util.UnexpectedOutput(_("unexpected response:"), d)
371 raise util.UnexpectedOutput(_("unexpected response:"), d)
372
372
373 def branches(self, nodes):
373 def branches(self, nodes):
374 n = " ".join(map(hex, nodes))
374 n = " ".join(map(hex, nodes))
375 d = self.do_read("branches", nodes=n)
375 d = self.do_read("branches", nodes=n)
376 try:
376 try:
377 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
377 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
378 return br
378 return br
379 except:
379 except:
380 raise util.UnexpectedOutput(_("unexpected response:"), d)
380 raise util.UnexpectedOutput(_("unexpected response:"), d)
381
381
382 def between(self, pairs):
382 def between(self, pairs):
383 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
383 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
384 d = self.do_read("between", pairs=n)
384 d = self.do_read("between", pairs=n)
385 try:
385 try:
386 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
386 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
387 return p
387 return p
388 except:
388 except:
389 raise util.UnexpectedOutput(_("unexpected response:"), d)
389 raise util.UnexpectedOutput(_("unexpected response:"), d)
390
390
391 def changegroup(self, nodes, kind):
391 def changegroup(self, nodes, kind):
392 n = " ".join(map(hex, nodes))
392 n = " ".join(map(hex, nodes))
393 f = self.do_cmd("changegroup", roots=n)
393 f = self.do_cmd("changegroup", roots=n)
394 return util.chunkbuffer(zgenerator(f))
394 return util.chunkbuffer(zgenerator(f))
395
395
396 def changegroupsubset(self, bases, heads, source):
396 def changegroupsubset(self, bases, heads, source):
397 self.requirecap('changegroupsubset', _('look up remote changes'))
397 self.requirecap('changegroupsubset', _('look up remote changes'))
398 baselst = " ".join([hex(n) for n in bases])
398 baselst = " ".join([hex(n) for n in bases])
399 headlst = " ".join([hex(n) for n in heads])
399 headlst = " ".join([hex(n) for n in heads])
400 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
400 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
401 return util.chunkbuffer(zgenerator(f))
401 return util.chunkbuffer(zgenerator(f))
402
402
403 def unbundle(self, cg, heads, source):
403 def unbundle(self, cg, heads, source):
404 # have to stream bundle to a temp file because we do not have
404 # have to stream bundle to a temp file because we do not have
405 # http 1.1 chunked transfer.
405 # http 1.1 chunked transfer.
406
406
407 type = ""
407 type = ""
408 types = self.capable('unbundle')
408 types = self.capable('unbundle')
409 # servers older than d1b16a746db6 will send 'unbundle' as a
409 # servers older than d1b16a746db6 will send 'unbundle' as a
410 # boolean capability
410 # boolean capability
411 try:
411 try:
412 types = types.split(',')
412 types = types.split(',')
413 except AttributeError:
413 except AttributeError:
414 types = [""]
414 types = [""]
415 if types:
415 if types:
416 for x in types:
416 for x in types:
417 if x in changegroup.bundletypes:
417 if x in changegroup.bundletypes:
418 type = x
418 type = x
419 break
419 break
420
420
421 tempname = changegroup.writebundle(cg, None, type)
421 tempname = changegroup.writebundle(cg, None, type)
422 fp = httpsendfile(tempname, "rb")
422 fp = httpsendfile(tempname, "rb")
423 try:
423 try:
424 try:
424 try:
425 rfp = self.do_cmd(
425 rfp = self.do_cmd(
426 'unbundle', data=fp,
426 'unbundle', data=fp,
427 headers={'Content-Type': 'application/octet-stream'},
427 headers={'Content-Type': 'application/octet-stream'},
428 heads=' '.join(map(hex, heads)))
428 heads=' '.join(map(hex, heads)))
429 try:
429 try:
430 ret = int(rfp.readline())
430 ret = int(rfp.readline())
431 self.ui.write(rfp.read())
431 self.ui.write(rfp.read())
432 return ret
432 return ret
433 finally:
433 finally:
434 rfp.close()
434 rfp.close()
435 except socket.error, err:
435 except socket.error, err:
436 if err[0] in (errno.ECONNRESET, errno.EPIPE):
436 if err[0] in (errno.ECONNRESET, errno.EPIPE):
437 raise util.Abort(_('push failed: %s') % err[1])
437 raise util.Abort(_('push failed: %s') % err[1])
438 raise util.Abort(err[1])
438 raise util.Abort(err[1])
439 finally:
439 finally:
440 fp.close()
440 fp.close()
441 os.unlink(tempname)
441 os.unlink(tempname)
442
442
443 def stream_out(self):
443 def stream_out(self):
444 return self.do_cmd('stream_out')
444 return self.do_cmd('stream_out')
445
445
446 class httpsrepository(httprepository):
446 class httpsrepository(httprepository):
447 def __init__(self, ui, path):
447 def __init__(self, ui, path):
448 if not has_https:
448 if not has_https:
449 raise util.Abort(_('Python support for SSL and HTTPS '
449 raise util.Abort(_('Python support for SSL and HTTPS '
450 'is not installed'))
450 'is not installed'))
451 httprepository.__init__(self, ui, path)
451 httprepository.__init__(self, ui, path)
452
452
453 def instance(ui, path, create):
453 def instance(ui, path, create):
454 if create:
454 if create:
455 raise util.Abort(_('cannot create new http repository'))
455 raise util.Abort(_('cannot create new http repository'))
456 if path.startswith('https:'):
456 if path.startswith('https:'):
457 return httpsrepository(ui, path)
457 return httpsrepository(ui, path)
458 return httprepository(ui, path)
458 return httprepository(ui, path)
@@ -1,88 +1,77 b''
1 # Copyright (C) 2005, 2006, 2008 by Intevation GmbH
1 # Copyright (C) 2005, 2006, 2008 by Intevation GmbH
2 # Author(s):
2 # Author(s):
3 # Thomas Arendsen Hein <thomas@intevation.de>
3 # Thomas Arendsen Hein <thomas@intevation.de>
4 #
4 #
5 # This program is free software under the GNU GPL (>=v2)
5 # This program is free software under the GNU GPL (>=v2)
6 # Read the file COPYING coming with the software for details.
6 # Read the file COPYING coming with the software for details.
7
7
8 """
8 """
9 Mercurial version
9 Mercurial version
10 """
10 """
11
11
12 import os
12 import os
13 import sys
14 import re
13 import re
15 import time
14 import time
16
15
17 unknown_version = 'unknown'
16 unknown_version = 'unknown'
18 remembered_version = False
17 remembered_version = False
19
18
20 def get_version(doreload=False):
19 def get_version(doreload=False):
21 """Return version information if available."""
20 """Return version information if available."""
22 try:
21 try:
23 import mercurial.__version__
22 import mercurial.__version__
24 if doreload:
23 if doreload:
25 reload(mercurial.__version__)
24 reload(mercurial.__version__)
26 version = mercurial.__version__.version
25 version = mercurial.__version__.version
27 except ImportError:
26 except ImportError:
28 version = unknown_version
27 version = unknown_version
29 return version
28 return version
30
29
31 def get_useragent():
32 """Return some extended version information for the User-Agent
33 field in http requests."""
34 hgver = get_version()
35 pyver = '%s(%s)' % (sys.version.split()[0], hex(sys.hexversion))
36 ostype = os.name
37 plat = sys.platform
38 return 'mercurial/proto-1.0 (Python/%s; Mercurial/%s; %s/%s)' % \
39 (pyver, hgver, ostype, plat)
40
41 def write_version(version):
30 def write_version(version):
42 """Overwrite version file."""
31 """Overwrite version file."""
43 if version == get_version():
32 if version == get_version():
44 return
33 return
45 directory = os.path.dirname(__file__)
34 directory = os.path.dirname(__file__)
46 for suffix in ['py', 'pyc', 'pyo']:
35 for suffix in ['py', 'pyc', 'pyo']:
47 try:
36 try:
48 os.unlink(os.path.join(directory, '__version__.%s' % suffix))
37 os.unlink(os.path.join(directory, '__version__.%s' % suffix))
49 except OSError:
38 except OSError:
50 pass
39 pass
51 f = open(os.path.join(directory, '__version__.py'), 'w')
40 f = open(os.path.join(directory, '__version__.py'), 'w')
52 f.write("# This file is auto-generated.\n")
41 f.write("# This file is auto-generated.\n")
53 f.write("version = %r\n" % version)
42 f.write("version = %r\n" % version)
54 f.close()
43 f.close()
55 # reload the file we've just written
44 # reload the file we've just written
56 get_version(True)
45 get_version(True)
57
46
58 def remember_version(version=None):
47 def remember_version(version=None):
59 """Store version information."""
48 """Store version information."""
60 global remembered_version
49 global remembered_version
61 if not version and os.path.isdir(".hg"):
50 if not version and os.path.isdir(".hg"):
62 f = os.popen("hg identify") # use real hg installation
51 f = os.popen("hg identify") # use real hg installation
63 ident = f.read()[:-1]
52 ident = f.read()[:-1]
64 if not f.close() and ident:
53 if not f.close() and ident:
65 ids = ident.split(' ', 1)
54 ids = ident.split(' ', 1)
66 version = ids.pop(0)
55 version = ids.pop(0)
67 if version[-1] == '+':
56 if version[-1] == '+':
68 version = version[:-1]
57 version = version[:-1]
69 modified = True
58 modified = True
70 else:
59 else:
71 modified = False
60 modified = False
72 if version.isalnum() and ids:
61 if version.isalnum() and ids:
73 for tag in ids[0].split('/'):
62 for tag in ids[0].split('/'):
74 # is a tag is suitable as a version number?
63 # is a tag is suitable as a version number?
75 if re.match(r'^(\d+\.)+[\w.-]+$', tag):
64 if re.match(r'^(\d+\.)+[\w.-]+$', tag):
76 version = tag
65 version = tag
77 break
66 break
78 if modified:
67 if modified:
79 version += time.strftime('+%Y%m%d')
68 version += time.strftime('+%Y%m%d')
80 if version:
69 if version:
81 remembered_version = True
70 remembered_version = True
82 write_version(version)
71 write_version(version)
83
72
84 def forget_version():
73 def forget_version():
85 """Remove version information."""
74 """Remove version information."""
86 if remembered_version:
75 if remembered_version:
87 write_version(unknown_version)
76 write_version(unknown_version)
88
77
General Comments 0
You need to be logged in to leave comments. Login now