rev-list: add print-info action to print missing object path

Missing objects identified through git-rev-list(1) can be printed by setting the `--missing=print` option. Additional information about the missing object, such as its path and type, may be present in its containing object. Add the `print-info` missing action for the `--missing` option that, when set, prints additional insight about the missing object inferred from its containing object. Each line of output for a missing object is 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. This format is kept generic so it can be extended in the future to support additional information. For now, only a missing object path info is implemented. It follows the form `path=<path>` and specifies the full path to the object from the top-level tree. A path containing SP or special characters is enclosed in double-quotes in the C style as needed. In a subsequent commit, missing object type info will also be added. Signed-off-by: Justin Tobler <jltobler@gmail.com> Acked-by: Christian Couder <christian.couder@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Justin Tobler committed Feb 4, 2025 at 18:41 UTC c6d896bcfde1cfb626c8bc25c71d00662ef7f22c
3 files changed +152 -17
Documentation/rev-list-options.txt
+16
@@ -1024,6 +1024,22 @@ Unexpected missing objects will raise an error.
1024 The form '--missing=print' is like 'allow-any', but will also print a
1025 list of the missing objects. Object IDs are prefixed with a ``?'' character.
1026 +
1027 +The form '--missing=print-info' is like 'print', but will also print additional
1028 +information about the missing object inferred from its containing object. The
1029 +information is all printed on the same line with the missing object ID in the
1030 +form: `?<oid> [<token>=<value>]...`. The `<token>=<value>` pairs containing
1031 +additional information are separated from each other by a SP. The value is
1032 +encoded in a token specific fashion, but SP or LF contained in value are always
1033 +expected to be represented in such a way that the resulting encoded value does
1034 +not have either of these two problematic bytes. Each `<token>=<value>` may be
1035 +one of the following:
1036 ++
1037 +--
1038 +* The `path=<path>` shows the path of the missing object inferred from a
1039 + containing object. A path containing SP or special characters is enclosed in
1040 + double-quotes in the C style as needed.
1041 +--
1042 ++
1043 If some tips passed to the traversal are missing, they will be
1044 considered as missing too, and the traversal will ignore them. In case
1045 we cannot get their Object ID though, an error will be raised.
builtin/rev-list.c
+84 -17
@@ -22,7 +22,10 @@
22 #include "progress.h"
23 #include "reflog-walk.h"
24 #include "oidset.h"
25 +#include "oidmap.h"
26 #include "packfile.h"
27 +#include "quote.h"
28 +#include "strbuf.h"
29
30 static const char rev_list_usage[] =
31 "git rev-list [<options>] <commit>... [--] [<path>...]\n"
@@ -73,11 +76,16 @@ static unsigned progress_counter;
76 static struct oidset omitted_objects;
77 static int arg_print_omitted; /* print objects omitted by filter */
78
76 -static struct oidset missing_objects;
79 +struct missing_objects_map_entry {
80 + struct oidmap_entry entry;
81 + const char *path;
82 +};
83 +static struct oidmap missing_objects;
84 enum missing_action {
85 MA_ERROR = 0, /* fail if any missing objects are encountered */
86 MA_ALLOW_ANY, /* silently allow ALL missing objects */
87 MA_PRINT, /* print ALL missing objects in special section */
88 + MA_PRINT_INFO, /* same as MA_PRINT but also prints missing object info */
89 MA_ALLOW_PROMISOR, /* silently allow all missing PROMISOR objects */
90 };
91 static enum missing_action arg_missing_action;
@@ -101,7 +109,45 @@ static off_t get_object_disk_usage(struct object *obj)
109 return size;
110 }
111
104 -static inline void finish_object__ma(struct object *obj)
112 +static void add_missing_object_entry(struct object_id *oid, const char *path)
113 +{
114 + struct missing_objects_map_entry *entry;
115 +
116 + if (oidmap_get(&missing_objects, oid))
117 + return;
118 +
119 + CALLOC_ARRAY(entry, 1);
120 + entry->entry.oid = *oid;
121 + if (path)
122 + entry->path = xstrdup(path);
123 + oidmap_put(&missing_objects, entry);
124 +}
125 +
126 +static void print_missing_object(struct missing_objects_map_entry *entry,
127 + int print_missing_info)
128 +{
129 + struct strbuf sb = STRBUF_INIT;
130 +
131 + if (!print_missing_info) {
132 + printf("?%s\n", oid_to_hex(&entry->entry.oid));
133 + return;
134 + }
135 +
136 + if (entry->path && *entry->path) {
137 + struct strbuf path = STRBUF_INIT;
138 +
139 + strbuf_addstr(&sb, " path=");
140 + quote_path(entry->path, NULL, &path, QUOTE_PATH_QUOTE_SP);
141 + strbuf_addbuf(&sb, &path);
142 +
143 + strbuf_release(&path);
144 + }
145 +
146 + printf("?%s%s\n", oid_to_hex(&entry->entry.oid), sb.buf);
147 + strbuf_release(&sb);
148 +}
149 +
150 +static inline void finish_object__ma(struct object *obj, const char *name)
151 {
152 /*
153 * Whether or not we try to dynamically fetch missing objects
@@ -119,7 +165,8 @@ static inline void finish_object__ma(struct object *obj)
165 return;
166
167 case MA_PRINT:
122 - oidset_insert(&missing_objects, &obj->oid);
168 + case MA_PRINT_INFO:
169 + add_missing_object_entry(&obj->oid, name);
170 return;
171
172 case MA_ALLOW_PROMISOR:
@@ -152,7 +199,7 @@ static void show_commit(struct commit *commit, void *data)
199
200 if (revs->do_not_die_on_missing_objects &&
201 oidset_contains(&revs->missing_commits, &commit->object.oid)) {
155 - finish_object__ma(&commit->object);
202 + finish_object__ma(&commit->object, NULL);
203 return;
204 }
205
@@ -268,12 +315,11 @@ static void show_commit(struct commit *commit, void *data)
315 finish_commit(commit);
316 }
317
271 -static int finish_object(struct object *obj, const char *name UNUSED,
272 - void *cb_data)
318 +static int finish_object(struct object *obj, const char *name, void *cb_data)
319 {
320 struct rev_list_info *info = cb_data;
321 if (oid_object_info_extended(the_repository, &obj->oid, NULL, 0) < 0) {
276 - finish_object__ma(obj);
322 + finish_object__ma(obj, name);
323 return 1;
324 }
325 if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT)
@@ -414,6 +460,12 @@ static inline int parse_missing_action_value(const char *value)
460 return 1;
461 }
462
463 + if (!strcmp(value, "print-info")) {
464 + arg_missing_action = MA_PRINT_INFO;
465 + fetch_if_missing = 0;
466 + return 1;
467 + }
468 +
469 if (!strcmp(value, "allow-promisor")) {
470 arg_missing_action = MA_ALLOW_PROMISOR;
471 fetch_if_missing = 0;
@@ -781,10 +833,18 @@ int cmd_rev_list(int argc,
833
834 if (arg_print_omitted)
835 oidset_init(&omitted_objects, DEFAULT_OIDSET_SIZE);
784 - if (arg_missing_action == MA_PRINT) {
785 - oidset_init(&missing_objects, DEFAULT_OIDSET_SIZE);
836 + if (arg_missing_action == MA_PRINT ||
837 + arg_missing_action == MA_PRINT_INFO) {
838 + struct oidset_iter iter;
839 + struct object_id *oid;
840 +
841 + oidmap_init(&missing_objects, DEFAULT_OIDSET_SIZE);
842 + oidset_iter_init(&revs.missing_commits, &iter);
843 +
844 /* Add missing tips */
787 - oidset_insert_from_set(&missing_objects, &revs.missing_commits);
845 + while ((oid = oidset_iter_next(&iter)))
846 + add_missing_object_entry(oid, NULL);
847 +
848 oidset_clear(&revs.missing_commits);
849 }
850
@@ -800,13 +860,20 @@ int cmd_rev_list(int argc,
860 printf("~%s\n", oid_to_hex(oid));
861 oidset_clear(&omitted_objects);
862 }
803 - if (arg_missing_action == MA_PRINT) {
804 - struct oidset_iter iter;
805 - struct object_id *oid;
806 - oidset_iter_init(&missing_objects, &iter);
807 - while ((oid = oidset_iter_next(&iter)))
808 - printf("?%s\n", oid_to_hex(oid));
809 - oidset_clear(&missing_objects);
863 + if (arg_missing_action == MA_PRINT ||
864 + arg_missing_action == MA_PRINT_INFO) {
865 + struct missing_objects_map_entry *entry;
866 + struct oidmap_iter iter;
867 +
868 + oidmap_iter_init(&missing_objects, &iter);
869 +
870 + while ((entry = oidmap_iter_next(&iter))) {
871 + print_missing_object(entry, arg_missing_action ==
872 + MA_PRINT_INFO);
873 + free((void *)entry->path);
874 + }
875 +
876 + oidmap_free(&missing_objects, true);
877 }
878
879 stop_progress(&progress);
t/t6022-rev-list-missing.sh
+52
@@ -145,4 +145,56 @@ do
145 done
146 done
147
148 +for obj in "HEAD~1" "HEAD^{tree}" "HEAD:foo" "HEAD:foo/bar" "HEAD:baz baz"
149 +do
150 + test_expect_success "--missing=print-info with missing '$obj'" '
151 + test_when_finished rm -rf missing-info &&
152 +
153 + git init missing-info &&
154 + (
155 + cd missing-info &&
156 + git commit --allow-empty -m first &&
157 +
158 + mkdir foo &&
159 + echo bar >foo/bar &&
160 + echo baz >"baz baz" &&
161 + echo bat >bat\" &&
162 + git add -A &&
163 + git commit -m second &&
164 +
165 + oid="$(git rev-parse "$obj")" &&
166 + path=".git/objects/$(test_oid_to_path $oid)" &&
167 +
168 + case $obj in
169 + HEAD:foo)
170 + path_info=" path=foo"
171 + ;;
172 + HEAD:foo/bar)
173 + path_info=" path=foo/bar"
174 + ;;
175 + "HEAD:baz baz")
176 + path_info=" path=\"baz baz\""
177 + ;;
178 + "HEAD:bat\"")
179 + path_info=" path=\"bat\\\"\""
180 + ;;
181 + esac &&
182 +
183 + # Before the object is made missing, we use rev-list to
184 + # get the expected oids.
185 + git rev-list --objects --no-object-names \
186 + HEAD ^"$obj" >expect.raw &&
187 + echo "?$oid$path_info" >>expect.raw &&
188 +
189 + mv "$path" "$path.hidden" &&
190 + git rev-list --objects --no-object-names \
191 + --missing=print-info HEAD >actual.raw &&
192 +
193 + sort actual.raw >actual &&
194 + sort expect.raw >expect &&
195 + test_cmp expect actual
196 + )
197 + '
198 +done
199 +
200 test_done