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