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