Merge branch 'jn/web' (early part)
[git] / builtin-log.c
1 /*
2  * Builtin "git log" and related commands (show, whatchanged)
3  *
4  * (C) Copyright 2006 Linus Torvalds
5  *               2006 Junio Hamano
6  */
7 #include "cache.h"
8 #include "commit.h"
9 #include "diff.h"
10 #include "revision.h"
11 #include "log-tree.h"
12 #include "builtin.h"
13 #include "tag.h"
14 #include <time.h>
15 #include <sys/time.h>
16
17 static int default_show_root = 1;
18
19 /* this is in builtin-diff.c */
20 void add_head(struct rev_info *revs);
21
22 static void cmd_log_init(int argc, const char **argv, const char *prefix,
23                       struct rev_info *rev)
24 {
25         rev->abbrev = DEFAULT_ABBREV;
26         rev->commit_format = CMIT_FMT_DEFAULT;
27         rev->verbose_header = 1;
28         rev->show_root_diff = default_show_root;
29         argc = setup_revisions(argc, argv, rev, "HEAD");
30         if (rev->diffopt.pickaxe || rev->diffopt.filter)
31                 rev->always_show_header = 0;
32         if (argc > 1)
33                 die("unrecognized argument: %s", argv[1]);
34 }
35
36 static int cmd_log_walk(struct rev_info *rev)
37 {
38         struct commit *commit;
39
40         prepare_revision_walk(rev);
41         while ((commit = get_revision(rev)) != NULL) {
42                 log_tree_commit(rev, commit);
43                 free(commit->buffer);
44                 commit->buffer = NULL;
45                 free_commit_list(commit->parents);
46                 commit->parents = NULL;
47         }
48         return 0;
49 }
50
51 static int git_log_config(const char *var, const char *value)
52 {
53         if (!strcmp(var, "log.showroot")) {
54                 default_show_root = git_config_bool(var, value);
55                 return 0;
56         }
57         return git_diff_ui_config(var, value);
58 }
59
60 int cmd_whatchanged(int argc, const char **argv, const char *prefix)
61 {
62         struct rev_info rev;
63
64         git_config(git_log_config);
65         init_revisions(&rev, prefix);
66         rev.diff = 1;
67         rev.diffopt.recursive = 1;
68         rev.simplify_history = 0;
69         cmd_log_init(argc, argv, prefix, &rev);
70         if (!rev.diffopt.output_format)
71                 rev.diffopt.output_format = DIFF_FORMAT_RAW;
72         return cmd_log_walk(&rev);
73 }
74
75 static int show_object(const unsigned char *sha1, int suppress_header)
76 {
77         unsigned long size;
78         char type[20];
79         char *buf = read_sha1_file(sha1, type, &size);
80         int offset = 0;
81
82         if (!buf)
83                 return error("Could not read object %s", sha1_to_hex(sha1));
84
85         if (suppress_header)
86                 while (offset < size && buf[offset++] != '\n') {
87                         int new_offset = offset;
88                         while (new_offset < size && buf[new_offset++] != '\n')
89                                 ; /* do nothing */
90                         offset = new_offset;
91                 }
92
93         if (offset < size)
94                 fwrite(buf + offset, size - offset, 1, stdout);
95         free(buf);
96         return 0;
97 }
98
99 static int show_tree_object(const unsigned char *sha1,
100                 const char *base, int baselen,
101                 const char *pathname, unsigned mode, int stage)
102 {
103         printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : "");
104         return 0;
105 }
106
107 int cmd_show(int argc, const char **argv, const char *prefix)
108 {
109         struct rev_info rev;
110         struct object_array_entry *objects;
111         int i, count, ret = 0;
112
113         git_config(git_log_config);
114         init_revisions(&rev, prefix);
115         rev.diff = 1;
116         rev.diffopt.recursive = 1;
117         rev.combine_merges = 1;
118         rev.dense_combined_merges = 1;
119         rev.always_show_header = 1;
120         rev.ignore_merges = 0;
121         rev.no_walk = 1;
122         cmd_log_init(argc, argv, prefix, &rev);
123
124         count = rev.pending.nr;
125         objects = rev.pending.objects;
126         for (i = 0; i < count && !ret; i++) {
127                 struct object *o = objects[i].item;
128                 const char *name = objects[i].name;
129                 switch (o->type) {
130                 case OBJ_BLOB:
131                         ret = show_object(o->sha1, 0);
132                         break;
133                 case OBJ_TAG: {
134                         struct tag *t = (struct tag *)o;
135
136                         printf("%stag %s%s\n\n",
137                                         diff_get_color(rev.diffopt.color_diff,
138                                                 DIFF_COMMIT),
139                                         t->tag,
140                                         diff_get_color(rev.diffopt.color_diff,
141                                                 DIFF_RESET));
142                         ret = show_object(o->sha1, 1);
143                         objects[i].item = (struct object *)t->tagged;
144                         i--;
145                         break;
146                 }
147                 case OBJ_TREE:
148                         printf("%stree %s%s\n\n",
149                                         diff_get_color(rev.diffopt.color_diff,
150                                                 DIFF_COMMIT),
151                                         name,
152                                         diff_get_color(rev.diffopt.color_diff,
153                                                 DIFF_RESET));
154                         read_tree_recursive((struct tree *)o, "", 0, 0, NULL,
155                                         show_tree_object);
156                         break;
157                 case OBJ_COMMIT:
158                         rev.pending.nr = rev.pending.alloc = 0;
159                         rev.pending.objects = NULL;
160                         add_object_array(o, name, &rev.pending);
161                         ret = cmd_log_walk(&rev);
162                         break;
163                 default:
164                         ret = error("Unknown type: %d", o->type);
165                 }
166         }
167         free(objects);
168         return ret;
169 }
170
171 int cmd_log(int argc, const char **argv, const char *prefix)
172 {
173         struct rev_info rev;
174
175         git_config(git_log_config);
176         init_revisions(&rev, prefix);
177         rev.always_show_header = 1;
178         cmd_log_init(argc, argv, prefix, &rev);
179         return cmd_log_walk(&rev);
180 }
181
182 static int istitlechar(char c)
183 {
184         return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
185                 (c >= '0' && c <= '9') || c == '.' || c == '_';
186 }
187
188 static char *extra_headers = NULL;
189 static int extra_headers_size = 0;
190
191 static int git_format_config(const char *var, const char *value)
192 {
193         if (!strcmp(var, "format.headers")) {
194                 int len = strlen(value);
195                 extra_headers_size += len + 1;
196                 extra_headers = xrealloc(extra_headers, extra_headers_size);
197                 extra_headers[extra_headers_size - len - 1] = 0;
198                 strcat(extra_headers, value);
199                 return 0;
200         }
201         if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
202                 return 0;
203         }
204         return git_log_config(var, value);
205 }
206
207
208 static FILE *realstdout = NULL;
209 static const char *output_directory = NULL;
210
211 static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
212 {
213         char filename[1024];
214         char *sol;
215         int len = 0;
216
217         if (output_directory) {
218                 strlcpy(filename, output_directory, 1010);
219                 len = strlen(filename);
220                 if (filename[len - 1] != '/')
221                         filename[len++] = '/';
222         }
223
224         sprintf(filename + len, "%04d", nr);
225         len = strlen(filename);
226
227         sol = strstr(commit->buffer, "\n\n");
228         if (sol) {
229                 int j, space = 1;
230
231                 sol += 2;
232                 /* strip [PATCH] or [PATCH blabla] */
233                 if (!keep_subject && !strncmp(sol, "[PATCH", 6)) {
234                         char *eos = strchr(sol + 6, ']');
235                         if (eos) {
236                                 while (isspace(*eos))
237                                         eos++;
238                                 sol = eos;
239                         }
240                 }
241
242                 for (j = 0; len < 1024 - 6 && sol[j] && sol[j] != '\n'; j++) {
243                         if (istitlechar(sol[j])) {
244                                 if (space) {
245                                         filename[len++] = '-';
246                                         space = 0;
247                                 }
248                                 filename[len++] = sol[j];
249                                 if (sol[j] == '.')
250                                         while (sol[j + 1] == '.')
251                                                 j++;
252                         } else
253                                 space = 1;
254                 }
255                 while (filename[len - 1] == '.' || filename[len - 1] == '-')
256                         len--;
257         }
258         strcpy(filename + len, ".txt");
259         fprintf(realstdout, "%s\n", filename);
260         freopen(filename, "w", stdout);
261 }
262
263 static int get_patch_id(struct commit *commit, struct diff_options *options,
264                 unsigned char *sha1)
265 {
266         if (commit->parents)
267                 diff_tree_sha1(commit->parents->item->object.sha1,
268                                commit->object.sha1, "", options);
269         else
270                 diff_root_tree_sha1(commit->object.sha1, "", options);
271         diffcore_std(options);
272         return diff_flush_patch_id(options, sha1);
273 }
274
275 static void get_patch_ids(struct rev_info *rev, struct diff_options *options, const char *prefix)
276 {
277         struct rev_info check_rev;
278         struct commit *commit;
279         struct object *o1, *o2;
280         unsigned flags1, flags2;
281         unsigned char sha1[20];
282
283         if (rev->pending.nr != 2)
284                 die("Need exactly one range.");
285
286         o1 = rev->pending.objects[0].item;
287         flags1 = o1->flags;
288         o2 = rev->pending.objects[1].item;
289         flags2 = o2->flags;
290
291         if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
292                 die("Not a range.");
293
294         diff_setup(options);
295         options->recursive = 1;
296         if (diff_setup_done(options) < 0)
297                 die("diff_setup_done failed");
298
299         /* given a range a..b get all patch ids for b..a */
300         init_revisions(&check_rev, prefix);
301         o1->flags ^= UNINTERESTING;
302         o2->flags ^= UNINTERESTING;
303         add_pending_object(&check_rev, o1, "o1");
304         add_pending_object(&check_rev, o2, "o2");
305         prepare_revision_walk(&check_rev);
306
307         while ((commit = get_revision(&check_rev)) != NULL) {
308                 /* ignore merges */
309                 if (commit->parents && commit->parents->next)
310                         continue;
311
312                 if (!get_patch_id(commit, options, sha1))
313                         created_object(sha1, xcalloc(1, sizeof(struct object)));
314         }
315
316         /* reset for next revision walk */
317         clear_commit_marks((struct commit *)o1,
318                         SEEN | UNINTERESTING | SHOWN | ADDED);
319         clear_commit_marks((struct commit *)o2,
320                         SEEN | UNINTERESTING | SHOWN | ADDED);
321         o1->flags = flags1;
322         o2->flags = flags2;
323 }
324
325 static void gen_message_id(char *dest, unsigned int length, char *base)
326 {
327         const char *committer = git_committer_info(1);
328         const char *email_start = strrchr(committer, '<');
329         const char *email_end = strrchr(committer, '>');
330         if(!email_start || !email_end || email_start > email_end - 1)
331                 die("Could not extract email from committer identity.");
332         snprintf(dest, length, "%s.%lu.git.%.*s", base,
333                  (unsigned long) time(NULL),
334                  (int)(email_end - email_start - 1), email_start + 1);
335 }
336
337 int cmd_format_patch(int argc, const char **argv, const char *prefix)
338 {
339         struct commit *commit;
340         struct commit **list = NULL;
341         struct rev_info rev;
342         int nr = 0, total, i, j;
343         int use_stdout = 0;
344         int numbered = 0;
345         int start_number = -1;
346         int keep_subject = 0;
347         int ignore_if_in_upstream = 0;
348         int thread = 0;
349         const char *in_reply_to = NULL;
350         struct diff_options patch_id_opts;
351         char *add_signoff = NULL;
352         char message_id[1024];
353         char ref_message_id[1024];
354
355         setup_ident();
356         git_config(git_format_config);
357         init_revisions(&rev, prefix);
358         rev.commit_format = CMIT_FMT_EMAIL;
359         rev.verbose_header = 1;
360         rev.diff = 1;
361         rev.combine_merges = 0;
362         rev.ignore_merges = 1;
363         rev.diffopt.msg_sep = "";
364         rev.diffopt.recursive = 1;
365
366         rev.extra_headers = extra_headers;
367
368         /*
369          * Parse the arguments before setup_revisions(), or something
370          * like "git fmt-patch -o a123 HEAD^.." may fail; a123 is
371          * possibly a valid SHA1.
372          */
373         for (i = 1, j = 1; i < argc; i++) {
374                 if (!strcmp(argv[i], "--stdout"))
375                         use_stdout = 1;
376                 else if (!strcmp(argv[i], "-n") ||
377                                 !strcmp(argv[i], "--numbered"))
378                         numbered = 1;
379                 else if (!strncmp(argv[i], "--start-number=", 15))
380                         start_number = strtol(argv[i] + 15, NULL, 10);
381                 else if (!strcmp(argv[i], "--start-number")) {
382                         i++;
383                         if (i == argc)
384                                 die("Need a number for --start-number");
385                         start_number = strtol(argv[i], NULL, 10);
386                 }
387                 else if (!strcmp(argv[i], "-k") ||
388                                 !strcmp(argv[i], "--keep-subject")) {
389                         keep_subject = 1;
390                         rev.total = -1;
391                 }
392                 else if (!strcmp(argv[i], "--output-directory") ||
393                          !strcmp(argv[i], "-o")) {
394                         i++;
395                         if (argc <= i)
396                                 die("Which directory?");
397                         if (output_directory)
398                                 die("Two output directories?");
399                         output_directory = argv[i];
400                 }
401                 else if (!strcmp(argv[i], "--signoff") ||
402                          !strcmp(argv[i], "-s")) {
403                         const char *committer;
404                         const char *endpos;
405                         committer = git_committer_info(1);
406                         endpos = strchr(committer, '>');
407                         if (!endpos)
408                                 die("bogos committer info %s\n", committer);
409                         add_signoff = xmalloc(endpos - committer + 2);
410                         memcpy(add_signoff, committer, endpos - committer + 1);
411                         add_signoff[endpos - committer + 1] = 0;
412                 }
413                 else if (!strcmp(argv[i], "--attach"))
414                         rev.mime_boundary = git_version_string;
415                 else if (!strncmp(argv[i], "--attach=", 9))
416                         rev.mime_boundary = argv[i] + 9;
417                 else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
418                         ignore_if_in_upstream = 1;
419                 else if (!strcmp(argv[i], "--thread"))
420                         thread = 1;
421                 else if (!strncmp(argv[i], "--in-reply-to=", 14))
422                         in_reply_to = argv[i] + 14;
423                 else if (!strcmp(argv[i], "--in-reply-to")) {
424                         i++;
425                         if (i == argc)
426                                 die("Need a Message-Id for --in-reply-to");
427                         in_reply_to = argv[i];
428                 }
429                 else
430                         argv[j++] = argv[i];
431         }
432         argc = j;
433
434         if (start_number < 0)
435                 start_number = 1;
436         if (numbered && keep_subject)
437                 die ("-n and -k are mutually exclusive.");
438
439         argc = setup_revisions(argc, argv, &rev, "HEAD");
440         if (argc > 1)
441                 die ("unrecognized argument: %s", argv[1]);
442
443         if (!rev.diffopt.output_format)
444                 rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
445
446         if (!output_directory)
447                 output_directory = prefix;
448
449         if (output_directory) {
450                 if (use_stdout)
451                         die("standard output, or directory, which one?");
452                 if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
453                         die("Could not create directory %s",
454                             output_directory);
455         }
456
457         if (rev.pending.nr == 1) {
458                 rev.pending.objects[0].item->flags |= UNINTERESTING;
459                 add_head(&rev);
460         }
461
462         if (ignore_if_in_upstream)
463                 get_patch_ids(&rev, &patch_id_opts, prefix);
464
465         if (!use_stdout)
466                 realstdout = fdopen(dup(1), "w");
467
468         prepare_revision_walk(&rev);
469         while ((commit = get_revision(&rev)) != NULL) {
470                 unsigned char sha1[20];
471
472                 /* ignore merges */
473                 if (commit->parents && commit->parents->next)
474                         continue;
475
476                 if (ignore_if_in_upstream &&
477                                 !get_patch_id(commit, &patch_id_opts, sha1) &&
478                                 lookup_object(sha1))
479                         continue;
480
481                 nr++;
482                 list = xrealloc(list, nr * sizeof(list[0]));
483                 list[nr - 1] = commit;
484         }
485         total = nr;
486         if (numbered)
487                 rev.total = total + start_number - 1;
488         rev.add_signoff = add_signoff;
489         rev.ref_message_id = in_reply_to;
490         while (0 <= --nr) {
491                 int shown;
492                 commit = list[nr];
493                 rev.nr = total - nr + (start_number - 1);
494                 /* Make the second and subsequent mails replies to the first */
495                 if (thread) {
496                         if (nr == (total - 2)) {
497                                 strncpy(ref_message_id, message_id,
498                                         sizeof(ref_message_id));
499                                 ref_message_id[sizeof(ref_message_id)-1]='\0';
500                                 rev.ref_message_id = ref_message_id;
501                         }
502                         gen_message_id(message_id, sizeof(message_id),
503                                        sha1_to_hex(commit->object.sha1));
504                         rev.message_id = message_id;
505                 }
506                 if (!use_stdout)
507                         reopen_stdout(commit, rev.nr, keep_subject);
508                 shown = log_tree_commit(&rev, commit);
509                 free(commit->buffer);
510                 commit->buffer = NULL;
511
512                 /* We put one extra blank line between formatted
513                  * patches and this flag is used by log-tree code
514                  * to see if it needs to emit a LF before showing
515                  * the log; when using one file per patch, we do
516                  * not want the extra blank line.
517                  */
518                 if (!use_stdout)
519                         rev.shown_one = 0;
520                 if (shown) {
521                         if (rev.mime_boundary)
522                                 printf("\n--%s%s--\n\n\n",
523                                        mime_boundary_leader,
524                                        rev.mime_boundary);
525                         else
526                                 printf("-- \n%s\n\n", git_version_string);
527                 }
528                 if (!use_stdout)
529                         fclose(stdout);
530         }
531         free(list);
532         return 0;
533 }
534
535 static int add_pending_commit(const char *arg, struct rev_info *revs, int flags)
536 {
537         unsigned char sha1[20];
538         if (get_sha1(arg, sha1) == 0) {
539                 struct commit *commit = lookup_commit_reference(sha1);
540                 if (commit) {
541                         commit->object.flags |= flags;
542                         add_pending_object(revs, &commit->object, arg);
543                         return 0;
544                 }
545         }
546         return -1;
547 }
548
549 static const char cherry_usage[] =
550 "git-cherry [-v] <upstream> [<head>] [<limit>]";
551 int cmd_cherry(int argc, const char **argv, const char *prefix)
552 {
553         struct rev_info revs;
554         struct diff_options patch_id_opts;
555         struct commit *commit;
556         struct commit_list *list = NULL;
557         const char *upstream;
558         const char *head = "HEAD";
559         const char *limit = NULL;
560         int verbose = 0;
561
562         if (argc > 1 && !strcmp(argv[1], "-v")) {
563                 verbose = 1;
564                 argc--;
565                 argv++;
566         }
567
568         switch (argc) {
569         case 4:
570                 limit = argv[3];
571                 /* FALLTHROUGH */
572         case 3:
573                 head = argv[2];
574                 /* FALLTHROUGH */
575         case 2:
576                 upstream = argv[1];
577                 break;
578         default:
579                 usage(cherry_usage);
580         }
581
582         init_revisions(&revs, prefix);
583         revs.diff = 1;
584         revs.combine_merges = 0;
585         revs.ignore_merges = 1;
586         revs.diffopt.recursive = 1;
587
588         if (add_pending_commit(head, &revs, 0))
589                 die("Unknown commit %s", head);
590         if (add_pending_commit(upstream, &revs, UNINTERESTING))
591                 die("Unknown commit %s", upstream);
592
593         /* Don't say anything if head and upstream are the same. */
594         if (revs.pending.nr == 2) {
595                 struct object_array_entry *o = revs.pending.objects;
596                 if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
597                         return 0;
598         }
599
600         get_patch_ids(&revs, &patch_id_opts, prefix);
601
602         if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
603                 die("Unknown commit %s", limit);
604
605         /* reverse the list of commits */
606         prepare_revision_walk(&revs);
607         while ((commit = get_revision(&revs)) != NULL) {
608                 /* ignore merges */
609                 if (commit->parents && commit->parents->next)
610                         continue;
611
612                 commit_list_insert(commit, &list);
613         }
614
615         while (list) {
616                 unsigned char sha1[20];
617                 char sign = '+';
618
619                 commit = list->item;
620                 if (!get_patch_id(commit, &patch_id_opts, sha1) &&
621                     lookup_object(sha1))
622                         sign = '-';
623
624                 if (verbose) {
625                         static char buf[16384];
626                         pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
627                                             buf, sizeof(buf), 0, NULL, NULL, 0);
628                         printf("%c %s %s\n", sign,
629                                sha1_to_hex(commit->object.sha1), buf);
630                 }
631                 else {
632                         printf("%c %s\n", sign,
633                                sha1_to_hex(commit->object.sha1));
634                 }
635
636                 list = list->next;
637         }
638
639         return 0;
640 }