Merge branch 'jk/ref-paranoia'

The ref iteration code used to optionally allow dangling refs to be
shown, which has been tightened up.

* jk/ref-paranoia:
  refs: drop "broken" flag from for_each_fullref_in()
  ref-filter: drop broken-ref code entirely
  ref-filter: stop setting FILTER_REFS_INCLUDE_BROKEN
  repack, prune: drop GIT_REF_PARANOIA settings
  refs: turn on GIT_REF_PARANOIA by default
  refs: omit dangling symrefs when using GIT_REF_PARANOIA
  refs: add DO_FOR_EACH_OMIT_DANGLING_SYMREFS flag
  refs-internal.h: reorganize DO_FOR_EACH_* flag documentation
  refs-internal.h: move DO_FOR_EACH_* flags next to each other
  t5312: be more assertive about command failure
  t5312: test non-destructive repack
  t5312: create bogus ref as necessary
  t5312: drop "verbose" helper
  t5600: provide detached HEAD for corruption failures
  t5516: don't use HEAD ref for invalid ref-deletion tests
  t7900: clean up some more broken refs
This commit is contained in:
Junio C Hamano 2021-10-11 10:21:47 -07:00
commit f6c075ad71
21 changed files with 142 additions and 116 deletions

View File

@ -867,15 +867,16 @@ for full details.
end user, to be recorded in the body of the reflog.
`GIT_REF_PARANOIA`::
If set to `1`, include broken or badly named refs when iterating
over lists of refs. In a normal, non-corrupted repository, this
does nothing. However, enabling it may help git to detect and
abort some operations in the presence of broken refs. Git sets
this variable automatically when performing destructive
operations like linkgit:git-prune[1]. You should not need to set
it yourself unless you want to be paranoid about making sure
an operation has touched every ref (e.g., because you are
cloning a repository to make a backup).
If set to `0`, ignore broken or badly named refs when iterating
over lists of refs. Normally Git will try to include any such
refs, which may cause some operations to fail. This is usually
preferable, as potentially destructive operations (e.g.,
linkgit:git-prune[1]) are better off aborting rather than
ignoring broken refs (and thus considering the history they
point to as not worth saving). The default value is `1` (i.e.,
be paranoid about detecting and aborting all operations). You
should not normally need to set this to `0`, but it may be
useful when trying to salvage data from a corrupted repository.
`GIT_ALLOW_PROTOCOL`::
If set to a colon-separated list of protocols, behave as if

View File

@ -427,7 +427,7 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
memset(&array, 0, sizeof(array));
filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN);
filter_refs(&array, filter, filter->kind);
if (filter->verbose)
maxwidth = calc_maxwidth(&array, strlen(remote_prefix));

View File

@ -77,7 +77,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
filter.name_patterns = argv;
filter.match_as_path = 1;
filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
filter_refs(&array, &filter, FILTER_REFS_ALL);
ref_array_sort(sorting, &array);
if (!maxcount || array.nr < maxcount)

View File

@ -143,7 +143,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
expire = TIME_MAX;
save_commit_buffer = 0;
read_replace_refs = 0;
ref_paranoia = 1;
repo_init_revisions(the_repository, &revs, prefix);
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);

View File

@ -586,15 +586,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
strvec_pushf(&cmd.args,
"--unpack-unreachable=%s",
unpack_unreachable);
strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
} else if (pack_everything & LOOSEN_UNREACHABLE) {
strvec_push(&cmd.args,
"--unpack-unreachable");
} else if (keep_unreachable) {
strvec_push(&cmd.args, "--keep-unreachable");
strvec_push(&cmd.args, "--pack-loose-unreachable");
} else {
strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
}
}
} else if (geometry) {

View File

@ -863,8 +863,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp(arg, "--bisect")) {
for_each_fullref_in("refs/bisect/bad", show_reference, NULL, 0);
for_each_fullref_in("refs/bisect/good", anti_reference, NULL, 0);
for_each_fullref_in("refs/bisect/bad", show_reference, NULL);
for_each_fullref_in("refs/bisect/good", anti_reference, NULL);
continue;
}
if (opt_with_value(arg, "--branches", &arg)) {

View File

@ -994,14 +994,6 @@ extern const char *core_fsmonitor;
extern int core_apply_sparse_checkout;
extern int core_sparse_checkout_cone;
/*
* Include broken refs in all ref iterations, which will
* generally choke dangerous operations rather than letting
* them silently proceed without taking the broken ref into
* account.
*/
extern int ref_paranoia;
/*
* Returns the boolean value of $GIT_OPTIONAL_LOCKS (or the default value).
*/

View File

@ -31,7 +31,6 @@ int prefer_symlink_refs;
int is_bare_repository_cfg = -1; /* unspecified */
int warn_ambiguous_refs = 1;
int warn_on_object_refname_ambiguity = 1;
int ref_paranoia = -1;
int repository_format_precious_objects;
int repository_format_worktree_config;
const char *git_commit_encoding;

View File

@ -189,7 +189,7 @@ int ls_refs(struct repository *r, struct packet_reader *request)
if (!data.prefixes.nr)
strvec_push(&data.prefixes, "");
for_each_fullref_in_prefixes(get_git_namespace(), data.prefixes.v,
send_ref, &data, 0);
send_ref, &data);
packet_fflush(stdout);
strvec_clear(&data.prefixes);
strbuf_release(&data.buf);

