blob: 25db76d30ba58e79099b6194433e1efa09d31b60 [file]
#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,
};
/* Values are enum tree_state, stored as a byte to keep the map small. */
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;
};
/* The entries of a tree that still need to be verified. */
struct work_list {
struct work_item *items;
size_t nr, alloc;
bool has_subtrees;
};
static void work_list_clear(struct work_list *list)
{
for (size_t i = 0; i < list->nr; i++)
oid_array_clear(&list->items[i].parent_trees);
free(list->items);
}
struct verify_state {
kh_oid_tree_t *trees;
struct oidset trusted_blobs;
size_t trees_loaded;
size_t blobs_checked;
int exclude_promisor_objects;
/*
* Scratch space for reading one tree at a time. It lives here
* rather than on the stack to keep the frame of the recursive
* verify_tree() small.
*/
struct tree_desc desc;
struct name_entry entry;
};
/*
* 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_list *list, size_t wi,
const struct name_entry *entry)
{
while (wi < list->nr) {
struct work_item *item = &list->items[wi];
int cmp = base_name_compare(item->entry.path,
item->entry.pathlen,
item->entry.mode,
entry->path, entry->pathlen,
entry->mode);
if (cmp > 0)
break;
if (cmp < 0) {
wi++;
continue;
}
if (S_ISDIR(item->entry.mode) && S_ISDIR(entry->mode))
oid_array_append(&item->parent_trees, &entry->oid);
return wi + 1;
}
return wi;
}
/*
* Report an object that could not be read, telling a missing object
* apart from one that exists but cannot be read, e.g. because it is
* corrupt.
*/
static int unreadable_object_error(struct repository *repo,
const struct object_id *oid,
const char *type)
{
if (!odb_has_object(repo->objects, oid, 0))
return error(_("missing %s object %s"), type, oid_to_hex(oid));
return error(_("unable to read %s object %s"), type, oid_to_hex(oid));
}
static int verify_blob(struct repository *repo,
const struct object_id *oid,
struct verify_state *vs)
{
struct object_info oi = OBJECT_INFO_INIT;
enum object_type type;
if (oidset_contains(&vs->trusted_blobs, oid))
return 0;
vs->blobs_checked++;
oi.typep = &type;
if (odb_read_object_info_extended(repo->objects, oid, &oi,
OBJECT_INFO_LOOKUP_REPLACE |
OBJECT_INFO_SKIP_FETCH_OBJECT)) {
if (vs->exclude_promisor_objects &&
is_promisor_object(repo, oid))
return 0;
return unreadable_object_error(repo, oid, "blob");
}
if (type != OBJ_BLOB)
return error(_("object %s is a %s, not a %s"),
oid_to_hex(oid), type_name(type), "blob");
oidset_insert(&vs->trusted_blobs, oid);
return 0;
}
/*
* Read a tree object into *bufp. Returns -1 if the object exists
* but is not a tree. Otherwise returns 0, leaving *bufp NULL if the
* object could not be read.
*/
static int read_tree_object(struct object_database *odb,
const struct object_id *oid,
void **bufp, size_t *sizep)
{
struct object_info oi = OBJECT_INFO_INIT;
enum object_type type;
oi.typep = &type;
oi.sizep = sizep;
oi.contentp = bufp;
if (odb_read_object_info_extended(odb, oid, &oi,
OBJECT_INFO_LOOKUP_REPLACE |
OBJECT_INFO_SKIP_FETCH_OBJECT))
*bufp = NULL;
if (*bufp && type != OBJ_TREE) {
FREE_AND_NULL(*bufp);
return error(_("object %s is a %s, not a %s"),
oid_to_hex(oid), type_name(type), "tree");
}
return 0;
}
/* List the entries of a tree that are not yet known to be trusted. */
static void list_untrusted_entries(struct verify_state *vs,
const struct object_id *tree_oid,
void *tree_buf, size_t tree_size,
struct work_list *list)
{
struct name_entry *entry = &vs->entry;
init_tree_desc(&vs->desc, tree_oid, tree_buf, tree_size);
while (tree_entry(&vs->desc, entry)) {
if (S_ISGITLINK(entry->mode))
continue;
if (S_ISDIR(entry->mode)) {
if (tree_map_get(vs->trees, &entry->oid) >=
TREE_TRUSTED)
continue;
list->has_subtrees = true;
} else {
if (oidset_contains(&vs->trusted_blobs, &entry->oid))
continue;
}
ALLOC_GROW(list->items, list->nr + 1, list->alloc);
memset(&list->items[list->nr], 0, sizeof(list->items[0]));
list->items[list->nr++].entry = *entry;
}
}
/*
* Scan one trusted base tree: expand it if that has not happened yet,
* and collect its same-path subtrees as bases for untrusted subtrees.
*/
static int scan_base_tree(struct repository *repo, struct verify_state *vs,
const struct object_id *base_oid,
struct work_list *list)
{
int expanded = tree_map_get(vs->trees, base_oid) >= TREE_EXPANDED;
struct name_entry *entry = &vs->entry;
size_t wi = 0, base_size;
void *base_buf;
if (expanded && !list->has_subtrees)
return 0;
if (read_tree_object(repo->objects, base_oid, &base_buf, &base_size))
return -1;
if (!base_buf) {
if (vs->exclude_promisor_objects &&
is_promisor_object(repo, base_oid))
return 0;
return unreadable_object_error(repo, base_oid, "tree");
}
vs->trees_loaded++;
init_tree_desc(&vs->desc, base_oid, base_buf, base_size);
while (tree_entry(&vs->desc, entry)) {
if (S_ISGITLINK(entry->mode))
continue;
/* Same-path subtree as a base for an untrusted one. */
if (list->has_subtrees)
wi = collect_subtree_bases(list, wi, entry);
/* Expand: the base is trusted, so are its entries. */
if (!expanded) {
if (S_ISDIR(entry->mode))
tree_map_add(vs->trees, &entry->oid,
TREE_TRUSTED);
else
oidset_insert(&vs->trusted_blobs,
&entry->oid);
}
}
if (!expanded)
tree_map_add(vs->trees, base_oid, TREE_EXPANDED);
free(base_buf);
return 0;
}
/*
* Verify a tree against the trusted base trees, loading as little as
* possible:
*
* First list the entries that are not yet known to be trusted. When
* the parent trees have been expanded before, only the changed entries
* remain.
*
* Then scan the trusted base trees (the same-path trees of the parent
* commits). A base that has not been expanded yet is read so that its
* entries can be marked trusted; this often shows that listed entries
* are trusted after all. An already expanded base is still read when
* there are untrusted subtrees, because its subtree at the same path is
* what lets the untrusted subtree skip its own unchanged entries in
* turn.
*
* Finally check that the remaining blobs exist, and recurse into the
* remaining subtrees with the same-path subtrees as their bases.
*/
static int verify_tree(struct repository *repo,
const struct object_id *tree_oid,
const struct oid_array *trusted_bases,
struct verify_state *vs, int depth)
{
struct work_list list = { 0 };
size_t i, tree_size;
void *tree_buf;
int ret = 0;
/*
* Enforce core.maxTreeDepth like process_tree() in list-objects.c,
* so that the full and incremental checks reject the same trees.
*/
if (depth > repo->settings.max_allowed_tree_depth)
return error(_("exceeded maximum allowed tree depth"));
if (tree_map_get(vs->trees, tree_oid) >= TREE_TRUSTED)
return 0;
if (read_tree_object(repo->objects, tree_oid, &tree_buf, &tree_size))
return -1;
if (!tree_buf) {
/*
* A missing tree that is promised can be fetched on demand,
* so it is not an error, as in the full check.
*/
if (vs->exclude_promisor_objects &&
is_promisor_object(repo, tree_oid))
return 0;
return unreadable_object_error(repo, tree_oid, "tree");
}
vs->trees_loaded++;
list_untrusted_entries(vs, tree_oid, tree_buf, tree_size, &list);
/* Scan the base trees, unless nothing is left to verify. */
for (i = 0; list.nr && i < trusted_bases->nr; i++) {
ret = scan_base_tree(repo, vs, &trusted_bases->oid[i], &list);
if (ret)
goto done;
}
/*
* Check the remaining entries in tree order. Verifying a subtree
* can show that later entries are trusted, which then makes them
* cheap to skip.
*/
for (i = 0; i < list.nr && !ret; i++) {
struct work_item *item = &list.items[i];
if (S_ISDIR(item->entry.mode))
ret = verify_tree(repo, &item->entry.oid,
&item->parent_trees, vs, depth + 1);
else
ret = verify_blob(repo, &item->entry.oid, vs);
}
if (!ret)
tree_map_add(vs->trees, tree_oid, TREE_EXPANDED);
done:
free(tree_buf);
work_list_clear(&list);
return ret;
}
static int verify_commit_tree(struct repository *repo,
struct commit *commit,
struct verify_state *vs)
{
struct oid_array parent_trees = OID_ARRAY_INIT;
const struct object_id *tree_oid;
struct commit_list *p;
int ret;
/*
* Parent trees are trusted: boundary parents are already
* connected, and earlier incoming parents were verified
* first due to the topological processing order.
*
* The revision walk skips missing parents that are promised
* (see process_parents()), so do the same here. Such a parent
* contributes no base, which only means less of the new tree
* can be skipped.
*/
for (p = commit->parents; p; p = p->next) {
if (repo_parse_commit_gently(repo, p->item, 1) < 0) {
if (vs->exclude_promisor_objects &&
is_promisor_object(repo, &p->item->object.oid))
continue;
ret = error(_("unable to parse commit %s"),
oid_to_hex(&p->item->object.oid));
goto out;
}
tree_oid = get_commit_tree_oid(p->item);
if (!tree_oid) {
ret = error(_("unable to load root tree for commit %s"),
oid_to_hex(&p->item->object.oid));
goto out;
}
tree_map_add(vs->trees, tree_oid, TREE_TRUSTED);
oid_array_append(&parent_trees, tree_oid);
}
tree_oid = get_commit_tree_oid(commit);
if (!tree_oid) {
ret = error(_("unable to load root tree for commit %s"),
oid_to_hex(&commit->object.oid));
goto out;
}
ret = verify_tree(repo, tree_oid, &parent_trees, vs, 0);
out:
oid_array_clear(&parent_trees);
return ret;
}
int 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;
int ret = 0;
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) {
ret = error(_("cycle detected in incoming commit graph"));
goto out;
}
*commits = commit_list_reverse(*commits);
for (iter = *commits; iter && !ret; iter = iter->next)
ret = verify_commit_tree(repo, iter->item, &vs);
out:
kh_destroy_oid_tree(vs.trees);
oidset_clear(&vs.trusted_blobs);
trace2_data_intmax("connectivity", repo,
"trees_loaded", (intmax_t)vs.trees_loaded);
trace2_data_intmax("connectivity", repo,
"blobs_checked", (intmax_t)vs.blobs_checked);
return ret;
}