Merge branch 'sa/rev-list-missing-only'

The git rev-list command has been augmented with a '--missing-only'
option that filters the output to only show missing objects,
stripping the leading '?' character and suppressing present objects,
which is useful when used in combination with '--missing=print' or
'--missing=print-info'.

* sa/rev-list-missing-only:
  rev-list: add --missing-only option to filter output
main
Junio C Hamano 2026-09-14 14:02:53 -07:00
commit 6c51b4c9ed
3 changed files with 99 additions and 5 deletions

View File

@ -1083,6 +1083,19 @@ If some tips passed to the traversal are missing, they will be
considered as missing too, and the traversal will ignore them. In case
we cannot get their Object ID though, an error will be raised.

`--missing-only`::
When used together with `--missing=print` or `--missing=print-info`,
suppress all output for present objects and print only the missing
ones. The selected `--missing=` format is preserved (so
`--missing=print-info` still emits `path=` / `type=` fields), but the
leading ``?'' prefix used by the non-`-z` forms is omitted. This is
useful for scripting, as a simpler and faster alternative to
post-processing the output of `--missing=print`.
+
This option is incompatible with `--count` and `--disk-usage`.
It is an error to use `--missing-only` without `--missing=print` or
`--missing=print-info`.

`--exclude-promisor-objects`::
(For internal use only.) Prefilter object traversal at
promisor boundary. This is used with partial clone. This is

View File

@ -111,6 +111,13 @@ enum missing_action {
MA_ALLOW_PROMISOR, /* silently allow all missing PROMISOR objects */
};
static enum missing_action arg_missing_action;
static int arg_missing_only;

static inline int should_collect_missing(void)
{
return arg_missing_action == MA_PRINT ||
arg_missing_action == MA_PRINT_INFO;
}

/* display only the oid of each object encountered */
static int arg_show_object_names = 1;
@ -156,7 +163,14 @@ static void print_missing_object(struct missing_objects_map_entry *entry,
{
struct strbuf sb = STRBUF_INIT;

if (line_term)
/*
* --missing-only filters present objects out of the walk output.
* It still uses the selected --missing= format for missing ones,
* except the human "?" prefix is omitted (script-friendly OIDs).
*/
if (arg_missing_only && line_term)
printf("%s", oid_to_hex(&entry->entry.oid));
else if (line_term)
printf("?%s", oid_to_hex(&entry->entry.oid));
else
printf("%s%cmissing=yes", oid_to_hex(&entry->entry.oid),
@ -246,6 +260,11 @@ static void show_commit(struct commit *commit, void *data)
return;
}

if (arg_missing_only) {
finish_commit(commit);
return;
}

if (show_disk_usage)
total_disk_usage += get_object_disk_usage(&commit->object);

@ -384,6 +403,8 @@ static void show_object(struct object *obj, const char *name, void *cb_data)
if (finish_object(obj, name, cb_data))
return;
display_progress(progress, ++progress_counter);
if (arg_missing_only)
return;
if (show_disk_usage)
total_disk_usage += get_object_disk_usage(obj);
if (info->flags & REV_LIST_QUIET)
@ -750,12 +771,17 @@ int cmd_rev_list(int argc,
revs.exclude_promisor_objects = 1;
} else if (skip_prefix(arg, "--missing=", &arg)) {
parse_missing_action_value(repo, arg);
} else if (!strcmp(arg, "--missing-only")) {
arg_missing_only = 1;
} else if (!strcmp(arg, "-z")) {
line_term = '\0';
info_term = '\0';
}
}

if (arg_missing_only && !should_collect_missing())
die(_("--missing-only requires --missing=print or --missing=print-info"));

die_for_incompatible_opt2(revs.exclude_promisor_objects,
"--exclude_promisor_objects",
arg_missing_action, "--missing");
@ -865,6 +891,9 @@ int cmd_rev_list(int argc,
continue;
}

if (!strcmp(arg, "--missing-only"))
continue;

usage(rev_list_usage);

}
@ -911,6 +940,11 @@ int cmd_rev_list(int argc,
(revs.left_right || revs.cherry_mark))
die(_("marked counting and '%s' cannot be used together"), "--objects");

die_for_incompatible_opt2(arg_missing_only, "--missing-only",
revs.count, "--count");
die_for_incompatible_opt2(arg_missing_only, "--missing-only",
show_disk_usage, "--disk-usage");

save_commit_buffer = (revs.verbose_header ||
revs.grep_filter.pattern_list ||
revs.grep_filter.header_list);
@ -968,8 +1002,7 @@ int cmd_rev_list(int argc,

if (arg_print_omitted)
oidset_init(&omitted_objects, DEFAULT_OIDSET_SIZE);
if (arg_missing_action == MA_PRINT ||
arg_missing_action == MA_PRINT_INFO) {
if (should_collect_missing()) {
struct oidset_iter iter;
struct object_id *oid;

@ -995,8 +1028,7 @@ int cmd_rev_list(int argc,
printf("~%s\n", oid_to_hex(oid));
oidset_clear(&omitted_objects);
}
if (arg_missing_action == MA_PRINT ||
arg_missing_action == MA_PRINT_INFO) {
if (should_collect_missing()) {
struct missing_objects_map_entry *entry;
struct oidmap_iter iter;


View File

@ -198,6 +198,55 @@ do
'
done

for obj in "HEAD~1" "HEAD~1^{tree}" "HEAD:1.t"
do
test_expect_success "rev-list --missing-only with missing $obj" '
oid="$(git rev-parse $obj)" &&
path=".git/objects/$(test_oid_to_path $oid)" &&

mv "$path" "$path.hidden" &&
test_when_finished "mv $path.hidden $path" &&

git rev-list --missing=print --missing-only --objects \
--no-object-names HEAD >actual &&

echo $oid >expect &&
test_cmp expect actual
'
done

test_expect_success "--missing-only requires --missing=print or --missing=print-info" '
test_must_fail git rev-list --missing-only --objects HEAD 2>err &&
test_grep "requires --missing=print" err
'

test_expect_success "--missing-only is incompatible with --count" '
test_must_fail git rev-list --missing=print --missing-only \
--count --objects HEAD 2>err &&
test_grep "cannot be used together" err
'

test_expect_success "--missing-only is incompatible with --disk-usage" '
test_must_fail git rev-list --missing=print --missing-only \
--disk-usage --objects HEAD 2>err &&
test_grep "cannot be used together" err
'

test_expect_success "--missing-only works with --missing=print-info" '
oid="$(git rev-parse HEAD:1.t)" &&
path=".git/objects/$(test_oid_to_path $oid)" &&

mv "$path" "$path.hidden" &&
test_when_finished "mv $path.hidden $path" &&

git rev-list --missing=print-info --missing-only --objects \
--no-object-names HEAD >actual &&

# Filter keeps print-info fields; only the "?" prefix is dropped.
echo "$oid path=1.t type=blob" >expect &&
test_cmp expect actual
'

test_expect_success "-z nul-delimited --missing" '
test_when_finished rm -rf repo &&