##// END OF EJS Templates
url: added authuri when login information is requested (issue3209)...
Lucas Moscovicz -
r20291:7d589d92 default
parent child Browse files
Show More
@@ -1,480 +1,484 b''
1 # url.py - HTTP handling for mercurial
1 # url.py - HTTP handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 import urllib, urllib2, httplib, os, socket, cStringIO
10 import urllib, urllib2, httplib, os, socket, cStringIO
11 from i18n import _
11 from i18n import _
12 import keepalive, util, sslutil
12 import keepalive, util, sslutil
13 import httpconnection as httpconnectionmod
13 import httpconnection as httpconnectionmod
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 self._writedebug(user, passwd)
25 self._writedebug(user, passwd)
26 return (user, passwd)
26 return (user, passwd)
27
27
28 if not user or not passwd:
28 if not user or not passwd:
29 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
29 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
30 if res:
30 if res:
31 group, auth = res
31 group, auth = res
32 user, passwd = auth.get('username'), auth.get('password')
32 user, passwd = auth.get('username'), auth.get('password')
33 self.ui.debug("using auth.%s.* for authentication\n" % group)
33 self.ui.debug("using auth.%s.* for authentication\n" % group)
34 if not user or not passwd:
34 if not user or not passwd:
35 u = util.url(authuri)
36 u.query = None
35 if not self.ui.interactive():
37 if not self.ui.interactive():
36 raise util.Abort(_('http authorization required'))
38 raise util.Abort(_('http authorization required for %s') %
39 util.hidepassword(str(u)))
37
40
38 self.ui.write(_("http authorization required\n"))
41 self.ui.write(_("http authorization required for %s\n") %
42 util.hidepassword(str(u)))
39 self.ui.write(_("realm: %s\n") % realm)
43 self.ui.write(_("realm: %s\n") % realm)
40 if user:
44 if user:
41 self.ui.write(_("user: %s\n") % user)
45 self.ui.write(_("user: %s\n") % user)
42 else:
46 else:
43 user = self.ui.prompt(_("user:"), default=None)
47 user = self.ui.prompt(_("user:"), default=None)
44
48
45 if not passwd:
49 if not passwd:
46 passwd = self.ui.getpass()
50 passwd = self.ui.getpass()
47
51
48 self.add_password(realm, authuri, user, passwd)
52 self.add_password(realm, authuri, user, passwd)
49 self._writedebug(user, passwd)
53 self._writedebug(user, passwd)
50 return (user, passwd)
54 return (user, passwd)
51
55
52 def _writedebug(self, user, passwd):
56 def _writedebug(self, user, passwd):
53 msg = _('http auth: user %s, password %s\n')
57 msg = _('http auth: user %s, password %s\n')
54 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
58 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
55
59
56 def find_stored_password(self, authuri):
60 def find_stored_password(self, authuri):
57 return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
61 return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
58 self, None, authuri)
62 self, None, authuri)
59
63
60 class proxyhandler(urllib2.ProxyHandler):
64 class proxyhandler(urllib2.ProxyHandler):
61 def __init__(self, ui):
65 def __init__(self, ui):
62 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
66 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
63 # XXX proxyauthinfo = None
67 # XXX proxyauthinfo = None
64
68
65 if proxyurl:
69 if proxyurl:
66 # proxy can be proper url or host[:port]
70 # proxy can be proper url or host[:port]
67 if not (proxyurl.startswith('http:') or
71 if not (proxyurl.startswith('http:') or
68 proxyurl.startswith('https:')):
72 proxyurl.startswith('https:')):
69 proxyurl = 'http://' + proxyurl + '/'
73 proxyurl = 'http://' + proxyurl + '/'
70 proxy = util.url(proxyurl)
74 proxy = util.url(proxyurl)
71 if not proxy.user:
75 if not proxy.user:
72 proxy.user = ui.config("http_proxy", "user")
76 proxy.user = ui.config("http_proxy", "user")
73 proxy.passwd = ui.config("http_proxy", "passwd")
77 proxy.passwd = ui.config("http_proxy", "passwd")
74
78
75 # see if we should use a proxy for this url
79 # see if we should use a proxy for this url
76 no_list = ["localhost", "127.0.0.1"]
80 no_list = ["localhost", "127.0.0.1"]
77 no_list.extend([p.lower() for
81 no_list.extend([p.lower() for
78 p in ui.configlist("http_proxy", "no")])
82 p in ui.configlist("http_proxy", "no")])
79 no_list.extend([p.strip().lower() for
83 no_list.extend([p.strip().lower() for
80 p in os.getenv("no_proxy", '').split(',')
84 p in os.getenv("no_proxy", '').split(',')
81 if p.strip()])
85 if p.strip()])
82 # "http_proxy.always" config is for running tests on localhost
86 # "http_proxy.always" config is for running tests on localhost
83 if ui.configbool("http_proxy", "always"):
87 if ui.configbool("http_proxy", "always"):
84 self.no_list = []
88 self.no_list = []
85 else:
89 else:
86 self.no_list = no_list
90 self.no_list = no_list
87
91
88 proxyurl = str(proxy)
92 proxyurl = str(proxy)
89 proxies = {'http': proxyurl, 'https': proxyurl}
93 proxies = {'http': proxyurl, 'https': proxyurl}
90 ui.debug('proxying through http://%s:%s\n' %
94 ui.debug('proxying through http://%s:%s\n' %
91 (proxy.host, proxy.port))
95 (proxy.host, proxy.port))
92 else:
96 else:
93 proxies = {}
97 proxies = {}
94
98
95 # urllib2 takes proxy values from the environment and those
99 # urllib2 takes proxy values from the environment and those
96 # will take precedence if found. So, if there's a config entry
100 # will take precedence if found. So, if there's a config entry
97 # defining a proxy, drop the environment ones
101 # defining a proxy, drop the environment ones
98 if ui.config("http_proxy", "host"):
102 if ui.config("http_proxy", "host"):
99 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
103 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
100 try:
104 try:
101 if env in os.environ:
105 if env in os.environ:
102 del os.environ[env]
106 del os.environ[env]
103 except OSError:
107 except OSError:
104 pass
108 pass
105
109
106 urllib2.ProxyHandler.__init__(self, proxies)
110 urllib2.ProxyHandler.__init__(self, proxies)
107 self.ui = ui
111 self.ui = ui
108
112
109 def proxy_open(self, req, proxy, type_):
113 def proxy_open(self, req, proxy, type_):
110 host = req.get_host().split(':')[0]
114 host = req.get_host().split(':')[0]
111 for e in self.no_list:
115 for e in self.no_list:
112 if host == e:
116 if host == e:
113 return None
117 return None
114 if e.startswith('*.') and host.endswith(e[2:]):
118 if e.startswith('*.') and host.endswith(e[2:]):
115 return None
119 return None
116 if e.startswith('.') and host.endswith(e[1:]):
120 if e.startswith('.') and host.endswith(e[1:]):
117 return None
121 return None
118
122
119 # work around a bug in Python < 2.4.2
123 # work around a bug in Python < 2.4.2
120 # (it leaves a "\n" at the end of Proxy-authorization headers)
124 # (it leaves a "\n" at the end of Proxy-authorization headers)
121 baseclass = req.__class__
125 baseclass = req.__class__
122 class _request(baseclass):
126 class _request(baseclass):
123 def add_header(self, key, val):
127 def add_header(self, key, val):
124 if key.lower() == 'proxy-authorization':
128 if key.lower() == 'proxy-authorization':
125 val = val.strip()
129 val = val.strip()
126 return baseclass.add_header(self, key, val)
130 return baseclass.add_header(self, key, val)
127 req.__class__ = _request
131 req.__class__ = _request
128
132
129 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
133 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
130
134
131 def _gen_sendfile(orgsend):
135 def _gen_sendfile(orgsend):
132 def _sendfile(self, data):
136 def _sendfile(self, data):
133 # send a file
137 # send a file
134 if isinstance(data, httpconnectionmod.httpsendfile):
138 if isinstance(data, httpconnectionmod.httpsendfile):
135 # if auth required, some data sent twice, so rewind here
139 # if auth required, some data sent twice, so rewind here
136 data.seek(0)
140 data.seek(0)
137 for chunk in util.filechunkiter(data):
141 for chunk in util.filechunkiter(data):
138 orgsend(self, chunk)
142 orgsend(self, chunk)
139 else:
143 else:
140 orgsend(self, data)
144 orgsend(self, data)
141 return _sendfile
145 return _sendfile
142
146
143 has_https = util.safehasattr(urllib2, 'HTTPSHandler')
147 has_https = util.safehasattr(urllib2, 'HTTPSHandler')
144 if has_https:
148 if has_https:
145 try:
149 try:
146 _create_connection = socket.create_connection
150 _create_connection = socket.create_connection
147 except AttributeError:
151 except AttributeError:
148 _GLOBAL_DEFAULT_TIMEOUT = object()
152 _GLOBAL_DEFAULT_TIMEOUT = object()
149
153
150 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
154 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
151 source_address=None):
155 source_address=None):
152 # lifted from Python 2.6
156 # lifted from Python 2.6
153
157
154 msg = "getaddrinfo returns an empty list"
158 msg = "getaddrinfo returns an empty list"
155 host, port = address
159 host, port = address
156 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
160 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
157 af, socktype, proto, canonname, sa = res
161 af, socktype, proto, canonname, sa = res
158 sock = None
162 sock = None
159 try:
163 try:
160 sock = socket.socket(af, socktype, proto)
164 sock = socket.socket(af, socktype, proto)
161 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
165 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
162 sock.settimeout(timeout)
166 sock.settimeout(timeout)
163 if source_address:
167 if source_address:
164 sock.bind(source_address)
168 sock.bind(source_address)
165 sock.connect(sa)
169 sock.connect(sa)
166 return sock
170 return sock
167
171
168 except socket.error, msg:
172 except socket.error, msg:
169 if sock is not None:
173 if sock is not None:
170 sock.close()
174 sock.close()
171
175
172 raise socket.error(msg)
176 raise socket.error(msg)
173
177
174 class httpconnection(keepalive.HTTPConnection):
178 class httpconnection(keepalive.HTTPConnection):
175 # must be able to send big bundle as stream.
179 # must be able to send big bundle as stream.
176 send = _gen_sendfile(keepalive.HTTPConnection.send)
180 send = _gen_sendfile(keepalive.HTTPConnection.send)
177
181
178 def connect(self):
182 def connect(self):
179 if has_https and self.realhostport: # use CONNECT proxy
183 if has_https and self.realhostport: # use CONNECT proxy
180 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
184 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
181 self.sock.connect((self.host, self.port))
185 self.sock.connect((self.host, self.port))
182 if _generic_proxytunnel(self):
186 if _generic_proxytunnel(self):
183 # we do not support client X.509 certificates
187 # we do not support client X.509 certificates
184 self.sock = sslutil.ssl_wrap_socket(self.sock, None, None)
188 self.sock = sslutil.ssl_wrap_socket(self.sock, None, None)
185 else:
189 else:
186 keepalive.HTTPConnection.connect(self)
190 keepalive.HTTPConnection.connect(self)
187
191
188 def getresponse(self):
192 def getresponse(self):
189 proxyres = getattr(self, 'proxyres', None)
193 proxyres = getattr(self, 'proxyres', None)
190 if proxyres:
194 if proxyres:
191 if proxyres.will_close:
195 if proxyres.will_close:
192 self.close()
196 self.close()
193 self.proxyres = None
197 self.proxyres = None
194 return proxyres
198 return proxyres
195 return keepalive.HTTPConnection.getresponse(self)
199 return keepalive.HTTPConnection.getresponse(self)
196
200
197 # general transaction handler to support different ways to handle
201 # general transaction handler to support different ways to handle
198 # HTTPS proxying before and after Python 2.6.3.
202 # HTTPS proxying before and after Python 2.6.3.
199 def _generic_start_transaction(handler, h, req):
203 def _generic_start_transaction(handler, h, req):
200 tunnel_host = getattr(req, '_tunnel_host', None)
204 tunnel_host = getattr(req, '_tunnel_host', None)
201 if tunnel_host:
205 if tunnel_host:
202 if tunnel_host[:7] not in ['http://', 'https:/']:
206 if tunnel_host[:7] not in ['http://', 'https:/']:
203 tunnel_host = 'https://' + tunnel_host
207 tunnel_host = 'https://' + tunnel_host
204 new_tunnel = True
208 new_tunnel = True
205 else:
209 else:
206 tunnel_host = req.get_selector()
210 tunnel_host = req.get_selector()
207 new_tunnel = False
211 new_tunnel = False
208
212
209 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
213 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
210 u = util.url(tunnel_host)
214 u = util.url(tunnel_host)
211 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
215 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
212 h.realhostport = ':'.join([u.host, (u.port or '443')])
216 h.realhostport = ':'.join([u.host, (u.port or '443')])
213 h.headers = req.headers.copy()
217 h.headers = req.headers.copy()
214 h.headers.update(handler.parent.addheaders)
218 h.headers.update(handler.parent.addheaders)
215 return
219 return
216
220
217 h.realhostport = None
221 h.realhostport = None
218 h.headers = None
222 h.headers = None
219
223
220 def _generic_proxytunnel(self):
224 def _generic_proxytunnel(self):
221 proxyheaders = dict(
225 proxyheaders = dict(
222 [(x, self.headers[x]) for x in self.headers
226 [(x, self.headers[x]) for x in self.headers
223 if x.lower().startswith('proxy-')])
227 if x.lower().startswith('proxy-')])
224 self._set_hostport(self.host, self.port)
228 self._set_hostport(self.host, self.port)
225 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
229 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
226 for header in proxyheaders.iteritems():
230 for header in proxyheaders.iteritems():
227 self.send('%s: %s\r\n' % header)
231 self.send('%s: %s\r\n' % header)
228 self.send('\r\n')
232 self.send('\r\n')
229
233
230 # majority of the following code is duplicated from
234 # majority of the following code is duplicated from
231 # httplib.HTTPConnection as there are no adequate places to
235 # httplib.HTTPConnection as there are no adequate places to
232 # override functions to provide the needed functionality
236 # override functions to provide the needed functionality
233 res = self.response_class(self.sock,
237 res = self.response_class(self.sock,
234 strict=self.strict,
238 strict=self.strict,
235 method=self._method)
239 method=self._method)
236
240
237 while True:
241 while True:
238 version, status, reason = res._read_status()
242 version, status, reason = res._read_status()
239 if status != httplib.CONTINUE:
243 if status != httplib.CONTINUE:
240 break
244 break
241 while True:
245 while True:
242 skip = res.fp.readline().strip()
246 skip = res.fp.readline().strip()
243 if not skip:
247 if not skip:
244 break
248 break
245 res.status = status
249 res.status = status
246 res.reason = reason.strip()
250 res.reason = reason.strip()
247
251
248 if res.status == 200:
252 if res.status == 200:
249 while True:
253 while True:
250 line = res.fp.readline()
254 line = res.fp.readline()
251 if line == '\r\n':
255 if line == '\r\n':
252 break
256 break
253 return True
257 return True
254
258
255 if version == 'HTTP/1.0':
259 if version == 'HTTP/1.0':
256 res.version = 10
260 res.version = 10
257 elif version.startswith('HTTP/1.'):
261 elif version.startswith('HTTP/1.'):
258 res.version = 11
262 res.version = 11
259 elif version == 'HTTP/0.9':
263 elif version == 'HTTP/0.9':
260 res.version = 9
264 res.version = 9
261 else:
265 else:
262 raise httplib.UnknownProtocol(version)
266 raise httplib.UnknownProtocol(version)
263
267
264 if res.version == 9:
268 if res.version == 9:
265 res.length = None
269 res.length = None
266 res.chunked = 0
270 res.chunked = 0
267 res.will_close = 1
271 res.will_close = 1
268 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
272 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
269 return False
273 return False
270
274
271 res.msg = httplib.HTTPMessage(res.fp)
275 res.msg = httplib.HTTPMessage(res.fp)
272 res.msg.fp = None
276 res.msg.fp = None
273
277
274 # are we using the chunked-style of transfer encoding?
278 # are we using the chunked-style of transfer encoding?
275 trenc = res.msg.getheader('transfer-encoding')
279 trenc = res.msg.getheader('transfer-encoding')
276 if trenc and trenc.lower() == "chunked":
280 if trenc and trenc.lower() == "chunked":
277 res.chunked = 1
281 res.chunked = 1
278 res.chunk_left = None
282 res.chunk_left = None
279 else:
283 else:
280 res.chunked = 0
284 res.chunked = 0
281
285
282 # will the connection close at the end of the response?
286 # will the connection close at the end of the response?
283 res.will_close = res._check_close()
287 res.will_close = res._check_close()
284
288
285 # do we have a Content-Length?
289 # do we have a Content-Length?
286 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
290 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
287 # transfer-encoding is "chunked"
291 # transfer-encoding is "chunked"
288 length = res.msg.getheader('content-length')
292 length = res.msg.getheader('content-length')
289 if length and not res.chunked:
293 if length and not res.chunked:
290 try:
294 try:
291 res.length = int(length)
295 res.length = int(length)
292 except ValueError:
296 except ValueError:
293 res.length = None
297 res.length = None
294 else:
298 else:
295 if res.length < 0: # ignore nonsensical negative lengths
299 if res.length < 0: # ignore nonsensical negative lengths
296 res.length = None
300 res.length = None
297 else:
301 else:
298 res.length = None
302 res.length = None
299
303
300 # does the body have a fixed length? (of zero)
304 # does the body have a fixed length? (of zero)
301 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
305 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
302 100 <= status < 200 or # 1xx codes
306 100 <= status < 200 or # 1xx codes
303 res._method == 'HEAD'):
307 res._method == 'HEAD'):
304 res.length = 0
308 res.length = 0
305
309
306 # if the connection remains open, and we aren't using chunked, and
310 # if the connection remains open, and we aren't using chunked, and
307 # a content-length was not provided, then assume that the connection
311 # a content-length was not provided, then assume that the connection
308 # WILL close.
312 # WILL close.
309 if (not res.will_close and
313 if (not res.will_close and
310 not res.chunked and
314 not res.chunked and
311 res.length is None):
315 res.length is None):
312 res.will_close = 1
316 res.will_close = 1
313
317
314 self.proxyres = res
318 self.proxyres = res
315
319
316 return False
320 return False
317
321
318 class httphandler(keepalive.HTTPHandler):
322 class httphandler(keepalive.HTTPHandler):
319 def http_open(self, req):
323 def http_open(self, req):
320 return self.do_open(httpconnection, req)
324 return self.do_open(httpconnection, req)
321
325
322 def _start_transaction(self, h, req):
326 def _start_transaction(self, h, req):
323 _generic_start_transaction(self, h, req)
327 _generic_start_transaction(self, h, req)
324 return keepalive.HTTPHandler._start_transaction(self, h, req)
328 return keepalive.HTTPHandler._start_transaction(self, h, req)
325
329
326 if has_https:
330 if has_https:
327 class httpsconnection(httplib.HTTPSConnection):
331 class httpsconnection(httplib.HTTPSConnection):
328 response_class = keepalive.HTTPResponse
332 response_class = keepalive.HTTPResponse
329 # must be able to send big bundle as stream.
333 # must be able to send big bundle as stream.
330 send = _gen_sendfile(keepalive.safesend)
334 send = _gen_sendfile(keepalive.safesend)
331 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
335 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
332
336
333 def connect(self):
337 def connect(self):
334 self.sock = _create_connection((self.host, self.port))
338 self.sock = _create_connection((self.host, self.port))
335
339
336 host = self.host
340 host = self.host
337 if self.realhostport: # use CONNECT proxy
341 if self.realhostport: # use CONNECT proxy
338 _generic_proxytunnel(self)
342 _generic_proxytunnel(self)
339 host = self.realhostport.rsplit(':', 1)[0]
343 host = self.realhostport.rsplit(':', 1)[0]
340 self.sock = sslutil.ssl_wrap_socket(
344 self.sock = sslutil.ssl_wrap_socket(
341 self.sock, self.key_file, self.cert_file,
345 self.sock, self.key_file, self.cert_file,
342 **sslutil.sslkwargs(self.ui, host))
346 **sslutil.sslkwargs(self.ui, host))
343 sslutil.validator(self.ui, host)(self.sock)
347 sslutil.validator(self.ui, host)(self.sock)
344
348
345 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
349 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
346 def __init__(self, ui):
350 def __init__(self, ui):
347 keepalive.KeepAliveHandler.__init__(self)
351 keepalive.KeepAliveHandler.__init__(self)
348 urllib2.HTTPSHandler.__init__(self)
352 urllib2.HTTPSHandler.__init__(self)
349 self.ui = ui
353 self.ui = ui
350 self.pwmgr = passwordmgr(self.ui)
354 self.pwmgr = passwordmgr(self.ui)
351
355
352 def _start_transaction(self, h, req):
356 def _start_transaction(self, h, req):
353 _generic_start_transaction(self, h, req)
357 _generic_start_transaction(self, h, req)
354 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
358 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
355
359
356 def https_open(self, req):
360 def https_open(self, req):
357 # req.get_full_url() does not contain credentials and we may
361 # req.get_full_url() does not contain credentials and we may
358 # need them to match the certificates.
362 # need them to match the certificates.
359 url = req.get_full_url()
363 url = req.get_full_url()
360 user, password = self.pwmgr.find_stored_password(url)
364 user, password = self.pwmgr.find_stored_password(url)
361 res = httpconnectionmod.readauthforuri(self.ui, url, user)
365 res = httpconnectionmod.readauthforuri(self.ui, url, user)
362 if res:
366 if res:
363 group, auth = res
367 group, auth = res
364 self.auth = auth
368 self.auth = auth
365 self.ui.debug("using auth.%s.* for authentication\n" % group)
369 self.ui.debug("using auth.%s.* for authentication\n" % group)
366 else:
370 else:
367 self.auth = None
371 self.auth = None
368 return self.do_open(self._makeconnection, req)
372 return self.do_open(self._makeconnection, req)
369
373
370 def _makeconnection(self, host, port=None, *args, **kwargs):
374 def _makeconnection(self, host, port=None, *args, **kwargs):
371 keyfile = None
375 keyfile = None
372 certfile = None
376 certfile = None
373
377
374 if len(args) >= 1: # key_file
378 if len(args) >= 1: # key_file
375 keyfile = args[0]
379 keyfile = args[0]
376 if len(args) >= 2: # cert_file
380 if len(args) >= 2: # cert_file
377 certfile = args[1]
381 certfile = args[1]
378 args = args[2:]
382 args = args[2:]
379
383
380 # if the user has specified different key/cert files in
384 # if the user has specified different key/cert files in
381 # hgrc, we prefer these
385 # hgrc, we prefer these
382 if self.auth and 'key' in self.auth and 'cert' in self.auth:
386 if self.auth and 'key' in self.auth and 'cert' in self.auth:
383 keyfile = self.auth['key']
387 keyfile = self.auth['key']
384 certfile = self.auth['cert']
388 certfile = self.auth['cert']
385
389
386 conn = httpsconnection(host, port, keyfile, certfile, *args,
390 conn = httpsconnection(host, port, keyfile, certfile, *args,
387 **kwargs)
391 **kwargs)
388 conn.ui = self.ui
392 conn.ui = self.ui
389 return conn
393 return conn
390
394
391 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
395 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
392 def __init__(self, *args, **kwargs):
396 def __init__(self, *args, **kwargs):
393 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
397 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
394 self.retried_req = None
398 self.retried_req = None
395
399
396 def reset_retry_count(self):
400 def reset_retry_count(self):
397 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
401 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
398 # forever. We disable reset_retry_count completely and reset in
402 # forever. We disable reset_retry_count completely and reset in
399 # http_error_auth_reqed instead.
403 # http_error_auth_reqed instead.
400 pass
404 pass
401
405
402 def http_error_auth_reqed(self, auth_header, host, req, headers):
406 def http_error_auth_reqed(self, auth_header, host, req, headers):
403 # Reset the retry counter once for each request.
407 # Reset the retry counter once for each request.
404 if req is not self.retried_req:
408 if req is not self.retried_req:
405 self.retried_req = req
409 self.retried_req = req
406 self.retried = 0
410 self.retried = 0
407 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
411 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
408 # it doesn't know about the auth type requested. This can happen if
412 # it doesn't know about the auth type requested. This can happen if
409 # somebody is using BasicAuth and types a bad password.
413 # somebody is using BasicAuth and types a bad password.
410 try:
414 try:
411 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
415 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
412 self, auth_header, host, req, headers)
416 self, auth_header, host, req, headers)
413 except ValueError, inst:
417 except ValueError, inst:
414 arg = inst.args[0]
418 arg = inst.args[0]
415 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
419 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
416 return
420 return
417 raise
421 raise
418
422
419 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
423 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
420 def __init__(self, *args, **kwargs):
424 def __init__(self, *args, **kwargs):
421 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
425 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
422 self.retried_req = None
426 self.retried_req = None
423
427
424 def reset_retry_count(self):
428 def reset_retry_count(self):
425 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
429 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
426 # forever. We disable reset_retry_count completely and reset in
430 # forever. We disable reset_retry_count completely and reset in
427 # http_error_auth_reqed instead.
431 # http_error_auth_reqed instead.
428 pass
432 pass
429
433
430 def http_error_auth_reqed(self, auth_header, host, req, headers):
434 def http_error_auth_reqed(self, auth_header, host, req, headers):
431 # Reset the retry counter once for each request.
435 # Reset the retry counter once for each request.
432 if req is not self.retried_req:
436 if req is not self.retried_req:
433 self.retried_req = req
437 self.retried_req = req
434 self.retried = 0
438 self.retried = 0
435 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
439 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
436 self, auth_header, host, req, headers)
440 self, auth_header, host, req, headers)
437
441
438 handlerfuncs = []
442 handlerfuncs = []
439
443
440 def opener(ui, authinfo=None):
444 def opener(ui, authinfo=None):
441 '''
445 '''
442 construct an opener suitable for urllib2
446 construct an opener suitable for urllib2
443 authinfo will be added to the password manager
447 authinfo will be added to the password manager
444 '''
448 '''
445 if ui.configbool('ui', 'usehttp2', False):
449 if ui.configbool('ui', 'usehttp2', False):
446 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
450 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
447 else:
451 else:
448 handlers = [httphandler()]
452 handlers = [httphandler()]
449 if has_https:
453 if has_https:
450 handlers.append(httpshandler(ui))
454 handlers.append(httpshandler(ui))
451
455
452 handlers.append(proxyhandler(ui))
456 handlers.append(proxyhandler(ui))
453
457
454 passmgr = passwordmgr(ui)
458 passmgr = passwordmgr(ui)
455 if authinfo is not None:
459 if authinfo is not None:
456 passmgr.add_password(*authinfo)
460 passmgr.add_password(*authinfo)
457 user, passwd = authinfo[2:4]
461 user, passwd = authinfo[2:4]
458 ui.debug('http auth: user %s, password %s\n' %
462 ui.debug('http auth: user %s, password %s\n' %
459 (user, passwd and '*' * len(passwd) or 'not set'))
463 (user, passwd and '*' * len(passwd) or 'not set'))
460
464
461 handlers.extend((httpbasicauthhandler(passmgr),
465 handlers.extend((httpbasicauthhandler(passmgr),
462 httpdigestauthhandler(passmgr)))
466 httpdigestauthhandler(passmgr)))
463 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
467 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
464 opener = urllib2.build_opener(*handlers)
468 opener = urllib2.build_opener(*handlers)
465
469
466 # 1.0 here is the _protocol_ version
470 # 1.0 here is the _protocol_ version
467 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
471 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
468 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
472 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
469 return opener
473 return opener
470
474
471 def open(ui, url_, data=None):
475 def open(ui, url_, data=None):
472 u = util.url(url_)
476 u = util.url(url_)
473 if u.scheme:
477 if u.scheme:
474 u.scheme = u.scheme.lower()
478 u.scheme = u.scheme.lower()
475 url_, authinfo = u.authinfo()
479 url_, authinfo = u.authinfo()
476 else:
480 else:
477 path = util.normpath(os.path.abspath(url_))
481 path = util.normpath(os.path.abspath(url_))
478 url_ = 'file://' + urllib.pathname2url(path)
482 url_ = 'file://' + urllib.pathname2url(path)
479 authinfo = None
483 authinfo = None
480 return opener(ui, authinfo).open(url_, data)
484 return opener(ui, authinfo).open(url_, data)
@@ -1,225 +1,237 b''
1 $ "$TESTDIR/hghave" serve || exit 80
1 $ "$TESTDIR/hghave" serve || exit 80
2
2
3 $ hg init test
3 $ hg init test
4 $ cd test
4 $ cd test
5 $ echo foo>foo
5 $ echo foo>foo
6 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
6 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
7 $ echo foo>foo.d/foo
7 $ echo foo>foo.d/foo
8 $ echo bar>foo.d/bAr.hg.d/BaR
8 $ echo bar>foo.d/bAr.hg.d/BaR
9 $ echo bar>foo.d/baR.d.hg/bAR
9 $ echo bar>foo.d/baR.d.hg/bAR
10 $ hg commit -A -m 1
10 $ hg commit -A -m 1
11 adding foo
11 adding foo
12 adding foo.d/bAr.hg.d/BaR
12 adding foo.d/bAr.hg.d/BaR
13 adding foo.d/baR.d.hg/bAR
13 adding foo.d/baR.d.hg/bAR
14 adding foo.d/foo
14 adding foo.d/foo
15 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
15 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
16 $ hg --config server.uncompressed=False serve -p $HGPORT1 -d --pid-file=../hg2.pid
16 $ hg --config server.uncompressed=False serve -p $HGPORT1 -d --pid-file=../hg2.pid
17
17
18 Test server address cannot be reused
18 Test server address cannot be reused
19
19
20 #if windows
20 #if windows
21 $ hg serve -p $HGPORT1 2>&1
21 $ hg serve -p $HGPORT1 2>&1
22 abort: cannot start server at ':$HGPORT1': * (glob)
22 abort: cannot start server at ':$HGPORT1': * (glob)
23 [255]
23 [255]
24 #else
24 #else
25 $ hg serve -p $HGPORT1 2>&1
25 $ hg serve -p $HGPORT1 2>&1
26 abort: cannot start server at ':$HGPORT1': Address already in use
26 abort: cannot start server at ':$HGPORT1': Address already in use
27 [255]
27 [255]
28 #endif
28 #endif
29 $ cd ..
29 $ cd ..
30 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
30 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
31
31
32 clone via stream
32 clone via stream
33
33
34 $ hg clone --uncompressed http://localhost:$HGPORT/ copy 2>&1
34 $ hg clone --uncompressed http://localhost:$HGPORT/ copy 2>&1
35 streaming all changes
35 streaming all changes
36 6 files to transfer, 606 bytes of data
36 6 files to transfer, 606 bytes of data
37 transferred * bytes in * seconds (*/sec) (glob)
37 transferred * bytes in * seconds (*/sec) (glob)
38 updating to branch default
38 updating to branch default
39 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
39 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 $ hg verify -R copy
40 $ hg verify -R copy
41 checking changesets
41 checking changesets
42 checking manifests
42 checking manifests
43 crosschecking files in changesets and manifests
43 crosschecking files in changesets and manifests
44 checking files
44 checking files
45 4 files, 1 changesets, 4 total revisions
45 4 files, 1 changesets, 4 total revisions
46
46
47 try to clone via stream, should use pull instead
47 try to clone via stream, should use pull instead
48
48
49 $ hg clone --uncompressed http://localhost:$HGPORT1/ copy2
49 $ hg clone --uncompressed http://localhost:$HGPORT1/ copy2
50 requesting all changes
50 requesting all changes
51 adding changesets
51 adding changesets
52 adding manifests
52 adding manifests
53 adding file changes
53 adding file changes
54 added 1 changesets with 4 changes to 4 files
54 added 1 changesets with 4 changes to 4 files
55 updating to branch default
55 updating to branch default
56 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
57
57
58 clone via pull
58 clone via pull
59
59
60 $ hg clone http://localhost:$HGPORT1/ copy-pull
60 $ hg clone http://localhost:$HGPORT1/ copy-pull
61 requesting all changes
61 requesting all changes
62 adding changesets
62 adding changesets
63 adding manifests
63 adding manifests
64 adding file changes
64 adding file changes
65 added 1 changesets with 4 changes to 4 files
65 added 1 changesets with 4 changes to 4 files
66 updating to branch default
66 updating to branch default
67 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 $ hg verify -R copy-pull
68 $ hg verify -R copy-pull
69 checking changesets
69 checking changesets
70 checking manifests
70 checking manifests
71 crosschecking files in changesets and manifests
71 crosschecking files in changesets and manifests
72 checking files
72 checking files
73 4 files, 1 changesets, 4 total revisions
73 4 files, 1 changesets, 4 total revisions
74 $ cd test
74 $ cd test
75 $ echo bar > bar
75 $ echo bar > bar
76 $ hg commit -A -d '1 0' -m 2
76 $ hg commit -A -d '1 0' -m 2
77 adding bar
77 adding bar
78 $ cd ..
78 $ cd ..
79
79
80 clone over http with --update
80 clone over http with --update
81
81
82 $ hg clone http://localhost:$HGPORT1/ updated --update 0
82 $ hg clone http://localhost:$HGPORT1/ updated --update 0
83 requesting all changes
83 requesting all changes
84 adding changesets
84 adding changesets
85 adding manifests
85 adding manifests
86 adding file changes
86 adding file changes
87 added 2 changesets with 5 changes to 5 files
87 added 2 changesets with 5 changes to 5 files
88 updating to branch default
88 updating to branch default
89 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 $ hg log -r . -R updated
90 $ hg log -r . -R updated
91 changeset: 0:8b6053c928fe
91 changeset: 0:8b6053c928fe
92 user: test
92 user: test
93 date: Thu Jan 01 00:00:00 1970 +0000
93 date: Thu Jan 01 00:00:00 1970 +0000
94 summary: 1
94 summary: 1
95
95
96 $ rm -rf updated
96 $ rm -rf updated
97
97
98 incoming via HTTP
98 incoming via HTTP
99
99
100 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
100 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
101 adding changesets
101 adding changesets
102 adding manifests
102 adding manifests
103 adding file changes
103 adding file changes
104 added 1 changesets with 4 changes to 4 files
104 added 1 changesets with 4 changes to 4 files
105 updating to branch default
105 updating to branch default
106 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
106 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
107 $ cd partial
107 $ cd partial
108 $ touch LOCAL
108 $ touch LOCAL
109 $ hg ci -qAm LOCAL
109 $ hg ci -qAm LOCAL
110 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
110 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
111 comparing with http://localhost:$HGPORT1/
111 comparing with http://localhost:$HGPORT1/
112 searching for changes
112 searching for changes
113 2
113 2
114 $ cd ..
114 $ cd ..
115
115
116 pull
116 pull
117
117
118 $ cd copy-pull
118 $ cd copy-pull
119 $ echo '[hooks]' >> .hg/hgrc
119 $ echo '[hooks]' >> .hg/hgrc
120 $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
120 $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
121 $ hg pull
121 $ hg pull
122 pulling from http://localhost:$HGPORT1/
122 pulling from http://localhost:$HGPORT1/
123 searching for changes
123 searching for changes
124 adding changesets
124 adding changesets
125 adding manifests
125 adding manifests
126 adding file changes
126 adding file changes
127 added 1 changesets with 1 changes to 1 files
127 added 1 changesets with 1 changes to 1 files
128 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=http://localhost:$HGPORT1/
128 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=http://localhost:$HGPORT1/
129 (run 'hg update' to get a working copy)
129 (run 'hg update' to get a working copy)
130 $ cd ..
130 $ cd ..
131
131
132 clone from invalid URL
132 clone from invalid URL
133
133
134 $ hg clone http://localhost:$HGPORT/bad
134 $ hg clone http://localhost:$HGPORT/bad
135 abort: HTTP Error 404: Not Found
135 abort: HTTP Error 404: Not Found
136 [255]
136 [255]
137
137
138 test http authentication
138 test http authentication
139 + use the same server to test server side streaming preference
139 + use the same server to test server side streaming preference
140
140
141 $ cd test
141 $ cd test
142 $ cat << EOT > userpass.py
142 $ cat << EOT > userpass.py
143 > import base64
143 > import base64
144 > from mercurial.hgweb import common
144 > from mercurial.hgweb import common
145 > def perform_authentication(hgweb, req, op):
145 > def perform_authentication(hgweb, req, op):
146 > auth = req.env.get('HTTP_AUTHORIZATION')
146 > auth = req.env.get('HTTP_AUTHORIZATION')
147 > if not auth:
147 > if not auth:
148 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
148 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
149 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
149 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
150 > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']:
150 > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']:
151 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
151 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
152 > def extsetup():
152 > def extsetup():
153 > common.permhooks.insert(0, perform_authentication)
153 > common.permhooks.insert(0, perform_authentication)
154 > EOT
154 > EOT
155 $ hg --config extensions.x=userpass.py serve -p $HGPORT2 -d --pid-file=pid \
155 $ hg --config extensions.x=userpass.py serve -p $HGPORT2 -d --pid-file=pid \
156 > --config server.preferuncompressed=True
156 > --config server.preferuncompressed=True
157 $ cat pid >> $DAEMON_PIDS
157 $ cat pid >> $DAEMON_PIDS
158
158
159 $ cat << EOF > get_pass.py
160 > import getpass
161 > def newgetpass(arg):
162 > return "pass"
163 > getpass.getpass = newgetpass
164 > EOF
165
159 $ hg id http://localhost:$HGPORT2/
166 $ hg id http://localhost:$HGPORT2/
160 abort: http authorization required
167 abort: http authorization required for http://localhost:$HGPORT2/
161 [255]
168 [255]
162 $ hg id http://user@localhost:$HGPORT2/
169 $ hg id http://localhost:$HGPORT2/
163 abort: http authorization required
170 abort: http authorization required for http://localhost:$HGPORT2/
164 [255]
171 [255]
172 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
173 http authorization required for http://localhost:$HGPORT2/
174 realm: mercurial
175 user: user
176 password: 5fed3813f7f5
165 $ hg id http://user:pass@localhost:$HGPORT2/
177 $ hg id http://user:pass@localhost:$HGPORT2/
166 5fed3813f7f5
178 5fed3813f7f5
167 $ echo '[auth]' >> .hg/hgrc
179 $ echo '[auth]' >> .hg/hgrc
168 $ echo 'l.schemes=http' >> .hg/hgrc
180 $ echo 'l.schemes=http' >> .hg/hgrc
169 $ echo 'l.prefix=lo' >> .hg/hgrc
181 $ echo 'l.prefix=lo' >> .hg/hgrc
170 $ echo 'l.username=user' >> .hg/hgrc
182 $ echo 'l.username=user' >> .hg/hgrc
171 $ echo 'l.password=pass' >> .hg/hgrc
183 $ echo 'l.password=pass' >> .hg/hgrc
172 $ hg id http://localhost:$HGPORT2/
184 $ hg id http://localhost:$HGPORT2/
173 5fed3813f7f5
185 5fed3813f7f5
174 $ hg id http://localhost:$HGPORT2/
186 $ hg id http://localhost:$HGPORT2/
175 5fed3813f7f5
187 5fed3813f7f5
176 $ hg id http://user@localhost:$HGPORT2/
188 $ hg id http://user@localhost:$HGPORT2/
177 5fed3813f7f5
189 5fed3813f7f5
178 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
190 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
179 streaming all changes
191 streaming all changes
180 7 files to transfer, 916 bytes of data
192 7 files to transfer, 916 bytes of data
181 transferred * bytes in * seconds (*/sec) (glob)
193 transferred * bytes in * seconds (*/sec) (glob)
182 updating to branch default
194 updating to branch default
183 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
184
196
185 $ hg id http://user2@localhost:$HGPORT2/
197 $ hg id http://user2@localhost:$HGPORT2/
186 abort: http authorization required
198 abort: http authorization required for http://localhost:$HGPORT2/
187 [255]
199 [255]
188 $ hg id http://user:pass2@localhost:$HGPORT2/
200 $ hg id http://user:pass2@localhost:$HGPORT2/
189 abort: HTTP Error 403: no
201 abort: HTTP Error 403: no
190 [255]
202 [255]
191
203
192 $ cd ..
204 $ cd ..
193
205
194 clone of serve with repo in root and unserved subrepo (issue2970)
206 clone of serve with repo in root and unserved subrepo (issue2970)
195
207
196 $ hg --cwd test init sub
208 $ hg --cwd test init sub
197 $ echo empty > test/sub/empty
209 $ echo empty > test/sub/empty
198 $ hg --cwd test/sub add empty
210 $ hg --cwd test/sub add empty
199 $ hg --cwd test/sub commit -qm 'add empty'
211 $ hg --cwd test/sub commit -qm 'add empty'
200 $ hg --cwd test/sub tag -r 0 something
212 $ hg --cwd test/sub tag -r 0 something
201 $ echo sub = sub > test/.hgsub
213 $ echo sub = sub > test/.hgsub
202 $ hg --cwd test add .hgsub
214 $ hg --cwd test add .hgsub
203 $ hg --cwd test commit -qm 'add subrepo'
215 $ hg --cwd test commit -qm 'add subrepo'
204 $ hg clone http://localhost:$HGPORT noslash-clone
216 $ hg clone http://localhost:$HGPORT noslash-clone
205 requesting all changes
217 requesting all changes
206 adding changesets
218 adding changesets
207 adding manifests
219 adding manifests
208 adding file changes
220 adding file changes
209 added 3 changesets with 7 changes to 7 files
221 added 3 changesets with 7 changes to 7 files
210 updating to branch default
222 updating to branch default
211 abort: HTTP Error 404: Not Found
223 abort: HTTP Error 404: Not Found
212 [255]
224 [255]
213 $ hg clone http://localhost:$HGPORT/ slash-clone
225 $ hg clone http://localhost:$HGPORT/ slash-clone
214 requesting all changes
226 requesting all changes
215 adding changesets
227 adding changesets
216 adding manifests
228 adding manifests
217 adding file changes
229 adding file changes
218 added 3 changesets with 7 changes to 7 files
230 added 3 changesets with 7 changes to 7 files
219 updating to branch default
231 updating to branch default
220 abort: HTTP Error 404: Not Found
232 abort: HTTP Error 404: Not Found
221 [255]
233 [255]
222
234
223 check error log
235 check error log
224
236
225 $ cat error.log
237 $ cat error.log
General Comments 0
You need to be logged in to leave comments. Login now