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