t / t5316-pack-delta-depth.shon commit notes: avoid potential use-after-free during insertion (60fe477)
   1#!/bin/sh
   2
   3test_description='pack-objects breaks long cross-pack delta chains'
   4. ./test-lib.sh
   5
   6# This mirrors a repeated push setup:
   7#
   8# 1. A client repeatedly modifies some files, makes a
   9#      commit, and pushes the result. It does this N times
  10#      before we get around to repacking.
  11#
  12# 2. Each push generates a thin pack with the new version of
  13#    various objects. Let's consider some file in the root tree
  14#    which is updated in each commit.
  15#
  16#    When generating push number X, we feed commit X-1 (and
  17#    thus blob X-1) as a preferred base. The resulting pack has
  18#    blob X as a thin delta against blob X-1.
  19#
  20#    On the receiving end, "index-pack --fix-thin" will
  21#    complete the pack with a base copy of blob X-1.
  22#
  23# 3. In older versions of git, if we used the delta from
  24#    pack X, then we'd always find blob X-1 as a base in the
  25#    same pack (and generate a fresh delta).
  26#
  27#    But with the pack mru, we jump from delta to delta
  28#    following the traversal order:
  29#
  30#      a. We grab blob X from pack X as a delta, putting it at
  31#         the tip of our mru list.
  32#
  33#      b. Eventually we move onto commit X-1. We need other
  34#         objects which are only in pack X-1 (in the test code
  35#         below, it's the containing tree). That puts pack X-1
  36#         at the tip of our mru list.
  37#
  38#      c. Eventually we look for blob X-1, and we find the
  39#         version in pack X-1 (because it's the mru tip).
  40#
  41# Now we have blob X as a delta against X-1, which is a delta
  42# against X-2, and so forth.
  43#
  44# In the real world, these small pushes would get exploded by
  45# unpack-objects rather than "index-pack --fix-thin", but the
  46# same principle applies to larger pushes (they only need one
  47# repeatedly-modified file to generate the delta chain).
  48
  49test_expect_success 'create series of packs' '
  50        test-tool genrandom foo 4096 >content &&
  51        prev= &&
  52        for i in $(test_seq 1 10)
  53        do
  54                cat content >file &&
  55                echo $i >>file &&
  56                git add file &&
  57                git commit -m $i &&
  58                cur=$(git rev-parse HEAD^{tree}) &&
  59                {
  60                        test -n "$prev" && echo "-$prev"
  61                        echo $cur
  62                        echo "$(git rev-parse :file) file"
  63                } | git pack-objects --stdout >tmp &&
  64                git index-pack --stdin --fix-thin <tmp || return 1
  65                prev=$cur
  66        done
  67'
  68
  69max_chain() {
  70        git index-pack --verify-stat-only "$1" >output &&
  71        perl -lne '
  72          /chain length = (\d+)/ and $len = $1;
  73          END { print $len }
  74        ' output
  75}
  76
  77# Note that this whole setup is pretty reliant on the current
  78# packing heuristics. We double-check that our test case
  79# actually produces a long chain. If it doesn't, it should be
  80# adjusted (or scrapped if the heuristics have become too unreliable)
  81test_expect_success 'packing produces a long delta' '
  82        # Use --window=0 to make sure we are seeing reused deltas,
  83        # not computing a new long chain.
  84        pack=$(git pack-objects --all --window=0 </dev/null pack) &&
  85        echo 9 >expect &&
  86        max_chain pack-$pack.pack >actual &&
  87        test_i18ncmp expect actual
  88'
  89
  90test_expect_success '--depth limits depth' '
  91        pack=$(git pack-objects --all --depth=5 </dev/null pack) &&
  92        echo 5 >expect &&
  93        max_chain pack-$pack.pack >actual &&
  94        test_i18ncmp expect actual
  95'
  96
  97test_done