Files
git-mirror/t/helper/test-reach.c
Patrick Steinhardt 7ec85185b1 object: add flag to peel_object() to verify object type
When peeling a tag to a non-tag object we repeatedly call
`parse_object()` on the tagged object until we find the first object
that isn't a tag. While this feels sensible at first, there is a big
catch here: `parse_object()` doesn't actually verify the type of the
tagged object.

The relevant code path here eventually ends up in `parse_tag_buffer()`.
Here, we parse the various fields of the tag, including the "type". Once
we've figured out the type and the tagged object ID, we call one of the
`lookup_${type}()` functions for whatever type we have found. There is
two possible outcomes in the successful case:

  1. The object is already part of our cached objects. In that case we
     double-check whether the type we're trying to look up matches the
     type that was cached.

  2. The object is _not_ part of our cached objects. In that case, we
     simply create a new object with the expected type, but we don't
     parse that object.

In the first case we might notice type mismatches, but only in the case
where our cache has the object with the correct type. In the second
case, we'll blindly assume that the type is correct and then go with it.
We'll only notice that the type might be wrong when we try to parse the
object at a later point.

Now arguably, we could change `parse_tag_buffer()` to verify the tagged
object's type for us. But that would have the effect that such a tag
cannot be parsed at all anymore, and we have a small bunch of tests for
exactly this case that assert we still can open such tags. So this
change does not feel like something we can retroactively tighten, even
though one shouldn't ever hit such corrupted tags.

Instead, add a new `flags` field to `peel_object()` that allows the
caller to opt in to strict object verification. This will be wired up at
a subset of callsites over the next few commits.

Note that this change also inlines `deref_tag_noverify()`. There's only
been two callsites of that function, the one we're changing and one in
our test helpers. The latter callsite can trivially use `deref_tag()`
instead, so by inlining the function we avoid having to pass down the
flag.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-04 07:32:25 -08:00

192 lines
4.9 KiB
C

