Merge branch 'jt/rev-list-missing-print-info'

"git rev-list --missing=" learned to accept "print-info" that gives
known details expected of the missing objects, like path and type.

* jt/rev-list-missing-print-info:
  rev-list: extend print-info to print missing object type
  rev-list: add print-info action to print missing object path
maint
Junio C Hamano 2025-02-18 15:30:32 -08:00
commit 7722b997c6
3 changed files with 161 additions and 17 deletions

View File

@ -1024,6 +1024,25 @@ Unexpected missing objects will raise an error.
The form '--missing=print' is like 'allow-any', but will also print a
list of the missing objects. Object IDs are prefixed with a ``?'' character.
+
The form '--missing=print-info' is like 'print', but will also print additional
information about the missing object inferred from its containing object. The
information is all printed on the same line with the missing object ID in the
form: `?<oid> [<token>=<value>]...`. The `<token>=<value>` pairs containing
additional information are separated from each other by a SP. The value is
encoded in a token specific fashion, but SP or LF contained in value are always
expected to be represented in such a way that the resulting encoded value does
not have either of these two problematic bytes. Each `<token>=<value>` may be
one of the following:
+
--
* The `path=<path>` shows the path of the missing object inferred from a
containing object. A path containing SP or special characters is enclosed in
double-quotes in the C style as needed.
+
* The `type=<type>` shows the type of the missing object inferred from a
containing object.
--
+
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.

View File

@ -22,7 +22,10 @@
#include "progress.h"
#include "reflog-walk.h"
#include "oidset.h"
#include "oidmap.h"
#include "packfile.h"
#include "quote.h"
#include "strbuf.h"

static const char rev_list_usage[] =
"git rev-list [<options>] <commit>... [--] [<path>...]\n"
@ -73,11 +76,17 @@ static unsigned progress_counter;
static struct oidset omitted_objects;
static int arg_print_omitted; /* print objects omitted by filter */

static struct oidset missing_objects;
struct missing_objects_map_entry {
struct oidmap_entry entry;
const char *path;
unsigned type;
};
static struct oidmap missing_objects;
enum missing_action {
MA_ERROR = 0, /* fail if any missing objects are encountered */
MA_ALLOW_ANY, /* silently allow ALL missing objects */
MA_PRINT, /* print ALL missing objects in special section */
MA_PRINT_INFO, /* same as MA_PRINT but also prints missing object info */
MA_ALLOW_PROMISOR, /* silently allow all missing PROMISOR objects */
};
static enum missing_action arg_missing_action;
@ -101,7 +110,49 @@ static off_t get_object_disk_usage(struct object *obj)
return size;
}

static inline void finish_object__ma(struct object *obj)
static void add_missing_object_entry(struct object_id *oid, const char *path,
unsigned type)
{
struct missing_objects_map_entry *entry;

if (oidmap_get(&missing_objects, oid))
return;

CALLOC_ARRAY(entry, 1);
entry->entry.oid = *oid;
entry->type = type;
if (path)
entry->path = xstrdup(path);
oidmap_put(&missing_objects, entry);
}

static void print_missing_object(struct missing_objects_map_entry *entry,
int print_missing_info)
{
struct strbuf sb = STRBUF_INIT;

if (!print_missing_info) {
printf("?%s\n", oid_to_hex(&entry->entry.oid));
return;
}

if (entry->path && *entry->path) {
struct strbuf path = STRBUF_INIT;

strbuf_addstr(&sb, " path=");
quote_path(entry->path, NULL, &path, QUOTE_PATH_QUOTE_SP);
strbuf_addbuf(&sb, &path);

strbuf_release(&path);
}
if (entry->type)
strbuf_addf(&sb, " type=%s", type_name(entry->type));

printf("?%s%s\n", oid_to_hex(&entry->entry.oid), sb.buf);
strbuf_release(&sb);
}

