git/tree-verify.c

317 lines
7.9 KiB
C

#include "git-compat-util.h"
#include "commit.h"
#include "gettext.h"
#include "hex.h"
#include "khash.h"
#include "object.h"
#include "odb.h"
#include "oid-array.h"
#include "oidset.h"
#include "tree.h"
#include "tree-walk.h"
#include "tree-verify.h"
#include "packfile.h"
#include "trace2.h"
enum tree_state {
TREE_UNTRUSTED = 0,
TREE_TRUSTED = 1,
TREE_EXPANDED = 2,
};
KHASH_INIT(oid_tree, struct object_id, unsigned char, 1,
oidhash_by_value, oideq_by_value)
static enum tree_state tree_map_get(kh_oid_tree_t *m,
const struct object_id *oid)
{
khint_t pos = kh_get_oid_tree(m, *oid);
if (pos == kh_end(m))
return TREE_UNTRUSTED;
return kh_val(m, pos);
}
static void tree_map_add(kh_oid_tree_t *m, const struct object_id *oid,
enum tree_state state)
{
int added;
khint_t pos = kh_put_oid_tree(m, *oid, &added);
if (added)
kh_val(m, pos) = state;
else if (state > kh_val(m, pos))
kh_val(m, pos) = state;
}
struct work_item {
struct name_entry entry;
struct oid_array parent_trees;
};
struct verify_state {
kh_oid_tree_t *trees;
struct oidset trusted_blobs;
int trees_loaded;
int blobs_checked;
int exclude_promisor_objects;
};
/*
* Merge-walk the work list against one base tree entry, recording
* same-path parent subtrees as recursive comparison bases.
* Returns the updated work-list cursor.
*/
static size_t collect_subtree_bases(struct work_item *work, size_t nr_work,
size_t wi, const struct name_entry *entry)
{
while (wi < nr_work) {
int cmp = base_name_compare(
work[wi].entry.path, work[wi].entry.pathlen,
work[wi].entry.mode,
entry->path, entry->pathlen,
entry->mode);
if (cmp > 0)
break;
if (cmp < 0) {
wi++;
continue;
}
if (S_ISDIR(work[wi].entry.mode) &&
S_ISDIR(entry->mode))
oid_array_append(&work[wi].parent_trees,
&entry->oid);
return wi + 1;
}
return wi;
}
static void verify_blob(struct repository *repo,
const struct object_id *oid,
struct verify_state *vs)
{
int type;
if (oidset_contains(&vs->trusted_blobs, oid))
return;
vs->blobs_checked++;
type = odb_read_object_info(repo->objects, oid, NULL);
if (type == OBJ_BLOB) {
oidset_insert(&vs->trusted_blobs, oid);
return;
}
if (type >= 0)
die(_("object %s is a %s, not a %s"),
oid_to_hex(oid), type_name(type), "blob");
if (vs->exclude_promisor_objects &&
is_promisor_object(repo, oid))
return;
die(_("missing blob object '%s'"), oid_to_hex(oid));
}
static void *read_tree_object(struct object_database *odb,
const struct object_id *oid,
size_t *sizep)
{
enum object_type type;
void *buf = odb_read_object(odb, oid, &type, sizep);
if (buf && type != OBJ_TREE) {
free(buf);
die(_("object %s is a %s, not a %s"),
oid_to_hex(oid), type_name(type), "tree");
}
return buf;
}
static void verify_tree(struct repository *repo,
const struct object_id *new_tree_oid,
const struct oid_array *base_trees,
struct verify_state *vs, int depth)
{
struct tree_desc desc;
struct name_entry entry;
struct work_item *work = NULL;
size_t nr_work = 0, alloc_work = 0;
int need_subtree_bases = 0;
size_t i, tree_size;
void *tree_buf;
if (depth > repo->settings.max_allowed_tree_depth)
die(_("exceeded maximum allowed tree depth"));
if (tree_map_get(vs->trees, new_tree_oid) >= TREE_TRUSTED)
return;
tree_buf = read_tree_object(repo->objects, new_tree_oid, &tree_size);
if (!tree_buf) {
if (vs->exclude_promisor_objects &&
is_promisor_object(repo, new_tree_oid))
return;
die(_("bad tree object %s"),
oid_to_hex(new_tree_oid));
}
vs->trees_loaded++;
init_tree_desc(&desc, new_tree_oid, tree_buf, tree_size);
while (tree_entry(&desc, &entry)) {
if (S_ISGITLINK(entry.mode))
continue;
if (S_ISDIR(entry.mode)) {
if (tree_map_get(vs->trees, &entry.oid) >= TREE_TRUSTED)
continue;
need_subtree_bases = 1;
} else {
if (oidset_contains(&vs->trusted_blobs, &entry.oid))
continue;
}
ALLOC_GROW(work, nr_work + 1, alloc_work);
memset(&work[nr_work], 0, sizeof(work[nr_work]));
work[nr_work].entry = entry;
nr_work++;
}
if (!nr_work) {
free(tree_buf);
goto done;
}
for (i = 0; base_trees && i < base_trees->nr; i++) {
const struct object_id *base_oid = &base_trees->oid[i];
int expanded = tree_map_get(vs->trees, base_oid) >= TREE_EXPANDED;
struct tree_desc base_desc;
struct name_entry scan_entry;
size_t wi = 0, base_size;
void *base_buf;
if (expanded && !need_subtree_bases)
continue;
base_buf = read_tree_object(repo->objects, base_oid,
&base_size);
if (!base_buf) {
if (vs->exclude_promisor_objects &&
is_promisor_object(repo, base_oid))
continue;
die(_("bad tree object %s"),
oid_to_hex(base_oid));
}
vs->trees_loaded++;
init_tree_desc(&base_desc, base_oid, base_buf, base_size);
while (tree_entry(&base_desc, &scan_entry)) {
if (S_ISGITLINK(scan_entry.mode))
continue;
if (need_subtree_bases)
wi = collect_subtree_bases(work, nr_work,
wi, &scan_entry);
if (!expanded) {
if (S_ISDIR(scan_entry.mode))
tree_map_add(vs->trees,
&scan_entry.oid,
TREE_TRUSTED);
else
oidset_insert(&vs->trusted_blobs,
&scan_entry.oid);
}
}
if (!expanded)
tree_map_add(vs->trees, base_oid, TREE_EXPANDED);
free(base_buf);
}
for (i = 0; i < nr_work; i++) {
if (S_ISDIR(work[i].entry.mode))
verify_tree(repo, &work[i].entry.oid,
&work[i].parent_trees, vs,
depth + 1);
else
verify_blob(repo, &work[i].entry.oid, vs);
}
free(tree_buf);
done:
tree_map_add(vs->trees, new_tree_oid, TREE_EXPANDED);
for (i = 0; i < nr_work; i++)
oid_array_clear(&work[i].parent_trees);
free(work);
}
static void verify_commit_tree(struct repository *repo,
struct commit *commit,
struct verify_state *vs)
{
struct oid_array base_trees = OID_ARRAY_INIT;
const struct object_id *tree_oid;
struct commit_list *p;
/*
* Parent trees are trusted: boundary parents are already
* connected, and earlier incoming parents were verified
* first due to the topological processing order.
*/
for (p = commit->parents; p; p = p->next) {
parse_commit_or_die(p->item);
tree_oid = get_commit_tree_oid(p->item);
if (!tree_oid)
die(_("unable to load root tree for commit %s"),
oid_to_hex(&p->item->object.oid));
tree_map_add(vs->trees, tree_oid, TREE_TRUSTED);
oid_array_append(&base_trees, tree_oid);
}
tree_oid = get_commit_tree_oid(commit);
if (!tree_oid)
die(_("unable to load root tree for commit %s"),
oid_to_hex(&commit->object.oid));
verify_tree(repo, tree_oid, &base_trees, vs, 0);
oid_array_clear(&base_trees);
}
void verify_commits_incremental(struct repository *repo,
struct commit_list **commits,
int exclude_promisor_objects)
{
struct verify_state vs = { 0 };
struct commit_list *iter;
unsigned nr_before;
if (repo->fetch_if_missing)
BUG("verify_commits_incremental must not be called "
"with fetch_if_missing set");
vs.trees = kh_init_oid_tree();
vs.exclude_promisor_objects = exclude_promisor_objects;
/*
* Ancestors must be verified before descendants so that parent
* trees can be trusted without re-verification. Sort explicitly
* rather than relying on the caller's ordering.
*
* sort_in_topological_order() silently drops cycle members,
* so explicitly check if the size has changed.
*/
nr_before = commit_list_count(*commits);
sort_in_topological_order(commits, REV_SORT_IN_GRAPH_ORDER);
if (commit_list_count(*commits) < nr_before)
die(_("cycle detected in incoming commit graph"));
*commits = commit_list_reverse(*commits);
for (iter = *commits; iter; iter = iter->next)
verify_commit_tree(repo, iter->item, &vs);
kh_destroy_oid_tree(vs.trees);
oidset_clear(&vs.trusted_blobs);
trace2_data_intmax("connectivity", repo,
"trees_loaded", vs.trees_loaded);
trace2_data_intmax("connectivity", repo,
"blobs_checked", vs.blobs_checked);
}