##// END OF EJS Templates
url: use url.url in url.open()
Brodie Rao -
r13818:bf6156ba default
parent child Browse files
Show More
@@ -1,977 +1,973
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, urlparse, httplib, os, re, socket, cStringIO
10 import urllib, urllib2, urlparse, httplib, os, socket, cStringIO
11 import __builtin__
11 import __builtin__
12 from i18n import _
12 from i18n import _
13 import keepalive, util
13 import keepalive, util
14
14
15 class url(object):
15 class url(object):
16 """Reliable URL parser.
16 """Reliable URL parser.
17
17
18 This parses URLs and provides attributes for the following
18 This parses URLs and provides attributes for the following
19 components:
19 components:
20
20
21 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
21 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
22
22
23 Missing components are set to None. The only exception is
23 Missing components are set to None. The only exception is
24 fragment, which is set to '' if present but empty.
24 fragment, which is set to '' if present but empty.
25
25
26 If parse_fragment is False, fragment is included in query. If
26 If parse_fragment is False, fragment is included in query. If
27 parse_query is False, query is included in path. If both are
27 parse_query is False, query is included in path. If both are
28 False, both fragment and query are included in path.
28 False, both fragment and query are included in path.
29
29
30 See http://www.ietf.org/rfc/rfc2396.txt for more information.
30 See http://www.ietf.org/rfc/rfc2396.txt for more information.
31
31
32 Note that for backward compatibility reasons, bundle URLs do not
32 Note that for backward compatibility reasons, bundle URLs do not
33 take host names. That means 'bundle://../' has a path of '../'.
33 take host names. That means 'bundle://../' has a path of '../'.
34
34
35 Examples:
35 Examples:
36
36
37 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
37 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
38 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
38 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
39 >>> url('ssh://[::1]:2200//home/joe/repo')
39 >>> url('ssh://[::1]:2200//home/joe/repo')
40 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
40 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
41 >>> url('file:///home/joe/repo')
41 >>> url('file:///home/joe/repo')
42 <url scheme: 'file', path: '/home/joe/repo'>
42 <url scheme: 'file', path: '/home/joe/repo'>
43 >>> url('bundle:foo')
43 >>> url('bundle:foo')
44 <url scheme: 'bundle', path: 'foo'>
44 <url scheme: 'bundle', path: 'foo'>
45 >>> url('bundle://../foo')
45 >>> url('bundle://../foo')
46 <url scheme: 'bundle', path: '../foo'>
46 <url scheme: 'bundle', path: '../foo'>
47 >>> url('c:\\\\foo\\\\bar')
47 >>> url('c:\\\\foo\\\\bar')
48 <url path: 'c:\\\\foo\\\\bar'>
48 <url path: 'c:\\\\foo\\\\bar'>
49
49
50 Authentication credentials:
50 Authentication credentials:
51
51
52 >>> url('ssh://joe:xyz@x/repo')
52 >>> url('ssh://joe:xyz@x/repo')
53 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
53 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
54 >>> url('ssh://joe@x/repo')
54 >>> url('ssh://joe@x/repo')
55 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
55 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
56
56
57 Query strings and fragments:
57 Query strings and fragments:
58
58
59 >>> url('http://host/a?b#c')
59 >>> url('http://host/a?b#c')
60 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
60 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
61 >>> url('http://host/a?b#c', parse_query=False, parse_fragment=False)
61 >>> url('http://host/a?b#c', parse_query=False, parse_fragment=False)
62 <url scheme: 'http', host: 'host', path: 'a?b#c'>
62 <url scheme: 'http', host: 'host', path: 'a?b#c'>
63 """
63 """
64
64
65 _safechars = "!~*'()+"
65 _safechars = "!~*'()+"
66 _safepchars = "/!~*'()+"
66 _safepchars = "/!~*'()+"
67
67
68 def __init__(self, path, parse_query=True, parse_fragment=True):
68 def __init__(self, path, parse_query=True, parse_fragment=True):
69 # We slowly chomp away at path until we have only the path left
69 # We slowly chomp away at path until we have only the path left
70 self.scheme = self.user = self.passwd = self.host = None
70 self.scheme = self.user = self.passwd = self.host = None
71 self.port = self.path = self.query = self.fragment = None
71 self.port = self.path = self.query = self.fragment = None
72 self._localpath = True
72 self._localpath = True
73
73
74 # special case for Windows drive letters
74 # special case for Windows drive letters
75 if has_drive_letter(path):
75 if has_drive_letter(path):
76 self.path = path
76 self.path = path
77 return
77 return
78
78
79 # For compatibility reasons, we can't handle bundle paths as
79 # For compatibility reasons, we can't handle bundle paths as
80 # normal URLS
80 # normal URLS
81 if path.startswith('bundle:'):
81 if path.startswith('bundle:'):
82 self.scheme = 'bundle'
82 self.scheme = 'bundle'
83 path = path[7:]
83 path = path[7:]
84 if path.startswith('//'):
84 if path.startswith('//'):
85 path = path[2:]
85 path = path[2:]
86 self.path = path
86 self.path = path
87 return
87 return
88
88
89 if not path.startswith('/') and ':' in path:
89 if not path.startswith('/') and ':' in path:
90 parts = path.split(':', 1)
90 parts = path.split(':', 1)
91 if parts[0]:
91 if parts[0]:
92 self.scheme, path = parts
92 self.scheme, path = parts
93 self._localpath = False
93 self._localpath = False
94
94
95 if not path:
95 if not path:
96 path = None
96 path = None
97 if self._localpath:
97 if self._localpath:
98 self.path = ''
98 self.path = ''
99 return
99 return
100 else:
100 else:
101 if parse_fragment and '#' in path:
101 if parse_fragment and '#' in path:
102 path, self.fragment = path.split('#', 1)
102 path, self.fragment = path.split('#', 1)
103 if not path:
103 if not path:
104 path = None
104 path = None
105 if self._localpath:
105 if self._localpath:
106 self.path = path
106 self.path = path
107 return
107 return
108
108
109 if parse_query and '?' in path:
109 if parse_query and '?' in path:
110 path, self.query = path.split('?', 1)
110 path, self.query = path.split('?', 1)
111 if not path:
111 if not path:
112 path = None
112 path = None
113 if not self.query:
113 if not self.query:
114 self.query = None
114 self.query = None
115
115
116 # // is required to specify a host/authority
116 # // is required to specify a host/authority
117 if path and path.startswith('//'):
117 if path and path.startswith('//'):
118 parts = path[2:].split('/', 1)
118 parts = path[2:].split('/', 1)
119 if len(parts) > 1:
119 if len(parts) > 1:
120 self.host, path = parts
120 self.host, path = parts
121 path = path
121 path = path
122 else:
122 else:
123 self.host = parts[0]
123 self.host = parts[0]
124 path = None
124 path = None
125 if not self.host:
125 if not self.host:
126 self.host = None
126 self.host = None
127 if path:
127 if path:
128 path = '/' + path
128 path = '/' + path
129
129
130 if self.host and '@' in self.host:
130 if self.host and '@' in self.host:
131 self.user, self.host = self.host.rsplit('@', 1)
131 self.user, self.host = self.host.rsplit('@', 1)
132 if ':' in self.user:
132 if ':' in self.user:
133 self.user, self.passwd = self.user.split(':', 1)
133 self.user, self.passwd = self.user.split(':', 1)
134 if not self.host:
134 if not self.host:
135 self.host = None
135 self.host = None
136
136
137 # Don't split on colons in IPv6 addresses without ports
137 # Don't split on colons in IPv6 addresses without ports
138 if (self.host and ':' in self.host and
138 if (self.host and ':' in self.host and
139 not (self.host.startswith('[') and self.host.endswith(']'))):
139 not (self.host.startswith('[') and self.host.endswith(']'))):
140 self.host, self.port = self.host.rsplit(':', 1)
140 self.host, self.port = self.host.rsplit(':', 1)
141 if not self.host:
141 if not self.host:
142 self.host = None
142 self.host = None
143
143
144 if (self.host and self.scheme == 'file' and
144 if (self.host and self.scheme == 'file' and
145 self.host not in ('localhost', '127.0.0.1', '[::1]')):
145 self.host not in ('localhost', '127.0.0.1', '[::1]')):
146 raise util.Abort(_('file:// URLs can only refer to localhost'))
146 raise util.Abort(_('file:// URLs can only refer to localhost'))
147
147
148 self.path = path
148 self.path = path
149
149
150 for a in ('user', 'passwd', 'host', 'port',
150 for a in ('user', 'passwd', 'host', 'port',
151 'path', 'query', 'fragment'):
151 'path', 'query', 'fragment'):
152 v = getattr(self, a)
152 v = getattr(self, a)
153 if v is not None:
153 if v is not None:
154 setattr(self, a, urllib.unquote(v))
154 setattr(self, a, urllib.unquote(v))
155
155
156 def __repr__(self):
156 def __repr__(self):
157 attrs = []
157 attrs = []
158 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
158 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
159 'query', 'fragment'):
159 'query', 'fragment'):
160 v = getattr(self, a)
160 v = getattr(self, a)
161 if v is not None:
161 if v is not None:
162 attrs.append('%s: %r' % (a, v))
162 attrs.append('%s: %r' % (a, v))
163 return '<url %s>' % ', '.join(attrs)
163 return '<url %s>' % ', '.join(attrs)
164
164
165 def __str__(self):
165 def __str__(self):
166 """Join the URL's components back into a URL string.
166 """Join the URL's components back into a URL string.
167
167
168 Examples:
168 Examples:
169
169
170 >>> str(url('http://user:pw@host:80/?foo#bar'))
170 >>> str(url('http://user:pw@host:80/?foo#bar'))
171 'http://user:pw@host:80/?foo#bar'
171 'http://user:pw@host:80/?foo#bar'
172 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
172 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
173 'ssh://user:pw@[::1]:2200//home/joe#'
173 'ssh://user:pw@[::1]:2200//home/joe#'
174 >>> str(url('http://localhost:80//'))
174 >>> str(url('http://localhost:80//'))
175 'http://localhost:80//'
175 'http://localhost:80//'
176 >>> str(url('http://localhost:80/'))
176 >>> str(url('http://localhost:80/'))
177 'http://localhost:80/'
177 'http://localhost:80/'
178 >>> str(url('http://localhost:80'))
178 >>> str(url('http://localhost:80'))
179 'http://localhost:80/'
179 'http://localhost:80/'
180 >>> str(url('bundle:foo'))
180 >>> str(url('bundle:foo'))
181 'bundle:foo'
181 'bundle:foo'
182 >>> str(url('bundle://../foo'))
182 >>> str(url('bundle://../foo'))
183 'bundle:../foo'
183 'bundle:../foo'
184 >>> str(url('path'))
184 >>> str(url('path'))
185 'path'
185 'path'
186 """
186 """
187 if self._localpath:
187 if self._localpath:
188 s = self.path
188 s = self.path
189 if self.scheme == 'bundle':
189 if self.scheme == 'bundle':
190 s = 'bundle:' + s
190 s = 'bundle:' + s
191 if self.fragment:
191 if self.fragment:
192 s += '#' + self.fragment
192 s += '#' + self.fragment
193 return s
193 return s
194
194
195 s = self.scheme + ':'
195 s = self.scheme + ':'
196 if (self.user or self.passwd or self.host or
196 if (self.user or self.passwd or self.host or
197 self.scheme and not self.path):
197 self.scheme and not self.path):
198 s += '//'
198 s += '//'
199 if self.user:
199 if self.user:
200 s += urllib.quote(self.user, safe=self._safechars)
200 s += urllib.quote(self.user, safe=self._safechars)
201 if self.passwd:
201 if self.passwd:
202 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
202 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
203 if self.user or self.passwd:
203 if self.user or self.passwd:
204 s += '@'
204 s += '@'
205 if self.host:
205 if self.host:
206 if not (self.host.startswith('[') and self.host.endswith(']')):
206 if not (self.host.startswith('[') and self.host.endswith(']')):
207 s += urllib.quote(self.host)
207 s += urllib.quote(self.host)
208 else:
208 else:
209 s += self.host
209 s += self.host
210 if self.port:
210 if self.port:
211 s += ':' + urllib.quote(self.port)
211 s += ':' + urllib.quote(self.port)
212 if self.host:
212 if self.host:
213 s += '/'
213 s += '/'
214 if self.path:
214 if self.path:
215 s += urllib.quote(self.path, safe=self._safepchars)
215 s += urllib.quote(self.path, safe=self._safepchars)
216 if self.query:
216 if self.query:
217 s += '?' + urllib.quote(self.query, safe=self._safepchars)
217 s += '?' + urllib.quote(self.query, safe=self._safepchars)
218 if self.fragment is not None:
218 if self.fragment is not None:
219 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
219 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
220 return s
220 return s
221
221
222 def authinfo(self):
222 def authinfo(self):
223 user, passwd = self.user, self.passwd
223 user, passwd = self.user, self.passwd
224 try:
224 try:
225 self.user, self.passwd = None, None
225 self.user, self.passwd = None, None
226 s = str(self)
226 s = str(self)
227 finally:
227 finally:
228 self.user, self.passwd = user, passwd
228 self.user, self.passwd = user, passwd
229 if not self.user:
229 if not self.user:
230 return (s, None)
230 return (s, None)
231 return (s, (None, (str(self), self.host),
231 return (s, (None, (str(self), self.host),
232 self.user, self.passwd or ''))
232 self.user, self.passwd or ''))
233
233
234 def has_scheme(path):
234 def has_scheme(path):
235 return bool(url(path).scheme)
235 return bool(url(path).scheme)
236
236
237 def has_drive_letter(path):
237 def has_drive_letter(path):
238 return path[1:2] == ':' and path[0:1].isalpha()
238 return path[1:2] == ':' and path[0:1].isalpha()
239
239
240 def hidepassword(u):
240 def hidepassword(u):
241 '''hide user credential in a url string'''
241 '''hide user credential in a url string'''
242 u = url(u)
242 u = url(u)
243 if u.passwd:
243 if u.passwd:
244 u.passwd = '***'
244 u.passwd = '***'
245 return str(u)
245 return str(u)
246
246
247 def removeauth(u):
247 def removeauth(u):
248 '''remove all authentication information from a url string'''
248 '''remove all authentication information from a url string'''
249 u = url(u)
249 u = url(u)
250 u.user = u.passwd = None
250 u.user = u.passwd = None
251 return str(u)
251 return str(u)
252
252
253 def netlocsplit(netloc):
253 def netlocsplit(netloc):
254 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
254 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
255
255
256 a = netloc.find('@')
256 a = netloc.find('@')
257 if a == -1:
257 if a == -1:
258 user, passwd = None, None
258 user, passwd = None, None
259 else:
259 else:
260 userpass, netloc = netloc[:a], netloc[a + 1:]
260 userpass, netloc = netloc[:a], netloc[a + 1:]
261 c = userpass.find(':')
261 c = userpass.find(':')
262 if c == -1:
262 if c == -1:
263 user, passwd = urllib.unquote(userpass), None
263 user, passwd = urllib.unquote(userpass), None
264 else:
264 else:
265 user = urllib.unquote(userpass[:c])
265 user = urllib.unquote(userpass[:c])
266 passwd = urllib.unquote(userpass[c + 1:])
266 passwd = urllib.unquote(userpass[c + 1:])
267 c = netloc.find(':')
267 c = netloc.find(':')
268 if c == -1:
268 if c == -1:
269 host, port = netloc, None
269 host, port = netloc, None
270 else:
270 else:
271 host, port = netloc[:c], netloc[c + 1:]
271 host, port = netloc[:c], netloc[c + 1:]
272 return host, port, user, passwd
272 return host, port, user, passwd
273
273
274 def netlocunsplit(host, port, user=None, passwd=None):
274 def netlocunsplit(host, port, user=None, passwd=None):
275 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
275 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
276 if port:
276 if port:
277 hostport = host + ':' + port
277 hostport = host + ':' + port
278 else:
278 else:
279 hostport = host
279 hostport = host
280 if user:
280 if user:
281 quote = lambda s: urllib.quote(s, safe='')
281 quote = lambda s: urllib.quote(s, safe='')
282 if passwd:
282 if passwd:
283 userpass = quote(user) + ':' + quote(passwd)
283 userpass = quote(user) + ':' + quote(passwd)
284 else:
284 else:
285 userpass = quote(user)
285 userpass = quote(user)
286 return userpass + '@' + hostport
286 return userpass + '@' + hostport
287 return hostport
287 return hostport
288
288
289 def readauthforuri(ui, uri):
289 def readauthforuri(ui, uri):
290 # Read configuration
290 # Read configuration
291 config = dict()
291 config = dict()
292 for key, val in ui.configitems('auth'):
292 for key, val in ui.configitems('auth'):
293 if '.' not in key:
293 if '.' not in key:
294 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
294 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
295 continue
295 continue
296 group, setting = key.rsplit('.', 1)
296 group, setting = key.rsplit('.', 1)
297 gdict = config.setdefault(group, dict())
297 gdict = config.setdefault(group, dict())
298 if setting in ('username', 'cert', 'key'):
298 if setting in ('username', 'cert', 'key'):
299 val = util.expandpath(val)
299 val = util.expandpath(val)
300 gdict[setting] = val
300 gdict[setting] = val
301
301
302 # Find the best match
302 # Find the best match
303 scheme, hostpath = uri.split('://', 1)
303 scheme, hostpath = uri.split('://', 1)
304 bestlen = 0
304 bestlen = 0
305 bestauth = None
305 bestauth = None
306 for group, auth in config.iteritems():
306 for group, auth in config.iteritems():
307 prefix = auth.get('prefix')
307 prefix = auth.get('prefix')
308 if not prefix:
308 if not prefix:
309 continue
309 continue
310 p = prefix.split('://', 1)
310 p = prefix.split('://', 1)
311 if len(p) > 1:
311 if len(p) > 1:
312 schemes, prefix = [p[0]], p[1]
312 schemes, prefix = [p[0]], p[1]
313 else:
313 else:
314 schemes = (auth.get('schemes') or 'https').split()
314 schemes = (auth.get('schemes') or 'https').split()
315 if (prefix == '*' or hostpath.startswith(prefix)) and \
315 if (prefix == '*' or hostpath.startswith(prefix)) and \
316 len(prefix) > bestlen and scheme in schemes:
316 len(prefix) > bestlen and scheme in schemes:
317 bestlen = len(prefix)
317 bestlen = len(prefix)
318 bestauth = group, auth
318 bestauth = group, auth
319 return bestauth
319 return bestauth
320
320
321 _safe = ('abcdefghijklmnopqrstuvwxyz'
321 _safe = ('abcdefghijklmnopqrstuvwxyz'
322 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
322 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
323 '0123456789' '_.-/')
323 '0123456789' '_.-/')
324 _safeset = None
324 _safeset = None
325 _hex = None
325 _hex = None
326 def quotepath(path):
326 def quotepath(path):
327 '''quote the path part of a URL
327 '''quote the path part of a URL
328
328
329 This is similar to urllib.quote, but it also tries to avoid
329 This is similar to urllib.quote, but it also tries to avoid
330 quoting things twice (inspired by wget):
330 quoting things twice (inspired by wget):
331
331
332 >>> quotepath('abc def')
332 >>> quotepath('abc def')
333 'abc%20def'
333 'abc%20def'
334 >>> quotepath('abc%20def')
334 >>> quotepath('abc%20def')
335 'abc%20def'
335 'abc%20def'
336 >>> quotepath('abc%20 def')
336 >>> quotepath('abc%20 def')
337 'abc%20%20def'
337 'abc%20%20def'
338 >>> quotepath('abc def%20')
338 >>> quotepath('abc def%20')
339 'abc%20def%20'
339 'abc%20def%20'
340 >>> quotepath('abc def%2')
340 >>> quotepath('abc def%2')
341 'abc%20def%252'
341 'abc%20def%252'
342 >>> quotepath('abc def%')
342 >>> quotepath('abc def%')
343 'abc%20def%25'
343 'abc%20def%25'
344 '''
344 '''
345 global _safeset, _hex
345 global _safeset, _hex
346 if _safeset is None:
346 if _safeset is None:
347 _safeset = set(_safe)
347 _safeset = set(_safe)
348 _hex = set('abcdefABCDEF0123456789')
348 _hex = set('abcdefABCDEF0123456789')
349 l = list(path)
349 l = list(path)
350 for i in xrange(len(l)):
350 for i in xrange(len(l)):
351 c = l[i]
351 c = l[i]
352 if (c == '%' and i + 2 < len(l) and
352 if (c == '%' and i + 2 < len(l) and
353 l[i + 1] in _hex and l[i + 2] in _hex):
353 l[i + 1] in _hex and l[i + 2] in _hex):
354 pass
354 pass
355 elif c not in _safeset:
355 elif c not in _safeset:
356 l[i] = '%%%02X' % ord(c)
356 l[i] = '%%%02X' % ord(c)
357 return ''.join(l)
357 return ''.join(l)
358
358
359 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
359 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
360 def __init__(self, ui):
360 def __init__(self, ui):
361 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
361 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
362 self.ui = ui
362 self.ui = ui
363
363
364 def find_user_password(self, realm, authuri):
364 def find_user_password(self, realm, authuri):
365 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
365 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
366 self, realm, authuri)
366 self, realm, authuri)
367 user, passwd = authinfo
367 user, passwd = authinfo
368 if user and passwd:
368 if user and passwd:
369 self._writedebug(user, passwd)
369 self._writedebug(user, passwd)
370 return (user, passwd)
370 return (user, passwd)
371
371
372 if not user:
372 if not user:
373 res = readauthforuri(self.ui, authuri)
373 res = readauthforuri(self.ui, authuri)
374 if res:
374 if res:
375 group, auth = res
375 group, auth = res
376 user, passwd = auth.get('username'), auth.get('password')
376 user, passwd = auth.get('username'), auth.get('password')
377 self.ui.debug("using auth.%s.* for authentication\n" % group)
377 self.ui.debug("using auth.%s.* for authentication\n" % group)
378 if not user or not passwd:
378 if not user or not passwd:
379 if not self.ui.interactive():
379 if not self.ui.interactive():
380 raise util.Abort(_('http authorization required'))
380 raise util.Abort(_('http authorization required'))
381
381
382 self.ui.write(_("http authorization required\n"))
382 self.ui.write(_("http authorization required\n"))
383 self.ui.write(_("realm: %s\n") % realm)
383 self.ui.write(_("realm: %s\n") % realm)
384 if user:
384 if user:
385 self.ui.write(_("user: %s\n") % user)
385 self.ui.write(_("user: %s\n") % user)
386 else:
386 else:
387 user = self.ui.prompt(_("user:"), default=None)
387 user = self.ui.prompt(_("user:"), default=None)
388
388
389 if not passwd:
389 if not passwd:
390 passwd = self.ui.getpass()
390 passwd = self.ui.getpass()
391
391
392 self.add_password(realm, authuri, user, passwd)
392 self.add_password(realm, authuri, user, passwd)
393 self._writedebug(user, passwd)
393 self._writedebug(user, passwd)
394 return (user, passwd)
394 return (user, passwd)
395
395
396 def _writedebug(self, user, passwd):
396 def _writedebug(self, user, passwd):
397 msg = _('http auth: user %s, password %s\n')
397 msg = _('http auth: user %s, password %s\n')
398 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
398 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
399
399
400 class proxyhandler(urllib2.ProxyHandler):
400 class proxyhandler(urllib2.ProxyHandler):
401 def __init__(self, ui):
401 def __init__(self, ui):
402 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
402 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
403 # XXX proxyauthinfo = None
403 # XXX proxyauthinfo = None
404
404
405 if proxyurl:
405 if proxyurl:
406 # proxy can be proper url or host[:port]
406 # proxy can be proper url or host[:port]
407 if not (proxyurl.startswith('http:') or
407 if not (proxyurl.startswith('http:') or
408 proxyurl.startswith('https:')):
408 proxyurl.startswith('https:')):
409 proxyurl = 'http://' + proxyurl + '/'
409 proxyurl = 'http://' + proxyurl + '/'
410 snpqf = urlparse.urlsplit(proxyurl)
410 snpqf = urlparse.urlsplit(proxyurl)
411 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
411 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
412 hpup = netlocsplit(proxynetloc)
412 hpup = netlocsplit(proxynetloc)
413
413
414 proxyhost, proxyport, proxyuser, proxypasswd = hpup
414 proxyhost, proxyport, proxyuser, proxypasswd = hpup
415 if not proxyuser:
415 if not proxyuser:
416 proxyuser = ui.config("http_proxy", "user")
416 proxyuser = ui.config("http_proxy", "user")
417 proxypasswd = ui.config("http_proxy", "passwd")
417 proxypasswd = ui.config("http_proxy", "passwd")
418
418
419 # see if we should use a proxy for this url
419 # see if we should use a proxy for this url
420 no_list = ["localhost", "127.0.0.1"]
420 no_list = ["localhost", "127.0.0.1"]
421 no_list.extend([p.lower() for
421 no_list.extend([p.lower() for
422 p in ui.configlist("http_proxy", "no")])
422 p in ui.configlist("http_proxy", "no")])
423 no_list.extend([p.strip().lower() for
423 no_list.extend([p.strip().lower() for
424 p in os.getenv("no_proxy", '').split(',')
424 p in os.getenv("no_proxy", '').split(',')
425 if p.strip()])
425 if p.strip()])
426 # "http_proxy.always" config is for running tests on localhost
426 # "http_proxy.always" config is for running tests on localhost
427 if ui.configbool("http_proxy", "always"):
427 if ui.configbool("http_proxy", "always"):
428 self.no_list = []
428 self.no_list = []
429 else:
429 else:
430 self.no_list = no_list
430 self.no_list = no_list
431
431
432 proxyurl = urlparse.urlunsplit((
432 proxyurl = urlparse.urlunsplit((
433 proxyscheme, netlocunsplit(proxyhost, proxyport,
433 proxyscheme, netlocunsplit(proxyhost, proxyport,
434 proxyuser, proxypasswd or ''),
434 proxyuser, proxypasswd or ''),
435 proxypath, proxyquery, proxyfrag))
435 proxypath, proxyquery, proxyfrag))
436 proxies = {'http': proxyurl, 'https': proxyurl}
436 proxies = {'http': proxyurl, 'https': proxyurl}
437 ui.debug('proxying through http://%s:%s\n' %
437 ui.debug('proxying through http://%s:%s\n' %
438 (proxyhost, proxyport))
438 (proxyhost, proxyport))
439 else:
439 else:
440 proxies = {}
440 proxies = {}
441
441
442 # urllib2 takes proxy values from the environment and those
442 # urllib2 takes proxy values from the environment and those
443 # will take precedence if found, so drop them
443 # will take precedence if found, so drop them
444 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
444 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
445 try:
445 try:
446 if env in os.environ:
446 if env in os.environ:
447 del os.environ[env]
447 del os.environ[env]
448 except OSError:
448 except OSError:
449 pass
449 pass
450
450
451 urllib2.ProxyHandler.__init__(self, proxies)
451 urllib2.ProxyHandler.__init__(self, proxies)
452 self.ui = ui
452 self.ui = ui
453
453
454 def proxy_open(self, req, proxy, type_):
454 def proxy_open(self, req, proxy, type_):
455 host = req.get_host().split(':')[0]
455 host = req.get_host().split(':')[0]
456 if host in self.no_list:
456 if host in self.no_list:
457 return None
457 return None
458
458
459 # work around a bug in Python < 2.4.2
459 # work around a bug in Python < 2.4.2
460 # (it leaves a "\n" at the end of Proxy-authorization headers)
460 # (it leaves a "\n" at the end of Proxy-authorization headers)
461 baseclass = req.__class__
461 baseclass = req.__class__
462 class _request(baseclass):
462 class _request(baseclass):
463 def add_header(self, key, val):
463 def add_header(self, key, val):
464 if key.lower() == 'proxy-authorization':
464 if key.lower() == 'proxy-authorization':
465 val = val.strip()
465 val = val.strip()
466 return baseclass.add_header(self, key, val)
466 return baseclass.add_header(self, key, val)
467 req.__class__ = _request
467 req.__class__ = _request
468
468
469 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
469 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
470
470
471 class httpsendfile(object):
471 class httpsendfile(object):
472 """This is a wrapper around the objects returned by python's "open".
472 """This is a wrapper around the objects returned by python's "open".
473
473
474 Its purpose is to send file-like objects via HTTP and, to do so, it
474 Its purpose is to send file-like objects via HTTP and, to do so, it
475 defines a __len__ attribute to feed the Content-Length header.
475 defines a __len__ attribute to feed the Content-Length header.
476 """
476 """
477
477
478 def __init__(self, ui, *args, **kwargs):
478 def __init__(self, ui, *args, **kwargs):
479 # We can't just "self._data = open(*args, **kwargs)" here because there
479 # We can't just "self._data = open(*args, **kwargs)" here because there
480 # is an "open" function defined in this module that shadows the global
480 # is an "open" function defined in this module that shadows the global
481 # one
481 # one
482 self.ui = ui
482 self.ui = ui
483 self._data = __builtin__.open(*args, **kwargs)
483 self._data = __builtin__.open(*args, **kwargs)
484 self.seek = self._data.seek
484 self.seek = self._data.seek
485 self.close = self._data.close
485 self.close = self._data.close
486 self.write = self._data.write
486 self.write = self._data.write
487 self._len = os.fstat(self._data.fileno()).st_size
487 self._len = os.fstat(self._data.fileno()).st_size
488 self._pos = 0
488 self._pos = 0
489 self._total = len(self) / 1024 * 2
489 self._total = len(self) / 1024 * 2
490
490
491 def read(self, *args, **kwargs):
491 def read(self, *args, **kwargs):
492 try:
492 try:
493 ret = self._data.read(*args, **kwargs)
493 ret = self._data.read(*args, **kwargs)
494 except EOFError:
494 except EOFError:
495 self.ui.progress(_('sending'), None)
495 self.ui.progress(_('sending'), None)
496 self._pos += len(ret)
496 self._pos += len(ret)
497 # We pass double the max for total because we currently have
497 # We pass double the max for total because we currently have
498 # to send the bundle twice in the case of a server that
498 # to send the bundle twice in the case of a server that
499 # requires authentication. Since we can't know until we try
499 # requires authentication. Since we can't know until we try
500 # once whether authentication will be required, just lie to
500 # once whether authentication will be required, just lie to
501 # the user and maybe the push succeeds suddenly at 50%.
501 # the user and maybe the push succeeds suddenly at 50%.
502 self.ui.progress(_('sending'), self._pos / 1024,
502 self.ui.progress(_('sending'), self._pos / 1024,
503 unit=_('kb'), total=self._total)
503 unit=_('kb'), total=self._total)
504 return ret
504 return ret
505
505
506 def __len__(self):
506 def __len__(self):
507 return self._len
507 return self._len
508
508
509 def _gen_sendfile(orgsend):
509 def _gen_sendfile(orgsend):
510 def _sendfile(self, data):
510 def _sendfile(self, data):
511 # send a file
511 # send a file
512 if isinstance(data, httpsendfile):
512 if isinstance(data, httpsendfile):
513 # if auth required, some data sent twice, so rewind here
513 # if auth required, some data sent twice, so rewind here
514 data.seek(0)
514 data.seek(0)
515 for chunk in util.filechunkiter(data):
515 for chunk in util.filechunkiter(data):
516 orgsend(self, chunk)
516 orgsend(self, chunk)
517 else:
517 else:
518 orgsend(self, data)
518 orgsend(self, data)
519 return _sendfile
519 return _sendfile
520
520
521 has_https = hasattr(urllib2, 'HTTPSHandler')
521 has_https = hasattr(urllib2, 'HTTPSHandler')
522 if has_https:
522 if has_https:
523 try:
523 try:
524 # avoid using deprecated/broken FakeSocket in python 2.6
524 # avoid using deprecated/broken FakeSocket in python 2.6
525 import ssl
525 import ssl
526 _ssl_wrap_socket = ssl.wrap_socket
526 _ssl_wrap_socket = ssl.wrap_socket
527 CERT_REQUIRED = ssl.CERT_REQUIRED
527 CERT_REQUIRED = ssl.CERT_REQUIRED
528 except ImportError:
528 except ImportError:
529 CERT_REQUIRED = 2
529 CERT_REQUIRED = 2
530
530
531 def _ssl_wrap_socket(sock, key_file, cert_file,
531 def _ssl_wrap_socket(sock, key_file, cert_file,
532 cert_reqs=CERT_REQUIRED, ca_certs=None):
532 cert_reqs=CERT_REQUIRED, ca_certs=None):
533 if ca_certs:
533 if ca_certs:
534 raise util.Abort(_(
534 raise util.Abort(_(
535 'certificate checking requires Python 2.6'))
535 'certificate checking requires Python 2.6'))
536
536
537 ssl = socket.ssl(sock, key_file, cert_file)
537 ssl = socket.ssl(sock, key_file, cert_file)
538 return httplib.FakeSocket(sock, ssl)
538 return httplib.FakeSocket(sock, ssl)
539
539
540 try:
540 try:
541 _create_connection = socket.create_connection
541 _create_connection = socket.create_connection
542 except AttributeError:
542 except AttributeError:
543 _GLOBAL_DEFAULT_TIMEOUT = object()
543 _GLOBAL_DEFAULT_TIMEOUT = object()
544
544
545 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
545 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
546 source_address=None):
546 source_address=None):
547 # lifted from Python 2.6
547 # lifted from Python 2.6
548
548
549 msg = "getaddrinfo returns an empty list"
549 msg = "getaddrinfo returns an empty list"
550 host, port = address
550 host, port = address
551 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
551 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
552 af, socktype, proto, canonname, sa = res
552 af, socktype, proto, canonname, sa = res
553 sock = None
553 sock = None
554 try:
554 try:
555 sock = socket.socket(af, socktype, proto)
555 sock = socket.socket(af, socktype, proto)
556 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
556 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
557 sock.settimeout(timeout)
557 sock.settimeout(timeout)
558 if source_address:
558 if source_address:
559 sock.bind(source_address)
559 sock.bind(source_address)
560 sock.connect(sa)
560 sock.connect(sa)
561 return sock
561 return sock
562
562
563 except socket.error, msg:
563 except socket.error, msg:
564 if sock is not None:
564 if sock is not None:
565 sock.close()
565 sock.close()
566
566
567 raise socket.error, msg
567 raise socket.error, msg
568
568
569 class httpconnection(keepalive.HTTPConnection):
569 class httpconnection(keepalive.HTTPConnection):
570 # must be able to send big bundle as stream.
570 # must be able to send big bundle as stream.
571 send = _gen_sendfile(keepalive.HTTPConnection.send)
571 send = _gen_sendfile(keepalive.HTTPConnection.send)
572
572
573 def connect(self):
573 def connect(self):
574 if has_https and self.realhostport: # use CONNECT proxy
574 if has_https and self.realhostport: # use CONNECT proxy
575 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
575 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
576 self.sock.connect((self.host, self.port))
576 self.sock.connect((self.host, self.port))
577 if _generic_proxytunnel(self):
577 if _generic_proxytunnel(self):
578 # we do not support client x509 certificates
578 # we do not support client x509 certificates
579 self.sock = _ssl_wrap_socket(self.sock, None, None)
579 self.sock = _ssl_wrap_socket(self.sock, None, None)
580 else:
580 else:
581 keepalive.HTTPConnection.connect(self)
581 keepalive.HTTPConnection.connect(self)
582
582
583 def getresponse(self):
583 def getresponse(self):
584 proxyres = getattr(self, 'proxyres', None)
584 proxyres = getattr(self, 'proxyres', None)
585 if proxyres:
585 if proxyres:
586 if proxyres.will_close:
586 if proxyres.will_close:
587 self.close()
587 self.close()
588 self.proxyres = None
588 self.proxyres = None
589 return proxyres
589 return proxyres
590 return keepalive.HTTPConnection.getresponse(self)
590 return keepalive.HTTPConnection.getresponse(self)
591
591
592 # general transaction handler to support different ways to handle
592 # general transaction handler to support different ways to handle
593 # HTTPS proxying before and after Python 2.6.3.
593 # HTTPS proxying before and after Python 2.6.3.
594 def _generic_start_transaction(handler, h, req):
594 def _generic_start_transaction(handler, h, req):
595 if hasattr(req, '_tunnel_host') and req._tunnel_host:
595 if hasattr(req, '_tunnel_host') and req._tunnel_host:
596 tunnel_host = req._tunnel_host
596 tunnel_host = req._tunnel_host
597 if tunnel_host[:7] not in ['http://', 'https:/']:
597 if tunnel_host[:7] not in ['http://', 'https:/']:
598 tunnel_host = 'https://' + tunnel_host
598 tunnel_host = 'https://' + tunnel_host
599 new_tunnel = True
599 new_tunnel = True
600 else:
600 else:
601 tunnel_host = req.get_selector()
601 tunnel_host = req.get_selector()
602 new_tunnel = False
602 new_tunnel = False
603
603
604 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
604 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
605 urlparts = urlparse.urlparse(tunnel_host)
605 urlparts = urlparse.urlparse(tunnel_host)
606 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
606 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
607 realhostport = urlparts[1]
607 realhostport = urlparts[1]
608 if realhostport[-1] == ']' or ':' not in realhostport:
608 if realhostport[-1] == ']' or ':' not in realhostport:
609 realhostport += ':443'
609 realhostport += ':443'
610
610
611 h.realhostport = realhostport
611 h.realhostport = realhostport
612 h.headers = req.headers.copy()
612 h.headers = req.headers.copy()
613 h.headers.update(handler.parent.addheaders)
613 h.headers.update(handler.parent.addheaders)
614 return
614 return
615
615
616 h.realhostport = None
616 h.realhostport = None
617 h.headers = None
617 h.headers = None
618
618
619 def _generic_proxytunnel(self):
619 def _generic_proxytunnel(self):
620 proxyheaders = dict(
620 proxyheaders = dict(
621 [(x, self.headers[x]) for x in self.headers
621 [(x, self.headers[x]) for x in self.headers
622 if x.lower().startswith('proxy-')])
622 if x.lower().startswith('proxy-')])
623 self._set_hostport(self.host, self.port)
623 self._set_hostport(self.host, self.port)
624 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
624 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
625 for header in proxyheaders.iteritems():
625 for header in proxyheaders.iteritems():
626 self.send('%s: %s\r\n' % header)
626 self.send('%s: %s\r\n' % header)
627 self.send('\r\n')
627 self.send('\r\n')
628
628
629 # majority of the following code is duplicated from
629 # majority of the following code is duplicated from
630 # httplib.HTTPConnection as there are no adequate places to
630 # httplib.HTTPConnection as there are no adequate places to
631 # override functions to provide the needed functionality
631 # override functions to provide the needed functionality
632 res = self.response_class(self.sock,
632 res = self.response_class(self.sock,
633 strict=self.strict,
633 strict=self.strict,
634 method=self._method)
634 method=self._method)
635
635
636 while True:
636 while True:
637 version, status, reason = res._read_status()
637 version, status, reason = res._read_status()
638 if status != httplib.CONTINUE:
638 if status != httplib.CONTINUE:
639 break
639 break
640 while True:
640 while True:
641 skip = res.fp.readline().strip()
641 skip = res.fp.readline().strip()
642 if not skip:
642 if not skip:
643 break
643 break
644 res.status = status
644 res.status = status
645 res.reason = reason.strip()
645 res.reason = reason.strip()
646
646
647 if res.status == 200:
647 if res.status == 200:
648 while True:
648 while True:
649 line = res.fp.readline()
649 line = res.fp.readline()
650 if line == '\r\n':
650 if line == '\r\n':
651 break
651 break
652 return True
652 return True
653
653
654 if version == 'HTTP/1.0':
654 if version == 'HTTP/1.0':
655 res.version = 10
655 res.version = 10
656 elif version.startswith('HTTP/1.'):
656 elif version.startswith('HTTP/1.'):
657 res.version = 11
657 res.version = 11
658 elif version == 'HTTP/0.9':
658 elif version == 'HTTP/0.9':
659 res.version = 9
659 res.version = 9
660 else:
660 else:
661 raise httplib.UnknownProtocol(version)
661 raise httplib.UnknownProtocol(version)
662
662
663 if res.version == 9:
663 if res.version == 9:
664 res.length = None
664 res.length = None
665 res.chunked = 0
665 res.chunked = 0
666 res.will_close = 1
666 res.will_close = 1
667 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
667 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
668 return False
668 return False
669
669
670 res.msg = httplib.HTTPMessage(res.fp)
670 res.msg = httplib.HTTPMessage(res.fp)
671 res.msg.fp = None
671 res.msg.fp = None
672
672
673 # are we using the chunked-style of transfer encoding?
673 # are we using the chunked-style of transfer encoding?
674 trenc = res.msg.getheader('transfer-encoding')
674 trenc = res.msg.getheader('transfer-encoding')
675 if trenc and trenc.lower() == "chunked":
675 if trenc and trenc.lower() == "chunked":
676 res.chunked = 1
676 res.chunked = 1
677 res.chunk_left = None
677 res.chunk_left = None
678 else:
678 else:
679 res.chunked = 0
679 res.chunked = 0
680
680
681 # will the connection close at the end of the response?
681 # will the connection close at the end of the response?
682 res.will_close = res._check_close()
682 res.will_close = res._check_close()
683
683
684 # do we have a Content-Length?
684 # do we have a Content-Length?
685 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
685 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
686 length = res.msg.getheader('content-length')
686 length = res.msg.getheader('content-length')
687 if length and not res.chunked:
687 if length and not res.chunked:
688 try:
688 try:
689 res.length = int(length)
689 res.length = int(length)
690 except ValueError:
690 except ValueError:
691 res.length = None
691 res.length = None
692 else:
692 else:
693 if res.length < 0: # ignore nonsensical negative lengths
693 if res.length < 0: # ignore nonsensical negative lengths
694 res.length = None
694 res.length = None
695 else:
695 else:
696 res.length = None
696 res.length = None
697
697
698 # does the body have a fixed length? (of zero)
698 # does the body have a fixed length? (of zero)
699 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
699 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
700 100 <= status < 200 or # 1xx codes
700 100 <= status < 200 or # 1xx codes
701 res._method == 'HEAD'):
701 res._method == 'HEAD'):
702 res.length = 0
702 res.length = 0
703
703
704 # if the connection remains open, and we aren't using chunked, and
704 # if the connection remains open, and we aren't using chunked, and
705 # a content-length was not provided, then assume that the connection
705 # a content-length was not provided, then assume that the connection
706 # WILL close.
706 # WILL close.
707 if (not res.will_close and
707 if (not res.will_close and
708 not res.chunked and
708 not res.chunked and
709 res.length is None):
709 res.length is None):
710 res.will_close = 1
710 res.will_close = 1
711
711
712 self.proxyres = res
712 self.proxyres = res
713
713
714 return False
714 return False
715
715
716 class httphandler(keepalive.HTTPHandler):
716 class httphandler(keepalive.HTTPHandler):
717 def http_open(self, req):
717 def http_open(self, req):
718 return self.do_open(httpconnection, req)
718 return self.do_open(httpconnection, req)
719
719
720 def _start_transaction(self, h, req):
720 def _start_transaction(self, h, req):
721 _generic_start_transaction(self, h, req)
721 _generic_start_transaction(self, h, req)
722 return keepalive.HTTPHandler._start_transaction(self, h, req)
722 return keepalive.HTTPHandler._start_transaction(self, h, req)
723
723
724 def _verifycert(cert, hostname):
724 def _verifycert(cert, hostname):
725 '''Verify that cert (in socket.getpeercert() format) matches hostname.
725 '''Verify that cert (in socket.getpeercert() format) matches hostname.
726 CRLs is not handled.
726 CRLs is not handled.
727
727
728 Returns error message if any problems are found and None on success.
728 Returns error message if any problems are found and None on success.
729 '''
729 '''
730 if not cert:
730 if not cert:
731 return _('no certificate received')
731 return _('no certificate received')
732 dnsname = hostname.lower()
732 dnsname = hostname.lower()
733 def matchdnsname(certname):
733 def matchdnsname(certname):
734 return (certname == dnsname or
734 return (certname == dnsname or
735 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
735 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
736
736
737 san = cert.get('subjectAltName', [])
737 san = cert.get('subjectAltName', [])
738 if san:
738 if san:
739 certnames = [value.lower() for key, value in san if key == 'DNS']
739 certnames = [value.lower() for key, value in san if key == 'DNS']
740 for name in certnames:
740 for name in certnames:
741 if matchdnsname(name):
741 if matchdnsname(name):
742 return None
742 return None
743 return _('certificate is for %s') % ', '.join(certnames)
743 return _('certificate is for %s') % ', '.join(certnames)
744
744
745 # subject is only checked when subjectAltName is empty
745 # subject is only checked when subjectAltName is empty
746 for s in cert.get('subject', []):
746 for s in cert.get('subject', []):
747 key, value = s[0]
747 key, value = s[0]
748 if key == 'commonName':
748 if key == 'commonName':
749 try:
749 try:
750 # 'subject' entries are unicode
750 # 'subject' entries are unicode
751 certname = value.lower().encode('ascii')
751 certname = value.lower().encode('ascii')
752 except UnicodeEncodeError:
752 except UnicodeEncodeError:
753 return _('IDN in certificate not supported')
753 return _('IDN in certificate not supported')
754 if matchdnsname(certname):
754 if matchdnsname(certname):
755 return None
755 return None
756 return _('certificate is for %s') % certname
756 return _('certificate is for %s') % certname
757 return _('no commonName or subjectAltName found in certificate')
757 return _('no commonName or subjectAltName found in certificate')
758
758
759 if has_https:
759 if has_https:
760 class httpsconnection(httplib.HTTPSConnection):
760 class httpsconnection(httplib.HTTPSConnection):
761 response_class = keepalive.HTTPResponse
761 response_class = keepalive.HTTPResponse
762 # must be able to send big bundle as stream.
762 # must be able to send big bundle as stream.
763 send = _gen_sendfile(keepalive.safesend)
763 send = _gen_sendfile(keepalive.safesend)
764 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
764 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
765
765
766 def connect(self):
766 def connect(self):
767 self.sock = _create_connection((self.host, self.port))
767 self.sock = _create_connection((self.host, self.port))
768
768
769 host = self.host
769 host = self.host
770 if self.realhostport: # use CONNECT proxy
770 if self.realhostport: # use CONNECT proxy
771 something = _generic_proxytunnel(self)
771 something = _generic_proxytunnel(self)
772 host = self.realhostport.rsplit(':', 1)[0]
772 host = self.realhostport.rsplit(':', 1)[0]
773
773
774 cacerts = self.ui.config('web', 'cacerts')
774 cacerts = self.ui.config('web', 'cacerts')
775 hostfingerprint = self.ui.config('hostfingerprints', host)
775 hostfingerprint = self.ui.config('hostfingerprints', host)
776
776
777 if cacerts and not hostfingerprint:
777 if cacerts and not hostfingerprint:
778 cacerts = util.expandpath(cacerts)
778 cacerts = util.expandpath(cacerts)
779 if not os.path.exists(cacerts):
779 if not os.path.exists(cacerts):
780 raise util.Abort(_('could not find '
780 raise util.Abort(_('could not find '
781 'web.cacerts: %s') % cacerts)
781 'web.cacerts: %s') % cacerts)
782 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
782 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
783 self.cert_file, cert_reqs=CERT_REQUIRED,
783 self.cert_file, cert_reqs=CERT_REQUIRED,
784 ca_certs=cacerts)
784 ca_certs=cacerts)
785 msg = _verifycert(self.sock.getpeercert(), host)
785 msg = _verifycert(self.sock.getpeercert(), host)
786 if msg:
786 if msg:
787 raise util.Abort(_('%s certificate error: %s '
787 raise util.Abort(_('%s certificate error: %s '
788 '(use --insecure to connect '
788 '(use --insecure to connect '
789 'insecurely)') % (host, msg))
789 'insecurely)') % (host, msg))
790 self.ui.debug('%s certificate successfully verified\n' % host)
790 self.ui.debug('%s certificate successfully verified\n' % host)
791 else:
791 else:
792 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
792 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
793 self.cert_file)
793 self.cert_file)
794 if hasattr(self.sock, 'getpeercert'):
794 if hasattr(self.sock, 'getpeercert'):
795 peercert = self.sock.getpeercert(True)
795 peercert = self.sock.getpeercert(True)
796 peerfingerprint = util.sha1(peercert).hexdigest()
796 peerfingerprint = util.sha1(peercert).hexdigest()
797 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
797 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
798 for x in xrange(0, len(peerfingerprint), 2)])
798 for x in xrange(0, len(peerfingerprint), 2)])
799 if hostfingerprint:
799 if hostfingerprint:
800 if peerfingerprint.lower() != \
800 if peerfingerprint.lower() != \
801 hostfingerprint.replace(':', '').lower():
801 hostfingerprint.replace(':', '').lower():
802 raise util.Abort(_('invalid certificate for %s '
802 raise util.Abort(_('invalid certificate for %s '
803 'with fingerprint %s') %
803 'with fingerprint %s') %
804 (host, nicefingerprint))
804 (host, nicefingerprint))
805 self.ui.debug('%s certificate matched fingerprint %s\n' %
805 self.ui.debug('%s certificate matched fingerprint %s\n' %
806 (host, nicefingerprint))
806 (host, nicefingerprint))
807 else:
807 else:
808 self.ui.warn(_('warning: %s certificate '
808 self.ui.warn(_('warning: %s certificate '
809 'with fingerprint %s not verified '
809 'with fingerprint %s not verified '
810 '(check hostfingerprints or web.cacerts '
810 '(check hostfingerprints or web.cacerts '
811 'config setting)\n') %
811 'config setting)\n') %
812 (host, nicefingerprint))
812 (host, nicefingerprint))
813 else: # python 2.5 ?
813 else: # python 2.5 ?
814 if hostfingerprint:
814 if hostfingerprint:
815 raise util.Abort(_('no certificate for %s with '
815 raise util.Abort(_('no certificate for %s with '
816 'configured hostfingerprint') % host)
816 'configured hostfingerprint') % host)
817 self.ui.warn(_('warning: %s certificate not verified '
817 self.ui.warn(_('warning: %s certificate not verified '
818 '(check web.cacerts config setting)\n') %
818 '(check web.cacerts config setting)\n') %
819 host)
819 host)
820
820
821 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
821 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
822 def __init__(self, ui):
822 def __init__(self, ui):
823 keepalive.KeepAliveHandler.__init__(self)
823 keepalive.KeepAliveHandler.__init__(self)
824 urllib2.HTTPSHandler.__init__(self)
824 urllib2.HTTPSHandler.__init__(self)
825 self.ui = ui
825 self.ui = ui
826 self.pwmgr = passwordmgr(self.ui)
826 self.pwmgr = passwordmgr(self.ui)
827
827
828 def _start_transaction(self, h, req):
828 def _start_transaction(self, h, req):
829 _generic_start_transaction(self, h, req)
829 _generic_start_transaction(self, h, req)
830 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
830 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
831
831
832 def https_open(self, req):
832 def https_open(self, req):
833 res = readauthforuri(self.ui, req.get_full_url())
833 res = readauthforuri(self.ui, req.get_full_url())
834 if res:
834 if res:
835 group, auth = res
835 group, auth = res
836 self.auth = auth
836 self.auth = auth
837 self.ui.debug("using auth.%s.* for authentication\n" % group)
837 self.ui.debug("using auth.%s.* for authentication\n" % group)
838 else:
838 else:
839 self.auth = None
839 self.auth = None
840 return self.do_open(self._makeconnection, req)
840 return self.do_open(self._makeconnection, req)
841
841
842 def _makeconnection(self, host, port=None, *args, **kwargs):
842 def _makeconnection(self, host, port=None, *args, **kwargs):
843 keyfile = None
843 keyfile = None
844 certfile = None
844 certfile = None
845
845
846 if len(args) >= 1: # key_file
846 if len(args) >= 1: # key_file
847 keyfile = args[0]
847 keyfile = args[0]
848 if len(args) >= 2: # cert_file
848 if len(args) >= 2: # cert_file
849 certfile = args[1]
849 certfile = args[1]
850 args = args[2:]
850 args = args[2:]
851
851
852 # if the user has specified different key/cert files in
852 # if the user has specified different key/cert files in
853 # hgrc, we prefer these
853 # hgrc, we prefer these
854 if self.auth and 'key' in self.auth and 'cert' in self.auth:
854 if self.auth and 'key' in self.auth and 'cert' in self.auth:
855 keyfile = self.auth['key']
855 keyfile = self.auth['key']
856 certfile = self.auth['cert']
856 certfile = self.auth['cert']
857
857
858 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
858 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
859 conn.ui = self.ui
859 conn.ui = self.ui
860 return conn
860 return conn
861
861
862 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
862 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
863 def __init__(self, *args, **kwargs):
863 def __init__(self, *args, **kwargs):
864 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
864 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
865 self.retried_req = None
865 self.retried_req = None
866
866
867 def reset_retry_count(self):
867 def reset_retry_count(self):
868 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
868 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
869 # forever. We disable reset_retry_count completely and reset in
869 # forever. We disable reset_retry_count completely and reset in
870 # http_error_auth_reqed instead.
870 # http_error_auth_reqed instead.
871 pass
871 pass
872
872
873 def http_error_auth_reqed(self, auth_header, host, req, headers):
873 def http_error_auth_reqed(self, auth_header, host, req, headers):
874 # Reset the retry counter once for each request.
874 # Reset the retry counter once for each request.
875 if req is not self.retried_req:
875 if req is not self.retried_req:
876 self.retried_req = req
876 self.retried_req = req
877 self.retried = 0
877 self.retried = 0
878 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
878 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
879 # it doesn't know about the auth type requested. This can happen if
879 # it doesn't know about the auth type requested. This can happen if
880 # somebody is using BasicAuth and types a bad password.
880 # somebody is using BasicAuth and types a bad password.
881 try:
881 try:
882 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
882 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
883 self, auth_header, host, req, headers)
883 self, auth_header, host, req, headers)
884 except ValueError, inst:
884 except ValueError, inst:
885 arg = inst.args[0]
885 arg = inst.args[0]
886 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
886 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
887 return
887 return
888 raise
888 raise
889
889
890 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
890 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
891 def __init__(self, *args, **kwargs):
891 def __init__(self, *args, **kwargs):
892 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
892 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
893 self.retried_req = None
893 self.retried_req = None
894
894
895 def reset_retry_count(self):
895 def reset_retry_count(self):
896 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
896 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
897 # forever. We disable reset_retry_count completely and reset in
897 # forever. We disable reset_retry_count completely and reset in
898 # http_error_auth_reqed instead.
898 # http_error_auth_reqed instead.
899 pass
899 pass
900
900
901 def http_error_auth_reqed(self, auth_header, host, req, headers):
901 def http_error_auth_reqed(self, auth_header, host, req, headers):
902 # Reset the retry counter once for each request.
902 # Reset the retry counter once for each request.
903 if req is not self.retried_req:
903 if req is not self.retried_req:
904 self.retried_req = req
904 self.retried_req = req
905 self.retried = 0
905 self.retried = 0
906 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
906 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
907 self, auth_header, host, req, headers)
907 self, auth_header, host, req, headers)
908
908
909 def getauthinfo(path):
909 def getauthinfo(path):
910 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
910 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
911 if not urlpath:
911 if not urlpath:
912 urlpath = '/'
912 urlpath = '/'
913 if scheme != 'file':
913 if scheme != 'file':
914 # XXX: why are we quoting the path again with some smart
914 # XXX: why are we quoting the path again with some smart
915 # heuristic here? Anyway, it cannot be done with file://
915 # heuristic here? Anyway, it cannot be done with file://
916 # urls since path encoding is os/fs dependent (see
916 # urls since path encoding is os/fs dependent (see
917 # urllib.pathname2url() for details).
917 # urllib.pathname2url() for details).
918 urlpath = quotepath(urlpath)
918 urlpath = quotepath(urlpath)
919 host, port, user, passwd = netlocsplit(netloc)
919 host, port, user, passwd = netlocsplit(netloc)
920
920
921 # urllib cannot handle URLs with embedded user or passwd
921 # urllib cannot handle URLs with embedded user or passwd
922 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
922 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
923 urlpath, query, frag))
923 urlpath, query, frag))
924 if user:
924 if user:
925 netloc = host
925 netloc = host
926 if port:
926 if port:
927 netloc += ':' + port
927 netloc += ':' + port
928 # Python < 2.4.3 uses only the netloc to search for a password
928 # Python < 2.4.3 uses only the netloc to search for a password
929 authinfo = (None, (url, netloc), user, passwd or '')
929 authinfo = (None, (url, netloc), user, passwd or '')
930 else:
930 else:
931 authinfo = None
931 authinfo = None
932 return url, authinfo
932 return url, authinfo
933
933
934 handlerfuncs = []
934 handlerfuncs = []
935
935
936 def opener(ui, authinfo=None):
936 def opener(ui, authinfo=None):
937 '''
937 '''
938 construct an opener suitable for urllib2
938 construct an opener suitable for urllib2
939 authinfo will be added to the password manager
939 authinfo will be added to the password manager
940 '''
940 '''
941 handlers = [httphandler()]
941 handlers = [httphandler()]
942 if has_https:
942 if has_https:
943 handlers.append(httpshandler(ui))
943 handlers.append(httpshandler(ui))
944
944
945 handlers.append(proxyhandler(ui))
945 handlers.append(proxyhandler(ui))
946
946
947 passmgr = passwordmgr(ui)
947 passmgr = passwordmgr(ui)
948 if authinfo is not None:
948 if authinfo is not None:
949 passmgr.add_password(*authinfo)
949 passmgr.add_password(*authinfo)
950 user, passwd = authinfo[2:4]
950 user, passwd = authinfo[2:4]
951 ui.debug('http auth: user %s, password %s\n' %
951 ui.debug('http auth: user %s, password %s\n' %
952 (user, passwd and '*' * len(passwd) or 'not set'))
952 (user, passwd and '*' * len(passwd) or 'not set'))
953
953
954 handlers.extend((httpbasicauthhandler(passmgr),
954 handlers.extend((httpbasicauthhandler(passmgr),
955 httpdigestauthhandler(passmgr)))
955 httpdigestauthhandler(passmgr)))
956 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
956 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
957 opener = urllib2.build_opener(*handlers)
957 opener = urllib2.build_opener(*handlers)
958
958
959 # 1.0 here is the _protocol_ version
959 # 1.0 here is the _protocol_ version
960 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
960 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
961 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
961 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
962 return opener
962 return opener
963
963
964 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
964 def open(ui, url_, data=None):
965
965 u = url(url_)
966 def open(ui, url, data=None):
966 if u.scheme:
967 scheme = None
967 u.scheme = u.scheme.lower()
968 m = scheme_re.search(url)
968 url_, authinfo = u.authinfo()
969 if m:
969 else:
970 scheme = m.group(1).lower()
970 path = util.normpath(os.path.abspath(url_))
971 if not scheme:
971 url_ = 'file://' + urllib.pathname2url(path)
972 path = util.normpath(os.path.abspath(url))
973 url = 'file://' + urllib.pathname2url(path)
974 authinfo = None
972 authinfo = None
975 else:
973 return opener(ui, authinfo).open(url_, data)
976 url, authinfo = getauthinfo(url)
977 return opener(ui, authinfo).open(url, data)
@@ -1,560 +1,560
1 Setting up test
1 Setting up test
2
2
3 $ hg init test
3 $ hg init test
4 $ cd test
4 $ cd test
5 $ echo 0 > afile
5 $ echo 0 > afile
6 $ hg add afile
6 $ hg add afile
7 $ hg commit -m "0.0"
7 $ hg commit -m "0.0"
8 $ echo 1 >> afile
8 $ echo 1 >> afile
9 $ hg commit -m "0.1"
9 $ hg commit -m "0.1"
10 $ echo 2 >> afile
10 $ echo 2 >> afile
11 $ hg commit -m "0.2"
11 $ hg commit -m "0.2"
12 $ echo 3 >> afile
12 $ echo 3 >> afile
13 $ hg commit -m "0.3"
13 $ hg commit -m "0.3"
14 $ hg update -C 0
14 $ hg update -C 0
15 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
15 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
16 $ echo 1 >> afile
16 $ echo 1 >> afile
17 $ hg commit -m "1.1"
17 $ hg commit -m "1.1"
18 created new head
18 created new head
19 $ echo 2 >> afile
19 $ echo 2 >> afile
20 $ hg commit -m "1.2"
20 $ hg commit -m "1.2"
21 $ echo "a line" > fred
21 $ echo "a line" > fred
22 $ echo 3 >> afile
22 $ echo 3 >> afile
23 $ hg add fred
23 $ hg add fred
24 $ hg commit -m "1.3"
24 $ hg commit -m "1.3"
25 $ hg mv afile adifferentfile
25 $ hg mv afile adifferentfile
26 $ hg commit -m "1.3m"
26 $ hg commit -m "1.3m"
27 $ hg update -C 3
27 $ hg update -C 3
28 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
28 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
29 $ hg mv afile anotherfile
29 $ hg mv afile anotherfile
30 $ hg commit -m "0.3m"
30 $ hg commit -m "0.3m"
31 $ hg verify
31 $ hg verify
32 checking changesets
32 checking changesets
33 checking manifests
33 checking manifests
34 crosschecking files in changesets and manifests
34 crosschecking files in changesets and manifests
35 checking files
35 checking files
36 4 files, 9 changesets, 7 total revisions
36 4 files, 9 changesets, 7 total revisions
37 $ cd ..
37 $ cd ..
38 $ hg init empty
38 $ hg init empty
39
39
40 Bundle --all
40 Bundle --all
41
41
42 $ hg -R test bundle --all all.hg
42 $ hg -R test bundle --all all.hg
43 9 changesets found
43 9 changesets found
44
44
45 Bundle test to full.hg
45 Bundle test to full.hg
46
46
47 $ hg -R test bundle full.hg empty
47 $ hg -R test bundle full.hg empty
48 searching for changes
48 searching for changes
49 9 changesets found
49 9 changesets found
50
50
51 Unbundle full.hg in test
51 Unbundle full.hg in test
52
52
53 $ hg -R test unbundle full.hg
53 $ hg -R test unbundle full.hg
54 adding changesets
54 adding changesets
55 adding manifests
55 adding manifests
56 adding file changes
56 adding file changes
57 added 0 changesets with 0 changes to 4 files
57 added 0 changesets with 0 changes to 4 files
58 (run 'hg update' to get a working copy)
58 (run 'hg update' to get a working copy)
59
59
60 Verify empty
60 Verify empty
61
61
62 $ hg -R empty heads
62 $ hg -R empty heads
63 [1]
63 [1]
64 $ hg -R empty verify
64 $ hg -R empty verify
65 checking changesets
65 checking changesets
66 checking manifests
66 checking manifests
67 crosschecking files in changesets and manifests
67 crosschecking files in changesets and manifests
68 checking files
68 checking files
69 0 files, 0 changesets, 0 total revisions
69 0 files, 0 changesets, 0 total revisions
70
70
71 Pull full.hg into test (using --cwd)
71 Pull full.hg into test (using --cwd)
72
72
73 $ hg --cwd test pull ../full.hg
73 $ hg --cwd test pull ../full.hg
74 pulling from ../full.hg
74 pulling from ../full.hg
75 searching for changes
75 searching for changes
76 no changes found
76 no changes found
77
77
78 Pull full.hg into empty (using --cwd)
78 Pull full.hg into empty (using --cwd)
79
79
80 $ hg --cwd empty pull ../full.hg
80 $ hg --cwd empty pull ../full.hg
81 pulling from ../full.hg
81 pulling from ../full.hg
82 requesting all changes
82 requesting all changes
83 adding changesets
83 adding changesets
84 adding manifests
84 adding manifests
85 adding file changes
85 adding file changes
86 added 9 changesets with 7 changes to 4 files (+1 heads)
86 added 9 changesets with 7 changes to 4 files (+1 heads)
87 (run 'hg heads' to see heads, 'hg merge' to merge)
87 (run 'hg heads' to see heads, 'hg merge' to merge)
88
88
89 Rollback empty
89 Rollback empty
90
90
91 $ hg -R empty rollback
91 $ hg -R empty rollback
92 repository tip rolled back to revision -1 (undo pull)
92 repository tip rolled back to revision -1 (undo pull)
93 working directory now based on revision -1
93 working directory now based on revision -1
94
94
95 Pull full.hg into empty again (using --cwd)
95 Pull full.hg into empty again (using --cwd)
96
96
97 $ hg --cwd empty pull ../full.hg
97 $ hg --cwd empty pull ../full.hg
98 pulling from ../full.hg
98 pulling from ../full.hg
99 requesting all changes
99 requesting all changes
100 adding changesets
100 adding changesets
101 adding manifests
101 adding manifests
102 adding file changes
102 adding file changes
103 added 9 changesets with 7 changes to 4 files (+1 heads)
103 added 9 changesets with 7 changes to 4 files (+1 heads)
104 (run 'hg heads' to see heads, 'hg merge' to merge)
104 (run 'hg heads' to see heads, 'hg merge' to merge)
105
105
106 Pull full.hg into test (using -R)
106 Pull full.hg into test (using -R)
107
107
108 $ hg -R test pull full.hg
108 $ hg -R test pull full.hg
109 pulling from full.hg
109 pulling from full.hg
110 searching for changes
110 searching for changes
111 no changes found
111 no changes found
112
112
113 Pull full.hg into empty (using -R)
113 Pull full.hg into empty (using -R)
114
114
115 $ hg -R empty pull full.hg
115 $ hg -R empty pull full.hg
116 pulling from full.hg
116 pulling from full.hg
117 searching for changes
117 searching for changes
118 no changes found
118 no changes found
119
119
120 Rollback empty
120 Rollback empty
121
121
122 $ hg -R empty rollback
122 $ hg -R empty rollback
123 repository tip rolled back to revision -1 (undo pull)
123 repository tip rolled back to revision -1 (undo pull)
124 working directory now based on revision -1
124 working directory now based on revision -1
125
125
126 Pull full.hg into empty again (using -R)
126 Pull full.hg into empty again (using -R)
127
127
128 $ hg -R empty pull full.hg
128 $ hg -R empty pull full.hg
129 pulling from full.hg
129 pulling from full.hg
130 requesting all changes
130 requesting all changes
131 adding changesets
131 adding changesets
132 adding manifests
132 adding manifests
133 adding file changes
133 adding file changes
134 added 9 changesets with 7 changes to 4 files (+1 heads)
134 added 9 changesets with 7 changes to 4 files (+1 heads)
135 (run 'hg heads' to see heads, 'hg merge' to merge)
135 (run 'hg heads' to see heads, 'hg merge' to merge)
136
136
137 Log -R full.hg in fresh empty
137 Log -R full.hg in fresh empty
138
138
139 $ rm -r empty
139 $ rm -r empty
140 $ hg init empty
140 $ hg init empty
141 $ cd empty
141 $ cd empty
142 $ hg -R bundle://../full.hg log
142 $ hg -R bundle://../full.hg log
143 changeset: 8:aa35859c02ea
143 changeset: 8:aa35859c02ea
144 tag: tip
144 tag: tip
145 parent: 3:eebf5a27f8ca
145 parent: 3:eebf5a27f8ca
146 user: test
146 user: test
147 date: Thu Jan 01 00:00:00 1970 +0000
147 date: Thu Jan 01 00:00:00 1970 +0000
148 summary: 0.3m
148 summary: 0.3m
149
149
150 changeset: 7:a6a34bfa0076
150 changeset: 7:a6a34bfa0076
151 user: test
151 user: test
152 date: Thu Jan 01 00:00:00 1970 +0000
152 date: Thu Jan 01 00:00:00 1970 +0000
153 summary: 1.3m
153 summary: 1.3m
154
154
155 changeset: 6:7373c1169842
155 changeset: 6:7373c1169842
156 user: test
156 user: test
157 date: Thu Jan 01 00:00:00 1970 +0000
157 date: Thu Jan 01 00:00:00 1970 +0000
158 summary: 1.3
158 summary: 1.3
159
159
160 changeset: 5:1bb50a9436a7
160 changeset: 5:1bb50a9436a7
161 user: test
161 user: test
162 date: Thu Jan 01 00:00:00 1970 +0000
162 date: Thu Jan 01 00:00:00 1970 +0000
163 summary: 1.2
163 summary: 1.2
164
164
165 changeset: 4:095197eb4973
165 changeset: 4:095197eb4973
166 parent: 0:f9ee2f85a263
166 parent: 0:f9ee2f85a263
167 user: test
167 user: test
168 date: Thu Jan 01 00:00:00 1970 +0000
168 date: Thu Jan 01 00:00:00 1970 +0000
169 summary: 1.1
169 summary: 1.1
170
170
171 changeset: 3:eebf5a27f8ca
171 changeset: 3:eebf5a27f8ca
172 user: test
172 user: test
173 date: Thu Jan 01 00:00:00 1970 +0000
173 date: Thu Jan 01 00:00:00 1970 +0000
174 summary: 0.3
174 summary: 0.3
175
175
176 changeset: 2:e38ba6f5b7e0
176 changeset: 2:e38ba6f5b7e0
177 user: test
177 user: test
178 date: Thu Jan 01 00:00:00 1970 +0000
178 date: Thu Jan 01 00:00:00 1970 +0000
179 summary: 0.2
179 summary: 0.2
180
180
181 changeset: 1:34c2bf6b0626
181 changeset: 1:34c2bf6b0626
182 user: test
182 user: test
183 date: Thu Jan 01 00:00:00 1970 +0000
183 date: Thu Jan 01 00:00:00 1970 +0000
184 summary: 0.1
184 summary: 0.1
185
185
186 changeset: 0:f9ee2f85a263
186 changeset: 0:f9ee2f85a263
187 user: test
187 user: test
188 date: Thu Jan 01 00:00:00 1970 +0000
188 date: Thu Jan 01 00:00:00 1970 +0000
189 summary: 0.0
189 summary: 0.0
190
190
191 Make sure bundlerepo doesn't leak tempfiles (issue2491)
191 Make sure bundlerepo doesn't leak tempfiles (issue2491)
192
192
193 $ ls .hg
193 $ ls .hg
194 00changelog.i
194 00changelog.i
195 cache
195 cache
196 requires
196 requires
197 store
197 store
198
198
199 Pull ../full.hg into empty (with hook)
199 Pull ../full.hg into empty (with hook)
200
200
201 $ echo '[hooks]' >> .hg/hgrc
201 $ echo '[hooks]' >> .hg/hgrc
202 $ echo 'changegroup = python "$TESTDIR"/printenv.py changegroup' >> .hg/hgrc
202 $ echo 'changegroup = python "$TESTDIR"/printenv.py changegroup' >> .hg/hgrc
203
203
204 doesn't work (yet ?)
204 doesn't work (yet ?)
205
205
206 hg -R bundle://../full.hg verify
206 hg -R bundle://../full.hg verify
207
207
208 $ hg pull bundle://../full.hg
208 $ hg pull bundle://../full.hg
209 pulling from bundle://../full.hg
209 pulling from bundle:../full.hg
210 requesting all changes
210 requesting all changes
211 adding changesets
211 adding changesets
212 adding manifests
212 adding manifests
213 adding file changes
213 adding file changes
214 added 9 changesets with 7 changes to 4 files (+1 heads)
214 added 9 changesets with 7 changes to 4 files (+1 heads)
215 changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_SOURCE=pull HG_URL=bundle:../full.hg
215 changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_SOURCE=pull HG_URL=bundle:../full.hg
216 (run 'hg heads' to see heads, 'hg merge' to merge)
216 (run 'hg heads' to see heads, 'hg merge' to merge)
217
217
218 Rollback empty
218 Rollback empty
219
219
220 $ hg rollback
220 $ hg rollback
221 repository tip rolled back to revision -1 (undo pull)
221 repository tip rolled back to revision -1 (undo pull)
222 working directory now based on revision -1
222 working directory now based on revision -1
223 $ cd ..
223 $ cd ..
224
224
225 Log -R bundle:empty+full.hg
225 Log -R bundle:empty+full.hg
226
226
227 $ hg -R bundle:empty+full.hg log --template="{rev} "; echo ""
227 $ hg -R bundle:empty+full.hg log --template="{rev} "; echo ""
228 8 7 6 5 4 3 2 1 0
228 8 7 6 5 4 3 2 1 0
229
229
230 Pull full.hg into empty again (using -R; with hook)
230 Pull full.hg into empty again (using -R; with hook)
231
231
232 $ hg -R empty pull full.hg
232 $ hg -R empty pull full.hg
233 pulling from full.hg
233 pulling from full.hg
234 requesting all changes
234 requesting all changes
235 adding changesets
235 adding changesets
236 adding manifests
236 adding manifests
237 adding file changes
237 adding file changes
238 added 9 changesets with 7 changes to 4 files (+1 heads)
238 added 9 changesets with 7 changes to 4 files (+1 heads)
239 changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_SOURCE=pull HG_URL=bundle:empty+full.hg
239 changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_SOURCE=pull HG_URL=bundle:empty+full.hg
240 (run 'hg heads' to see heads, 'hg merge' to merge)
240 (run 'hg heads' to see heads, 'hg merge' to merge)
241
241
242 Create partial clones
242 Create partial clones
243
243
244 $ rm -r empty
244 $ rm -r empty
245 $ hg init empty
245 $ hg init empty
246 $ hg clone -r 3 test partial
246 $ hg clone -r 3 test partial
247 adding changesets
247 adding changesets
248 adding manifests
248 adding manifests
249 adding file changes
249 adding file changes
250 added 4 changesets with 4 changes to 1 files
250 added 4 changesets with 4 changes to 1 files
251 updating to branch default
251 updating to branch default
252 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
252 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
253 $ hg clone partial partial2
253 $ hg clone partial partial2
254 updating to branch default
254 updating to branch default
255 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
255 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
256 $ cd partial
256 $ cd partial
257
257
258 Log -R full.hg in partial
258 Log -R full.hg in partial
259
259
260 $ hg -R bundle://../full.hg log
260 $ hg -R bundle://../full.hg log
261 changeset: 8:aa35859c02ea
261 changeset: 8:aa35859c02ea
262 tag: tip
262 tag: tip
263 parent: 3:eebf5a27f8ca
263 parent: 3:eebf5a27f8ca
264 user: test
264 user: test
265 date: Thu Jan 01 00:00:00 1970 +0000
265 date: Thu Jan 01 00:00:00 1970 +0000
266 summary: 0.3m
266 summary: 0.3m
267
267
268 changeset: 7:a6a34bfa0076
268 changeset: 7:a6a34bfa0076
269 user: test
269 user: test
270 date: Thu Jan 01 00:00:00 1970 +0000
270 date: Thu Jan 01 00:00:00 1970 +0000
271 summary: 1.3m
271 summary: 1.3m
272
272
273 changeset: 6:7373c1169842
273 changeset: 6:7373c1169842
274 user: test
274 user: test
275 date: Thu Jan 01 00:00:00 1970 +0000
275 date: Thu Jan 01 00:00:00 1970 +0000
276 summary: 1.3
276 summary: 1.3
277
277
278 changeset: 5:1bb50a9436a7
278 changeset: 5:1bb50a9436a7
279 user: test
279 user: test
280 date: Thu Jan 01 00:00:00 1970 +0000
280 date: Thu Jan 01 00:00:00 1970 +0000
281 summary: 1.2
281 summary: 1.2
282
282
283 changeset: 4:095197eb4973
283 changeset: 4:095197eb4973
284 parent: 0:f9ee2f85a263
284 parent: 0:f9ee2f85a263
285 user: test
285 user: test
286 date: Thu Jan 01 00:00:00 1970 +0000
286 date: Thu Jan 01 00:00:00 1970 +0000
287 summary: 1.1
287 summary: 1.1
288
288
289 changeset: 3:eebf5a27f8ca
289 changeset: 3:eebf5a27f8ca
290 user: test
290 user: test
291 date: Thu Jan 01 00:00:00 1970 +0000
291 date: Thu Jan 01 00:00:00 1970 +0000
292 summary: 0.3
292 summary: 0.3
293
293
294 changeset: 2:e38ba6f5b7e0
294 changeset: 2:e38ba6f5b7e0
295 user: test
295 user: test
296 date: Thu Jan 01 00:00:00 1970 +0000
296 date: Thu Jan 01 00:00:00 1970 +0000
297 summary: 0.2
297 summary: 0.2
298
298
299 changeset: 1:34c2bf6b0626
299 changeset: 1:34c2bf6b0626
300 user: test
300 user: test
301 date: Thu Jan 01 00:00:00 1970 +0000
301 date: Thu Jan 01 00:00:00 1970 +0000
302 summary: 0.1
302 summary: 0.1
303
303
304 changeset: 0:f9ee2f85a263
304 changeset: 0:f9ee2f85a263
305 user: test
305 user: test
306 date: Thu Jan 01 00:00:00 1970 +0000
306 date: Thu Jan 01 00:00:00 1970 +0000
307 summary: 0.0
307 summary: 0.0
308
308
309
309
310 Incoming full.hg in partial
310 Incoming full.hg in partial
311
311
312 $ hg incoming bundle://../full.hg
312 $ hg incoming bundle://../full.hg
313 comparing with bundle://../full.hg
313 comparing with bundle:../full.hg
314 searching for changes
314 searching for changes
315 changeset: 4:095197eb4973
315 changeset: 4:095197eb4973
316 parent: 0:f9ee2f85a263
316 parent: 0:f9ee2f85a263
317 user: test
317 user: test
318 date: Thu Jan 01 00:00:00 1970 +0000
318 date: Thu Jan 01 00:00:00 1970 +0000
319 summary: 1.1
319 summary: 1.1
320
320
321 changeset: 5:1bb50a9436a7
321 changeset: 5:1bb50a9436a7
322 user: test
322 user: test
323 date: Thu Jan 01 00:00:00 1970 +0000
323 date: Thu Jan 01 00:00:00 1970 +0000
324 summary: 1.2
324 summary: 1.2
325
325
326 changeset: 6:7373c1169842
326 changeset: 6:7373c1169842
327 user: test
327 user: test
328 date: Thu Jan 01 00:00:00 1970 +0000
328 date: Thu Jan 01 00:00:00 1970 +0000
329 summary: 1.3
329 summary: 1.3
330
330
331 changeset: 7:a6a34bfa0076
331 changeset: 7:a6a34bfa0076
332 user: test
332 user: test
333 date: Thu Jan 01 00:00:00 1970 +0000
333 date: Thu Jan 01 00:00:00 1970 +0000
334 summary: 1.3m
334 summary: 1.3m
335
335
336 changeset: 8:aa35859c02ea
336 changeset: 8:aa35859c02ea
337 tag: tip
337 tag: tip
338 parent: 3:eebf5a27f8ca
338 parent: 3:eebf5a27f8ca
339 user: test
339 user: test
340 date: Thu Jan 01 00:00:00 1970 +0000
340 date: Thu Jan 01 00:00:00 1970 +0000
341 summary: 0.3m
341 summary: 0.3m
342
342
343
343
344 Outgoing -R full.hg vs partial2 in partial
344 Outgoing -R full.hg vs partial2 in partial
345
345
346 $ hg -R bundle://../full.hg outgoing ../partial2
346 $ hg -R bundle://../full.hg outgoing ../partial2
347 comparing with ../partial2
347 comparing with ../partial2
348 searching for changes
348 searching for changes
349 changeset: 4:095197eb4973
349 changeset: 4:095197eb4973
350 parent: 0:f9ee2f85a263
350 parent: 0:f9ee2f85a263
351 user: test
351 user: test
352 date: Thu Jan 01 00:00:00 1970 +0000
352 date: Thu Jan 01 00:00:00 1970 +0000
353 summary: 1.1
353 summary: 1.1
354
354
355 changeset: 5:1bb50a9436a7
355 changeset: 5:1bb50a9436a7
356 user: test
356 user: test
357 date: Thu Jan 01 00:00:00 1970 +0000
357 date: Thu Jan 01 00:00:00 1970 +0000
358 summary: 1.2
358 summary: 1.2
359
359
360 changeset: 6:7373c1169842
360 changeset: 6:7373c1169842
361 user: test
361 user: test
362 date: Thu Jan 01 00:00:00 1970 +0000
362 date: Thu Jan 01 00:00:00 1970 +0000
363 summary: 1.3
363 summary: 1.3
364
364
365 changeset: 7:a6a34bfa0076
365 changeset: 7:a6a34bfa0076
366 user: test
366 user: test
367 date: Thu Jan 01 00:00:00 1970 +0000
367 date: Thu Jan 01 00:00:00 1970 +0000
368 summary: 1.3m
368 summary: 1.3m
369
369
370 changeset: 8:aa35859c02ea
370 changeset: 8:aa35859c02ea
371 tag: tip
371 tag: tip
372 parent: 3:eebf5a27f8ca
372 parent: 3:eebf5a27f8ca
373 user: test
373 user: test
374 date: Thu Jan 01 00:00:00 1970 +0000
374 date: Thu Jan 01 00:00:00 1970 +0000
375 summary: 0.3m
375 summary: 0.3m
376
376
377
377
378 Outgoing -R does-not-exist.hg vs partial2 in partial
378 Outgoing -R does-not-exist.hg vs partial2 in partial
379
379
380 $ hg -R bundle://../does-not-exist.hg outgoing ../partial2
380 $ hg -R bundle://../does-not-exist.hg outgoing ../partial2
381 abort: No such file or directory: ../does-not-exist.hg
381 abort: No such file or directory: ../does-not-exist.hg
382 [255]
382 [255]
383 $ cd ..
383 $ cd ..
384
384
385 Direct clone from bundle (all-history)
385 Direct clone from bundle (all-history)
386
386
387 $ hg clone full.hg full-clone
387 $ hg clone full.hg full-clone
388 requesting all changes
388 requesting all changes
389 adding changesets
389 adding changesets
390 adding manifests
390 adding manifests
391 adding file changes
391 adding file changes
392 added 9 changesets with 7 changes to 4 files (+1 heads)
392 added 9 changesets with 7 changes to 4 files (+1 heads)
393 updating to branch default
393 updating to branch default
394 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
394 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
395 $ hg -R full-clone heads
395 $ hg -R full-clone heads
396 changeset: 8:aa35859c02ea
396 changeset: 8:aa35859c02ea
397 tag: tip
397 tag: tip
398 parent: 3:eebf5a27f8ca
398 parent: 3:eebf5a27f8ca
399 user: test
399 user: test
400 date: Thu Jan 01 00:00:00 1970 +0000
400 date: Thu Jan 01 00:00:00 1970 +0000
401 summary: 0.3m
401 summary: 0.3m
402
402
403 changeset: 7:a6a34bfa0076
403 changeset: 7:a6a34bfa0076
404 user: test
404 user: test
405 date: Thu Jan 01 00:00:00 1970 +0000
405 date: Thu Jan 01 00:00:00 1970 +0000
406 summary: 1.3m
406 summary: 1.3m
407
407
408 $ rm -r full-clone
408 $ rm -r full-clone
409
409
410 When cloning from a non-copiable repository into '', do not
410 When cloning from a non-copiable repository into '', do not
411 recurse infinitely (issue 2528)
411 recurse infinitely (issue 2528)
412
412
413 $ hg clone full.hg ''
413 $ hg clone full.hg ''
414 abort: No such file or directory
414 abort: No such file or directory
415 [255]
415 [255]
416
416
417 test for http://mercurial.selenic.com/bts/issue216
417 test for http://mercurial.selenic.com/bts/issue216
418
418
419 Unbundle incremental bundles into fresh empty in one go
419 Unbundle incremental bundles into fresh empty in one go
420
420
421 $ rm -r empty
421 $ rm -r empty
422 $ hg init empty
422 $ hg init empty
423 $ hg -R test bundle --base null -r 0 ../0.hg
423 $ hg -R test bundle --base null -r 0 ../0.hg
424 1 changesets found
424 1 changesets found
425 $ hg -R test bundle --base 0 -r 1 ../1.hg
425 $ hg -R test bundle --base 0 -r 1 ../1.hg
426 1 changesets found
426 1 changesets found
427 $ hg -R empty unbundle -u ../0.hg ../1.hg
427 $ hg -R empty unbundle -u ../0.hg ../1.hg
428 adding changesets
428 adding changesets
429 adding manifests
429 adding manifests
430 adding file changes
430 adding file changes
431 added 1 changesets with 1 changes to 1 files
431 added 1 changesets with 1 changes to 1 files
432 adding changesets
432 adding changesets
433 adding manifests
433 adding manifests
434 adding file changes
434 adding file changes
435 added 1 changesets with 1 changes to 1 files
435 added 1 changesets with 1 changes to 1 files
436 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
436 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
437
437
438 test for 540d1059c802
438 test for 540d1059c802
439
439
440 test for 540d1059c802
440 test for 540d1059c802
441
441
442 $ hg init orig
442 $ hg init orig
443 $ cd orig
443 $ cd orig
444 $ echo foo > foo
444 $ echo foo > foo
445 $ hg add foo
445 $ hg add foo
446 $ hg ci -m 'add foo'
446 $ hg ci -m 'add foo'
447
447
448 $ hg clone . ../copy
448 $ hg clone . ../copy
449 updating to branch default
449 updating to branch default
450 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
450 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
451 $ hg tag foo
451 $ hg tag foo
452
452
453 $ cd ../copy
453 $ cd ../copy
454 $ echo >> foo
454 $ echo >> foo
455 $ hg ci -m 'change foo'
455 $ hg ci -m 'change foo'
456 $ hg bundle ../bundle.hg ../orig
456 $ hg bundle ../bundle.hg ../orig
457 searching for changes
457 searching for changes
458 1 changesets found
458 1 changesets found
459
459
460 $ cd ../orig
460 $ cd ../orig
461 $ hg incoming ../bundle.hg
461 $ hg incoming ../bundle.hg
462 comparing with ../bundle.hg
462 comparing with ../bundle.hg
463 searching for changes
463 searching for changes
464 changeset: 2:ed1b79f46b9a
464 changeset: 2:ed1b79f46b9a
465 tag: tip
465 tag: tip
466 parent: 0:bbd179dfa0a7
466 parent: 0:bbd179dfa0a7
467 user: test
467 user: test
468 date: Thu Jan 01 00:00:00 1970 +0000
468 date: Thu Jan 01 00:00:00 1970 +0000
469 summary: change foo
469 summary: change foo
470
470
471 $ cd ..
471 $ cd ..
472
472
473 test for http://mercurial.selenic.com/bts/issue1144
473 test for http://mercurial.selenic.com/bts/issue1144
474
474
475 test that verify bundle does not traceback
475 test that verify bundle does not traceback
476
476
477 partial history bundle, fails w/ unkown parent
477 partial history bundle, fails w/ unkown parent
478
478
479 $ hg -R bundle.hg verify
479 $ hg -R bundle.hg verify
480 abort: 00changelog.i@bbd179dfa0a7: unknown parent!
480 abort: 00changelog.i@bbd179dfa0a7: unknown parent!
481 [255]
481 [255]
482
482
483 full history bundle, refuses to verify non-local repo
483 full history bundle, refuses to verify non-local repo
484
484
485 $ hg -R all.hg verify
485 $ hg -R all.hg verify
486 abort: cannot verify bundle or remote repos
486 abort: cannot verify bundle or remote repos
487 [255]
487 [255]
488
488
489 but, regular verify must continue to work
489 but, regular verify must continue to work
490
490
491 $ hg -R orig verify
491 $ hg -R orig verify
492 checking changesets
492 checking changesets
493 checking manifests
493 checking manifests
494 crosschecking files in changesets and manifests
494 crosschecking files in changesets and manifests
495 checking files
495 checking files
496 2 files, 2 changesets, 2 total revisions
496 2 files, 2 changesets, 2 total revisions
497
497
498 diff against bundle
498 diff against bundle
499
499
500 $ hg init b
500 $ hg init b
501 $ cd b
501 $ cd b
502 $ hg -R ../all.hg diff -r tip
502 $ hg -R ../all.hg diff -r tip
503 diff -r aa35859c02ea anotherfile
503 diff -r aa35859c02ea anotherfile
504 --- a/anotherfile Thu Jan 01 00:00:00 1970 +0000
504 --- a/anotherfile Thu Jan 01 00:00:00 1970 +0000
505 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
505 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
506 @@ -1,4 +0,0 @@
506 @@ -1,4 +0,0 @@
507 -0
507 -0
508 -1
508 -1
509 -2
509 -2
510 -3
510 -3
511 $ cd ..
511 $ cd ..
512
512
513 bundle single branch
513 bundle single branch
514
514
515 $ hg init branchy
515 $ hg init branchy
516 $ cd branchy
516 $ cd branchy
517 $ echo a >a
517 $ echo a >a
518 $ hg ci -Ama
518 $ hg ci -Ama
519 adding a
519 adding a
520 $ echo b >b
520 $ echo b >b
521 $ hg ci -Amb
521 $ hg ci -Amb
522 adding b
522 adding b
523 $ echo b1 >b1
523 $ echo b1 >b1
524 $ hg ci -Amb1
524 $ hg ci -Amb1
525 adding b1
525 adding b1
526 $ hg up 0
526 $ hg up 0
527 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
527 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
528 $ echo c >c
528 $ echo c >c
529 $ hg ci -Amc
529 $ hg ci -Amc
530 adding c
530 adding c
531 created new head
531 created new head
532 $ echo c1 >c1
532 $ echo c1 >c1
533 $ hg ci -Amc1
533 $ hg ci -Amc1
534 adding c1
534 adding c1
535 $ hg clone -q .#tip part
535 $ hg clone -q .#tip part
536
536
537 == bundling via incoming
537 == bundling via incoming
538
538
539 $ hg in -R part --bundle incoming.hg --template "{node}\n" .
539 $ hg in -R part --bundle incoming.hg --template "{node}\n" .
540 comparing with .
540 comparing with .
541 searching for changes
541 searching for changes
542 d2ae7f538514cd87c17547b0de4cea71fe1af9fb
542 d2ae7f538514cd87c17547b0de4cea71fe1af9fb
543 5ece8e77363e2b5269e27c66828b72da29e4341a
543 5ece8e77363e2b5269e27c66828b72da29e4341a
544
544
545 == bundling
545 == bundling
546
546
547 $ hg bundle bundle.hg part --debug
547 $ hg bundle bundle.hg part --debug
548 searching for changes
548 searching for changes
549 common changesets up to c0025332f9ed
549 common changesets up to c0025332f9ed
550 2 changesets found
550 2 changesets found
551 list of changesets:
551 list of changesets:
552 d2ae7f538514cd87c17547b0de4cea71fe1af9fb
552 d2ae7f538514cd87c17547b0de4cea71fe1af9fb
553 5ece8e77363e2b5269e27c66828b72da29e4341a
553 5ece8e77363e2b5269e27c66828b72da29e4341a
554 bundling: 1 changesets
554 bundling: 1 changesets
555 bundling: 2 changesets
555 bundling: 2 changesets
556 bundling: 1/2 manifests (50.00%)
556 bundling: 1/2 manifests (50.00%)
557 bundling: 2/2 manifests (100.00%)
557 bundling: 2/2 manifests (100.00%)
558 bundling: b 0/2 files (0.00%)
558 bundling: b 0/2 files (0.00%)
559 bundling: b1 1/2 files (50.00%)
559 bundling: b1 1/2 files (50.00%)
560
560
General Comments 0
You need to be logged in to leave comments. Login now