[PATCH] git-apply: Don't barf when --stat'ing a diff with no line changes.
[git.git] / commit.c
1 #include <ctype.h>
2 #include "tag.h"
3 #include "commit.h"
4 #include "cache.h"
5
6 const char *commit_type = "commit";
7
8 static 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
18 struct 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
29 struct 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
44 static 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
62 int 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
90 int 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
117 struct 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
126 void 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
135 void 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         
149 void 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
159 struct 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  */
184 static 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
199 static 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
220 static int is_empty_line(const char *line, int len)
221 {
222         while (len && isspace(line[len-1]))
223                 len--;
224         return !len;
225 }
226
227 static 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
245 unsigned 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
310 struct 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
322 int 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