builtin/repack: add guards for --drop-filtered

--drop-filtered removes local promisor blobs. That is only safe when the
repository is not mid-operation and when the blobs are not actively in
use, so add two guards, both skipped for bare repositories which have
neither a worktree nor an index.

First, refuse to run while a merge, rebase, am, cherry-pick, revert, or
bisect is in progress. During these operations the working tree and
index are in an intermediate state, and rewriting packs and deleting
objects underneath a half-finished operation is unsafe.

Second, refuse to drop a blob that the current index references. Such a
blob is needed by the working tree, so dropping it would only cause the
next command that touches the worktree to lazy-fetch it straight back,
reclaiming nothing. The offending path is reported so the user can see
why the drop was refused.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Siddharth Asthana <siddharthasthana31@gmail.com>
Signed-off-by: Siddharth Shrimali <r.siddharth.shrimali@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
main
Siddharth Shrimali 2026-08-14 01:38:30 +05:30 committed by Junio C Hamano
parent 0c4142a25b
commit c6fed8b7a6
3 changed files with 96 additions and 0 deletions

View File

@ -204,6 +204,15 @@ and with bitmap writing (`-b`/`--write-bitmap-index`), since filtering
breaks the single-pack closure that bitmaps require. A bitmap setting
coming from configuration is silently disabled for the duration of the
command.
+
As a convenience, since dropped objects remain recoverable by lazy fetch,
`--drop-filtered` refuses to run while another operation
(merge, rebase, am, cherry-pick, revert, or bisect) is in progress, to
avoid a surprising network fetch mid-operation, and refuses to drop any
blob that the current index references, since such a blob would only be
lazily re-fetched by the next command that inspects the working tree.
These checks are skipped in bare repositories, which have neither a
working tree nor an index.

--dry-run::
Only meaningful with `--drop-filtered`. List the objects that

View File

@ -17,6 +17,8 @@
#include "list-objects-filter-options.h"
#include "oidset.h"
#include "hex.h"
#include "wt-status.h"
#include "read-cache-ll.h"

#define ALL_INTO_ONE 1
#define LOOSEN_UNREACHABLE 2
@ -317,6 +319,33 @@ int cmd_repack(int argc,
if (!repo_has_promisor_remote(repo))
die(_("--drop-filtered requires a promisor remote"));

/*
* Refuse to run while another operation is in progress. A
* dropped object would just be lazily re-fetched when the
* operation resumes, but triggering a network fetch in the
* middle of a half-finished
* merge/rebase/cherry-pick/revert/bisect is a poor
* experience, so this is a UX convenience rather than a
* safety measure. Bare repositories have no such state, so
* the check is skipped there.
*/
if (!is_bare_repository(repo)) {
struct wt_status_state state = { 0 };

wt_status_get_state(repo, &state, 0);
if (state.merge_in_progress || state.revert_in_progress ||
state.rebase_in_progress || state.bisect_in_progress ||
state.cherry_pick_in_progress || state.am_in_progress ||
state.rebase_interactive_in_progress) {
wt_status_state_free_buffers(&state);
die(_("--drop-filtered cannot be used while "
"another operation (merge, rebase, am, "
"cherry-pick, revert, or bisect) is in "
"progress"));
}
wt_status_state_free_buffers(&state);
}

write_bitmaps = 0;

/*
@ -332,6 +361,29 @@ int cmd_repack(int argc,
if (ret)
goto cleanup;

/*
* Refuse to drop blobs that the current index references.
* Such a blob would only be lazily re-fetched by the next
* command that touches the worktree, so dropping it reclaims
* nothing. This guard just avoids that churn. Bare
* repositories have no index, so the check is skipped there.
*/
if (!is_bare_repository(repo) && oidset_size(&drop_oids)) {
struct index_state *istate = repo->index;
unsigned int i;

if (repo_read_index(repo) < 0)
die(_("could not read the index"));

for (i = 0; i < istate->cache_nr; i++) {
const struct cache_entry *ce = istate->cache[i];

if (oidset_contains(&drop_oids, &ce->oid))
die(_("cannot drop '%s' (%s): it is referenced by the current index"),
ce->name, oid_to_hex(&ce->oid));
}
}

if (dry_run) {
struct oidset_iter iter;
const struct object_id *oid;

View File

@ -147,4 +147,39 @@ test_expect_success '--drop-filtered removes the promisor blob locally' '
test_grep "$SMALL" present
'

test_expect_success '--drop-filtered refuses when a merge is in progress' '
test_when_finished "git -C repo merge --abort || :" &&

# Create a conflicting merge so wt_status reports it.
git -C repo checkout -B mergebase base &&
echo one >repo/conflict.txt &&
git -C repo add conflict.txt &&
git -C repo commit -m one &&

git -C repo checkout -B mergeother base &&
echo two >repo/conflict.txt &&
git -C repo add conflict.txt &&
git -C repo commit -m two &&

test_must_fail git -C repo merge mergebase &&

test_must_fail git -C repo -c repack.writeBitmaps=false \
repack --drop-filtered --filter=blob:limit=1k --dry-run -a 2>err &&
test_grep "in progress" err
'

test_expect_success '--drop-filtered refuses to drop an index-referenced blob' '
# Create a large blob, add it to the index and make it a promisor object
# so the index references it and enumeration picks it up.
test-tool genrandom idx 4096 >repo/tracked-big.bin &&
git -C repo add tracked-big.bin &&
OID=$(git -C repo rev-parse :tracked-big.bin) &&
printf "%s\n" "$OID" | pack_as_from_promisor >/dev/null &&
delete_object repo "$OID" &&

test_must_fail git -C repo -c repack.writeBitmaps=false \
repack --drop-filtered --filter=blob:limit=1k --dry-run -a 2>err &&
test_grep "referenced by the current index" err
'

test_done