View File

@ -2100,8 +2100,7 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname)
*/
static int for_each_fullref_in_pattern(struct ref_filter *filter,
each_ref_fn cb,
void *cb_data,
int broken)
void *cb_data)
{
if (!filter->match_as_path) {
/*
@ -2109,7 +2108,7 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
* prefixes like "refs/heads/" etc. are stripped off,
* so we have to look at everything:
*/
return for_each_fullref_in("", cb, cb_data, broken);
return for_each_fullref_in("", cb, cb_data);
}
if (filter->ignore_case) {
@ -2118,16 +2117,16 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
* so just return everything and let the caller
* sort it out.
*/
return for_each_fullref_in("", cb, cb_data, broken);
return for_each_fullref_in("", cb, cb_data);
}
if (!filter->name_patterns[0]) {
/* no patterns; we have to look at everything */
return for_each_fullref_in("", cb, cb_data, broken);
return for_each_fullref_in("", cb, cb_data);
}
return for_each_fullref_in_prefixes(NULL, filter->name_patterns,
cb, cb_data, broken);
cb, cb_data);
}
/*
@ -2405,13 +2404,10 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
{
struct ref_filter_cbdata ref_cbdata;
int ret = 0;
unsigned int broken = 0;
ref_cbdata.array = array;
ref_cbdata.filter = filter;
if (type & FILTER_REFS_INCLUDE_BROKEN)
broken = 1;
filter->kind = type & FILTER_REFS_KIND_MASK;
init_contains_cache(&ref_cbdata.contains_cache);
@ -2428,13 +2424,13 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
* of filter_ref_kind().
*/
if (filter->kind == FILTER_REFS_BRANCHES)
ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata, broken);
ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata);
else if (filter->kind == FILTER_REFS_REMOTES)
ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata, broken);
ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata);
else if (filter->kind == FILTER_REFS_TAGS)
ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata, broken);
ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata);
else if (filter->kind & FILTER_REFS_ALL)
ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata, broken);
ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata);
if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))
head_ref(ref_filter_handler, &ref_cbdata);
}

View File

@ -13,7 +13,6 @@
#define QUOTE_PYTHON 4
#define QUOTE_TCL 8
#define FILTER_REFS_INCLUDE_BROKEN 0x0001
#define FILTER_REFS_TAGS 0x0002
#define FILTER_REFS_BRANCHES 0x0004
#define FILTER_REFS_REMOTES 0x0008

42
refs.c
View File

