threads / patch / 66486

patchrepack: do not rebuild packs on --dry-run

Subject: [PATCH] repack: do not rebuild packs on --dry-run

## tl;dr

3 messages between Oct 8, 2026 and Oct 8, 2026. Diffs are folded; open one to read it.

replies: 2people: 3as markdown or json

Siddharth Shrimali· Oct 8, 2026, 06:25 UTC · lore

"git repack --drop-filtered --dry-run" is documented to list the objects that would be dropped "without rebuilding any pack or deleting anything", but it does both.

This is a bug in cmd_repack(): after printing the candidates, the --dry-run block falls through into the regular repack code. repack_promisor_objects() writes a new promisor pack, and with -d, existing_packs_remove_redundant() deletes the old packs. The command still exits successfully, so the user is not told that the repository was modified.

The existing guard only skips the implied "delete_redundant = 1", so it does not stop an explicit -d, nor the new pack from being written.

Fix it by returning right after the candidates are listed, and add a test that checks the pack directory is unchanged with and without -d.

Reported-by: Coy Geek <coygeek@gmail.com>
Signed-off-by: Siddharth Shrimali <r.siddharth.shrimali@gmail.com>
---
Bug report:
https://lore.kernel.org/git/CACgTecOm+=vbf50tZNXhcYvRi1ZTsQwbjVoJAbQqs2CmXdJCxg@mail.gmail.com/
 builtin/repack.c                |  8 ++++++++
 t/t7706-repack-drop-filtered.sh | 19 +++++++++++++++++++
 2 files changed, 27 insertions(+)
Show changes to 2 files +27 −0

builtin/repack.c, t/t7706-repack-drop-filtered.sh

diff --git a/builtin/repack.c b/builtin/repack.c
index c4360382c1..c048053912 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -391,6 +391,14 @@ int cmd_repack(int argc,
 			oidset_iter_init(&drop_oids, &iter);
 			while ((oid = oidset_iter_next(&iter)))
 				printf("%s\n", oid_to_hex(oid));
+
+			/*
+			 * add an exit here, so that dry run does not
+			 * go on to rebuild any pack or delete anything, even
+			 * if the user explicitly asked for -d
+			 */
+			ret = 0;
+			goto cleanup;
 		}
 	}
 
diff --git a/t/t7706-repack-drop-filtered.sh b/t/t7706-repack-drop-filtered.sh
index cb36115834..a1c475e4ec 100755
--- a/t/t7706-repack-drop-filtered.sh
+++ b/t/t7706-repack-drop-filtered.sh
@@ -135,6 +135,25 @@ test_expect_success '--dry-run does not remove the filtered objects' '
 	git -C repo cat-file -e "$BIG"
 '
 
+test_expect_success '--dry-run leaves the pack directory untouched' '
+	BIG=$(cat big_oid) &&
+	packdir=repo/.git/objects/pack &&
+
+	for opt in "" -d
+	do
+		ls $packdir >before &&
+
+		git -C repo -c repack.writeBitmaps=false \
+			repack --drop-filtered --filter=blob:limit=1k \
+			--dry-run -a $opt >out &&
+
+		ls $packdir >after &&
+		test_cmp before after &&
+		test_grep "$BIG" out &&
+		git -C repo cat-file -e "$BIG" || return 1
+	done
+'
+
 test_expect_success '--drop-filtered removes the promisor blob locally' '
 	BIG=$(cat big_oid) &&
 	SMALL=$(cat small_oid) &&
-- 
2.56.0
D. Ben Knoble· Oct 8, 2026, 12:56 UTC · re: Siddharth Shrimali · lore

Re: [PATCH] repack: do not rebuild packs on --dry-run

Hi Siddharth,

On Thu, Oct 8, 2026 at 2:25 AM Siddharth Shrimali <r.siddharth.shrimali@gmail.com> wrote:

