[PATCH] bundle: don't send too many changesets (Issue1704)

Peter Arrenbrecht peter.arrenbrecht at gmail.com
Fri Nov 6 16:12:36 CST 2009


# HG changeset patch
# User Peter Arrenbrecht <peter.arrenbrecht at gmail.com>
# Date 1257544871 -3600
bundle: don't send too many changesets (Issue1704)

The fast path in changegroupsubset can send too many csets. This happens
because it uses the parents of all bases as common nodes and then goes
forward from this again. If a base has a parent that has another child,
which is -not- a base, then this other child will nevertheless end up in
the changegroup.

The fix is to not use findmissing(), but use nodesbetween() instead, as
do the slow path and incoming/outgoing.

The change to test-notify.out is correct, because it actually hits this
bug, as can be seen by glog'ing the two repos:

@    22c88
|\
| o  0a184
| |
o |  0647d
|/
o  cb9a9

and

o  0647d
|
@  cb9a9

It used to pull 0647d again, which is unnecessary.

test-bundle-vs-outgoing was contributed by Nicolas Dumazet.

diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1564,7 +1564,8 @@
 
         if revs is None:
             # use the fast path, no race possible on push
-            cg = self._changegroup(common.keys(), 'push')
+            nodes = self.changelog.findmissing(common.keys())
+            cg = self._changegroup(nodes, 'push')
         else:
             cg = self.changegroupsubset(update, revs, 'push')
         return cg, remote_heads
@@ -1622,19 +1623,22 @@
         the linkrev.
         """
 
+        # Set up some initial variables
+        # Make it easy to refer to self.changelog
+        cl = self.changelog
+        # msng is short for missing - compute the list of changesets in this
+        # changegroup.
+        if not bases:
+            bases = [nullid]
+        msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
+
         if extranodes is None:
             # can we go through the fast path ?
             heads.sort()
             allheads = self.heads()
             allheads.sort()
             if heads == allheads:
-                common = []
-                # parents of bases are known from both sides
-                for n in bases:
-                    for p in self.changelog.parents(n):
-                        if p != nullid:
-                            common.append(p)
-                return self._changegroup(common, source)
+                return self._changegroup(msng_cl_lst, source)
 
         self.hook('preoutgoing', throw=True, source=source)
 
@@ -1903,7 +1907,7 @@
         # to avoid a race we use changegroupsubset() (issue1320)
         return self.changegroupsubset(basenodes, self.heads(), source)
 
-    def _changegroup(self, common, source):
+    def _changegroup(self, nodes, source):
         """Compute the changegroup of all nodes that we have that a recipient
         doesn't.  Return a chunkbuffer object whose read() method will return
         successive changegroup chunks.
@@ -1911,12 +1915,11 @@
         This is much easier than the previous function as we can assume that
         the recipient has any changenode we aren't sending them.
 
-        common is the set of common nodes between remote and self"""
+        nodes is the set of nodes to send"""
 
         self.hook('preoutgoing', throw=True, source=source)
 
         cl = self.changelog
-        nodes = cl.findmissing(common)
         revset = set([cl.rev(n) for n in nodes])
         self.changegroupinfo(nodes, source)
 
diff --git a/tests/test-bundle b/tests/test-bundle
--- a/tests/test-bundle
+++ b/tests/test-bundle
@@ -146,4 +146,23 @@
 hg -R ../all.hg diff -r tip
 cd ..
 
+echo "====== bundle single branch"
+hg init branchy
+cd branchy
+echo a >a
+hg ci -Ama
+echo b >b
+hg ci -Amb
+echo b1 >b1
+hg ci -Amb1
+hg up 0
+echo c >c
+hg ci -Amc
+echo c1 >c1
+hg ci -Amc1
+hg clone -q .#tip part
+echo "== bundling via incoming"
+hg in -R part --bundle incoming.hg --template "{node}\n" .
+echo "== bundling"
+hg bundle bundle.hg part --debug
 