@ -1409,14 +1409,21 @@ int head_ref(each_ref_fn fn, void *cb_data)
struct ref_iterator *refs_ref_iterator_begin(
struct ref_store *refs,
const char *prefix, int trim, int flags)
const char *prefix, int trim,
enum do_for_each_ref_flags flags)
{
struct ref_iterator *iter;
if (ref_paranoia < 0)
ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 0);
if (ref_paranoia)
flags |= DO_FOR_EACH_INCLUDE_BROKEN;
if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
static int ref_paranoia = -1;
if (ref_paranoia < 0)
ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 1);
if (ref_paranoia) {
flags |= DO_FOR_EACH_INCLUDE_BROKEN;
flags |= DO_FOR_EACH_OMIT_DANGLING_SYMREFS;
}
}
iter = refs->be->iterator_begin(refs, prefix, flags);
@ -1475,7 +1482,8 @@ static int do_for_each_ref_helper(struct repository *r,
}
static int do_for_each_ref(struct ref_store *refs, const char *prefix,
each_ref_fn fn, int trim, int flags, void *cb_data)
each_ref_fn fn, int trim,
enum do_for_each_ref_flags flags, void *cb_data)
{
struct ref_iterator *iter;
struct do_for_each_ref_help hp = { fn, cb_data };
@ -1510,25 +1518,16 @@ int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
return refs_for_each_ref_in(get_main_ref_store(the_repository), prefix, fn, cb_data);
}
int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data)
{
unsigned int flag = 0;
if (broken)
flag = DO_FOR_EACH_INCLUDE_BROKEN;
return do_for_each_ref(get_main_ref_store(the_repository),
prefix, fn, 0, flag, cb_data);
prefix, fn, 0, 0, cb_data);
}
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
each_ref_fn fn, void *cb_data,
unsigned int broken)
each_ref_fn fn, void *cb_data)
{
unsigned int flag = 0;
if (broken)
flag = DO_FOR_EACH_INCLUDE_BROKEN;
return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data);
return do_for_each_ref(refs, prefix, fn, 0, 0, cb_data);
}
int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data)
@ -1620,8 +1619,7 @@ static void find_longest_prefixes(struct string_list *out,
int for_each_fullref_in_prefixes(const char *namespace,
const char **patterns,
each_ref_fn fn, void *cb_data,
unsigned int broken)
each_ref_fn fn, void *cb_data)
{
struct string_list prefixes = STRING_LIST_INIT_DUP;
struct string_list_item *prefix;
@ -1636,7 +1634,7 @@ int for_each_fullref_in_prefixes(const char *namespace,
for_each_string_list_item(prefix, &prefixes) {
strbuf_addstr(&buf, prefix->string);
ret = for_each_fullref_in(buf.buf, fn, cb_data, broken);
ret = for_each_fullref_in(buf.buf, fn, cb_data);
if (ret)
break;
strbuf_setlen(&buf, namespace_len);

9
refs.h
View File

@ -342,10 +342,8 @@ int for_each_ref(each_ref_fn fn, void *cb_data);
int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
each_ref_fn fn, void *cb_data,
unsigned int broken);
int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
unsigned int broken);
each_ref_fn fn, void *cb_data);
int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data);
/**
* iterate all refs in "patterns" by partitioning patterns into disjoint sets
@ -354,8 +352,7 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
* callers should be prepared to ignore references that they did not ask for.
*/
int for_each_fullref_in_prefixes(const char *namespace, const char **patterns,
each_ref_fn fn, void *cb_data,
unsigned int broken);
each_ref_fn fn, void *cb_data);
/**
* iterate refs from the respective area.
*/

View File

