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