]> asedeno.scripts.mit.edu Git - git.git/blob - log-tree.c
61680f4664e5be109b9e1471ea18ee1818f2f9c9
[git.git] / log-tree.c
1 #include "cache.h"
2 #include "diff.h"
3 #include "commit.h"
4 #include "tag.h"
5 #include "graph.h"
6 #include "log-tree.h"
7 #include "reflog-walk.h"
8 #include "refs.h"
9 #include "string-list.h"
10 #include "color.h"
11
12 struct decoration name_decoration = { "object names" };
13
14 enum decoration_type {
15         DECORATION_NONE = 0,
16         DECORATION_REF_LOCAL,
17         DECORATION_REF_REMOTE,
18         DECORATION_REF_TAG,
19         DECORATION_REF_STASH,
20         DECORATION_REF_HEAD,
21 };
22
23 static char decoration_colors[][COLOR_MAXLEN] = {
24         GIT_COLOR_RESET,
25         GIT_COLOR_BOLD_GREEN,   /* REF_LOCAL */
26         GIT_COLOR_BOLD_RED,     /* REF_REMOTE */
27         GIT_COLOR_BOLD_YELLOW,  /* REF_TAG */
28         GIT_COLOR_BOLD_MAGENTA, /* REF_STASH */
29         GIT_COLOR_BOLD_CYAN,    /* REF_HEAD */
30 };
31
32 static const char *decorate_get_color(int decorate_use_color, enum decoration_type ix)
33 {
34         if (decorate_use_color)
35                 return decoration_colors[ix];
36         return "";
37 }
38
39 /*
40  * log-tree.c uses DIFF_OPT_TST for determining whether to use color
41  * for showing the commit sha1, use the same check for --decorate
42  */
43 #define decorate_get_color_opt(o, ix) \
44         decorate_get_color(DIFF_OPT_TST((o), COLOR_DIFF), ix)
45
46 static void add_name_decoration(enum decoration_type type, const char *name, struct object *obj)
47 {
48         int nlen = strlen(name);
49         struct name_decoration *res = xmalloc(sizeof(struct name_decoration) + nlen);
50         memcpy(res->name, name, nlen + 1);
51         res->type = type;
52         res->next = add_decoration(&name_decoration, obj, res);
53 }
54
55 static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
56 {
57         struct object *obj = parse_object(sha1);
58         enum decoration_type type = DECORATION_NONE;
59         if (!obj)
60                 return 0;
61
62         if (!prefixcmp(refname, "refs/heads"))
63                 type = DECORATION_REF_LOCAL;
64         else if (!prefixcmp(refname, "refs/remotes"))
65                 type = DECORATION_REF_REMOTE;
66         else if (!prefixcmp(refname, "refs/tags"))
67                 type = DECORATION_REF_TAG;
68         else if (!prefixcmp(refname, "refs/stash"))
69                 type = DECORATION_REF_STASH;
70         else if (!prefixcmp(refname, "HEAD"))
71                 type = DECORATION_REF_HEAD;
72
73         if (!cb_data || *(int *)cb_data == DECORATE_SHORT_REFS)
74                 refname = prettify_refname(refname);
75         add_name_decoration(type, refname, obj);
76         while (obj->type == OBJ_TAG) {
77                 obj = ((struct tag *)obj)->tagged;
78                 if (!obj)
79                         break;
80                 add_name_decoration(DECORATION_REF_TAG, refname, obj);
81         }
82         return 0;
83 }
84
85 void load_ref_decorations(int flags)
86 {
87         static int loaded;
88         if (!loaded) {
89                 loaded = 1;
90                 for_each_ref(add_ref_decoration, &flags);
91                 head_ref(add_ref_decoration, &flags);
92         }
93 }
94
95 static void show_parents(struct commit *commit, int abbrev)
96 {
97         struct commit_list *p;
98         for (p = commit->parents; p ; p = p->next) {
99                 struct commit *parent = p->item;
100                 printf(" %s", find_unique_abbrev(parent->object.sha1, abbrev));
101         }
102 }
103
104 void show_decorations(struct rev_info *opt, struct commit *commit)
105 {
106         const char *prefix;
107         struct name_decoration *decoration;
108         const char *color_commit =
109                 diff_get_color_opt(&opt->diffopt, DIFF_COMMIT);
110         const char *color_reset =
111                 decorate_get_color_opt(&opt->diffopt, DECORATION_NONE);
112
113         if (opt->show_source && commit->util)
114                 printf("\t%s", (char *) commit->util);
115         if (!opt->show_decorations)
116                 return;
117         decoration = lookup_decoration(&name_decoration, &commit->object);
118         if (!decoration)
119                 return;
120         prefix = " (";
121         while (decoration) {
122                 printf("%s", prefix);
123                 fputs(decorate_get_color_opt(&opt->diffopt, decoration->type),
124                       stdout);
125                 if (decoration->type == DECORATION_REF_TAG)
126                         fputs("tag: ", stdout);
127                 printf("%s", decoration->name);
128                 fputs(color_reset, stdout);
129                 fputs(color_commit, stdout);
130                 prefix = ", ";
131                 decoration = decoration->next;
132         }
133         putchar(')');
134 }
135
136 /*
137  * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches
138  * Signed-off-by: and Acked-by: lines.
139  */
140 static int detect_any_signoff(char *letter, int size)
141 {
142         char *cp;
143         int seen_colon = 0;
144         int seen_at = 0;
145         int seen_name = 0;
146         int seen_head = 0;
147
148         cp = letter + size;
149         while (letter <= --cp && *cp == '\n')
150                 continue;
151
152         while (letter <= cp) {
153                 char ch = *cp--;
154                 if (ch == '\n')
155                         break;
156
157                 if (!seen_at) {
158                         if (ch == '@')
159                                 seen_at = 1;
160                         continue;
161                 }
162                 if (!seen_colon) {
163                         if (ch == '@')
164                                 return 0;
165                         else if (ch == ':')
166                                 seen_colon = 1;
167                         else
168                                 seen_name = 1;
169                         continue;
170                 }
171                 if (('A' <= ch && ch <= 'Z') ||
172                     ('a' <= ch && ch <= 'z') ||
173                     ch == '-') {
174                         seen_head = 1;
175                         continue;
176                 }
177                 /* no empty last line doesn't match */
178                 return 0;
179         }
180         return seen_head && seen_name;
181 }
182
183 static void append_signoff(struct strbuf *sb, const char *signoff)
184 {
185         static const char signed_off_by[] = "Signed-off-by: ";
186         size_t signoff_len = strlen(signoff);
187         int has_signoff = 0;
188         char *cp;
189
190         cp = sb->buf;
191
192         /* First see if we already have the sign-off by the signer */
193         while ((cp = strstr(cp, signed_off_by))) {
194
195                 has_signoff = 1;
196
197                 cp += strlen(signed_off_by);
198                 if (cp + signoff_len >= sb->buf + sb->len)
199                         break;
200                 if (strncmp(cp, signoff, signoff_len))
201                         continue;
202                 if (!isspace(cp[signoff_len]))
203                         continue;
204                 /* we already have him */
205                 return;
206         }
207
208         if (!has_signoff)
209                 has_signoff = detect_any_signoff(sb->buf, sb->len);
210
211         if (!has_signoff)
212                 strbuf_addch(sb, '\n');
213
214         strbuf_addstr(sb, signed_off_by);
215         strbuf_add(sb, signoff, signoff_len);
216         strbuf_addch(sb, '\n');
217 }
218
219 static unsigned int digits_in_number(unsigned int number)
220 {
221         unsigned int i = 10, result = 1;
222         while (i <= number) {
223                 i *= 10;
224                 result++;
225         }
226         return result;
227 }
228
229 void get_patch_filename(struct commit *commit, int nr, const char *suffix,
230                         struct strbuf *buf)
231 {
232         int suffix_len = strlen(suffix) + 1;
233         int start_len = buf->len;
234
235         strbuf_addf(buf, commit ? "%04d-" : "%d", nr);
236         if (commit) {
237                 int max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len;
238                 struct pretty_print_context ctx = {0};
239                 ctx.date_mode = DATE_NORMAL;
240
241                 format_commit_message(commit, "%f", buf, &ctx);
242                 if (max_len < buf->len)
243                         strbuf_setlen(buf, max_len);
244                 strbuf_addstr(buf, suffix);
245         }
246 }
247
248 void log_write_email_headers(struct rev_info *opt, struct commit *commit,
249                              const char **subject_p,
250                              const char **extra_headers_p,
251                              int *need_8bit_cte_p)
252 {
253         const char *subject = NULL;
254         const char *extra_headers = opt->extra_headers;
255         const char *name = sha1_to_hex(commit->object.sha1);
256
257         *need_8bit_cte_p = 0; /* unknown */
258         if (opt->total > 0) {
259                 static char buffer[64];
260                 snprintf(buffer, sizeof(buffer),
261                          "Subject: [%s %0*d/%d] ",
262                          opt->subject_prefix,
263                          digits_in_number(opt->total),
264                          opt->nr, opt->total);
265                 subject = buffer;
266         } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
267                 static char buffer[256];
268                 snprintf(buffer, sizeof(buffer),
269                          "Subject: [%s] ",
270                          opt->subject_prefix);
271                 subject = buffer;
272         } else {
273                 subject = "Subject: ";
274         }
275
276         printf("From %s Mon Sep 17 00:00:00 2001\n", name);
277         graph_show_oneline(opt->graph);
278         if (opt->message_id) {
279                 printf("Message-Id: <%s>\n", opt->message_id);
280                 graph_show_oneline(opt->graph);
281         }
282         if (opt->ref_message_ids && opt->ref_message_ids->nr > 0) {
283                 int i, n;
284                 n = opt->ref_message_ids->nr;
285                 printf("In-Reply-To: <%s>\n", opt->ref_message_ids->items[n-1].string);
286                 for (i = 0; i < n; i++)
287                         printf("%s<%s>\n", (i > 0 ? "\t" : "References: "),
288                                opt->ref_message_ids->items[i].string);
289                 graph_show_oneline(opt->graph);
290         }
291         if (opt->mime_boundary) {
292                 static char subject_buffer[1024];
293                 static char buffer[1024];
294                 struct strbuf filename =  STRBUF_INIT;
295                 *need_8bit_cte_p = -1; /* NEVER */
296                 snprintf(subject_buffer, sizeof(subject_buffer) - 1,
297                          "%s"
298                          "MIME-Version: 1.0\n"
299                          "Content-Type: multipart/mixed;"
300                          " boundary=\"%s%s\"\n"
301                          "\n"
302                          "This is a multi-part message in MIME "
303                          "format.\n"
304                          "--%s%s\n"
305                          "Content-Type: text/plain; "
306                          "charset=UTF-8; format=fixed\n"
307                          "Content-Transfer-Encoding: 8bit\n\n",
308                          extra_headers ? extra_headers : "",
309                          mime_boundary_leader, opt->mime_boundary,
310                          mime_boundary_leader, opt->mime_boundary);
311                 extra_headers = subject_buffer;
312
313                 get_patch_filename(opt->numbered_files ? NULL : commit, opt->nr,
314                                     opt->patch_suffix, &filename);
315                 snprintf(buffer, sizeof(buffer) - 1,
316                          "\n--%s%s\n"
317                          "Content-Type: text/x-patch;"
318                          " name=\"%s\"\n"
319                          "Content-Transfer-Encoding: 8bit\n"
320                          "Content-Disposition: %s;"
321                          " filename=\"%s\"\n\n",
322                          mime_boundary_leader, opt->mime_boundary,
323                          filename.buf,
324                          opt->no_inline ? "attachment" : "inline",
325                          filename.buf);
326                 opt->diffopt.stat_sep = buffer;
327                 strbuf_release(&filename);
328         }
329         *subject_p = subject;
330         *extra_headers_p = extra_headers;
331 }
332
333 void show_log(struct rev_info *opt)
334 {
335         struct strbuf msgbuf = STRBUF_INIT;
336         struct log_info *log = opt->loginfo;
337         struct commit *commit = log->commit, *parent = log->parent;
338         int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
339         const char *extra_headers = opt->extra_headers;
340         struct pretty_print_context ctx = {0};
341
342         opt->loginfo = NULL;
343         ctx.show_notes = opt->show_notes;
344         if (!opt->verbose_header) {
345                 graph_show_commit(opt->graph);
346
347                 if (!opt->graph) {
348                         if (commit->object.flags & BOUNDARY)
349                                 putchar('-');
350                         else if (commit->object.flags & UNINTERESTING)
351                                 putchar('^');
352                         else if (opt->left_right) {
353                                 if (commit->object.flags & SYMMETRIC_LEFT)
354                                         putchar('<');
355                                 else
356                                         putchar('>');
357                         }
358                 }
359                 fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
360                 if (opt->print_parents)
361                         show_parents(commit, abbrev_commit);
362                 show_decorations(opt, commit);
363                 if (opt->graph && !graph_is_commit_finished(opt->graph)) {
364                         putchar('\n');
365                         graph_show_remainder(opt->graph);
366                 }
367                 putchar(opt->diffopt.line_termination);
368                 return;
369         }
370
371         /*
372          * If use_terminator is set, we already handled any record termination
373          * at the end of the last record.
374          * Otherwise, add a diffopt.line_termination character before all
375          * entries but the first.  (IOW, as a separator between entries)
376          */
377         if (opt->shown_one && !opt->use_terminator) {
378                 /*
379                  * If entries are separated by a newline, the output
380                  * should look human-readable.  If the last entry ended
381                  * with a newline, print the graph output before this
382                  * newline.  Otherwise it will end up as a completely blank
383                  * line and will look like a gap in the graph.
384                  *
385                  * If the entry separator is not a newline, the output is
386                  * primarily intended for programmatic consumption, and we
387                  * never want the extra graph output before the entry
388                  * separator.
389                  */
390                 if (opt->diffopt.line_termination == '\n' &&
391                     !opt->missing_newline)
392                         graph_show_padding(opt->graph);
393                 putchar(opt->diffopt.line_termination);
394         }
395         opt->shown_one = 1;
396
397         /*
398          * If the history graph was requested,
399          * print the graph, up to this commit's line
400          */
401         graph_show_commit(opt->graph);
402
403         /*
404          * Print header line of header..
405          */
406
407         if (opt->commit_format == CMIT_FMT_EMAIL) {
408                 log_write_email_headers(opt, commit, &ctx.subject, &extra_headers,
409                                         &ctx.need_8bit_cte);
410         } else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
411                 fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout);
412                 if (opt->commit_format != CMIT_FMT_ONELINE)
413                         fputs("commit ", stdout);
414
415                 if (!opt->graph) {
416                         if (commit->object.flags & BOUNDARY)
417                                 putchar('-');
418                         else if (commit->object.flags & UNINTERESTING)
419                                 putchar('^');
420                         else if (opt->left_right) {
421                                 if (commit->object.flags & SYMMETRIC_LEFT)
422                                         putchar('<');
423                                 else
424                                         putchar('>');
425                         }
426                 }
427                 fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit),
428                       stdout);
429                 if (opt->print_parents)
430                         show_parents(commit, abbrev_commit);
431                 if (parent)
432                         printf(" (from %s)",
433                                find_unique_abbrev(parent->object.sha1,
434                                                   abbrev_commit));
435                 show_decorations(opt, commit);
436                 printf("%s", diff_get_color_opt(&opt->diffopt, DIFF_RESET));
437                 if (opt->commit_format == CMIT_FMT_ONELINE) {
438                         putchar(' ');
439                 } else {
440                         putchar('\n');
441                         graph_show_oneline(opt->graph);
442                 }
443                 if (opt->reflog_info) {
444                         /*
445                          * setup_revisions() ensures that opt->reflog_info
446                          * and opt->graph cannot both be set,
447                          * so we don't need to worry about printing the
448                          * graph info here.
449                          */
450                         show_reflog_message(opt->reflog_info,
451                                     opt->commit_format == CMIT_FMT_ONELINE,
452                                     opt->date_mode_explicit ?
453                                         opt->date_mode :
454                                         DATE_NORMAL);
455                         if (opt->commit_format == CMIT_FMT_ONELINE)
456                                 return;
457                 }
458         }
459
460         if (!commit->buffer)
461                 return;
462
463         /*
464          * And then the pretty-printed message itself
465          */
466         if (ctx.need_8bit_cte >= 0)
467                 ctx.need_8bit_cte = has_non_ascii(opt->add_signoff);
468         ctx.date_mode = opt->date_mode;
469         ctx.abbrev = opt->diffopt.abbrev;
470         ctx.after_subject = extra_headers;
471         ctx.reflog_info = opt->reflog_info;
472         pretty_print_commit(opt->commit_format, commit, &msgbuf, &ctx);
473
474         if (opt->add_signoff)
475                 append_signoff(&msgbuf, opt->add_signoff);
476         if (opt->show_log_size) {
477                 printf("log size %i\n", (int)msgbuf.len);
478                 graph_show_oneline(opt->graph);
479         }
480
481         /*
482          * Set opt->missing_newline if msgbuf doesn't
483          * end in a newline (including if it is empty)
484          */
485         if (!msgbuf.len || msgbuf.buf[msgbuf.len - 1] != '\n')
486                 opt->missing_newline = 1;
487         else
488                 opt->missing_newline = 0;
489
490         if (opt->graph)
491                 graph_show_commit_msg(opt->graph, &msgbuf);
492         else
493                 fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
494         if (opt->use_terminator) {
495                 if (!opt->missing_newline)
496                         graph_show_padding(opt->graph);
497                 putchar('\n');
498         }
499
500         strbuf_release(&msgbuf);
501 }
502
503 int log_tree_diff_flush(struct rev_info *opt)
504 {
505         diffcore_std(&opt->diffopt);
506
507         if (diff_queue_is_empty()) {
508                 int saved_fmt = opt->diffopt.output_format;
509                 opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
510                 diff_flush(&opt->diffopt);
511                 opt->diffopt.output_format = saved_fmt;
512                 return 0;
513         }
514
515         if (opt->loginfo && !opt->no_commit_id) {
516                 /* When showing a verbose header (i.e. log message),
517                  * and not in --pretty=oneline format, we would want
518                  * an extra newline between the end of log and the
519                  * output for readability.
520                  */
521                 show_log(opt);
522                 if ((opt->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) &&
523                     opt->verbose_header &&
524                     opt->commit_format != CMIT_FMT_ONELINE) {
525                         int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
526                         if ((pch & opt->diffopt.output_format) == pch)
527                                 printf("---");
528                         if (opt->diffopt.output_prefix) {
529                                 struct strbuf *msg = NULL;
530                                 msg = opt->diffopt.output_prefix(&opt->diffopt,
531                                         opt->diffopt.output_prefix_data);
532                                 fwrite(msg->buf, msg->len, 1, stdout);
533                         }
534                         putchar('\n');
535                 }
536         }
537         diff_flush(&opt->diffopt);
538         return 1;
539 }
540
541 static int do_diff_combined(struct rev_info *opt, struct commit *commit)
542 {
543         unsigned const char *sha1 = commit->object.sha1;
544
545         diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);
546         return !opt->loginfo;
547 }
548
549 /*
550  * Show the diff of a commit.
551  *
552  * Return true if we printed any log info messages
553  */
554 static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log_info *log)
555 {
556         int showed_log;
557         struct commit_list *parents;
558         unsigned const char *sha1 = commit->object.sha1;
559
560         if (!opt->diff && !DIFF_OPT_TST(&opt->diffopt, EXIT_WITH_STATUS))
561                 return 0;
562
563         /* Root commit? */
564         parents = commit->parents;
565         if (!parents) {
566                 if (opt->show_root_diff) {
567                         diff_root_tree_sha1(sha1, "", &opt->diffopt);
568                         log_tree_diff_flush(opt);
569                 }
570                 return !opt->loginfo;
571         }
572
573         /* More than one parent? */
574         if (parents && parents->next) {
575                 if (opt->ignore_merges)
576                         return 0;
577                 else if (opt->combine_merges)
578                         return do_diff_combined(opt, commit);
579                 else if (opt->first_parent_only) {
580                         /*
581                          * Generate merge log entry only for the first
582                          * parent, showing summary diff of the others
583                          * we merged _in_.
584                          */
585                         diff_tree_sha1(parents->item->object.sha1, sha1, "", &opt->diffopt);
586                         log_tree_diff_flush(opt);
587                         return !opt->loginfo;
588                 }
589
590                 /* If we show individual diffs, show the parent info */
591                 log->parent = parents->item;
592         }
593
594         showed_log = 0;
595         for (;;) {
596                 struct commit *parent = parents->item;
597
598                 diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
599                 log_tree_diff_flush(opt);
600
601                 showed_log |= !opt->loginfo;
602
603                 /* Set up the log info for the next parent, if any.. */
604                 parents = parents->next;
605                 if (!parents)
606                         break;
607                 log->parent = parents->item;
608                 opt->loginfo = log;
609         }
610         return showed_log;
611 }
612
613 int log_tree_commit(struct rev_info *opt, struct commit *commit)
614 {
615         struct log_info log;
616         int shown;
617
618         log.commit = commit;
619         log.parent = NULL;
620         opt->loginfo = &log;
621
622         shown = log_tree_diff(opt, commit, &log);
623         if (!shown && opt->loginfo && opt->always_show_header) {
624                 log.parent = NULL;
625                 show_log(opt);
626                 shown = 1;
627         }
628         opt->loginfo = NULL;
629         maybe_flush_or_die(stdout, "stdout");
630         return shown;
631 }