Fix some printf format warnings
[git/dscho.git] / log-tree.c
blob1c9eefee33b38a44f34d21c47bc1f16af1443a6c
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"
11 struct decoration name_decoration = { "object names" };
13 static void add_name_decoration(const char *prefix, const char *name, struct object *obj)
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);
23 static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
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;
40 void load_ref_decorations(int flags)
42         static int loaded;
43         if (!loaded) {
44                 loaded = 1;
45                 for_each_ref(add_ref_decoration, &flags);
46         }
49 static void show_parents(struct commit *commit, int abbrev)
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         }
58 void show_decorations(struct rev_info *opt, struct commit *commit)
60         const char *prefix;
61         struct name_decoration *decoration;
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(')');
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)
85         char *cp;
86         int seen_colon = 0;
87         int seen_at = 0;
88         int seen_name = 0;
89         int seen_head = 0;
91         cp = letter + size;
92         while (letter <= --cp && *cp == '\n')
93                 continue;
95         while (letter <= cp) {
96                 char ch = *cp--;
97                 if (ch == '\n')
98                         break;
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;
126 static void append_signoff(struct strbuf *sb, const char *signoff)
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;
133         cp = sb->buf;
135         /* First see if we already have the sign-off by the signer */
136         while ((cp = strstr(cp, signed_off_by))) {
138                 has_signoff = 1;
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         }
151         if (!has_signoff)
152                 has_signoff = detect_any_signoff(sb->buf, sb->len);
154         if (!has_signoff)
155                 strbuf_addch(sb, '\n');
157         strbuf_addstr(sb, signed_off_by);
158         strbuf_add(sb, signoff, signoff_len);
159         strbuf_addch(sb, '\n');
162 static unsigned int digits_in_number(unsigned int number)
164         unsigned int i = 10, result = 1;
165         while (i <= number) {
166                 i *= 10;
167                 result++;
168         }
169         return result;
172 void get_patch_filename(struct commit *commit, int nr, const char *suffix,
173                         struct strbuf *buf)
175         int suffix_len = strlen(suffix) + 1;
176         int start_len = buf->len;
178         strbuf_addf(buf, commit ? "%04d-" : "%d", nr);
179         if (commit) {
180                 int max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len;
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         }
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)
194         const char *subject = NULL;
195         const char *extra_headers = opt->extra_headers;
196         const char *name = sha1_to_hex(commit->object.sha1);
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         }
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;
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;
274 void show_log(struct rev_info *opt)
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;
284         opt->loginfo = NULL;
285         if (!opt->verbose_header) {
286                 graph_show_commit(opt->graph);
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         }
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;
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);
344         /*
345          * Print header line of header..
346          */
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);
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);
394                         if (opt->commit_format == CMIT_FMT_ONELINE)
395                                 return;
396                 }
397         }
399         if (!commit->buffer)
400                 return;
402         /*
403          * And then the pretty-printed message itself
404          */
405         if (need_8bit_cte >= 0)
406                 need_8bit_cte = has_non_ascii(opt->add_signoff);
407         pretty_print_commit(opt->commit_format, commit, &msgbuf,
408                             abbrev, subject, extra_headers, opt->date_mode,
409                             need_8bit_cte);
411         if (opt->add_signoff)
412                 append_signoff(&msgbuf, opt->add_signoff);
413         if (opt->show_log_size) {
414                 printf("log size %i\n", (int)msgbuf.len);
415                 graph_show_oneline(opt->graph);
416         }
418         /*
419          * Set opt->missing_newline if msgbuf doesn't
420          * end in a newline (including if it is empty)
421          */
422         if (!msgbuf.len || msgbuf.buf[msgbuf.len - 1] != '\n')
423                 opt->missing_newline = 1;
424         else
425                 opt->missing_newline = 0;
427         if (opt->graph)
428                 graph_show_commit_msg(opt->graph, &msgbuf);
429         else
430                 fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
431         if (opt->use_terminator) {
432                 if (!opt->missing_newline)
433                         graph_show_padding(opt->graph);
434                 putchar('\n');
435         }
437         strbuf_release(&msgbuf);
440 int log_tree_diff_flush(struct rev_info *opt)
442         diffcore_std(&opt->diffopt);
444         if (diff_queue_is_empty()) {
445                 int saved_fmt = opt->diffopt.output_format;
446                 opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
447                 diff_flush(&opt->diffopt);
448                 opt->diffopt.output_format = saved_fmt;
449                 return 0;
450         }
452         if (opt->loginfo && !opt->no_commit_id) {
453                 /* When showing a verbose header (i.e. log message),
454                  * and not in --pretty=oneline format, we would want
455                  * an extra newline between the end of log and the
456                  * output for readability.
457                  */
458                 show_log(opt);
459                 if ((opt->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) &&
460                     opt->verbose_header &&
461                     opt->commit_format != CMIT_FMT_ONELINE) {
462                         int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
463                         if ((pch & opt->diffopt.output_format) == pch)
464                                 printf("---");
465                         putchar('\n');
466                 }
467         }
468         diff_flush(&opt->diffopt);
469         return 1;
472 static int do_diff_combined(struct rev_info *opt, struct commit *commit)
474         unsigned const char *sha1 = commit->object.sha1;
476         diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);
477         return !opt->loginfo;
481  * Show the diff of a commit.
483  * Return true if we printed any log info messages
484  */
485 static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log_info *log)
487         int showed_log;
488         struct commit_list *parents;
489         unsigned const char *sha1 = commit->object.sha1;
491         if (!opt->diff && !DIFF_OPT_TST(&opt->diffopt, EXIT_WITH_STATUS))
492                 return 0;
494         /* Root commit? */
495         parents = commit->parents;
496         if (!parents) {
497                 if (opt->show_root_diff) {
498                         diff_root_tree_sha1(sha1, "", &opt->diffopt);
499                         log_tree_diff_flush(opt);
500                 }
501                 return !opt->loginfo;
502         }
504         /* More than one parent? */
505         if (parents && parents->next) {
506                 if (opt->ignore_merges)
507                         return 0;
508                 else if (opt->combine_merges)
509                         return do_diff_combined(opt, commit);
511                 /* If we show individual diffs, show the parent info */
512                 log->parent = parents->item;
513         }
515         showed_log = 0;
516         for (;;) {
517                 struct commit *parent = parents->item;
519                 diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
520                 log_tree_diff_flush(opt);
522                 showed_log |= !opt->loginfo;
524                 /* Set up the log info for the next parent, if any.. */
525                 parents = parents->next;
526                 if (!parents)
527                         break;
528                 log->parent = parents->item;
529                 opt->loginfo = log;
530         }
531         return showed_log;
534 int log_tree_commit(struct rev_info *opt, struct commit *commit)
536         struct log_info log;
537         int shown;
539         log.commit = commit;
540         log.parent = NULL;
541         opt->loginfo = &log;
543         shown = log_tree_diff(opt, commit, &log);
544         if (!shown && opt->loginfo && opt->always_show_header) {
545                 log.parent = NULL;
546                 show_log(opt);
547                 shown = 1;
548         }
549         opt->loginfo = NULL;
550         maybe_flush_or_die(stdout, "stdout");
551         return shown;