#define USE_THE_REPOSITORY_VARIABLE
#include "test-tool.h"
#include "commit.h"
#include "commit-reach.h"
#include "gettext.h"
#include "hex.h"
#include "object-name.h"
#include "ref-filter.h"
#include "setup.h"
#include "string-list.h"
#include "tag.h"
static void print_sorted_commit_ids(struct commit_list *list)
{
struct string_list s = STRING_LIST_INIT_DUP;
while (list) {
string_list_append(&s, oid_to_hex(&list->item->object.oid));
list = list->next;
}
string_list_sort(&s);
for (size_t i = 0; i < s.nr; i++)
printf("%s\n", s.items[i].string);
string_list_clear(&s, 0);
}
int cmd__reach(int ac, const char **av)
{
struct object_id oid_A, oid_B;
struct commit *A, *B;
struct commit_list *X, *Y;
struct object_array X_obj = OBJECT_ARRAY_INIT;
struct commit **X_array, **Y_array;
size_t X_nr, X_alloc, Y_nr, Y_alloc;
struct strbuf buf = STRBUF_INIT;
struct repository *r = the_repository;
setup_git_directory();
if (ac < 2)
exit(1);
A = B = NULL;
X = Y = NULL;
X_nr = Y_nr = 0;
X_alloc = Y_alloc = 16;
ALLOC_ARRAY(X_array, X_alloc);
ALLOC_ARRAY(Y_array, Y_alloc);
while (strbuf_getline(&buf, stdin) != EOF) {
struct object_id oid;
struct object *orig;
struct object *peeled;
struct commit *c;
if (buf.len < 3)
continue;
if (repo_get_oid_committish(the_repository, buf.buf + 2, &oid))
die("failed to resolve %s", buf.buf + 2);
orig = parse_object(r, &oid);
peeled = deref_tag(the_repository, orig, NULL, 0);
if (!peeled)
die("failed to load commit for input %s resulting in oid %s",
buf.buf, oid_to_hex(&oid));
c = object_as_type(peeled, OBJ_COMMIT, 0);
if (!c)
die("failed to load commit for input %s resulting in oid %s",
buf.buf, oid_to_hex(&oid));
switch (buf.buf[0]) {
case 'A':
oidcpy(&oid_A, &oid);
A = c;
break;
case 'B':
oidcpy(&oid_B, &oid);
B = c;
break;
case 'X':
commit_list_insert(c, &X);
ALLOC_GROW(X_array, X_nr + 1, X_alloc);
X_array[X_nr++] = c;
add_object_array(orig, NULL, &X_obj);
break;
case 'Y':
commit_list_insert(c, &Y);
ALLOC_GROW(Y_array, Y_nr + 1, Y_alloc);
Y_array[Y_nr++] = c;
break;
default:
die("unexpected start of line: %c", buf.buf[0]);
}
}
strbuf_release(&buf);
if (!strcmp(av[1], "ref_newer"))
printf("%s(A,B):%d\n", av[1], ref_newer(&oid_A, &oid_B));
else if (!strcmp(av[1], "in_merge_bases"))
printf("%s(A,B):%d\n", av[1],
repo_in_merge_bases(the_repository, A, B));
else if (!strcmp(av[1], "in_merge_bases_many"))
printf("%s(A,X):%d\n", av[1],
repo_in_merge_bases_many(the_repository, A, X_nr, X_array, 0));
else if (!strcmp(av[1], "is_descendant_of"))
printf("%s(A,X):%d\n", av[1], repo_is_descendant_of(r, A, X));
else if (!strcmp(av[1], "get_branch_base_for_tip"))
printf("%s(A,X):%d\n", av[1], get_branch_base_for_tip(r, A, X_array, X_nr));
else if (!strcmp(av[1], "get_merge_bases_many")) {
struct commit_list *list = NULL;
if (repo_get_merge_bases_many(the_repository,
A, X_nr,
X_array,
&list) < 0)
exit(128);
printf("%s(A,X):\n", av[1]);
print_sorted_commit_ids(list);
free_commit_list(list);
} else if (!strcmp(av[1], "reduce_heads")) {
struct commit_list *list = reduce_heads(X);
printf("%s(X):\n", av[1]);
print_sorted_commit_ids(list);
free_commit_list(list);
} else if (!strcmp(av[1], "can_all_from_reach")) {
printf("%s(X,Y):%d\n", av[1], can_all_from_reach(X, Y, 1));
} else if (!strcmp(av[1], "can_all_from_reach_with_flag")) {
struct commit_list *iter = Y;
while (iter) {
iter->item->object.flags |= 2;
iter = iter->next;
}
printf("%s(X,_,_,0,0):%d\n", av[1], can_all_from_reach_with_flag(&X_obj, 2, 4, 0, 0));
} else if (!strcmp(av[1], "commit_contains")) {
struct ref_filter filter = REF_FILTER_INIT;
struct contains_cache cache;
init_contains_cache(&cache);
if (ac > 2 && !strcmp(av[2], "--tag"))
filter.with_commit_tag_algo = 1;
else
filter.with_commit_tag_algo = 0;
printf("%s(_,A,X,_):%d\n", av[1], commit_contains(&filter, A, X, &cache));
clear_contains_cache(&cache);
} else if (!strcmp(av[1], "get_reachable_subset")) {
const int reachable_flag = 1;
int count = 0;
struct commit_list *current;
struct commit_list *list = get_reachable_subset(X_array, X_nr,
Y_array, Y_nr,
reachable_flag);
printf("get_reachable_subset(X,Y)\n");
for (current = list; current; current = current->next) {
if (!(list->item->object.flags & reachable_flag))
die(_("commit %s is not marked reachable"),
oid_to_hex(&list->item->object.oid));
count++;
}
for (size_t i = 0; i < Y_nr; i++) {
if (Y_array[i]->object.flags & reachable_flag)
count--;
}
if (count < 0)
die(_("too many commits marked reachable"));
print_sorted_commit_ids(list);
free_commit_list(list);
}
object_array_clear(&X_obj);
strbuf_release(&buf);
free_commit_list(X);
free_commit_list(Y);
free(X_array);
free(Y_array);
return 0;
}