mercurial/exchange.py
changeset 26623 5a95fe44121d
parent 26587 56b2bcea2529
child 26639 92d67e5729b9
equal deleted inserted replaced
26622:9e15286609ae 26623:5a95fe44121d
     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()