]> git.ipfire.org Git - thirdparty/git.git/commitdiff
pack-objects: introduce `--no-ref-delta`
authorTaylor Blau <ttaylorr@openai.com>
Mon, 13 Jul 2026 01:11:57 +0000 (18:11 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 13 Jul 2026 01:39:02 +0000 (18:39 -0700)
Some consumers of 'pack-objects' may wish to avoid packs which contain
`REF_DELTA` entries. For instance, a 'receive-pack' implementation which
retains the resulting pack without building an index of object IDs may
prefer every delta base to be discoverable from an earlier entry in the
same pack.

Teach 'pack-objects' a new `--no-ref-delta` option to avoid writing
`REF_DELTA` entries, without changing whether `OFS_DELTA` is allowed.

When used without `--delta-base-offset`, no delta representation
remains, so avoid delta search entirely. Otherwise, allow new deltas
whose bases appear earlier in the same pack.

For now, disable delta- and bitmap-reuse under `--no-ref-delta`, since
either may copy an existing `REF_DELTA` entry. This is overly
pessimistic, but simplifies the changes in this commit. The next commit
re-enables reuse in the cases which do not require `REF_DELTA`.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-pack-objects.adoc
builtin/pack-objects.c
t/t5300-pack-object.sh

index 65cd00c152f4955b772b0aacfb84cc8d1b896c15..5e42e4429d0b8eceb2ac182d06ba5e0b6cc2ae08 100644 (file)
@@ -10,7 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git pack-objects' [-q | --progress | --all-progress] [--all-progress-implied]
-                  [--no-reuse-delta] [--delta-base-offset] [--non-empty]
+                  [--no-reuse-delta] [--delta-base-offset] [--no-ref-delta]
+                  [--non-empty]
                   [--local] [--incremental] [--window=<n>] [--depth=<n>]
                   [--revs [--unpacked | --all]] [--keep-pack=<pack-name>]
                   [--cruft] [--cruft-expiration=<time>]
@@ -297,6 +298,11 @@ Note: Porcelain commands such as `git gc` (see linkgit:git-gc[1]),
 in modern Git when they put objects in your repository into pack files.
 So does `git bundle` (see linkgit:git-bundle[1]) when it creates a bundle.
 
+--no-ref-delta::
+       Do not emit deltas which represent their base by their literal
+       object ID. This is independent of `--delta-base-offset`;
+       without that option, no deltas are emitted.
+
 --threads=<n>::
        Specifies the number of threads to spawn when searching for best
        delta matches.  This requires that pack-objects be compiled with
index e3760b34925bbfef004cf1c187da15c7459b66a0..c3574fcb8a7d0ae30b68dcbf50813869cad11b29 100644 (file)
@@ -190,7 +190,8 @@ static inline void oe_set_delta_size(struct packing_data *pack,
 
 static const char *const pack_usage[] = {
        N_("git pack-objects [-q | --progress | --all-progress] [--all-progress-implied]\n"
-          "                 [--no-reuse-delta] [--delta-base-offset] [--non-empty]\n"
+          "                 [--no-reuse-delta] [--delta-base-offset] [--no-ref-delta]\n"
+          "                 [--non-empty]\n"
           "                 [--local] [--incremental] [--window=<n>] [--depth=<n>]\n"
           "                 [--revs [--unpacked | --all]] [--keep-pack=<pack-name>]\n"
           "                 [--cruft] [--cruft-expiration=<time>]\n"
@@ -221,6 +222,7 @@ static int ignore_packed_keep_in_core;
 static int ignore_packed_keep_in_core_open;
 static int ignore_packed_keep_in_core_has_cruft;
 static int allow_ofs_delta;
+static int allow_ref_delta = 1;
 static struct pack_idx_option pack_idx_opts;
 static const char *base_name;
 static int progress = 1;
@@ -3405,6 +3407,9 @@ static int should_attempt_deltas(struct object_entry *entry)
        if (entry->no_try_delta)
                return 0;
 
+       if (entry->preferred_base && !allow_ref_delta)
+               return 0;
+
        if (!entry->preferred_base) {
                if (oe_type(entry) < 0)
                        die(_("unable to get type of object %s"),
@@ -3647,7 +3652,8 @@ static void prepare_pack(int window, int depth)
        if (!pack_to_stdout)
                do_check_packed_object_crc = 1;
 
-       if (!to_pack.nr_objects || !window || !depth)
+       if (!to_pack.nr_objects || !window || !depth ||
+           (!allow_ref_delta && !allow_ofs_delta))
                return;
 
        if (path_walk)
@@ -4662,7 +4668,7 @@ static int pack_options_allow_reuse(void)
               !ignore_packed_keep_on_disk &&
               !ignore_packed_keep_in_core &&
               (!local || !have_non_local_packs) &&
-              !incremental;
+              !incremental && allow_ref_delta;
 }
 
 static int get_object_list_from_bitmap(struct rev_info *revs)
@@ -5111,6 +5117,8 @@ int cmd_pack_objects(int argc,
                         N_("reuse existing objects")),
                OPT_BOOL(0, "delta-base-offset", &allow_ofs_delta,
                         N_("use OFS_DELTA objects")),
+               OPT_BOOL(0, "ref-delta", &allow_ref_delta,
+                        N_("use REF_DELTA objects")),
                OPT_INTEGER(0, "threads", &delta_search_threads,
                            N_("use threads when searching for best delta matches")),
                OPT_BOOL(0, "non-empty", &non_empty,
@@ -5309,7 +5317,7 @@ int cmd_pack_objects(int argc,
        if (unpack_unreachable || keep_unreachable || pack_loose_unreachable)
                use_internal_rev_list = 1;
 
-       if (!reuse_object)
+       if (!reuse_object || !allow_ref_delta)
                reuse_delta = 0;
        if (cfg->pack_compression_level == -1)
                cfg->pack_compression_level = Z_DEFAULT_COMPRESSION;
index 4bee490ff6ff2513c346a296b96f0c645f6b26b1..b9e36044b90ee8b56321b2351ded12152166f1b9 100755 (executable)
@@ -211,6 +211,58 @@ test_expect_success 'pack with OFS_DELTA' '
        test_grep " OFS_DELTA " deltas
 '
 
+test_expect_success 'pack without REF_DELTA' '
+       git pack-objects --no-ref-delta --stdout <obj-list >no-ref.pack &&
+       git index-pack -o no-ref.idx no-ref.pack &&
+
+       test-tool pack-deltas --list-deltas no-ref.idx >deltas &&
+       test_must_be_empty deltas
+'
+
+test_expect_success 'pack without REF_DELTA with OFS_DELTA' '
+       git pack-objects --delta-base-offset --no-ref-delta --stdout \
+               <obj-list >no-ref-ofs.pack &&
+       git index-pack -o no-ref-ofs.idx no-ref-ofs.pack &&
+
+       test-tool pack-deltas --list-deltas no-ref-ofs.idx >deltas &&
+       test_grep " OFS_DELTA " deltas &&
+       test_grep ! " REF_DELTA " deltas
+'
+
+test_expect_success 'pack without REF_DELTA skips excluded delta bases' '
+       test_when_finished "git read-tree $tree" &&
+
+       echo bar >>d &&
+       git update-index --add d &&
+       thin_tree=$(git write-tree) &&
+       thin_commit=$(git commit-tree $thin_tree -p $commit </dev/null) &&
+
+       {
+               echo $thin_commit &&
+               echo ^$commit
+       } >thin-revs &&
+
+       # Each type appears only once in the output, so any delta must
+       # use an excluded base and therefore be a REF_DELTA.
+       git pack-objects --thin --stdout --revs \
+               <thin-revs >thin.pack &&
+       git index-pack --fix-thin --stdin thin-fixed.pack \
+               <thin.pack >/dev/null &&
+
+       test-tool pack-deltas --list-deltas thin-fixed.idx >deltas &&
+       test_grep ! " OFS_DELTA " deltas &&
+       test_grep " REF_DELTA " deltas &&
+
+       git pack-objects --thin --stdout --revs \
+               --delta-base-offset --no-ref-delta \
+               <thin-revs >no-ref-thin.pack &&
+       git index-pack --fix-thin --stdin no-ref-thin-fixed.pack \
+               <no-ref-thin.pack >/dev/null &&
+
+       test-tool pack-deltas --list-deltas no-ref-thin-fixed.idx >deltas &&
+       test_must_be_empty deltas
+'
+
 test_expect_success 'unpack with OFS_DELTA' '
        check_unpack test-3-${packname_3} obj-list
 '