static inline void finish_object__ma(struct object *obj, const char *name)
{
/*
* Whether or not we try to dynamically fetch missing objects
@ -119,7 +170,8 @@ static inline void finish_object__ma(struct object *obj)
return;

case MA_PRINT:
oidset_insert(&missing_objects, &obj->oid);
case MA_PRINT_INFO:
add_missing_object_entry(&obj->oid, name, obj->type);
return;

case MA_ALLOW_PROMISOR:
@ -152,7 +204,7 @@ static void show_commit(struct commit *commit, void *data)

if (revs->do_not_die_on_missing_objects &&
oidset_contains(&revs->missing_commits, &commit->object.oid)) {
finish_object__ma(&commit->object);
finish_object__ma(&commit->object, NULL);
return;
}

@ -268,12 +320,11 @@ static void show_commit(struct commit *commit, void *data)
finish_commit(commit);
}

static int finish_object(struct object *obj, const char *name UNUSED,
void *cb_data)
static int finish_object(struct object *obj, const char *name, void *cb_data)
{
struct rev_list_info *info = cb_data;
if (oid_object_info_extended(the_repository, &obj->oid, NULL, 0) < 0) {
finish_object__ma(obj);
finish_object__ma(obj, name);
return 1;
}
if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT)
@ -414,6 +465,12 @@ static inline int parse_missing_action_value(const char *value)
return 1;
}

if (!strcmp(value, "print-info")) {
arg_missing_action = MA_PRINT_INFO;
fetch_if_missing = 0;
return 1;
}

if (!strcmp(value, "allow-promisor")) {
arg_missing_action = MA_ALLOW_PROMISOR;
fetch_if_missing = 0;
@ -781,10 +838,18 @@ int cmd_rev_list(int argc,

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

oidmap_init(&missing_objects, DEFAULT_OIDSET_SIZE);
oidset_iter_init(&revs.missing_commits, &iter);

/* Add missing tips */
oidset_insert_from_set(&missing_objects, &revs.missing_commits);
while ((oid = oidset_iter_next(&iter)))
add_missing_object_entry(oid, NULL, 0);

oidset_clear(&revs.missing_commits);
}

@ -800,13 +865,20 @@ int cmd_rev_list(int argc,
printf("~%s\n", oid_to_hex(oid));
oidset_clear(&omitted_objects);
}
if (arg_missing_action == MA_PRINT) {
struct oidset_iter iter;
struct object_id *oid;
oidset_iter_init(&missing_objects, &iter);
while ((oid = oidset_iter_next(&iter)))
printf("?%s\n", oid_to_hex(oid));
oidset_clear(&missing_objects);
if (arg_missing_action == MA_PRINT ||
arg_missing_action == MA_PRINT_INFO) {
struct missing_objects_map_entry *entry;
struct oidmap_iter iter;

oidmap_iter_init(&missing_objects, &iter);

while ((entry = oidmap_iter_next(&iter))) {
print_missing_object(entry, arg_missing_action ==
MA_PRINT_INFO);
free((void *)entry->path);
}

oidmap_free(&missing_objects, true);
}

stop_progress(&progress);

View File

@ -145,4 +145,57 @@ do
done
done

for obj in "HEAD~1" "HEAD^{tree}" "HEAD:foo" "HEAD:foo/bar" "HEAD:baz baz"
do
test_expect_success "--missing=print-info with missing '$obj'" '
test_when_finished rm -rf missing-info &&

git init missing-info &&
(
cd missing-info &&
git commit --allow-empty -m first &&

mkdir foo &&
echo bar >foo/bar &&
echo baz >"baz baz" &&
echo bat >bat\" &&
git add -A &&
git commit -m second &&

oid="$(git rev-parse "$obj")" &&
path=".git/objects/$(test_oid_to_path $oid)" &&
type_info=" type=$(git cat-file -t $oid)" &&

case $obj in
HEAD:foo)
path_info=" path=foo"
;;
HEAD:foo/bar)
path_info=" path=foo/bar"
;;
"HEAD:baz baz")
path_info=" path=\"baz baz\""
;;
"HEAD:bat\"")
path_info=" path=\"bat\\\"\""
;;
esac &&

# Before the object is made missing, we use rev-list to
# get the expected oids.
git rev-list --objects --no-object-names \
HEAD ^"$obj" >expect.raw &&
echo "?$oid$path_info$type_info" >>expect.raw &&

mv "$path" "$path.hidden" &&
git rev-list --objects --no-object-names \
--missing=print-info HEAD >actual.raw &&

sort actual.raw >actual &&
sort expect.raw >expect &&
test_cmp expect actual
)
'
done

test_done