@ -744,6 +744,11 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)
ref_type(iter->iter0->refname) != REF_TYPE_PER_WORKTREE)
continue;
if ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) &&
(iter->iter0->flags & REF_ISSYMREF) &&
(iter->iter0->flags & REF_ISBROKEN))
continue;
if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
!ref_resolves_to_object(iter->iter0->refname,
iter->iter0->oid,

View File

@ -245,8 +245,36 @@ int refs_rename_ref_available(struct ref_store *refs,
/* We allow "recursive" symbolic refs. Only within reason, though */
#define SYMREF_MAXDEPTH 5
/* Include broken references in a do_for_each_ref*() iteration: */
#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
/*
* These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),
* which feeds it).
*/
enum do_for_each_ref_flags {
/*
* Include broken references in a do_for_each_ref*() iteration, which
* would normally be omitted. This includes both refs that point to
* missing objects (a true repository corruption), ones with illegal
* names (which we prefer not to expose to callers), as well as
* dangling symbolic refs (i.e., those that point to a non-existent
* ref; this is not a corruption, but as they have no valid oid, we
* omit them from normal iteration results).
*/
DO_FOR_EACH_INCLUDE_BROKEN = (1 << 0),
/*
* Only include per-worktree refs in a do_for_each_ref*() iteration.
* Normally this will be used with a files ref_store, since that's
* where all reference backends will presumably store their
* per-worktree refs.
*/
DO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),
/*
* Omit dangling symrefs from output; this only has an effect with
* INCLUDE_BROKEN, since they are otherwise not included at all.
*/
DO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),
};
/*
* Reference iterators
@ -349,16 +377,12 @@ int is_empty_ref_iterator(struct ref_iterator *ref_iterator);
* Return an iterator that goes over each reference in `refs` for
* which the refname begins with prefix. If trim is non-zero, then
* trim that many characters off the beginning of each refname.
* The output is ordered by refname. The following flags are supported:
*
* DO_FOR_EACH_INCLUDE_BROKEN: include broken references in
* the iteration.
*
* DO_FOR_EACH_PER_WORKTREE_ONLY: only produce REF_TYPE_PER_WORKTREE refs.
* The output is ordered by refname.
*/
struct ref_iterator *refs_ref_iterator_begin(
struct ref_store *refs,
const char *prefix, int trim, int flags);
const char *prefix, int trim,
enum do_for_each_ref_flags flags);
/*
* A callback function used to instruct merge_ref_iterator how to
@ -446,10 +470,8 @@ void base_ref_iterator_free(struct ref_iterator *iter);
/*
* backend-specific implementation of ref_iterator_advance. For symrefs, the
* function should set REF_ISSYMREF, and it should also dereference the symref
* to provide the OID referent. If DO_FOR_EACH_INCLUDE_BROKEN is set, symrefs
* with non-existent referents and refs pointing to non-existent object names
* should also be returned. If DO_FOR_EACH_PER_WORKTREE_ONLY, only
* REF_TYPE_PER_WORKTREE refs should be returned.
* to provide the OID referent. It should respect do_for_each_ref_flags
* that were passed to refs_ref_iterator_begin().
*/
typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator);
@ -498,14 +520,6 @@ int do_for_each_repo_ref_iterator(struct repository *r,
struct ref_iterator *iter,
each_repo_ref_fn fn, void *cb_data);
/*
* Only include per-worktree refs in a do_for_each_ref*() iteration.
* Normally this will be used with a files ref_store, since that's
* where all reference backends will presumably store their
* per-worktree refs.
*/
#define DO_FOR_EACH_PER_WORKTREE_ONLY 0x02
struct ref_store;
/* refs backends */

View File

@ -2548,7 +2548,7 @@ static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn,
struct strbuf bisect_refs = STRBUF_INIT;
int status;
strbuf_addf(&bisect_refs, "refs/bisect/%s", term);
status = refs_for_each_fullref_in(refs, bisect_refs.buf, fn, cb_data, 0);
status = refs_for_each_fullref_in(refs, bisect_refs.buf, fn, cb_data);
strbuf_release(&bisect_refs);
return status;
}

View File

@ -170,7 +170,7 @@ test_expect_success 'for-each-ref emits warnings for broken names' '
! grep -e "badname" output &&
! grep -e "broken\.\.\.symref" output &&
test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.ref" error &&
test_i18ngrep "ignoring broken ref refs/heads/badname" error &&
test_i18ngrep ! "ignoring broken ref refs/heads/badname" error &&
test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.symref" error
'

View File