Show 17 quoted lines
>
> "git repack --drop-filtered --dry-run" is documented to list the
> objects that would be dropped "without rebuilding any pack or
> deleting anything", but it does both.
>
> This is a bug in cmd_repack(): after printing the candidates, the
> --dry-run block falls through into the regular repack code.
> repack_promisor_objects() writes a new promisor pack, and with -d,
> existing_packs_remove_redundant() deletes the old packs. The command
> still exits successfully, so the user is not told that the repository
> was modified.
>
> The existing guard only skips the implied "delete_redundant = 1", so
> it does not stop an explicit -d, nor the new pack from being written.
>
> Fix it by returning right after the candidates are listed, and add a
> test that checks the pack directory is unchanged with and without -d.
Makes sense
Show 18 quoted lines
> Reported-by: Coy Geek <coygeek@gmail.com>
> Signed-off-by: Siddharth Shrimali <r.siddharth.shrimali@gmail.com>
> ---
> Bug report:
> https://lore.kernel.org/git/CACgTecOm+=vbf50tZNXhcYvRi1ZTsQwbjVoJAbQqs2CmXdJCxg@mail.gmail.com/
>
>  builtin/repack.c                |  8 ++++++++
>  t/t7706-repack-drop-filtered.sh | 19 +++++++++++++++++++
>  2 files changed, 27 insertions(+)
>
> diff --git a/builtin/repack.c b/builtin/repack.c
> index c4360382c1..c048053912 100644
> --- a/builtin/repack.c
> +++ b/builtin/repack.c
> @@ -391,6 +391,14 @@ int cmd_repack(int argc,
>                         oidset_iter_init(&drop_oids, &iter);
>                         while ((oid = oidset_iter_next(&iter)))
>                                 printf("%s\n", oid_to_hex(oid));

Just outside the patch context is the "if (dry_run)" conditional, so this is the right place. (In this case, formatting with a larger "-U" value might help.)

Show 6 quoted lines
> +
> +                       /*
> +                        * add an exit here, so that dry run does not
> +                        * go on to rebuild any pack or delete anything, even
> +                        * if the user explicitly asked for -d
> +                        */
2 notes:
1. "add an exit" will stop making sense as soon as the patch becomes a
commit; that is, it only makes sense in the context of proposed
changes. Once those changes are part of the code base, the comment and
code are not adding anything. They simply are. So, if we need a
comment (see 2), it might be best phrased as "exit here so that […]",
keeping some of your original wording. Or we could be more terse:
"skip non-dry-run operations" or something.
2. Do we need such a comment, I wonder? git-blame will point folks
towards this commit :)
> +                       ret = 0;
> +                       goto cleanup;
This matches the pattern elsewhere in this procedure, so that looks good.
Show 35 quoted lines
>                 }
>         }
>
> diff --git a/t/t7706-repack-drop-filtered.sh b/t/t7706-repack-drop-filtered.sh
> index cb36115834..a1c475e4ec 100755
> --- a/t/t7706-repack-drop-filtered.sh
> +++ b/t/t7706-repack-drop-filtered.sh
> @@ -135,6 +135,25 @@ test_expect_success '--dry-run does not remove the filtered objects' '
>         git -C repo cat-file -e "$BIG"
>  '
>
> +test_expect_success '--dry-run leaves the pack directory untouched' '
> +       BIG=$(cat big_oid) &&
> +       packdir=repo/.git/objects/pack &&
> +
> +       for opt in "" -d
> +       do
> +               ls $packdir >before &&
> +
> +               git -C repo -c repack.writeBitmaps=false \
> +                       repack --drop-filtered --filter=blob:limit=1k \
> +                       --dry-run -a $opt >out &&
> +
> +               ls $packdir >after &&
> +               test_cmp before after &&
> +               test_grep "$BIG" out &&
> +               git -C repo cat-file -e "$BIG" || return 1
> +       done
> +'
> +
>  test_expect_success '--drop-filtered removes the promisor blob locally' '
>         BIG=$(cat big_oid) &&
>         SMALL=$(cat small_oid) &&
> --
> 2.56.0

