Merge branch 'maint'
[git] / log-tree.c
1 #include "cache.h"
2 #include "diff.h"
3 #include "commit.h"
4 #include "log-tree.h"
5
6 static void show_parents(struct commit *commit, int abbrev)
7 {
8         struct commit_list *p;
9         for (p = commit->parents; p ; p = p->next) {
10                 struct commit *parent = p->item;
11                 printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));
12         }
13 }
14
15 static int append_signoff(char *buf, int buf_sz, int at, const char *signoff)
16 {
17         int signoff_len = strlen(signoff);
18         static const char signed_off_by[] = "Signed-off-by: ";
19         char *cp = buf;
20
21         /* Do we have enough space to add it? */
22         if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 3)
23                 return at;
24
25         /* First see if we already have the sign-off by the signer */
26         while (1) {
27                 cp = strstr(cp, signed_off_by);
28                 if (!cp)
29                         break;
30                 cp += strlen(signed_off_by);
31                 if ((cp + signoff_len < buf + at) &&
32                     !strncmp(cp, signoff, signoff_len) &&
33                     isspace(cp[signoff_len]))
34                         return at; /* we already have him */
35         }
36
37         /* Does the last line already end with "^[-A-Za-z]+: [^@]+@"?
38          * If not, add a blank line to separate the message from
39          * the run of Signed-off-by: and Acked-by: lines.
40          */
41         {
42                 char ch;
43                 int seen_colon, seen_at, seen_name, seen_head, not_signoff;
44                 seen_colon = 0;
45                 seen_at = 0;
46                 seen_name = 0;
47                 seen_head = 0;
48                 not_signoff = 0;
49                 cp = buf + at;
50                 while (buf <= --cp && (ch = *cp) == '\n')
51                         ;
52                 while (!not_signoff && buf <= cp && (ch = *cp--) != '\n') {
53                         if (!seen_at) {
54                                 if (ch == '@')
55                                         seen_at = 1;
56                                 continue;
57                         }
58                         if (!seen_colon) {
59                                 if (ch == '@')
60                                         not_signoff = 1;
61                                 else if (ch == ':')
62                                         seen_colon = 1;
63                                 else
64                                         seen_name = 1;
65                                 continue;
66                         }
67                         if (('A' <= ch && ch <= 'Z') ||
68                             ('a' <= ch && ch <= 'z') ||
69                             ch == '-') {
70                                 seen_head = 1;
71                                 continue;
72                         }
73                         not_signoff = 1;
74                 }
75                 if (not_signoff || !seen_head || !seen_name)
76                         buf[at++] = '\n';
77         }
78
79         strcpy(buf + at, signed_off_by);
80         at += strlen(signed_off_by);
81         strcpy(buf + at, signoff);
82         at += signoff_len;
83         buf[at++] = '\n';
84         buf[at] = 0;
85         return at;
86 }
87
88 void show_log(struct rev_info *opt, const char *sep)
89 {
90         static char this_header[16384];
91         struct log_info *log = opt->loginfo;
92         struct commit *commit = log->commit, *parent = log->parent;
93         int abbrev = opt->diffopt.abbrev;
94         int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
95         const char *extra;
96         int len;
97         const char *subject = NULL, *extra_headers = opt->extra_headers;
98
99         opt->loginfo = NULL;
100         if (!opt->verbose_header) {
101                 fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
102                 if (opt->parents)
103                         show_parents(commit, abbrev_commit);
104                 putchar(opt->diffopt.line_termination);
105                 return;
106         }
107
108         /*
109          * The "oneline" format has several special cases:
110          *  - The pretty-printed commit lacks a newline at the end
111          *    of the buffer, but we do want to make sure that we
112          *    have a newline there. If the separator isn't already
113          *    a newline, add an extra one.
114          *  - unlike other log messages, the one-line format does
115          *    not have an empty line between entries.
116          */
117         extra = "";
118         if (*sep != '\n' && opt->commit_format == CMIT_FMT_ONELINE)
119                 extra = "\n";
120         if (opt->shown_one && opt->commit_format != CMIT_FMT_ONELINE)
121                 putchar('\n');
122         opt->shown_one = 1;
123
124         /*
125          * Print header line of header..
126          */
127
128         if (opt->commit_format == CMIT_FMT_EMAIL) {
129                 char *sha1 = sha1_to_hex(commit->object.sha1);
130                 if (opt->total > 0) {
131                         static char buffer[64];
132                         snprintf(buffer, sizeof(buffer),
133                                         "Subject: [PATCH %d/%d] ",
134                                         opt->nr, opt->total);
135                         subject = buffer;
136                 } else if (opt->total == 0)
137                         subject = "Subject: [PATCH] ";
138                 else
139                         subject = "Subject: ";
140
141                 printf("From %s Mon Sep 17 00:00:00 2001\n", sha1);
142                 if (opt->message_id)
143                         printf("Message-Id: <%s>\n", opt->message_id);
144                 if (opt->ref_message_id)
145                         printf("In-Reply-To: <%s>\nReferences: <%s>\n",
146                                opt->ref_message_id, opt->ref_message_id);
147                 if (opt->mime_boundary) {
148                         static char subject_buffer[1024];
149                         static char buffer[1024];
150                         snprintf(subject_buffer, sizeof(subject_buffer) - 1,
151                                  "%s"
152                                  "MIME-Version: 1.0\n"
153                                  "Content-Type: multipart/mixed;\n"
154                                  " boundary=\"%s%s\"\n"
155                                  "\n"
156                                  "This is a multi-part message in MIME "
157                                  "format.\n"
158                                  "--%s%s\n"
159                                  "Content-Type: text/plain; "
160                                  "charset=UTF-8; format=fixed\n"
161                                  "Content-Transfer-Encoding: 8bit\n\n",
162                                  extra_headers ? extra_headers : "",
163                                  mime_boundary_leader, opt->mime_boundary,
164                                  mime_boundary_leader, opt->mime_boundary);
165                         extra_headers = subject_buffer;
166
167                         snprintf(buffer, sizeof(buffer) - 1,
168                                  "--%s%s\n"
169                                  "Content-Type: text/x-patch;\n"
170                                  " name=\"%s.diff\"\n"
171                                  "Content-Transfer-Encoding: 8bit\n"
172                                  "Content-Disposition: inline;\n"
173                                  " filename=\"%s.diff\"\n\n",
174                                  mime_boundary_leader, opt->mime_boundary,
175                                  sha1, sha1);
176                         opt->diffopt.stat_sep = buffer;
177                 }
178         } else {
179                 printf("%s%s%s",
180                        diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
181                        opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
182                        diff_unique_abbrev(commit->object.sha1, abbrev_commit));
183                 if (opt->parents)
184                         show_parents(commit, abbrev_commit);
185                 if (parent)
186                         printf(" (from %s)",
187                                diff_unique_abbrev(parent->object.sha1,
188                                                   abbrev_commit));
189                 printf("%s",
190                        diff_get_color(opt->diffopt.color_diff, DIFF_RESET));
191                 putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
192         }
193
194         /*
195          * And then the pretty-printed message itself
196          */
197         len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev, subject, extra_headers);
198
199         if (opt->add_signoff)
200                 len = append_signoff(this_header, sizeof(this_header), len,
201                                      opt->add_signoff);
202         printf("%s%s%s", this_header, extra, sep);
203 }
204
205 int log_tree_diff_flush(struct rev_info *opt)
206 {
207         diffcore_std(&opt->diffopt);
208
209         if (diff_queue_is_empty()) {
210                 int saved_fmt = opt->diffopt.output_format;
211                 opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
212                 diff_flush(&opt->diffopt);
213                 opt->diffopt.output_format = saved_fmt;
214                 return 0;
215         }
216
217         if (opt->loginfo && !opt->no_commit_id) {
218                 /* When showing a verbose header (i.e. log message),
219                  * and not in --pretty=oneline format, we would want
220                  * an extra newline between the end of log and the
221                  * output for readability.
222                  */
223                 show_log(opt, opt->diffopt.msg_sep);
224                 if (opt->verbose_header &&
225                     opt->commit_format != CMIT_FMT_ONELINE) {
226                         int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
227                         if ((pch & opt->diffopt.output_format) == pch)
228                                 printf("---%c", opt->diffopt.line_termination);
229                         else
230                                 putchar(opt->diffopt.line_termination);
231                 }
232         }
233         diff_flush(&opt->diffopt);
234         return 1;
235 }
236
237 static int diff_root_tree(struct rev_info *opt,
238                           const unsigned char *new, const char *base)
239 {
240         int retval;
241         void *tree;
242         struct tree_desc empty, real;
243
244         tree = read_object_with_reference(new, tree_type, &real.size, NULL);
245         if (!tree)
246                 die("unable to read root tree (%s)", sha1_to_hex(new));
247         real.buf = tree;
248
249         empty.buf = "";
250         empty.size = 0;
251         retval = diff_tree(&empty, &real, base, &opt->diffopt);
252         free(tree);
253         log_tree_diff_flush(opt);
254         return retval;
255 }
256
257 static int do_diff_combined(struct rev_info *opt, struct commit *commit)
258 {
259         unsigned const char *sha1 = commit->object.sha1;
260
261         diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);
262         return !opt->loginfo;
263 }
264
265 /*
266  * Show the diff of a commit.
267  *
268  * Return true if we printed any log info messages
269  */
270 static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log_info *log)
271 {
272         int showed_log;
273         struct commit_list *parents;
274         unsigned const char *sha1 = commit->object.sha1;
275
276         if (!opt->diff)
277                 return 0;
278
279         /* Root commit? */
280         parents = commit->parents;
281         if (!parents) {
282                 if (opt->show_root_diff)
283                         diff_root_tree(opt, sha1, "");
284                 return !opt->loginfo;
285         }
286
287         /* More than one parent? */
288         if (parents && parents->next) {
289                 if (opt->ignore_merges)
290                         return 0;
291                 else if (opt->combine_merges)
292                         return do_diff_combined(opt, commit);
293
294                 /* If we show individual diffs, show the parent info */
295                 log->parent = parents->item;
296         }
297
298         showed_log = 0;
299         for (;;) {
300                 struct commit *parent = parents->item;
301
302                 diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
303                 log_tree_diff_flush(opt);
304
305                 showed_log |= !opt->loginfo;
306
307                 /* Set up the log info for the next parent, if any.. */
308                 parents = parents->next;
309                 if (!parents)
310                         break;
311                 log->parent = parents->item;
312                 opt->loginfo = log;
313         }
314         return showed_log;
315 }
316
317 int log_tree_commit(struct rev_info *opt, struct commit *commit)
318 {
319         struct log_info log;
320         int shown;
321
322         log.commit = commit;
323         log.parent = NULL;
324         opt->loginfo = &log;
325
326         shown = log_tree_diff(opt, commit, &log);
327         if (!shown && opt->loginfo && opt->always_show_header) {
328                 log.parent = NULL;
329                 show_log(opt, "");
330                 shown = 1;
331         }
332         opt->loginfo = NULL;
333         return shown;
334 }