[PATCH 2 of 2] bundle2: add a part to import external bundles

Mike Hommey mh at glandium.org
Sat Oct 11 03:38:21 CDT 2014


# HG changeset patch
# User Mike Hommey <mh at glandium.org>
# Date 1413016657 -32400
#      Sat Oct 11 17:37:37 2014 +0900
# Node ID 538073e6c7f6ae680cbdd9bcb44c214fd6653eb1
# Parent  225306662bbf7c8cc6d867d484eab1e038f6c2a5
bundle2: add a part to import external bundles

Bundle2 opens doors to advanced features allowing to reduce load on
mercurial servers, and improve clone experience for users on unstable or
slow networks.

For instance, it could be possible to pre-generate a bundle of a
repository, and give a pointer to it to clients cloning the repository,
followed by another changegroup with the remainder. For significantly
big repositories, this could come as several base bundles with e.g. 10k
changesets, which, combined with checkpoints (not part of this change),
would prevent users with flaky networks from starting over any time
their connection fails.

While the server-side support for those features doesn't exist yet, it
is preferable to have client-side support for this early-on.

Note, I'm not sure the exchange.py change is right. Also, I'm not sure what
pullop.fetch means. Is it possible to have pullop.fetch set, but to have e.g.
only bookmark updates? In that case, the exchange.py change might be right.