Thanks for adding a test to cover this. It looks reasonable to me, but I didn't study the surrounding tests to think very critically about it.

-- 
D. Ben Knoble
Junio C Hamano· Oct 8, 2026, 14:23 UTC · re: Siddharth Shrimali · lore

Re: [PATCH] repack: do not rebuild packs on --dry-run

Siddharth Shrimali <r.siddharth.shrimali@gmail.com> writes:
Show 10 quoted lines
> "git repack --drop-filtered --dry-run" is documented to list the
> objects that would be dropped "without rebuilding any pack or
> deleting anything", but it does both.
>
> This is a bug in cmd_repack(): after printing the candidates, the
> --dry-run block falls through into the regular repack code.
> repack_promisor_objects() writes a new promisor pack, and with -d,
> existing_packs_remove_redundant() deletes the old packs. The command
> still exits successfully, so the user is not told that the repository
> was modified.

Very interesting finding. I am curious if this was the case from the beginning, or we broke --dry-run unknowingly as a side effect of some unrelated changes. If it is not too much, can you bisect and document where we broke it in the log message?

Show 65 quoted lines
> The existing guard only skips the implied "delete_redundant = 1", so
> it does not stop an explicit -d, nor the new pack from being written.
>
> Fix it by returning right after the candidates are listed, and add a
> test that checks the pack directory is unchanged with and without -d.
>
> Reported-by: Coy Geek <coygeek@gmail.com>
> Signed-off-by: Siddharth Shrimali <r.siddharth.shrimali@gmail.com>
> ---
> Bug report:
> https://lore.kernel.org/git/CACgTecOm+=vbf50tZNXhcYvRi1ZTsQwbjVoJAbQqs2CmXdJCxg@mail.gmail.com/
>
>  builtin/repack.c                |  8 ++++++++
>  t/t7706-repack-drop-filtered.sh | 19 +++++++++++++++++++
>  2 files changed, 27 insertions(+)
>
> diff --git a/builtin/repack.c b/builtin/repack.c
> index c4360382c1..c048053912 100644
> --- a/builtin/repack.c
> +++ b/builtin/repack.c
> @@ -391,6 +391,14 @@ int cmd_repack(int argc,
>  			oidset_iter_init(&drop_oids, &iter);
>  			while ((oid = oidset_iter_next(&iter)))
>  				printf("%s\n", oid_to_hex(oid));
> +
> +			/*
> +			 * add an exit here, so that dry run does not
> +			 * go on to rebuild any pack or delete anything, even
> +			 * if the user explicitly asked for -d
> +			 */
> +			ret = 0;
> +			goto cleanup;
>  		}
>  	}
>  
> diff --git a/t/t7706-repack-drop-filtered.sh b/t/t7706-repack-drop-filtered.sh
> index cb36115834..a1c475e4ec 100755
> --- a/t/t7706-repack-drop-filtered.sh
> +++ b/t/t7706-repack-drop-filtered.sh
> @@ -135,6 +135,25 @@ test_expect_success '--dry-run does not remove the filtered objects' '
>  	git -C repo cat-file -e "$BIG"
>  '
>  
> +test_expect_success '--dry-run leaves the pack directory untouched' '
> +	BIG=$(cat big_oid) &&
> +	packdir=repo/.git/objects/pack &&
> +
> +	for opt in "" -d
> +	do
> +		ls $packdir >before &&
> +
> +		git -C repo -c repack.writeBitmaps=false \
> +			repack --drop-filtered --filter=blob:limit=1k \
> +			--dry-run -a $opt >out &&
> +
> +		ls $packdir >after &&
> +		test_cmp before after &&
> +		test_grep "$BIG" out &&
> +		git -C repo cat-file -e "$BIG" || return 1
> +	done
> +'
> +
>  test_expect_success '--drop-filtered removes the promisor blob locally' '
>  	BIG=$(cat big_oid) &&
>  	SMALL=$(cat small_oid) &&

← back to recent threads