| /* | |
| * path-walk.c: implementation for path-based walks of the object graph. | |
| */ | |
| #include "git-compat-util.h" | |
| #include "path-walk.h" | |
| #include "blob.h" | |
| #include "commit.h" | |
| #include "dir.h" | |
| #include "hashmap.h" | |
| #include "hex.h" | |
| #include "list-objects.h" | |
| #include "list-objects-filter-options.h" | |
| #include "object-name.h" | |
| #include "odb.h" | |
| #include "object.h" | |
| #include "oid-array.h" | |
| #include "path.h" | |
| #include "prio-queue.h" | |
| #include "repository.h" | |
| #include "revision.h" | |
| #include "string-list.h" | |
| #include "strmap.h" | |
| #include "tag.h" | |
| #include "trace2.h" | |
| #include "tree.h" | |
| #include "tree-walk.h" | |
| static const char *root_path = ""; | |
| struct type_and_oid_list { | |
| enum object_type type; | |
| struct oid_array oids; | |
| int maybe_interesting; | |
| }; | |
| #define TYPE_AND_OID_LIST_INIT { \ | |
| .type = OBJ_NONE, \ | |
| .oids = OID_ARRAY_INIT \ | |
| } | |
| struct path_walk_context { | |
| /** | |
| * Repeats of data in 'struct path_walk_info' for | |
| * access with fewer characters. | |
| */ | |
| struct repository *repo; | |
| struct rev_info *revs; | |
| struct path_walk_info *info; | |
| /** | |
| * Map a path to a 'struct type_and_oid_list' | |
| * containing the objects discovered at that | |
| * path. | |
| */ | |
| struct strmap paths_to_lists; | |
| /** | |
| * Store the current list of paths in a priority queue, | |
| * using object type as a sorting mechanism, mostly to | |
| * make sure blobs are popped off the stack first. No | |
| * other sort is made, so within each object type it acts | |
| * like a stack and performs a DFS within the trees. | |
| * | |
| * Use path_stack_pushed to indicate whether a path | |
| * was previously added to path_stack. | |
| */ | |
| struct prio_queue path_stack; | |
| struct strset path_stack_pushed; | |
| unsigned exact_pathspecs:1; | |
| }; | |
| static int compare_by_type(const void *one, const void *two, void *cb_data) | |
| { | |
| struct type_and_oid_list *list1, *list2; | |
| const char *str1 = one; | |
| const char *str2 = two; | |
| struct path_walk_context *ctx = cb_data; | |
| list1 = strmap_get(&ctx->paths_to_lists, str1); | |
| list2 = strmap_get(&ctx->paths_to_lists, str2); | |
| /* | |
| * If object types are equal, then use path comparison. | |
| */ | |
| if (!list1 || !list2 || list1->type == list2->type) | |
| return strcmp(str1, str2); | |
| /* Prefer tags to be popped off first. */ | |
| if (list1->type == OBJ_TAG) | |
| return -1; | |
| if (list2->type == OBJ_TAG) | |
| return 1; | |
| /* Prefer blobs to be popped off second. */ | |
| if (list1->type == OBJ_BLOB) | |
| return -1; | |
| if (list2->type == OBJ_BLOB) | |
| return 1; | |
| return 0; | |
| } | |
| static void push_to_stack(struct path_walk_context *ctx, | |
| const char *path) | |
| { | |
| if (strset_contains(&ctx->path_stack_pushed, path)) | |
| return; | |
| strset_add(&ctx->path_stack_pushed, path); | |
| prio_queue_put(&ctx->path_stack, xstrdup(path)); | |
| } | |
| static void add_path_to_list(struct path_walk_context *ctx, | |
| const char *path, | |
| enum object_type type, | |
| struct object_id *oid, | |
| int interesting) | |
| { | |
| struct type_and_oid_list *list = strmap_get(&ctx->paths_to_lists, path); | |
| if (!list) { | |
| CALLOC_ARRAY(list, 1); | |
| list->type = type; | |
| strmap_put(&ctx->paths_to_lists, path, list); | |
| } | |
| list->maybe_interesting |= interesting; | |
| oid_array_append(&list->oids, oid); | |
| } | |
| static int add_tree_entries(struct path_walk_context *ctx, | |
| const char *base_path, | |
| struct object_id *oid) | |
| { | |
| struct tree_desc desc; | |
| struct name_entry entry; | |
| struct strbuf path = STRBUF_INIT; | |
| size_t base_len; | |
| struct tree *tree = lookup_tree(ctx->repo, oid); | |
| if (!tree) { | |
| error(_("failed to walk children of tree %s: not found"), | |
| oid_to_hex(oid)); | |
| return -1; | |
| } else if (repo_parse_tree_gently(ctx->repo, tree, 1)) { | |
| error("bad tree object %s", oid_to_hex(oid)); | |
| return -1; | |
| } | |
| strbuf_addstr(&path, base_path); | |
| base_len = path.len; | |
| init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size); | |
| while (tree_entry(&desc, &entry)) { | |
| struct object *o; | |
| /* Not actually true, but we will ignore submodules later. */ | |
| enum object_type type = S_ISDIR(entry.mode) ? OBJ_TREE : OBJ_BLOB; | |
| /* Skip submodules. */ | |
| if (S_ISGITLINK(entry.mode)) | |
| continue; | |
| /* If the caller doesn't want blobs, then don't bother. */ | |
| if (!ctx->info->blobs && type == OBJ_BLOB) | |
| continue; | |
| if (type == OBJ_TREE) { | |
| struct tree *child = lookup_tree(ctx->repo, &entry.oid); | |
| o = child ? &child->object : NULL; | |
| } else if (type == OBJ_BLOB) { | |
| struct blob *child = lookup_blob(ctx->repo, &entry.oid); | |
| o = child ? &child->object : NULL; | |
| } else { | |
| BUG("invalid type for tree entry: %d", type); | |
| } | |
| if (!o) { | |
| error(_("failed to find object %s"), | |
| oid_to_hex(&entry.oid)); | |
| return -1; | |
| } | |
| strbuf_setlen(&path, base_len); | |
| strbuf_add(&path, entry.path, entry.pathlen); | |
| /* | |
| * Trees will end with "/" for concatenation and distinction | |
| * from blobs at the same path. | |
| */ | |
| if (type == OBJ_TREE) | |
| strbuf_addch(&path, '/'); | |
| if (o->flags & SEEN) { | |
| /* | |
| * A tree with a shared OID may appear at multiple | |
| * paths. Even though we already added this tree to | |
| * the output at some other path, we still need to | |
| * walk into it at this in-cone path to discover | |
| * blobs that were not found at the earlier | |
| * out-of-cone path. | |
| * | |
| * Only do this for paths not yet in our map, to | |
| * avoid duplicate entries when the same tree OID | |
| * appears at the same path across multiple commits. | |
| */ | |
| if (type == OBJ_TREE && ctx->info->pl && | |
| ctx->info->pl->use_cone_patterns && | |
| !ctx->info->pl_sparse_trees && | |
| !strmap_contains(&ctx->paths_to_lists, path.buf)) { | |
| int dtype; | |
| enum pattern_match_result m; | |
| m = path_matches_pattern_list(path.buf, path.len, | |
| path.buf + base_len, | |
| &dtype, | |
| ctx->info->pl, | |
| ctx->repo->index); | |
| if (m != NOT_MATCHED) { | |
| add_path_to_list(ctx, path.buf, type, | |
| &entry.oid, | |
| !(o->flags & UNINTERESTING)); | |
| push_to_stack(ctx, path.buf); | |
| } | |
| } | |
| continue; | |
| } | |
| if (ctx->info->pl) { | |
| int dtype; | |
| enum pattern_match_result match; | |
| match = path_matches_pattern_list(path.buf, path.len, | |
| path.buf + base_len, &dtype, | |
| ctx->info->pl, | |
| ctx->repo->index); | |
| if (ctx->info->pl->use_cone_patterns && | |
| match == NOT_MATCHED && | |
| (type == OBJ_BLOB || ctx->info->pl_sparse_trees)) | |
| continue; | |
| else if (!ctx->info->pl->use_cone_patterns && | |
| type == OBJ_BLOB && | |
| match != MATCHED) | |
| continue; | |
| } | |
| if (ctx->revs->prune_data.nr && ctx->exact_pathspecs) { | |
| struct pathspec *pd = &ctx->revs->prune_data; | |
| bool found = false; | |
| int did_strip_suffix = strbuf_strip_suffix(&path, "/"); | |
| for (int i = 0; i < pd->nr; i++) { | |
| struct pathspec_item *item = &pd->items[i]; | |
| /* | |
| * Continue if either is a directory prefix | |
| * of the other. | |
| */ | |
| if (dir_prefix(path.buf, item->match) || | |
| dir_prefix(item->match, path.buf)) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (did_strip_suffix) | |
| strbuf_addch(&path, '/'); | |
| /* Skip paths that do not match the prefix. */ | |
| if (!found) | |
| continue; | |
| } | |
| o->flags |= SEEN; | |
| add_path_to_list(ctx, path.buf, type, &entry.oid, | |
| !(o->flags & UNINTERESTING)); | |
| push_to_stack(ctx, path.buf); | |
| } | |
| free_tree_buffer(tree); | |
| strbuf_release(&path); | |
| return 0; | |
| } | |
| /* | |
| * Paths starting with '/' (e.g., "/tags", "/tagged-blobs") hold objects that | |
| * were directly requested by 'pending' objects rather than discovered during | |
| * tree traversal. | |
| */ | |
| static int path_is_for_direct_objects(const char *path) | |
| { | |
| ASSERT(path); | |
| return path[0] == '/'; | |
| } | |
| /* | |
| * For each path in paths_to_explore, walk the trees another level | |
| * and add any found blobs to the batch (but only if they exist and | |
| * haven't been added yet). | |
| */ | |
| static int walk_path(struct path_walk_context *ctx, | |
| const char *path) | |
| { | |
| struct type_and_oid_list *list; | |
| int ret = 0; | |
| list = strmap_get(&ctx->paths_to_lists, path); | |
| if (!list) | |
| BUG("provided path '%s' that had no associated list", path); | |
| if (!list->oids.nr) | |
| return 0; | |
| if (ctx->info->prune_all_uninteresting) { | |
| /* | |
| * This is true if all objects were UNINTERESTING | |
| * when added to the list. | |
| */ | |
| if (!list->maybe_interesting) | |
| return 0; | |
| /* | |
| * But it's still possible that the objects were set | |
| * as UNINTERESTING after being added. Do a quick check. | |
| */ | |
| list->maybe_interesting = 0; | |
| for (size_t i = 0; | |
| !list->maybe_interesting && i < list->oids.nr; | |
| i++) { | |
| if (list->type == OBJ_TREE) { | |
| struct tree *t = lookup_tree(ctx->repo, | |
| &list->oids.oid[i]); | |
| if (t && !(t->object.flags & UNINTERESTING)) | |
| list->maybe_interesting = 1; | |
| } else if (list->type == OBJ_BLOB) { | |
| struct blob *b = lookup_blob(ctx->repo, | |
| &list->oids.oid[i]); | |
| if (b && !(b->object.flags & UNINTERESTING)) | |
| list->maybe_interesting = 1; | |
| } else { | |
| /* Tags are always interesting if visited. */ | |
| list->maybe_interesting = 1; | |
| } | |
| } | |
| /* We have confirmed that all objects are UNINTERESTING. */ | |
| if (!list->maybe_interesting) | |
| return 0; | |
| } | |
| if (list->type == OBJ_BLOB && | |
| ctx->revs->prune_data.nr && | |
| !path_is_for_direct_objects(path) && | |
| !match_pathspec(ctx->repo->index, &ctx->revs->prune_data, | |
| path, strlen(path), 0, | |
| NULL, 0)) | |
| return 0; | |
| /* | |
| * Evaluate function pointer on this data, if requested. | |
| * Ignore object type filters for tagged objects (path starts | |
| * with `/`), first for blobs and then other types. | |
| */ | |
| if (list->type == OBJ_BLOB && | |
| ctx->info->blob_limit && | |
| !path_is_for_direct_objects(path)) { | |
| struct oid_array filtered = OID_ARRAY_INIT; | |
| for (size_t i = 0; i < list->oids.nr; i++) { | |
| size_t size; | |
| if (odb_read_object_info(ctx->repo->objects, | |
| &list->oids.oid[i], | |
| &size) != OBJ_BLOB || | |
| size < ctx->info->blob_limit) | |
| oid_array_append(&filtered, | |
| &list->oids.oid[i]); | |
| } | |
| if (filtered.nr) | |
| ret = ctx->info->path_fn(path, &filtered, list->type, | |
| ctx->info->path_fn_data); | |
| oid_array_clear(&filtered); | |
| } else if ((!ctx->info->strict_types && path_is_for_direct_objects(path)) || | |
| (list->type == OBJ_TREE && ctx->info->trees) || | |
| (list->type == OBJ_BLOB && ctx->info->blobs) || | |
| (list->type == OBJ_TAG && ctx->info->tags)) { | |
| ret = ctx->info->path_fn(path, &list->oids, list->type, | |
| ctx->info->path_fn_data); | |
| } | |
| /* | |
| * Expand tree children, except when the set is directly requested | |
| * _and_ we are otherwise filtering out trees. | |
| */ | |
| if (list->type == OBJ_TREE && | |
| (!path_is_for_direct_objects(path) || ctx->info->trees)) { | |
| /* Use root path if expanding from tagged/direct trees. */ | |
| const char *expand_path = !strcmp(path, "/tagged-trees") | |
| ? root_path : path; | |
| for (size_t i = 0; i < list->oids.nr; i++) { | |
| ret |= add_tree_entries(ctx, | |
| expand_path, | |
| &list->oids.oid[i]); | |
| } | |
| } | |
| oid_array_clear(&list->oids); | |
| strmap_remove(&ctx->paths_to_lists, path, 1); | |
| return ret; | |
| } | |
| static void clear_paths_to_lists(struct strmap *map) | |
| { | |
| struct hashmap_iter iter; | |
| struct strmap_entry *e; | |
| hashmap_for_each_entry(&map->map, &iter, e, ent) { | |
| struct type_and_oid_list *list = e->value; | |
| oid_array_clear(&list->oids); | |
| } | |
| strmap_clear(map, 1); | |
| strmap_init(map); | |
| } | |
| static struct repository *edge_repo; | |
| static struct type_and_oid_list *edge_tree_list; | |
| static void show_edge(struct commit *commit) | |
| { | |
| struct tree *t = repo_get_commit_tree(edge_repo, commit); | |
| if (!t) | |
| return; | |
| if (commit->object.flags & UNINTERESTING) | |
| t->object.flags |= UNINTERESTING; | |
| if (t->object.flags & SEEN) | |
| return; | |
| t->object.flags |= SEEN; | |
| oid_array_append(&edge_tree_list->oids, &t->object.oid); | |
| } | |
| static int setup_pending_objects(struct path_walk_info *info, | |
| struct path_walk_context *ctx) | |
| { | |
| struct type_and_oid_list *tags = NULL; | |
| struct type_and_oid_list *tagged_blobs = NULL; | |
| struct type_and_oid_list *tagged_trees = NULL; | |
| if (info->tags) | |
| CALLOC_ARRAY(tags, 1); | |
| CALLOC_ARRAY(tagged_blobs, 1); | |
| CALLOC_ARRAY(tagged_trees, 1); | |
| /* | |
| * Pending objects include: | |
| * * Commits at branch tips. | |
| * * Annotated tags at tag tips. | |
| * * Any kind of object at lightweight tag tips. | |
| * * Trees and blobs in the index (with an associated path). | |
| */ | |
| for (size_t i = 0; i < info->revs->pending.nr; i++) { | |
| struct object_array_entry *pending = info->revs->pending.objects + i; | |
| struct object *obj = pending->item; | |
| /* Commits will be picked up by revision walk. */ | |
| if (obj->type == OBJ_COMMIT) | |
| continue; | |
| /* Navigate annotated tag object chains. */ | |
| while (obj->type == OBJ_TAG) { | |
| struct tag *tag = lookup_tag(info->revs->repo, &obj->oid); | |
| if (!tag) { | |
| error(_("failed to find tag %s"), | |
| oid_to_hex(&obj->oid)); | |
| return -1; | |
| } | |
| if (tag->object.flags & SEEN) | |
| break; | |
| tag->object.flags |= SEEN; | |
| if (tags) | |
| oid_array_append(&tags->oids, &obj->oid); | |
| obj = tag->tagged; | |
| } | |
| if (obj->type == OBJ_TAG) | |
| continue; | |
| /* We are now at a non-tag object. */ | |
| if (obj->flags & SEEN) | |
| continue; | |
| obj->flags |= SEEN; | |
| switch (obj->type) { | |
| case OBJ_TREE: | |
| if (pending->path && *pending->path) { | |
| char *path = xstrfmt("%s/", pending->path); | |
| add_path_to_list(ctx, path, OBJ_TREE, &obj->oid, 1); | |
| free(path); | |
| } else if (!pending->path || !info->trees) { | |
| oid_array_append(&tagged_trees->oids, &obj->oid); | |
| } else { | |
| add_path_to_list(ctx, root_path, OBJ_TREE, | |
| &obj->oid, 1); | |
| } | |
| break; | |
| case OBJ_BLOB: | |
| if (pending->path) | |
| add_path_to_list(ctx, pending->path, OBJ_BLOB, &obj->oid, 1); | |
| else | |
| oid_array_append(&tagged_blobs->oids, &obj->oid); | |
| break; | |
| case OBJ_COMMIT: | |
| /* Make sure it is in the object walk */ | |
| if (obj != pending->item) | |
| add_pending_object(info->revs, obj, ""); | |
| break; | |
| default: | |
| BUG("should not see any other type here"); | |
| } | |
| } | |
| /* | |
| * Add tag objects and tagged blobs if they exist. | |
| */ | |
| if (tagged_blobs) { | |
| if (tagged_blobs->oids.nr) { | |
| const char *tagged_blob_path = "/tagged-blobs"; | |
| tagged_blobs->type = OBJ_BLOB; | |
| tagged_blobs->maybe_interesting = 1; | |
| strmap_put(&ctx->paths_to_lists, tagged_blob_path, tagged_blobs); | |
| push_to_stack(ctx, tagged_blob_path); | |
| } else { | |
| oid_array_clear(&tagged_blobs->oids); | |
| free(tagged_blobs); | |
| } | |
| } | |
| if (tagged_trees) { | |
| if (tagged_trees->oids.nr) { | |
| const char *tagged_tree_path = "/tagged-trees"; | |
| tagged_trees->type = OBJ_TREE; | |
| tagged_trees->maybe_interesting = 1; | |
| strmap_put(&ctx->paths_to_lists, tagged_tree_path, tagged_trees); | |
| push_to_stack(ctx, tagged_tree_path); | |
| } else { | |
| oid_array_clear(&tagged_trees->oids); | |
| free(tagged_trees); | |
| } | |
| } | |
| if (tags) { | |
| if (tags->oids.nr) { | |
| const char *tag_path = "/tags"; | |
| tags->type = OBJ_TAG; | |
| tags->maybe_interesting = 1; | |
| strmap_put(&ctx->paths_to_lists, tag_path, tags); | |
| push_to_stack(ctx, tag_path); | |
| } else { | |
| oid_array_clear(&tags->oids); | |
| free(tags); | |
| } | |
| } | |
| return 0; | |
| } | |
| static int prepare_filters_one(struct path_walk_info *info, | |
| struct list_objects_filter_options *options) | |
| { | |
| switch (options->choice) { | |
| case LOFC_DISABLED: | |
| return 1; | |
| case LOFC_BLOB_NONE: | |
| if (info) { | |
| info->blobs = 0; | |
| list_objects_filter_release(options); | |
| } | |
| return 1; | |
| case LOFC_BLOB_LIMIT: | |
| if (info) { | |
| if (!options->blob_limit_value) | |
| info->blobs = 0; | |
| else if (!info->blob_limit || | |
| info->blob_limit > options->blob_limit_value) | |
| info->blob_limit = options->blob_limit_value; | |
| list_objects_filter_release(options); | |
| } | |
| return 1; | |
| case LOFC_TREE_DEPTH: | |
| if (options->tree_exclude_depth) { | |
| error(_("tree:%lu filter not supported by the path-walk API"), | |
| options->tree_exclude_depth); | |
| return 0; | |
| } | |
| if (info) { | |
| info->trees = 0; | |
| info->blobs = 0; | |
| } | |
| return 1; | |
| case LOFC_OBJECT_TYPE: | |
| if (info) { | |
| info->commits &= options->object_type == OBJ_COMMIT; | |
| info->tags &= options->object_type == OBJ_TAG; | |
| info->trees &= options->object_type == OBJ_TREE; | |
| info->blobs &= options->object_type == OBJ_BLOB; | |
| info->strict_types = 1; | |
| list_objects_filter_release(options); | |
| } | |
| return 1; | |
| case LOFC_SPARSE_OID: | |
| if (info) { | |
| struct object_id sparse_oid; | |
| struct repository *repo = info->revs->repo; | |
| if (info->pl) { | |
| warning(_("sparse filter cannot be combined with existing sparse patterns")); | |
| return 0; | |
| } | |
| if (repo_get_oid_with_flags(repo, | |
| options->sparse_oid_name, | |
| &sparse_oid, | |
| GET_OID_BLOB)) { | |
| error(_("unable to access sparse blob in '%s'"), | |
| options->sparse_oid_name); | |
| return 0; | |
| } | |
| CALLOC_ARRAY(info->pl, 1); | |
| info->pl->use_cone_patterns = 1; | |
| if (add_patterns_from_blob_to_list(&sparse_oid, "", 0, | |
| info->pl) < 0) { | |
| clear_pattern_list(info->pl); | |
| FREE_AND_NULL(info->pl); | |
| error(_("unable to parse sparse filter data in '%s'"), | |
| oid_to_hex(&sparse_oid)); | |
| return 0; | |
| } | |
| if (!info->pl->use_cone_patterns) { | |
| clear_pattern_list(info->pl); | |
| FREE_AND_NULL(info->pl); | |
| warning(_("sparse filter is not cone-mode compatible")); | |
| return 0; | |
| } | |
| } | |
| return 1; | |
| case LOFC_COMBINE: | |
| for (size_t i = 0; i < options->sub_nr; i++) { | |
| if (!prepare_filters_one(info, &options->sub[i])) | |
| return 0; | |
| } | |
| return 1; | |
| default: | |
| error(_("object filter '%s' not supported by the path-walk API"), | |
| list_objects_filter_spec(options)); | |
| return 0; | |
| } | |
| } | |
| static int prepare_filters(struct path_walk_info *info, | |
| struct list_objects_filter_options *options) | |
| { | |
| if (!prepare_filters_one(info, options)) | |
| return 0; | |
| if (info) | |
| list_objects_filter_release(options); | |
| return 1; | |
| } | |
| int path_walk_filter_compatible(struct list_objects_filter_options *options) | |
| { | |
| return prepare_filters(NULL, options); | |
| } | |
| /** | |
| * Given the configuration of 'info', walk the commits based on 'info->revs' and | |
| * call 'info->path_fn' on each discovered path. | |
| * | |
| * Returns nonzero on an error. | |
| */ | |
| int walk_objects_by_path(struct path_walk_info *info) | |
| { | |
| int ret; | |
| size_t commits_nr = 0, paths_nr = 0; | |
| struct commit *c; | |
| char *path; | |
| struct type_and_oid_list *root_tree_list; | |
| struct type_and_oid_list *commit_list; | |
| struct path_walk_context ctx = { | |
| .repo = info->revs->repo, | |
| .revs = info->revs, | |
| .info = info, | |
| .path_stack = { | |
| .compare = compare_by_type, | |
| .cb_data = &ctx | |
| }, | |
| .path_stack_pushed = STRSET_INIT, | |
| .paths_to_lists = STRMAP_INIT | |
| }; | |
| trace2_region_enter("path-walk", "commit-walk", info->revs->repo); | |
| if (!prepare_filters(info, &info->revs->filter)) | |
| return -1; | |
| CALLOC_ARRAY(commit_list, 1); | |
| commit_list->type = OBJ_COMMIT; | |
| if (info->tags) | |
| info->revs->tag_objects = 1; | |
| if (ctx.revs->prune_data.nr) { | |
| if (!ctx.revs->prune_data.has_wildcard && | |
| !ctx.revs->prune_data.magic) | |
| ctx.exact_pathspecs = 1; | |
| } | |
| /* Insert a single list for the root tree into the paths. */ | |
| CALLOC_ARRAY(root_tree_list, 1); | |
| root_tree_list->type = OBJ_TREE; | |
| root_tree_list->maybe_interesting = 1; | |
| strmap_put(&ctx.paths_to_lists, root_path, root_tree_list); | |
| push_to_stack(&ctx, root_path); | |
| /* | |
| * Ensure that prepare_revision_walk() keeps all pending objects | |
| * even through an object type filter. | |
| */ | |
| info->revs->blob_objects = info->revs->tree_objects = 1; | |
| if (prepare_revision_walk(info->revs)) | |
| die(_("failed to setup revision walk")); | |
| info->revs->blob_objects = info->blobs; | |
| info->revs->tree_objects = info->trees; | |
| /* | |
| * Walk trees to mark them as UNINTERESTING. | |
| * This is particularly important when 'edge_aggressive' is set. | |
| */ | |
| info->revs->edge_hint_aggressive = info->edge_aggressive; | |
| edge_repo = info->revs->repo; | |
| edge_tree_list = root_tree_list; | |
| mark_edges_uninteresting(info->revs, show_edge, | |
| info->prune_all_uninteresting); | |
| edge_repo = NULL; | |
| edge_tree_list = NULL; | |
| info->revs->blob_objects = info->revs->tree_objects = 0; | |
| trace2_region_enter("path-walk", "pending-walk", info->revs->repo); | |
| ret = setup_pending_objects(info, &ctx); | |
| trace2_region_leave("path-walk", "pending-walk", info->revs->repo); | |
| if (ret) | |
| return ret; | |
| while ((c = get_revision(info->revs))) { | |
| struct object_id *oid; | |
| struct tree *t; | |
| commits_nr++; | |
| if (info->commits) | |
| oid_array_append(&commit_list->oids, | |
| &c->object.oid); | |
| /* If we only care about commits, then skip trees. */ | |
| if (!info->trees && !info->blobs) | |
| continue; | |
| oid = get_commit_tree_oid(c); | |
| t = lookup_tree(info->revs->repo, oid); | |
| if (!t) { | |
| error("could not find tree %s", oid_to_hex(oid)); | |
| return -1; | |
| } | |
| if (t->object.flags & SEEN) | |
| continue; | |
| t->object.flags |= SEEN; | |
| oid_array_append(&root_tree_list->oids, oid); | |
| } | |
| trace2_data_intmax("path-walk", ctx.repo, "commits", commits_nr); | |
| trace2_region_leave("path-walk", "commit-walk", info->revs->repo); | |
| /* Track all commits. */ | |
| if (info->commits && commit_list->oids.nr) | |
| ret = info->path_fn("", &commit_list->oids, OBJ_COMMIT, | |
| info->path_fn_data); | |
| oid_array_clear(&commit_list->oids); | |
| free(commit_list); | |
| trace2_region_enter("path-walk", "path-walk", info->revs->repo); | |
| while (!ret && (path = prio_queue_get(&ctx.path_stack))) { | |
| paths_nr++; | |
| ret = walk_path(&ctx, path); | |
| free(path); | |
| } | |
| /* Are there paths remaining? Likely they are from indexed objects. */ | |
| if (!strmap_empty(&ctx.paths_to_lists)) { | |
| struct hashmap_iter iter; | |
| struct strmap_entry *entry; | |
| char *path; | |
| strmap_for_each_entry(&ctx.paths_to_lists, &iter, entry) | |
| push_to_stack(&ctx, entry->key); | |
| while (!ret && (path = prio_queue_get(&ctx.path_stack))) { | |
| paths_nr++; | |
| ret = walk_path(&ctx, path); | |
| free(path); | |
| } | |
| } | |
| trace2_data_intmax("path-walk", ctx.repo, "paths", paths_nr); | |
| trace2_region_leave("path-walk", "path-walk", info->revs->repo); | |
| clear_paths_to_lists(&ctx.paths_to_lists); | |
| strset_clear(&ctx.path_stack_pushed); | |
| clear_prio_queue(&ctx.path_stack); | |
| return ret; | |
| } | |
| void path_walk_info_init(struct path_walk_info *info) | |
| { | |
| struct path_walk_info empty = PATH_WALK_INFO_INIT; | |
| memcpy(info, &empty, sizeof(empty)); | |
| } | |
| void path_walk_info_clear(struct path_walk_info *info) | |
| { | |
| if (info->pl) { | |
| clear_pattern_list(info->pl); | |
| free(info->pl); | |
| } | |
| } |