I'm also unsure if it makes sense to do so many tests. Clone and pull are not
so different from each other, and the different test scenarios are more
testing the multi-part infrastructure than the feature itself. It however
tests all the use-cases I do plan to use.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -143,16 +143,18 @@ preserve.
 """
 
 import util
 import struct
 import urllib
 import string
 import obsolete
 import pushkey
+import urllib2
+import urlparse
 
 import changegroup, error
 from i18n import _
 
 _pack = struct.pack
 _unpack = struct.unpack
 
 _magicstring = 'HG2X'
@@ -771,20 +773,81 @@ class unbundlepart(unpackermixin):
         if size is None:
             data = self._payloadstream.read()
         else:
             data = self._payloadstream.read(size)
         if size is None or len(data) < size:
             self.consumed = True
         return data
 
+class digester(object):
+    """helper to compute multiple digests for the same data."""
+
+    DIGESTS = {
+        'md5': util.md5,
+        'sha1': util.sha1,
+    }
+
+    def __init__(self, digests, s=''):
+        self._hashes = {}
+        for k in digests:
+            if k not in self.DIGESTS:
+                raise util.Abort(_('unknown digest type: %s') % k)
+            self._hashes[k] = self.DIGESTS[k]()
+        if s:
+            self.update(s)
+
+    def update(self, data):
+        for h in self._hashes.values():
+            h.update(data)
+
+    def __getitem__(self, key):
+        if key not in self.DIGESTS:
+            raise util.Abort(_('unknown digest type: %s') % k)
+        return self._hashes[key].hexdigest()
+
+    def __iter__(self):
+        return iter(self._hashes)
+
+
+class digestchecker(object):
+    """file handle wrapper that additionally checks content against a given
+    size and hash digests.
+
+        d = digestchecker(fh, size, {'md5': '...'})
+
+    When multiple digests are given, all of them are verified.
+    """
+
+    def __init__(self, fh, size, digests):
+        self._fh = fh
+        self._size = size
+        self._digests = dict(digests)
+        self._digester = digester(self._digests.keys())
+
+    def read(self, length=-1):
+        content = self._fh.read(length)
+        self._digester.update(content)
+        self._size -= len(content)
+        return content
+
+    def validate(self):
+        if self._size != 0:
+            return False
+        for k, v in self._digests.items():
+            if v != self._digester[k]:
+                return False
+        return True
+
+
 capabilities = {'HG2X': (),
                 'b2x:listkeys': (),
                 'b2x:pushkey': (),
                 'b2x:changegroup': (),
+                'digests': tuple(digester.DIGESTS.keys()),
                }
 
 def getrepocaps(repo):
     """return the bundle2 capabilities for a given repo
 
     Exists to allow extensions (like evolution) to mutate the capabilities.
     """
     caps = capabilities.copy()
@@ -826,16 +889,76 @@ def handlechangegroup(op, inpart):
     if op.reply is not None:
         # This is definitly not the final form of this
         # return. But one need to start somewhere.
         part = op.reply.newpart('b2x:reply:changegroup')
         part.addparam('in-reply-to', str(inpart.id), mandatory=False)
         part.addparam('return', '%i' % ret, mandatory=False)
     assert not inpart.read()
 
+ at parthandler('b2x:import-bundle')
+def handleimportbundle(op, inpart):
+    """apply a bundle on the repo, given an url and validation information
+
+    The part contains a bundle url, as well as hash digest(s) and size
+    information to validate the downloaded content.
+    The expected format for this part is:
+        <URL>
+        <SIZE in bytes>
+        <DIGEST-TYPE>: <DIGEST-VALUE>
+        ...
+    Multiple digest types are supported in the same part, in which case they
+    are all validated.
+    This currently only supports bundle10, but is intended to support bundle20
+    eventually.
+    """
+    lines = inpart.read().splitlines()
+    try:
+        url = lines.pop(0)
+    except IndexError:
+        raise util.Abort(_('import-bundle format error'))
+    parsed_url = urlparse.urlparse(url)
+    if parsed_url.scheme not in ('http', 'https'):
+        raise util.Abort(_('import-bundle only supports http/https urls'))
+
+    try:
+        size = int(lines.pop(0))
+    except (ValueError, IndexError):
+        raise util.Abort(_('import-bundle format error'))
+    digests = {}
+    for line in lines:
+        if ':' not in line:
+            raise util.Abort(_('import-bundle format error'))
+        k, v = line.split(':', 1)
+        digests[k.strip()] = v.strip()
+
+    real_part = digestchecker(urllib2.urlopen(url), size, digests)
+
+    # Make sure we trigger a transaction creation
+    #
+    # The addchangegroup function will get a transaction object by itself, but
+    # we need to make sure we trigger the creation of a transaction object used
+    # for the whole processing scope.
+    op.gettransaction()
+    import exchange
+    cg = exchange.readbundle(op.repo.ui, real_part, None)
+    if isinstance(cg, unbundle20):
+        raise util.Abort(_('import-bundle only supports bundle10'))
+    ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
+    op.records.add('import-bundle', {'return': ret})
+    if op.reply is not None:
+        # This is definitly not the final form of this
+        # return. But one need to start somewhere.
+        part = op.reply.newpart('b2x:reply:import-bundle')
+        part.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        part.addparam('return', '%i' % ret, mandatory=False)
+    if not real_part.validate():
+        raise util.Abort(_('bundle at %s is corrupted') % url)
+    assert not inpart.read()
+
 @parthandler('b2x:reply:changegroup', ('return', 'in-reply-to'))
 def handlereplychangegroup(op, inpart):
     ret = int(inpart.params['return'])
     replyto = int(inpart.params['in-reply-to'])
     op.records.add('changegroup', {'return': ret}, replyto)
 
 @parthandler('b2x:check:heads')
 def handlecheckheads(op, inpart):
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -908,17 +908,17 @@ def _pullbundle2(pullop):
     if kwargs.keys() == ['format']:
         return # nothing to pull
     bundle = pullop.remote.getbundle('pull', **kwargs)
     try:
         op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
     except error.BundleValueError, exc:
         raise util.Abort('missing support for %s' % exc)
 
-    if pullop.fetch:
+    if pullop.fetch and op.records['changegroup']:
         assert len(op.records['changegroup']) == 1
         pullop.cgresult = op.records['changegroup'][0]['return']
 
     # processing phases change
     for namespace, value in op.records['listkeys']:
         if namespace == 'phases':
             _pullapplyphases(pullop, value)
 
diff --git a/tests/test-bundle2-import.t b/tests/test-bundle2-import.t
new file mode 100644
--- /dev/null
+++ b/tests/test-bundle2-import.t
@@ -0,0 +1,535 @@
+#require killdaemons
+
+Create an extension to test bundle2 import-bundle parts
+
+  $ cat > bundle2.py << EOF
+  > """A small extension to test bundle2 import-bundle parts.
+  > 
+  > Current bundle2 implementation doesn't provide a way to generate those
+  > parts, so they must be created by extensions.
+  > """
+  > from mercurial import bundle2, changegroup, commands, exchange
+  > from mercurial import extensions, scmutil
+  > import os
+  > 
+  > def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
+  >                               b2caps=None, heads=None, common=None,
+  >                               **kwargs):
+  >     for line in open(repo.join('bundle2maker'), 'r'):
+  >         line = line.strip()
+  >         try:
+  >             verb, args = line.split(None, 1)
+  >         except ValueError:
+  >             verb, args = line, ''
+  >         if verb == 'import-bundle':
+  >            url, file = args.split()
+  >            bundledata = open(file, 'rb').read()
+  >            data = '%s\n%d\n' % (url, len(bundledata))
+  >            d = bundle2.digester(b2caps['digests'], bundledata)
+  >            for k in b2caps['digests']:
+  >                data += '%s: %s\n' % (k, d[k])
+  >            bundler.newpart('b2x:import-bundle', data=data)
+  >         elif verb == 'raw-import-bundle':
+  >            bundler.newpart('b2x:import-bundle',
+  >                data=args.decode('string-escape'))
+  >         elif verb == 'changegroup':
+  >             _common, heads = args.split()
+  >             common.extend(repo.lookup(r) for r in repo.revs(_common))
+  >             heads = [repo.lookup(r) for r in repo.revs(heads)]
+  >             cg = changegroup.getchangegroup(repo, 'changegroup',
+  >                 heads=heads, common=common)
+  >             bundler.newpart('b2x:changegroup', data=cg.getchunks())
+  >         else:
+  >             raise Exception('unknown verb')
+  > 
+  > exchange.getbundle2partsmapping['changegroup'] = _getbundlechangegrouppart
+  > EOF
+
+Start a simple HTTP server to serve bundles (stolen from test-static-http.t)
+
+  $ cat > dumb.py <<EOF
+  > import BaseHTTPServer, SimpleHTTPServer, os, signal, sys
+  > 
+  > def run(server_class=BaseHTTPServer.HTTPServer,
+  >         handler_class=SimpleHTTPServer.SimpleHTTPRequestHandler):
+  >     server_address = ('localhost', int(os.environ['HGPORT']))
+  >     httpd = server_class(server_address, handler_class)
+  >     httpd.serve_forever()
+  > 
+  > signal.signal(signal.SIGTERM, lambda x, y: sys.exit(0))
+  > fp = file('dumb.pid', 'wb')
+  > fp.write(str(os.getpid()) + '\n')
+  > fp.close()
+  > run()
+  > EOF
+  $ python dumb.py 2>/dev/null &
+
+Cannot just read $!, it will not be set to the right value on Windows/MinGW
+
+  $ cat > wait.py <<EOF
+  > import time
+  > while True:
+  >     try:
+  >         if '\n' in file('dumb.pid', 'rb').read():
+  >             break
+  >     except IOError:
+  >         pass
+  >     time.sleep(0.2)
+  > EOF
+  $ python wait.py
+  $ cat dumb.pid >> $DAEMON_PIDS
+
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > bundle2-exp=True
+  > [ui]
+  > ssh=python "$TESTDIR/dummyssh"
+  > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
+  > EOF
+
+  $ hg init repo
+
+  $ hg -R repo unbundle $TESTDIR/bundles/rebase.hg
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+2 heads)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+
+  $ hg -R repo log -G
+  o  7:02de42196ebe draft Nicolas Dumazet <nicdumz.commits at gmail.com>  H
+  |
+  | o  6:eea13746799a draft Nicolas Dumazet <nicdumz.commits at gmail.com>  G
+  |/|
+  o |  5:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits at gmail.com>  F
+  | |
+  | o  4:9520eea781bc draft Nicolas Dumazet <nicdumz.commits at gmail.com>  E
+  |/
+  | o  3:32af7686d403 draft Nicolas Dumazet <nicdumz.commits at gmail.com>  D
+  | |
+  | o  2:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits at gmail.com>  C
+  | |
+  | o  1:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits at gmail.com>  B
+  |/
+  o  0:cd010b8cd998 draft Nicolas Dumazet <nicdumz.commits at gmail.com>  A
+  
+  $ hg clone repo orig
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ cat > repo/.hg/hgrc << EOF
+  > [extensions]
+  > bundle2=$TESTTMP/bundle2.py
+  > EOF
+
+Test a clone with a single import-bundle.
+
+  $ hg bundle -R repo -a bundle.hg
+  8 changesets found
+  $ cat > repo/.hg/bundle2maker << EOF
+  > import-bundle http://localhost:$HGPORT/bundle.hg bundle.hg
+  > EOF
+  $ hg clone ssh://user@dummy/repo clone
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+2 heads)
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R clone log -G
+  @  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits at gmail.com>  H
+  |
+  | o  6:eea13746799a public Nicolas Dumazet <nicdumz.commits at gmail.com>  G
+  |/|
+  o |  5:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits at gmail.com>  F
+  | |
+  | o  4:9520eea781bc public Nicolas Dumazet <nicdumz.commits at gmail.com>  E
+  |/
+  | o  3:32af7686d403 public Nicolas Dumazet <nicdumz.commits at gmail.com>  D
+  | |
+  | o  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits at gmail.com>  C
+  | |
+  | o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits at gmail.com>  B
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits at gmail.com>  A
+  
+  $ rm -rf clone
+
+Test a clone with an import-bundle and a following changegroup
+
+  $ hg bundle -R repo --base 000000000000 -r '0:4' bundle2.hg
+  5 changesets found
+  $ cat > repo/.hg/bundle2maker << EOF
+  > import-bundle http://localhost:$HGPORT/bundle2.hg bundle2.hg
+  > changegroup 0:4 5:7
+  > EOF
+  $ hg clone ssh://user@dummy/repo clone
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 5 changesets with 5 changes to 5 files (+1 heads)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 2 changes to 2 files (+1 heads)
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R clone log -G
+  @  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits at gmail.com>  H
+  |
+  | o  6:eea13746799a public Nicolas Dumazet <nicdumz.commits at gmail.com>  G
+  |/|
+  o |  5:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits at gmail.com>  F
+  | |
+  | o  4:9520eea781bc public Nicolas Dumazet <nicdumz.commits at gmail.com>  E
+  |/
+  | o  3:32af7686d403 public Nicolas Dumazet <nicdumz.commits at gmail.com>  D
+  | |
+  | o  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits at gmail.com>  C
+  | |
+  | o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits at gmail.com>  B
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits at gmail.com>  A
+  
+  $ rm -rf clone
+
+Test a clone with a changegroup followed by an import-bundle
+
+  $ hg bundle -R repo --base '0:4' -r '5:7' bundle3.hg
+  3 changesets found
+  $ cat > repo/.hg/bundle2maker << EOF
+  > changegroup 000000000000 :4
+  > import-bundle http://localhost:$HGPORT/bundle3.hg bundle3.hg
+  > EOF
+  $ hg clone ssh://user@dummy/repo clone
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 5 changesets with 5 changes to 5 files (+1 heads)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 2 changes to 2 files (+1 heads)
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R clone log -G
+  @  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits at gmail.com>  H
+  |
+  | o  6:eea13746799a public Nicolas Dumazet <nicdumz.commits at gmail.com>  G
+  |/|
+  o |  5:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits at gmail.com>  F
+  | |
+  | o  4:9520eea781bc public Nicolas Dumazet <nicdumz.commits at gmail.com>  E
+  |/
+  | o  3:32af7686d403 public Nicolas Dumazet <nicdumz.commits at gmail.com>  D
+  | |
+  | o  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits at gmail.com>  C
+  | |
+  | o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits at gmail.com>  B
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits at gmail.com>  A
+  
+  $ rm -rf clone
+
+Test a pull with an import-bundle
+
+  $ hg bundle -R repo --base '0:4' -r '5:7' bundle4.hg
+  3 changesets found
+  $ cat > repo/.hg/bundle2maker << EOF
+  > import-bundle http://localhost:$HGPORT/bundle4.hg bundle4.hg
+  > EOF
+  $ hg clone orig clone -r 3 -r 4
+  adding changesets
+  adding manifests
+  adding file changes
+  added 5 changesets with 5 changes to 5 files (+1 heads)
+  updating to branch default
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 2 changes to 2 files (+1 heads)
+  (run 'hg update' to get a working copy)
+  $ hg -R clone log -G
+  o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits at gmail.com>  H
+  |
+  | o  6:eea13746799a public Nicolas Dumazet <nicdumz.commits at gmail.com>  G
+  |/|
+  o |  5:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits at gmail.com>  F
+  | |
+  | o  4:9520eea781bc public Nicolas Dumazet <nicdumz.commits at gmail.com>  E
+  |/
+  | @  3:32af7686d403 public Nicolas Dumazet <nicdumz.commits at gmail.com>  D
+  | |
+  | o  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits at gmail.com>  C
+  | |
+  | o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits at gmail.com>  B
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits at gmail.com>  A
+  
+  $ rm -rf clone
+
+Test a pull with an import-bundle and a following changegroup
+
+  $ hg bundle -R repo --base 2 -r '3:4' bundle5.hg
+  2 changesets found
+  $ cat > repo/.hg/bundle2maker << EOF
+  > import-bundle http://localhost:$HGPORT/bundle5.hg bundle5.hg
+  > changegroup 0:4 5:7
+  > EOF
+  $ hg clone orig clone -r 2
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 2 changes to 2 files (+1 heads)
+  (run 'hg heads .' to see heads, 'hg merge' to merge)
+  $ hg -R clone log -G
+  o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits at gmail.com>  H
+  |
+  | o  6:eea13746799a public Nicolas Dumazet <nicdumz.commits at gmail.com>  G
+  |/|
+  o |  5:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits at gmail.com>  F
+  | |
+  | o  4:9520eea781bc public Nicolas Dumazet <nicdumz.commits at gmail.com>  E
+  |/
+  | o  3:32af7686d403 public Nicolas Dumazet <nicdumz.commits at gmail.com>  D
+  | |
+  | @  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits at gmail.com>  C
+  | |
+  | o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits at gmail.com>  B
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits at gmail.com>  A
+  
+  $ rm -rf clone
+
+Test a pull with a changegroup followed by an import-bundle
+
+  $ hg bundle -R repo --base '0:4' -r '5:7' bundle6.hg
+  3 changesets found
+  $ cat > repo/.hg/bundle2maker << EOF
+  > changegroup 000000000000 :4
+  > import-bundle http://localhost:$HGPORT/bundle6.hg bundle6.hg
+  > EOF
+  $ hg clone orig clone -r 2
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 2 changes to 2 files (+1 heads)
+  (run 'hg heads .' to see heads, 'hg merge' to merge)
+  $ hg -R clone log -G
+  o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits at gmail.com>  H
+  |
+  | o  6:eea13746799a public Nicolas Dumazet <nicdumz.commits at gmail.com>  G
+  |/|
+  o |  5:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits at gmail.com>  F
+  | |
+  | o  4:9520eea781bc public Nicolas Dumazet <nicdumz.commits at gmail.com>  E
+  |/
+  | o  3:32af7686d403 public Nicolas Dumazet <nicdumz.commits at gmail.com>  D
+  | |
+  | @  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits at gmail.com>  C
+  | |
+  | o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits at gmail.com>  B
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits at gmail.com>  A
+  
+  $ rm -rf clone
+
+Test a pull with two import-bundles and a changegroup
+
+  $ hg bundle -R repo --base 2 -r '3:4' bundle7.hg
+  2 changesets found
+  $ hg bundle -R repo --base '3:4' -r '5:6' bundle8.hg
+  2 changesets found
+  $ cat > repo/.hg/bundle2maker << EOF
+  > import-bundle http://localhost:$HGPORT/bundle7.hg bundle7.hg
+  > import-bundle http://localhost:$HGPORT/bundle8.hg bundle8.hg
+  > changegroup 0:6 7
+  > EOF
+  $ hg clone orig clone -r 2
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 1 changes to 1 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  (run 'hg heads .' to see heads, 'hg merge' to merge)
+  $ hg -R clone log -G
+  o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits at gmail.com>  H
+  |
+  | o  6:eea13746799a public Nicolas Dumazet <nicdumz.commits at gmail.com>  G
+  |/|
+  o |  5:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits at gmail.com>  F
+  | |
+  | o  4:9520eea781bc public Nicolas Dumazet <nicdumz.commits at gmail.com>  E
+  |/
+  | o  3:32af7686d403 public Nicolas Dumazet <nicdumz.commits at gmail.com>  D
+  | |
+  | @  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits at gmail.com>  C
+  | |
+  | o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits at gmail.com>  B
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits at gmail.com>  A
+  
+  $ rm -rf clone
+
+Corruption tests
+
+  $ hg clone orig clone -r 2
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > import-bundle http://localhost:$HGPORT/bundle7.hg bundle7.hg
+  > import-bundle http://localhost:$HGPORT/bundle8.hg bundle7.hg
+  > changegroup 0:6 7
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 1 changes to 1 files
+  transaction abort!
+  rollback completed
+  abort: bundle at http://localhost:$HGPORT/bundle8.hg is corrupted
+  [255]
+
+No content
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-import-bundle
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  abort: import-bundle format error
+  [255]
+
+Missing size
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-import-bundle http://localhost:$HGPORT/bundle7.hg
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  abort: import-bundle format error
+  [255]
+
+Size mismatch
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-import-bundle http://localhost:$HGPORT/bundle7.hg\n42
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  transaction abort!
+  rollback completed
+  abort: bundle at http://localhost:$HGPORT/bundle7.hg is corrupted
+  [255]
+
+Garbage data
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-import-bundle http://localhost:$HGPORT/bundle7.hg\n581\ngarbage
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  abort: import-bundle format error
+  [255]
+
+Unknown digest
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-import-bundle http://localhost:$HGPORT/bundle7.hg\n581\nfoo: bar
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  abort: unknown digest type: foo
+  [255]
+
+Not an HTTP url
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-import-bundle ssh://localhost:$HGPORT/bundle7.hg\n581
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  abort: import-bundle only supports http/https urls
+  [255]
+
+  $ hg -R clone log -G
+  @  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits at gmail.com>  C
+  |
+  o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits at gmail.com>  B
+  |
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits at gmail.com>  A
+  
+  $ rm -rf clone
+
+  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS


More information about the Mercurial-devel mailing list