1 /*
2  * Copyright (C) the libgit2 contributors. All rights reserved.
3  *
4  * This file is part of libgit2, distributed under the GNU GPL v2 with
5  * a Linking Exception. For full terms see the included COPYING file.
6  */
7 
8 #include "commit_list.h"
9 
10 #include "revwalk.h"
11 #include "pool.h"
12 #include "odb.h"
13 
git_commit_list_time_cmp(const void * a,const void * b)14 int git_commit_list_time_cmp(const void *a, const void *b)
15 {
16 	int64_t time_a = ((git_commit_list_node *) a)->time;
17 	int64_t time_b = ((git_commit_list_node *) b)->time;
18 
19 	if (time_a < time_b)
20 		return 1;
21 	if (time_a > time_b)
22 		return -1;
23 
24 	return 0;
25 }
26 
git_commit_list_insert(git_commit_list_node * item,git_commit_list ** list_p)27 git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p)
28 {
29 	git_commit_list *new_list = git__malloc(sizeof(git_commit_list));
30 	if (new_list != NULL) {
31 		new_list->item = item;
32 		new_list->next = *list_p;
33 	}
34 	*list_p = new_list;
35 	return new_list;
36 }
37 
git_commit_list_insert_by_date(git_commit_list_node * item,git_commit_list ** list_p)38 git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p)
39 {
40 	git_commit_list **pp = list_p;
41 	git_commit_list *p;
42 
43 	while ((p = *pp) != NULL) {
44 		if (git_commit_list_time_cmp(p->item, item) > 0)
45 			break;
46 
47 		pp = &p->next;
48 	}
49 
50 	return git_commit_list_insert(item, pp);
51 }
52 
git_commit_list_alloc_node(git_revwalk * walk)53 git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk)
54 {
55 	return (git_commit_list_node *)git_pool_mallocz(&walk->commit_pool, 1);
56 }
57 
commit_error(git_commit_list_node * commit,const char * msg)58 static int commit_error(git_commit_list_node *commit, const char *msg)
59 {
60 	char commit_oid[GIT_OID_HEXSZ + 1];
61 	git_oid_fmt(commit_oid, &commit->oid);
62 	commit_oid[GIT_OID_HEXSZ] = '\0';
63 
64 	git_error_set(GIT_ERROR_ODB, "failed to parse commit %s - %s", commit_oid, msg);
65 
66 	return -1;
67 }
68 
alloc_parents(git_revwalk * walk,git_commit_list_node * commit,size_t n_parents)69 static git_commit_list_node **alloc_parents(
70 	git_revwalk *walk, git_commit_list_node *commit, size_t n_parents)
71 {
72 	size_t bytes;
73 
74 	if (n_parents <= PARENTS_PER_COMMIT)
75 		return (git_commit_list_node **)((char *)commit + sizeof(git_commit_list_node));
76 
77 	if (git__multiply_sizet_overflow(&bytes, n_parents, sizeof(git_commit_list_node *)))
78 		return NULL;
79 
80 	return (git_commit_list_node **)git_pool_malloc(&walk->commit_pool, bytes);
81 }
82 
83 
git_commit_list_free(git_commit_list ** list_p)84 void git_commit_list_free(git_commit_list **list_p)
85 {
86 	git_commit_list *list = *list_p;
87 
88 	if (list == NULL)
89 		return;
90 
91 	while (list) {
92 		git_commit_list *temp = list;
93 		list = temp->next;
94 		git__free(temp);
95 	}
96 
97 	*list_p = NULL;
98 }
99 
git_commit_list_pop(git_commit_list ** stack)100 git_commit_list_node *git_commit_list_pop(git_commit_list **stack)
101 {
102 	git_commit_list *top = *stack;
103 	git_commit_list_node *item = top ? top->item : NULL;
104 
105 	if (top) {
106 		*stack = top->next;
107 		git__free(top);
108 	}
109 	return item;
110 }
111 
commit_quick_parse(git_revwalk * walk,git_commit_list_node * commit,const uint8_t * buffer,size_t buffer_len)112 static int commit_quick_parse(
113 	git_revwalk *walk,
114 	git_commit_list_node *commit,
115 	const uint8_t *buffer,
116 	size_t buffer_len)
117 {
118 	const size_t parent_len = strlen("parent ") + GIT_OID_HEXSZ + 1;
119 	const uint8_t *buffer_end = buffer + buffer_len;
120 	const uint8_t *parents_start, *committer_start;
121 	int i, parents = 0;
122 	int64_t commit_time;
123 
124 	buffer += strlen("tree ") + GIT_OID_HEXSZ + 1;
125 
126 	parents_start = buffer;
127 	while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", strlen("parent ")) == 0) {
128 		parents++;
129 		buffer += parent_len;
130 	}
131 
132 	commit->parents = alloc_parents(walk, commit, parents);
133 	GIT_ERROR_CHECK_ALLOC(commit->parents);
134 
135 	buffer = parents_start;
136 	for (i = 0; i < parents; ++i) {
137 		git_oid oid;
138 
139 		if (git_oid_fromstr(&oid, (const char *)buffer + strlen("parent ")) < 0)
140 			return -1;
141 
142 		commit->parents[i] = git_revwalk__commit_lookup(walk, &oid);
143 		if (commit->parents[i] == NULL)
144 			return -1;
145 
146 		buffer += parent_len;
147 	}
148 
149 	commit->out_degree = (unsigned short)parents;
150 
151 	if ((committer_start = buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)
152 		return commit_error(commit, "object is corrupted");
153 
154 	buffer++;
155 
156 	if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)
157 		return commit_error(commit, "object is corrupted");
158 
159 	/* Skip trailing spaces */
160 	while (buffer > committer_start && git__isspace(*buffer))
161 		buffer--;
162 
163 	/* Seek for the beginning of the pack of digits */
164 	while (buffer > committer_start && git__isdigit(*buffer))
165 		buffer--;
166 
167 	/* Skip potential timezone offset */
168 	if ((buffer > committer_start) && (*buffer == '+' || *buffer == '-')) {
169 		buffer--;
170 
171 		while (buffer > committer_start && git__isspace(*buffer))
172 			buffer--;
173 
174 		while (buffer > committer_start && git__isdigit(*buffer))
175 			buffer--;
176 	}
177 
178 	if ((buffer == committer_start) ||
179 	    (git__strntol64(&commit_time, (char *)(buffer + 1),
180 			    buffer_end - buffer + 1, NULL, 10) < 0))
181 		return commit_error(commit, "cannot parse commit time");
182 
183 	commit->time = commit_time;
184 	commit->parsed = 1;
185 	return 0;
186 }
187 
git_commit_list_parse(git_revwalk * walk,git_commit_list_node * commit)188 int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit)
189 {
190 	git_odb_object *obj;
191 	int error;
192 
193 	if (commit->parsed)
194 		return 0;
195 
196 	if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0)
197 		return error;
198 
199 	if (obj->cached.type != GIT_OBJECT_COMMIT) {
200 		git_error_set(GIT_ERROR_INVALID, "object is no commit object");
201 		error = -1;
202 	} else
203 		error = commit_quick_parse(
204 			walk, commit,
205 			(const uint8_t *)git_odb_object_data(obj),
206 			git_odb_object_size(obj));
207 
208 	git_odb_object_free(obj);
209 	return error;
210 }
211 
212