1#include <ctype.h>
2#include "tag.h"
3#include "commit.h"
4#include "cache.h"
5
6const char *commit_type = "commit";
7
8static struct commit *check_commit(struct object *obj, const unsigned char *sha1)
9{
10 if (obj->type != commit_type) {
11 error("Object %s is a %s, not a commit",
12 sha1_to_hex(sha1), obj->type);
13 return NULL;
14 }
15 return (struct commit *) obj;
16}
17
18struct commit *lookup_commit_reference(const unsigned char *sha1)
19{
20 struct object *obj = parse_object(sha1);
21
22 if (!obj)
23 return NULL;
24 if (obj->type == tag_type)
25 obj = ((struct tag *)obj)->tagged;
26 return check_commit(obj, sha1);
27}
28
29struct commit *lookup_commit(const unsigned char *sha1)
30{
31 struct object *obj = lookup_object(sha1);
32 if (!obj) {
33 struct commit *ret = xmalloc(sizeof(struct commit));
34 memset(ret, 0, sizeof(struct commit));
35 created_object(sha1, &ret->object);
36 ret->object.type = commit_type;
37 return ret;
38 }
39 if (!obj->type)
40 obj->type = commit_type;
41 return check_commit(obj, sha1);
42}
43
44static unsigned long parse_commit_date(const char *buf)
45{
46 unsigned long date;
47
48 if (memcmp(buf, "author", 6))
49 return 0;
50 while (*buf++ != '\n')
51 /* nada */;
52 if (memcmp(buf, "committer", 9))
53 return 0;
54 while (*buf++ != '>')
55 /* nada */;
56 date = strtoul(buf, NULL, 10);
57 if (date == ULONG_MAX)
58 date = 0;
59 return date;
60}
61
62int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
63{
64 void *bufptr = buffer;
65 unsigned char parent[20];
66 struct commit_list **pptr;
67
68 if (item->object.parsed)
69 return 0;
70 item->object.parsed = 1;
71 get_sha1_hex(bufptr + 5, parent);
72 item->tree = lookup_tree(parent);
73 if (item->tree)
74 add_ref(&item->object, &item->tree->object);
75 bufptr += 46; /* "tree " + "hex sha1" + "\n" */
76 pptr = &item->parents;
77 while (!memcmp(bufptr, "parent ", 7) &&
78 !get_sha1_hex(bufptr + 7, parent)) {
79 struct commit *new_parent = lookup_commit(parent);
80 if (new_parent) {
81 pptr = &commit_list_insert(new_parent, pptr)->next;
82 add_ref(&item->object, &new_parent->object);
83 }
84 bufptr += 48;
85 }
86 item->date = parse_commit_date(bufptr);
87 return 0;
88}
89
90int parse_commit(struct commit *item)
91{
92 char type[20];
93 void *buffer;
94 unsigned long size;
95 int ret;
96
97 if (item->object.parsed)
98 return 0;
99 buffer = read_sha1_file(item->object.sha1, type, &size);
100 if (!buffer)
101 return error("Could not read %s",
102 sha1_to_hex(item->object.sha1));
103 if (strcmp(type, commit_type)) {
104 free(buffer);
105 return error("Object %s not a commit",
106 sha1_to_hex(item->object.sha1));
107 }
108 ret = parse_commit_buffer(item, buffer, size);
109 if (!ret) {
110 item->buffer = buffer;
111 return 0;
112 }
113 free(buffer);
114 return ret;
115}
116
117struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
118{
119 struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
120 new_list->item = item;
121 new_list->next = *list_p;
122 *list_p = new_list;
123 return new_list;
124}
125
126void free_commit_list(struct commit_list *list)
127{
128 while (list) {
129 struct commit_list *temp = list;
130 list = temp->next;
131 free(temp);
132 }
133}
134
135void insert_by_date(struct commit_list **list, struct commit *item)
136{
137 struct commit_list **pp = list;
138 struct commit_list *p;
139 while ((p = *pp) != NULL) {
140 if (p->item->date < item->date) {
141 break;
142 }
143 pp = &p->next;
144 }
145 commit_list_insert(item, pp);
146}
147
148
149void sort_by_date(struct commit_list **list)
150{
151 struct commit_list *ret = NULL;
152 while (*list) {
153 insert_by_date(&ret, (*list)->item);
154 *list = (*list)->next;
155 }
156 *list = ret;
157}
158
159struct commit *pop_most_recent_commit(struct commit_list **list,
160 unsigned int mark)
161{
162 struct commit *ret = (*list)->item;
163 struct commit_list *parents = ret->parents;
164 struct commit_list *old = *list;
165
166 *list = (*list)->next;
167 free(old);
168
169 while (parents) {
170 struct commit *commit = parents->item;
171 parse_commit(commit);
172 if (!(commit->object.flags & mark)) {
173 commit->object.flags |= mark;
174 insert_by_date(list, commit);
175 }
176 parents = parents->next;
177 }
178 return ret;
179}
180
181/*
182 * Generic support for pretty-printing the header
183 */
184static int get_one_line(const char *msg, unsigned long len)
185{
186 int ret = 0;
187
188 while (len--) {
189 char c = *msg++;
190 ret++;
191 if (c == '\n')
192 break;
193 if (!c)
194 return 0;
195 }
196 return ret;
197}
198
199static int add_author_info(enum cmit_fmt fmt, char *buf, const char *line, int len)
200{
201 char *date;
202 unsigned int namelen;
203 unsigned long time;
204 int tz, ret;
205
206 line += strlen("author ");
207 date = strchr(line, '>');
208 if (!date)
209 return 0;
210 namelen = ++date - line;
211 time = strtoul(date, &date, 10);
212 tz = strtol(date, NULL, 10);
213
214 ret = sprintf(buf, "Author: %.*s\n", namelen, line);
215 if (fmt == CMIT_FMT_MEDIUM)
216 ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz));
217 return ret;
218}
219
220static int is_empty_line(const char *line, int len)
221{
222 while (len && isspace(line[len-1]))
223 len--;
224 return !len;
225}
226
227static int add_parent_info(enum cmit_fmt fmt, char *buf, const char *line, int parents)
228{
229 int offset = 0;
230 switch (parents) {
231 case 1:
232 break;
233 case 2:
234 /* Go back to the previous line: 40 characters of previous parent, and one '\n' */
235 offset = sprintf(buf, "Merge: %.40s\n", line-41);
236 /* Fallthrough */
237 default:
238 /* Replace the previous '\n' with a space */
239 buf[offset-1] = ' ';
240 offset += sprintf(buf + offset, "%.40s\n", line+7);
241 }
242 return offset;
243}
244
245unsigned long pretty_print_commit(enum cmit_fmt fmt, const char *msg, unsigned long len, char *buf, unsigned long space)
246{
247 int hdr = 1, body = 0;
248 unsigned long offset = 0;
249 int parents = 0;
250
251 for (;;) {
252 const char *line = msg;
253 int linelen = get_one_line(msg, len);
254
255 if (!linelen)
256 break;
257
258 /*
259 * We want some slop for indentation and a possible
260 * final "...". Thus the "+ 20".
261 */
262 if (offset + linelen + 20 > space) {
263 memcpy(buf + offset, " ...\n", 8);
264 offset += 8;
265 break;
266 }
267
268 msg += linelen;
269 len -= linelen;
270 if (hdr) {
271 if (linelen == 1) {
272 hdr = 0;
273 buf[offset++] = '\n';
274 continue;
275 }
276 if (fmt == CMIT_FMT_RAW) {
277 memcpy(buf + offset, line, linelen);
278 offset += linelen;
279 continue;
280 }
281 if (!memcmp(line, "parent ", 7)) {
282 if (linelen != 48)
283 die("bad parent line in commit");
284 offset += add_parent_info(fmt, buf + offset, line, ++parents);
285 }
286 if (!memcmp(line, "author ", 7))
287 offset += add_author_info(fmt, buf + offset, line, linelen);
288 continue;
289 }
290
291 if (is_empty_line(line, linelen)) {
292 if (!body)
293 continue;
294 if (fmt == CMIT_FMT_SHORT)
295 break;
296 } else {
297 body = 1;
298 }
299 memset(buf + offset, ' ', 4);
300 memcpy(buf + offset + 4, line, linelen);
301 offset += linelen + 4;
302 }
303 /* Make sure there is an EOLN */
304 if (buf[offset - 1] != '\n')
305 buf[offset++] = '\n';
306 buf[offset] = '\0';
307 return offset;
308}
309
310struct commit *pop_commit(struct commit_list **stack)
311{
312 struct commit_list *top = *stack;
313 struct commit *item = top ? top->item : NULL;
314
315 if (top) {
316 *stack = top->next;
317 free(top);
318 }
319 return item;
320}
321
322int count_parents(struct commit * commit)
323{
324 int count = 0;
325 struct commit_list * parents = commit->parents;
326 for (count=0;parents; parents=parents->next,count++)
327 ;
328 return count;
329}
330