diff --git a/tests/test-bundle-vs-outgoing b/tests/test-bundle-vs-outgoing
new file mode 100755
--- /dev/null
+++ b/tests/test-bundle-vs-outgoing
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+# this structure seems to tickle a bug in bundle's search for
+# changesets, so first we have to recreate it
+#
+# o  8
+# |
+# | o  7
+# | |
+# | o  6
+# |/|
+# o |  5
+# | |
+# o |  4
+# | |
+# | o  3
+# | |
+# | o  2
+# |/
+# o  1
+# |
+# o  0
+
+mkrev()
+{
+    revno=$1
+    echo "rev $revno"
+    echo "rev $revno" > foo.txt
+    hg -q ci -m"rev $revno"
+}
+
+set -e
+echo "% setup test repo1"
+hg init repo1
+cd repo1
+echo "rev 0" > foo.txt
+hg ci -Am"rev 0"
+mkrev 1
+
+# first branch
+mkrev 2
+mkrev 3
+
+# back to rev 1 to create second branch
+hg up -r1
+mkrev 4
+mkrev 5
+
+# merge first branch to second branch
+hg up -C -r5
+HGMERGE=internal:local hg merge
+echo "merge rev 5, rev 3" > foo.txt
+hg ci -m"merge first branch to second branch"
+
+# one more commit following the merge
+mkrev 7
+
+# back to "second branch" to make another head
+hg up -r5
+mkrev 8
+
+echo "[extensions]" >> $HGRCPATH
+echo "graphlog=" >> $HGRCPATH
+
+echo "% the story so far"
+hg glog --template "{rev}\n"
+
+# check that "hg outgoing" really does the right thing
+echo "% sanity check of outgoing: expect revs 4 5 6 7 8"
+hg clone -r3 . ../repo2
+# this should (and does) report 5 outgoing revisions: 4 5 6 7 8
+hg outgoing --template "{rev}\n" ../repo2
+
+echo "% test bundle (destination repo): expect 5 revisions"
+# this should bundle the same 5 revisions that outgoing reported, but it
+# actually bundles 7
+hg bundle foo.bundle ../repo2
+
+echo "% test bundle (base revision): expect 5 revisions"
+# this should (and does) give exactly the same result as bundle
+# with a destination repo... i.e. it's wrong too
+hg bundle --base 3 foo.bundle
+
+
diff --git a/tests/test-bundle-vs-outgoing.out b/tests/test-bundle-vs-outgoing.out
new file mode 100644
--- /dev/null
+++ b/tests/test-bundle-vs-outgoing.out
@@ -0,0 +1,53 @@
+% setup test repo1
+adding foo.txt
+rev 1
+rev 2
+rev 3
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+rev 4
+rev 5
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
+rev 7
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+rev 8
+% the story so far
+@  8
+|
+| o  7
+| |
+| o  6
+|/|
+o |  5
+| |
+o |  4
+| |
+| o  3
+| |
+| o  2
+|/
+o  1
+|
+o  0
+
+% sanity check of outgoing: expect revs 4 5 6 7 8
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 4 changesets with 4 changes to 1 files
+updating to branch default
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+comparing with ../repo2
+searching for changes
+4
+5
+6
+7
+8
+% test bundle (destination repo): expect 5 revisions
+searching for changes
+5 changesets found
+% test bundle (base revision): expect 5 revisions
+5 changesets found
diff --git a/tests/test-bundle.out b/tests/test-bundle.out
--- a/tests/test-bundle.out
+++ b/tests/test-bundle.out
@@ -326,3 +326,23 @@
 -1
 -2
 -3
+====== bundle single branch
+adding a
+adding b
+adding b1
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+adding c
+created new head
+adding c1
+== bundling via incoming
+comparing with .
+searching for changes
+d2ae7f538514cd87c17547b0de4cea71fe1af9fb
+5ece8e77363e2b5269e27c66828b72da29e4341a
+== bundling
+searching for changes
+common changesets up to c0025332f9ed
+2 changesets found
+list of changesets:
+d2ae7f538514cd87c17547b0de4cea71fe1af9fb
+5ece8e77363e2b5269e27c66828b72da29e4341a
diff --git a/tests/test-notify.out b/tests/test-notify.out
--- a/tests/test-notify.out
+++ b/tests/test-notify.out
@@ -174,7 +174,7 @@
 adding changesets
 adding manifests
 adding file changes
-added 2 changesets with 0 changes to 1 files
+added 2 changesets with 0 changes to 0 files
 Content-Type: text/plain; charset="us-ascii"
 MIME-Version: 1.0
 Content-Transfer-Encoding: 7bit


More information about the Mercurial-devel mailing list