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