5 # This software may be used and distributed according to the terms of the |
5 # This software may be used and distributed according to the terms of the |
6 # GNU General Public License version 2 or any later version. |
6 # GNU General Public License version 2 or any later version. |
7 |
7 |
8 from i18n import _ |
8 from i18n import _ |
9 from node import hex, nullid |
9 from node import hex, nullid |
10 import errno, urllib |
10 import errno, urllib, urllib2 |
11 import util, scmutil, changegroup, base85, error |
11 import util, scmutil, changegroup, base85, error |
12 import discovery, phases, obsolete, bookmarks as bookmod, bundle2, pushkey |
12 import discovery, phases, obsolete, bookmarks as bookmod, bundle2, pushkey |
13 import lock as lockmod |
13 import lock as lockmod |
14 import streamclone |
14 import streamclone |
15 import tags |
15 import tags |
|
16 import url as urlmod |
16 |
17 |
17 def readbundle(ui, fh, fname, vfs=None): |
18 def readbundle(ui, fh, fname, vfs=None): |
18 header = changegroup.readexactly(fh, 4) |
19 header = changegroup.readexactly(fh, 4) |
19 |
20 |
20 alg = None |
21 alg = None |
971 |
972 |
972 lock = pullop.repo.lock() |
973 lock = pullop.repo.lock() |
973 try: |
974 try: |
974 pullop.trmanager = transactionmanager(repo, 'pull', remote.url()) |
975 pullop.trmanager = transactionmanager(repo, 'pull', remote.url()) |
975 streamclone.maybeperformlegacystreamclone(pullop) |
976 streamclone.maybeperformlegacystreamclone(pullop) |
|
977 # This should ideally be in _pullbundle2(). However, it needs to run |
|
978 # before discovery to avoid extra work. |
|
979 _maybeapplyclonebundle(pullop) |
976 _pulldiscovery(pullop) |
980 _pulldiscovery(pullop) |
977 if pullop.canusebundle2: |
981 if pullop.canusebundle2: |
978 _pullbundle2(pullop) |
982 _pullbundle2(pullop) |
979 _pullchangeset(pullop) |
983 _pullchangeset(pullop) |
980 _pullphase(pullop) |
984 _pullphase(pullop) |
1497 finally: |
1501 finally: |
1498 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0]) |
1502 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0]) |
1499 if recordout is not None: |
1503 if recordout is not None: |
1500 recordout(repo.ui.popbuffer()) |
1504 recordout(repo.ui.popbuffer()) |
1501 return r |
1505 return r |
|
1506 |
|
1507 def _maybeapplyclonebundle(pullop): |
|
1508 """Apply a clone bundle from a remote, if possible.""" |
|
1509 |
|
1510 repo = pullop.repo |
|
1511 remote = pullop.remote |
|
1512 |
|
1513 if not repo.ui.configbool('experimental', 'clonebundles', False): |
|
1514 return |
|
1515 |
|
1516 if pullop.heads: |
|
1517 return |
|
1518 |
|
1519 if not remote.capable('clonebundles'): |
|
1520 return |
|
1521 |
|
1522 res = remote._call('clonebundles') |
|
1523 entries = parseclonebundlesmanifest(res) |
|
1524 |
|
1525 # TODO filter entries by supported features. |
|
1526 # TODO sort entries by user preferences. |
|
1527 |
|
1528 if not entries: |
|
1529 repo.ui.note(_('no clone bundles available on remote; ' |
|
1530 'falling back to regular clone\n')) |
|
1531 return |
|
1532 |
|
1533 url = entries[0]['URL'] |
|
1534 repo.ui.status(_('applying clone bundle from %s\n') % url) |
|
1535 if trypullbundlefromurl(repo.ui, repo, url): |
|
1536 repo.ui.status(_('finished applying clone bundle\n')) |
|
1537 # Bundle failed. |
|
1538 # |
|
1539 # We abort by default to avoid the thundering herd of |
|
1540 # clients flooding a server that was expecting expensive |
|
1541 # clone load to be offloaded. |
|
1542 elif repo.ui.configbool('ui', 'clonebundlefallback', False): |
|
1543 repo.ui.warn(_('falling back to normal clone\n')) |
|
1544 else: |
|
1545 raise error.Abort(_('error applying bundle'), |
|
1546 hint=_('consider contacting the server ' |
|
1547 'operator if this error persists')) |
|
1548 |
|
1549 def parseclonebundlesmanifest(s): |
|
1550 """Parses the raw text of a clone bundles manifest. |
|
1551 |
|
1552 Returns a list of dicts. The dicts have a ``URL`` key corresponding |
|
1553 to the URL and other keys are the attributes for the entry. |
|
1554 """ |
|
1555 m = [] |
|
1556 for line in s.splitlines(): |
|
1557 fields = line.split() |
|
1558 if not fields: |
|
1559 continue |
|
1560 attrs = {'URL': fields[0]} |
|
1561 for rawattr in fields[1:]: |
|
1562 key, value = rawattr.split('=', 1) |
|
1563 attrs[urllib.unquote(key)] = urllib.unquote(value) |
|
1564 |
|
1565 m.append(attrs) |
|
1566 |
|
1567 return m |
|
1568 |
|
1569 def trypullbundlefromurl(ui, repo, url): |
|
1570 """Attempt to apply a bundle from a URL.""" |
|
1571 lock = repo.lock() |
|
1572 try: |
|
1573 tr = repo.transaction('bundleurl') |
|
1574 try: |
|
1575 try: |
|
1576 fh = urlmod.open(ui, url) |
|
1577 cg = readbundle(ui, fh, 'stream') |
|
1578 changegroup.addchangegroup(repo, cg, 'clonebundles', url) |
|
1579 tr.close() |
|
1580 return True |
|
1581 except urllib2.HTTPError as e: |
|
1582 ui.warn(_('HTTP error fetching bundle: %s\n') % str(e)) |
|
1583 except urllib2.URLError as e: |
|
1584 ui.warn(_('error fetching bundle: %s\n') % e.reason) |
|
1585 |
|
1586 return False |
|
1587 finally: |
|
1588 tr.release() |
|
1589 finally: |
|
1590 lock.release() |