@ -7,6 +7,9 @@ if we see, for example, a ref with a bogus name, it is OK either to
bail out or to proceed using it as a reachable tip, but it is _not_
OK to proceed as if it did not exist. Otherwise we might silently
delete objects that cannot be recovered.
Note that we do assert command failure in these cases, because that is
what currently happens. If that changes, these tests should be revisited.
'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
@ -18,39 +21,58 @@ test_expect_success 'disable reflogs' '
git reflog expire --expire=all --all
'
create_bogus_ref () {
test_when_finished 'rm -f .git/refs/heads/bogus..name' &&
echo $bogus >.git/refs/heads/bogus..name
}
test_expect_success 'create history reachable only from a bogus-named ref' '
test_tick && git commit --allow-empty -m main &&
base=$(git rev-parse HEAD) &&
test_tick && git commit --allow-empty -m bogus &&
bogus=$(git rev-parse HEAD) &&
git cat-file commit $bogus >saved &&
echo $bogus >.git/refs/heads/bogus..name &&
git reset --hard HEAD^
'
test_expect_success 'pruning does not drop bogus object' '
test_when_finished "git hash-object -w -t commit saved" &&
test_might_fail git prune --expire=now &&
verbose git cat-file -e $bogus
create_bogus_ref &&
test_must_fail git prune --expire=now &&
git cat-file -e $bogus
'
test_expect_success 'put bogus object into pack' '
git tag reachable $bogus &&
git repack -ad &&
git tag -d reachable &&
verbose git cat-file -e $bogus
git cat-file -e $bogus
'
test_expect_success 'non-destructive repack bails on bogus ref' '
create_bogus_ref &&
test_must_fail git repack -adk
'
test_expect_success 'GIT_REF_PARANOIA=0 overrides safety' '
create_bogus_ref &&
GIT_REF_PARANOIA=0 git repack -adk
'
test_expect_success 'destructive repack keeps packed object' '
test_might_fail git repack -Ad --unpack-unreachable=now &&
verbose git cat-file -e $bogus &&
test_might_fail git repack -ad &&
verbose git cat-file -e $bogus
create_bogus_ref &&
test_must_fail git repack -Ad --unpack-unreachable=now &&
git cat-file -e $bogus &&
test_must_fail git repack -ad &&
git cat-file -e $bogus
'
# subsequent tests will have different corruptions
test_expect_success 'clean up bogus ref' '
rm .git/refs/heads/bogus..name
test_expect_success 'destructive repack not confused by dangling symref' '
test_when_finished "git symbolic-ref -d refs/heads/dangling" &&
git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
git repack -ad &&
test_must_fail git cat-file -e $bogus
'
# We create two new objects here, "one" and "two". Our
@ -77,8 +99,8 @@ test_expect_success 'create history with missing tip commit' '
test_expect_success 'pruning with a corrupted tip does not drop history' '
test_when_finished "git hash-object -w -t commit saved" &&
test_might_fail git prune --expire=now &&
verbose git cat-file -e $recoverable
test_must_fail git prune --expire=now &&
git cat-file -e $recoverable
'
test_expect_success 'pack-refs does not silently delete broken loose ref' '

View File

@ -662,10 +662,10 @@ test_expect_success 'push does not update local refs on failure' '
test_expect_success 'allow deleting an invalid remote ref' '
mk_test testrepo heads/main &&
mk_test testrepo heads/branch &&
rm -f testrepo/.git/objects/??/* &&
git push testrepo :refs/heads/main &&
(cd testrepo && test_must_fail git rev-parse --verify refs/heads/main)
git push testrepo :refs/heads/branch &&
(cd testrepo && test_must_fail git rev-parse --verify refs/heads/branch)
'
@ -706,25 +706,26 @@ test_expect_success 'pushing valid refs triggers post-receive and post-update ho
'
test_expect_success 'deleting dangling ref triggers hooks with correct args' '
mk_test_with_hooks testrepo heads/main &&
mk_test_with_hooks testrepo heads/branch &&
orig=$(git -C testrepo rev-parse refs/heads/branch) &&
rm -f testrepo/.git/objects/??/* &&
git push testrepo :refs/heads/main &&
git push testrepo :refs/heads/branch &&
(
cd testrepo/.git &&
cat >pre-receive.expect <<-EOF &&
$ZERO_OID $ZERO_OID refs/heads/main
$orig $ZERO_OID refs/heads/branch
EOF
cat >update.expect <<-EOF &&
refs/heads/main $ZERO_OID $ZERO_OID
refs/heads/branch $orig $ZERO_OID
EOF
cat >post-receive.expect <<-EOF &&
$ZERO_OID $ZERO_OID refs/heads/main
$orig $ZERO_OID refs/heads/branch
EOF
cat >post-update.expect <<-EOF &&
refs/heads/main
refs/heads/branch
EOF
test_cmp pre-receive.expect pre-receive.actual &&

View File

@ -35,7 +35,9 @@ test_expect_success 'create a repo to clone' '
'
test_expect_success 'create objects in repo for later corruption' '
test_commit -C foo file
test_commit -C foo file &&
git -C foo checkout --detach &&
test_commit -C foo detached
'
# source repository given to git clone should be relative to the

View File

@ -276,7 +276,7 @@ test_expect_success 'incremental-repack task' '
# Delete refs that have not been repacked in these packs.
git for-each-ref --format="delete %(refname)" \
refs/prefetch refs/tags >refs &&
refs/prefetch refs/tags refs/remotes >refs &&
git update-ref --stdin <refs &&
# Replace the object directory with this pack layout.
@ -285,6 +285,10 @@ test_expect_success 'incremental-repack task' '
ls $packDir/*.pack >packs-before &&
test_line_count = 3 packs-before &&
# make sure we do not have any broken refs that were
# missed in the deletion above
git for-each-ref &&
# the job repacks the two into a new pack, but does not
# delete the old ones.
git maintenance run --task=incremental-repack &&