diff options
Diffstat (limited to 'src/libgit2/commit_list.c')
-rw-r--r-- | src/libgit2/commit_list.c | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/src/libgit2/commit_list.c b/src/libgit2/commit_list.c new file mode 100644 index 000000000..4585508bc --- /dev/null +++ b/src/libgit2/commit_list.c @@ -0,0 +1,208 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "commit_list.h" + +#include "revwalk.h" +#include "pool.h" +#include "odb.h" +#include "commit.h" + +int git_commit_list_generation_cmp(const void *a, const void *b) +{ + uint32_t generation_a = ((git_commit_list_node *) a)->generation; + uint32_t generation_b = ((git_commit_list_node *) b)->generation; + + if (!generation_a || !generation_b) { + /* Fall back to comparing by timestamps if at least one commit lacks a generation. */ + return git_commit_list_time_cmp(a, b); + } + + if (generation_a < generation_b) + return 1; + if (generation_a > generation_b) + return -1; + + return 0; +} + +int git_commit_list_time_cmp(const void *a, const void *b) +{ + int64_t time_a = ((git_commit_list_node *) a)->time; + int64_t time_b = ((git_commit_list_node *) b)->time; + + if (time_a < time_b) + return 1; + if (time_a > time_b) + return -1; + + return 0; +} + +git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p) +{ + git_commit_list *new_list = git__malloc(sizeof(git_commit_list)); + if (new_list != NULL) { + new_list->item = item; + new_list->next = *list_p; + } + *list_p = new_list; + return new_list; +} + +git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p) +{ + git_commit_list **pp = list_p; + git_commit_list *p; + + while ((p = *pp) != NULL) { + if (git_commit_list_time_cmp(p->item, item) > 0) + break; + + pp = &p->next; + } + + return git_commit_list_insert(item, pp); +} + +git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk) +{ + return (git_commit_list_node *)git_pool_mallocz(&walk->commit_pool, 1); +} + +static git_commit_list_node **alloc_parents( + git_revwalk *walk, git_commit_list_node *commit, size_t n_parents) +{ + size_t bytes; + + if (n_parents <= PARENTS_PER_COMMIT) + return (git_commit_list_node **)((char *)commit + sizeof(git_commit_list_node)); + + if (git__multiply_sizet_overflow(&bytes, n_parents, sizeof(git_commit_list_node *))) + return NULL; + + return (git_commit_list_node **)git_pool_malloc(&walk->commit_pool, bytes); +} + + +void git_commit_list_free(git_commit_list **list_p) +{ + git_commit_list *list = *list_p; + + if (list == NULL) + return; + + while (list) { + git_commit_list *temp = list; + list = temp->next; + git__free(temp); + } + + *list_p = NULL; +} + +git_commit_list_node *git_commit_list_pop(git_commit_list **stack) +{ + git_commit_list *top = *stack; + git_commit_list_node *item = top ? top->item : NULL; + + if (top) { + *stack = top->next; + git__free(top); + } + return item; +} + +static int commit_quick_parse( + git_revwalk *walk, + git_commit_list_node *node, + git_odb_object *obj) +{ + git_oid *parent_oid; + git_commit *commit; + size_t i; + + commit = git__calloc(1, sizeof(*commit)); + GIT_ERROR_CHECK_ALLOC(commit); + commit->object.repo = walk->repo; + + if (git_commit__parse_ext(commit, obj, GIT_COMMIT_PARSE_QUICK) < 0) { + git__free(commit); + return -1; + } + + if (!git__is_uint16(git_array_size(commit->parent_ids))) { + git__free(commit); + git_error_set(GIT_ERROR_INVALID, "commit has more than 2^16 parents"); + return -1; + } + + node->generation = 0; + node->time = commit->committer->when.time; + node->out_degree = (uint16_t) git_array_size(commit->parent_ids); + node->parents = alloc_parents(walk, node, node->out_degree); + GIT_ERROR_CHECK_ALLOC(node->parents); + + git_array_foreach(commit->parent_ids, i, parent_oid) { + node->parents[i] = git_revwalk__commit_lookup(walk, parent_oid); + } + + git_commit__free(commit); + + node->parsed = 1; + + return 0; +} + +int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit) +{ + git_odb_object *obj; + git_commit_graph_file *cgraph_file = NULL; + int error; + + if (commit->parsed) + return 0; + + /* Let's try to use the commit graph first. */ + git_odb__get_commit_graph_file(&cgraph_file, walk->odb); + if (cgraph_file) { + git_commit_graph_entry e; + + error = git_commit_graph_entry_find(&e, cgraph_file, &commit->oid, GIT_OID_RAWSZ); + if (error == 0 && git__is_uint16(e.parent_count)) { + size_t i; + commit->generation = (uint32_t)e.generation; + commit->time = e.commit_time; + commit->out_degree = (uint16_t)e.parent_count; + commit->parents = alloc_parents(walk, commit, commit->out_degree); + GIT_ERROR_CHECK_ALLOC(commit->parents); + + for (i = 0; i < commit->out_degree; ++i) { + git_commit_graph_entry parent; + error = git_commit_graph_entry_parent(&parent, cgraph_file, &e, i); + if (error < 0) + return error; + commit->parents[i] = git_revwalk__commit_lookup(walk, &parent.sha1); + } + commit->parsed = 1; + return 0; + } + } + + if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0) + return error; + + if (obj->cached.type != GIT_OBJECT_COMMIT) { + git_error_set(GIT_ERROR_INVALID, "object is no commit object"); + error = -1; + } else + error = commit_quick_parse(walk, commit, obj); + + git_odb_object_free(obj); + return error; +} + |